Metadata-Version: 2.1
Name: construction-requirements-integrator
Version: 0.2.1
Summary: With the help of this module, classes can be inherited that are built and configured after their needs are met (instead of being launched immediately after creation).
Home-page: https://github.com/BehzadShayegh/construction-requirements-integrator
Author: Behzad Shayegh
Author-email: behzad.shayegh@ut.ac.ir
License: UNKNOWN
Project-URL: Bug Tracker, https://github.com/BehzadShayegh/construction-requirements-integrator/issues
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE

# Construction Requirements Integrator Package

With the help of this module, classes can be inherited that are built and configured after their needs are met (instead of being launched immediately after creation).
You can see an example of this application below.

In this example, the `Example` class needs 3 arguments `x`,`y` and `z` to be constructed. For example, it will calculate volume of a cube in its constructor, so it needs all the arguments at the same time.
We want to initialize `x` and `y` for our `Example` instance using instances of `XProvider` and `YProvider` classes.
The problem is there both `XProvider` and `YProvder` need their target object to provide their values.
So we need to have an uncompleted instance of `Example` till `XProvider` and `YProvider` finish their processes. Then the instance can complete its construction.

* Inherit your class, that needs uncompleted construction, from `CRI` abstract class.
* Pass the construction reqired arguments to the `CRI.__init__` (in the `__init__` function of inherited class) as below. We will call them "construction requirements". Don't forget to set default value of the delayable construction requirements in the `__init__` function of inherited class to `None`. The `None` value is what `CRI` knows as "NOT YET"!
* Override abstract `__construct__` function in the inherited class. Arguments are the same as construction requirements.
* Once you get an instance of your inherited class, you can pass it each construction requirement value that you already know, as initialization arguments. After that, you can assign values to construction requirements using `instance.meet_requirement` function as in the example below.
* The instance starts to complete the construction, As soon as the class requirements are met.
* Use `construction_required` decorator to avoid running a function before completion of the construction.
In the example below, `get_construction_status` can be called before completion of construction but `get_volume` can not.

```python
from construction_requirements_integrator import CRI, construction_required
from random import random

class XProvider:
    def __init__(self):
        self.x = int((random()*10))

    def provide_for(self, obj):
        obj.meet_requirement(x=self.x)

class YProvider:
    def __init__(self):
        self.y = int((random()*5))

    def provide_for(self, obj):
        obj.meet_requirement(y=self.y)

class Example(CRI):
    def __init__(self, x=None, y=None, z=None):
        CRI.__init__(self, x=x, y=y, z=z)

    def __construct__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        self.volume = x*y*z

    def get_construction_status(self):
        return self.is_constructed

    @construction_required
    def get_volume(self):
        return self.volume

example1 = Example(z=2)
XProvider().provide_for(example1)
YProvider().provide_for(example1)
print(example1.get_construction_status())
# >>> True
print(example1.x, example1.y, example1.z)
# >>> 6 2 2
print(example1.get_volume())
# >>> 24

example2 = Example(z=2)
print(example2.get_construction_status())
# >>> False
print(example2.get_volume())
# Exception: The object is not constructed yet!
```

When calling the `__init__` function from the `CRI` class, you can input settings:

* `overwrite_requirement (default: False)`: If true, if one construction requirement meets multiple times, the previous values will be ignored and the new value replaced. Else, based on `ignore_overwrite_error` setting, new value will be ignored or cause an exception.
* `ignore_overwrite_error (default: False)`: If `overwrite_requirement` be not true and one construction requirement meets multiple times, the object raises an error. This error will not be published if `ignore_overwrite_error` is true.
* `auto_construct (default: True)`: If true, the class starts to complete the construction, As soon as the class requirements are met. If false, You must call `integrate_requirements` function to complete the construction.
Use `ignore_requirements_meeting_error` argument of `integrate_requirements` function to manage raising exception it.
* `purge_after_construction (default: True)`: The class does not need the construction requirements after completion of cunstruction (unless it is stored again during the construction process).
Therefore, after completing this process, it will delete them.

```python
print(example1.__dict__)
# >>> {'_CRI__reconstruct': False, 'is_constructed': True, 'x': 6, 'y': 1, 'z': 2, 'volume': 12}
print(example2.__dict__)
# >>> {'_CRI__requirements': {'x': None, 'y': None, 'z': 2}, '_CRI__overwrite_requirement': False, '_CRI__ignore_overwrite_error': False, '_CRI__auto_construct': True, '_CRI__purge_after_construction': True, '_CRI__reconstruct': False, 'is_constructed': False}
```

You can prevent this deletion by setting `purge_after_construction` to `False`.
* `reconstruct (default: False)`: If true, allows the class to be reconstructed with new values. Note that you can not set both `purge_after_construction` and `reconstruct` to `True` because reconstruction needs construction requirements. Also note that if `auto_construct` be true, every `meet_requirement` call has the potential to reconstruct the object.
* `construction_permission (default: True)`: While it is false, the object can not be constructed. If you want the object be auto constructable but you want to ensure it will not be constructed till some event, you can initialize it to `False` and after the event, flip it to `True`. Change construnction permission using `set_construction_permission` function.

**add_to_construction_requirements(self, \*\*requirements):** Use this function to add to construction requirements after initialization. Its very useful when you are using inheritance.

**A technique:** If `auto_construct` be true and all the requirements defined in the initialization satisfied befor calling `add_to_construction_requirements`, the object will be completly constructed and will not catch new requirements. To prevent this state, you can simply set `construction_permission` to `False`. It will prevent the object to be auto constructed untill you call `instance.set_construction_permission(True)`. Use this function after calling `add_to_construction_requirements`.

```python
from construction_requirements_integrator import CRI, construction_required
from random import random


class Parent(CRI):
    def __init__(self, x=None, y=None, construction_permission=True):
        CRI.__init__(self, x=x, y=y, construction_permission=construction_permission)

    def __construct__(self, x, y):
        self.x = x
        self.y = y
        self.s = self.x*self.y

    @construction_required
    def get_s(self):
        return self.s

class Child(Parent):
    def __init__(self, z=None, **kwargs):
        super().__init__(construction_permission=False, **kwargs)
        self.add_to_construction_requirements(z=z)
        self.set_construction_permission(True)

    def __construct__(self, z, **kwargs):
        super().__construct__(**kwargs)
        self.z = z
        self.v = self.x*self.y*self.z

    @construction_required
    def get_v(self):
        return self.v

p = Parent(x=2, y=3)
print(p.get_s())
# >>> 6
c = Child(x=2, y=3)
print(c.is_constructed)
# >>> False
c.meet_requirement(z=4)
print(c.is_constructed)
# >>> True
print(c.get_v())
# >>> 24
c2 = Child(x=2, y=3, z=4)
print(c2.get_v())
# >>> 24
```

## Installation

```pip install construction-requirements-integrator```

