Repository: sealor/junkie

Issue:
Support default arguments
How to reproduce:

```
from junkie import Context

class Foo:
    def __init__(self, bar=None):
        self.bar = bar

context = Context({
    "foo": Foo
})

with context.build(Foo) as foo:
    pass
```

Current behaviour: `Exception: Not found: bar`

Expected behaviour: Since `bar` has a default value, one would expect using that default value if not found in the context.

Discussion:
sealor: @Rollmops 
Thank you for your PR. Nice feature!

Does this implementation also work for factories where some default arguments are used and others not?
Could you please provide a test for this situation?
Rollmops: Good point! Should be resolved now.
sealor: I've squashed all commits into one and made some small improvements.
Are you fine with that?
Rollmops: Accepted 😄

Patch:
diff --git a/junkie/core_context.py b/junkie/core_context.py
index 46baafd..6001fc1 100644
--- a/junkie/core_context.py
+++ b/junkie/core_context.py
@@ -1,5 +1,6 @@
 import inspect
 import logging
+from collections import OrderedDict
 from contextlib import contextmanager, ExitStack
 from typing import Union, Set, List, Dict, Callable, Tuple
 
@@ -68,8 +69,7 @@ class CoreContext:
             yield self._call(constructor, stack, constructor.__name__)
 
     def _call(self, factory_func: Callable, stack: ExitStack, instance_name: str):
-        arg_instances = []
-        arg_names = []
+        args = OrderedDict()
 
         for name, annotation in inspect.signature(factory_func).parameters.items():
             if name in self._instances:
@@ -78,14 +78,16 @@ class CoreContext:
             elif name in self._factories:
                 arg = self._call(self._factories[name], stack, name)
 
+            elif annotation.default is not inspect.Parameter.empty:
+                continue
+
             else:
                 raise Exception("Not found: " + name)
 
-            arg_instances.append(arg)
-            arg_names.append(name)
+            args[name] = arg
 
-        self.logger.debug("%s = %s(%s)", instance_name, factory_func.__name__, arg_names)
-        instance = factory_func(*arg_instances)
+        self.logger.debug("%s = %s(%s)", instance_name, factory_func.__name__, list(args.keys()))
+        instance = factory_func(**args)
 
         if hasattr(instance, "__enter__"):
             self.logger.debug("%s.__enter__()", instance_name)


Test patch:
diff --git a/test/core_context_test.py b/test/core_context_test.py
index 9c134ca..696be01 100644
--- a/test/core_context_test.py
+++ b/test/core_context_test.py
@@ -87,6 +87,36 @@ class CoreContextTest(unittest.TestCase):
 
             self.assertNotEqual(instance1, instance2)
 
+    def test_default_argument_usage(self):
+        class MyClassWithDefaultArguments:
+            def __init__(self, argument: str, default_argument: int = 10, default_argument2: str = None):
+                self.argument = argument
+                self.default_argument = default_argument
+                self.default_argument2 = default_argument2 or "Hello"
+
+        core_context = CoreContext()
+        core_context.add_instances({"argument": "value"})
+
+        with core_context.build_instance_by_type(MyClassWithDefaultArguments) as instance:
+            self.assertEqual("value", instance.argument)
+            self.assertEqual(10, instance.default_argument)
+            self.assertEqual("Hello", instance.default_argument2)
+
+    def test_partial_default_arguments_usage(self):
+        class MyClassWithDefaultArguments:
+            def __init__(self, argument: str, default_argument: int = 10, default_argument2: str = None):
+                self.argument = argument
+                self.default_argument = default_argument
+                self.default_argument2 = default_argument2 or "Hello"
+
+        core_context = CoreContext()
+        core_context.add_instances({"argument": "value", "default_argument2": "set from context"})
+
+        with core_context.build_instance_by_type(MyClassWithDefaultArguments) as instance:
+            self.assertEqual("value", instance.argument)
+            self.assertEqual(10, instance.default_argument)
+            self.assertEqual("set from context", instance.default_argument2)
+
     def test_context_manager_enter_and_exit(self):
         class Class:
             def __init__(self, message_service, database_context):