factory-manager 0.1.0
A package to create objects with their dependencies from descriptions in the form of a dictionary
Requires Python: >=3.6

# Factory manager

`factory-manager` is a python package to create objects with their dependencies
from descriptions in the form of a dictionary.

## Example usage

Given the following hierarchy of Vehicles with and without a motor:

```python
>>> import abc
>>>
>>> class Vehicle(metaclass=abc.ABCMeta):
...     def __init__(self):
...         pass
...     @abc.abstractmethod
...     def getVehicleType(self):
...         pass
...
>>> class Bicycle(Vehicle):
...     name = 'bicycle'
...     def getVehicleType(self):
...         return 'Bicycle'
...
>>> class Engine(metaclass=abc.ABCMeta):
...     def __init__(self):
...         self.engine_type = self.name
...     def getEngineType(self):
...         return self.engine_type
...
>>> class GasolineEngine(Engine):
...     name = 'gasoline'
...
>>> class ElectroEngine(Engine):
...     name = 'electro'
...
>>> class DieselEngine(Engine):
...     name = 'diesel'
...
>>> class MotorizedVehicle(Vehicle):
...     def __init__(self, engine: Engine, registered_drivers:set = None):
...         self.engine = engine
...         if registered_drivers is None:
...             self.registered_drivers = set()
...         else:
...             self.registered_drivers = registered_drivers
...
>>> class Car(MotorizedVehicle):
...     name = 'car'
...     def getVehicleType(self):
...         return 'Car' + ' with ' + self.engine.getEngineType() + ' engine'
...
>>> class Motorbike(MotorizedVehicle):
...     name = 'motorbike'
...     def getVehicleType(self):
...         return 'Motorbike' + ' with ' + self.engine.getEngineType() + ' engine'
```

We can now use a `FactoryManager` to create a factory for vehicles:  

```python
>>> from factory_manager import FactoryManager
>>> my_factories = FactoryManager()
>>> my_factories.add_object_hierarchy("vehicle", Vehicle)
```

The `FactoryManager` searches for subclasses of the given class (`Vehicle`) and
uses the existence of the class attribute `name` as an indicator that the subclass should be a type of a vehicle that the factory provides.
In the given example `my_factories` can be used to create bicycles, cars and motorbikes.

A bicycle can be instantiated as follows (either by using the bass class or the name given to `add_object_hierarchy` to select the factory):

```python
>>> my_factories.create_from_cls(Vehicle, {'type': 'bicycle'}).getVehicleType()
'Bicycle'
>>> my_factories.create_from_name('vehicle', {'type': 'bicycle'}).getVehicleType()
'Bicycle'
```

When creating a car, an engine has to be provided:

```python
>>> my_factories.create_from_name('vehicle', {'type': 'car'}).getVehicleType()
Traceback <...>
ValueError: Missing options [engine] for vehicle of type car
>>> my_factories.create_from_name('vehicle', {'type': 'car', 'options': {
...     'engine': ElectroEngine()}}).getVehicleType()
'Car with electro engine'
```

Now we can also register `Engine` with the `FactoryManager`:

```python
>>> my_factories.add_object_hierarchy("engine", Engine)
```

This allows us to create an engine similar to vehicle:


```python
>>> my_factories.create_from_name('engine', {'type': 'electro'}).getEngineType()
'electro'
```

But it also allows us to create a vehicle and an engine in one step as `my_factories` knows from the type hint that a motorized vehicle needs an engine:

```python
>>> my_factories.create_from_name('vehicle', {'type': 'car', 'options': {
...     'engine': {'type': 'electro'}}}).getVehicleType()
'Car with electro engine'
```

`registered_drivers` does not need to be provided as it has a default value.
But it can be provided:

```python
>>> my_factories.create_from_name('vehicle', {'type': 'car', 'options': {
...     'engine': {'type': 'electro'}}}).registered_drivers
set()
>>> sorted(
...     my_factories.create_from_name('vehicle', {'type': 'car', 'options': {
...         'engine': {'type': 'electro'},
...         'registered_drivers': set(['driver1', 'driver2'])
...     }}).registered_drivers)
['driver1', 'driver2']
```

There is no type checking, so the value for `registered_drivers` does not have to be a `set`:

```python
>>> my_factories.create_from_name('vehicle', {'type': 'car', 'options': {
...    'engine': {'type': 'electro'},
...    'registered_drivers': ['driver1', 'driver2', 'driver1']
... }}).registered_drivers
['driver1', 'driver2', 'driver1']
```

However, we can register a factory method for the type `set`.
Now, the factory for `Vehicle` will call this factory for the provided
attribute if it is not already a `set`:

```python
>>> my_factories.add_factory_method(set, lambda s: set(s))
```

```python
>>> sorted(
...     my_factories.create_from_name('vehicle', {'type': 'car', 'options': {
...         'engine': {'type': 'electro'},
...         'registered_drivers': ['driver1', 'driver2', 'driver1']
...     }}).registered_drivers)
['driver1', 'driver2']
```

This is useful if the dictionary used to instantiate the object comes
from json data.