Merge pull request #84 from rmk135/0.7.6

0.7.6 current progress
This commit is contained in:
Roman 2015-07-24 19:50:47 +03:00
commit 55a3e94d57
69 changed files with 2114 additions and 651 deletions

3
.gitignore vendored
View File

@ -64,3 +64,6 @@ venv/
# JointJS Experiments # JointJS Experiments
jointjs/ jointjs/
# Vim Rope
.ropeproject/

View File

@ -1,4 +1,4 @@
Copyright (c) 2015, Roman Copyright (c) 2015, Roman Mogilatov
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -1,27 +1,27 @@
Objects Objects
======= =======
Dependency management tool for Python projects Dependency injection framework for Python projects.
+---------------------------------------+-------------------------------------------------------------------+ +---------------------------------------+-------------------------------------------------------------------+
| *PyPi* | .. image:: https://pypip.in/version/Objects/badge.svg | | *PyPi* | .. image:: https://img.shields.io/pypi/v/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Latest Version | | | :alt: Latest Version |
| | .. image:: https://pypip.in/download/Objects/badge.svg | | | .. image:: https://img.shields.io/pypi/dm/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Downloads | | | :alt: Downloads |
| | .. image:: https://pypip.in/license/Objects/badge.svg | | | .. image:: https://img.shields.io/pypi/l/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: License | | | :alt: License |
+---------------------------------------+-------------------------------------------------------------------+ +---------------------------------------+-------------------------------------------------------------------+
| *Python versions and implementations* | .. image:: https://pypip.in/py_versions/Objects/badge.svg | | *Python versions and implementations* | .. image:: https://img.shields.io/pypi/pyversions/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Supported Python versions | | | :alt: Supported Python versions |
| | .. image:: https://pypip.in/implementation/Objects/badge.svg | | | .. image:: https://img.shields.io/pypi/implementation/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Supported Python implementations | | | :alt: Supported Python implementations |
+---------------------------------------+-------------------------------------------------------------------+ +---------------------------------------+-------------------------------------------------------------------+
| *Builds and test coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master | | *Builds and tests coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master |
| | :target: https://travis-ci.org/rmk135/objects | | | :target: https://travis-ci.org/rmk135/objects |
| | :alt: Build Status | | | :alt: Build Status |
| | .. image:: https://coveralls.io/repos/rmk135/objects/badge.svg | | | .. image:: https://coveralls.io/repos/rmk135/objects/badge.svg |
@ -32,31 +32,45 @@ Dependency management tool for Python projects
Introduction Introduction
------------ ------------
Python ecosystem consists of a big amount of various classes, functions and Python ecosystem consists of a big amount of various libraries that contain
objects that could be used for applications development. Each of them has its different classes and functions that could be used for applications
own role. development. Each of them has its own role.
Modern Python applications are mostly the composition of well-known open Modern Python applications are mostly the composition of well-known open
source systems, frameworks, libraries and some turnkey functionality. source systems / frameworks / libraries and some turnkey functionality.
When application goes bigger, its amount of objects and their dependencies When application goes bigger, its complexity and SLOC_ are also increased.
also increased extremely fast and became hard to maintain. Being driven by SOLID_ (for example), developers often start to split
application's sources into not so big classes, functions and modules. It
always helps, but there is another problem on the horizon.
**Objects** is designed to be developer's friendly tool for managing objects It sounds like "I have so many classes and functions! They are great, now I can
and their dependencies in formal, pretty way. Main idea of **Objects** is to understand each of them, but it is so hard to see the whole picture! How are
keep dependencies under control. they linked with each other? What dependencies does this class have?". And
this is a key question: "What dependencies do certain class / function have?".
To resolve this issues developers have to go inside with IoC_ principles and
implementation patterns.
One of such IoC_ implementation patterns is called `dependency injection`_.
*Objects* is a dependency injection framework for Python projects.
It was designed to be developer's friendly tool for managing any kind of
Python objects and their dependencies in formal, pretty way.
Main idea of *Objects* is to keep dependencies under control.
Installation Installation
------------ ------------
**Objects** library is available on PyPi_:: *Objects* library is available on PyPi_::
pip install objects pip install objects
Documentation Documentation
------------- -------------
**Objects** documentation is hosted on ReadTheDocs: *Objects* documentation is hosted on ReadTheDocs:
- `Stable version`_ - `Stable version`_
- `Latest version`_ - `Latest version`_
@ -70,12 +84,12 @@ Examples
from objects.catalog import AbstractCatalog from objects.catalog import AbstractCatalog
from objects.providers import Factory
from objects.providers import Singleton from objects.providers import Singleton
from objects.providers import NewInstance
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import Attribute from objects.injections import Attribute
from objects.injections import inject from objects.decorators import inject
import sqlite3 import sqlite3
@ -108,19 +122,19 @@ Examples
Attribute('row_factory', sqlite3.Row)) Attribute('row_factory', sqlite3.Row))
""":type: (objects.Provider) -> sqlite3.Connection""" """:type: (objects.Provider) -> sqlite3.Connection"""
object_a = NewInstance(ObjectA, object_a_factory = Factory(ObjectA,
KwArg('db', database)) KwArg('db', database))
""":type: (objects.Provider) -> ObjectA""" """:type: (objects.Provider) -> ObjectA"""
object_b = NewInstance(ObjectB, object_b_factory = Factory(ObjectB,
KwArg('a', object_a), KwArg('a', object_a_factory),
KwArg('db', database)) KwArg('db', database))
""":type: (objects.Provider) -> ObjectB""" """:type: (objects.Provider) -> ObjectB"""
# Catalog static provides. # Catalog static provides.
a1, a2 = Catalog.object_a(), Catalog.object_a() a1, a2 = Catalog.object_a_factory(), Catalog.object_a_factory()
b1, b2 = Catalog.object_b(), Catalog.object_b() b1, b2 = Catalog.object_b_factory(), Catalog.object_b_factory()
assert a1 is not a2 assert a1 is not a2
assert b1 is not b2 assert b1 is not b2
@ -128,8 +142,8 @@ Examples
# Example of inline injections. # Example of inline injections.
@inject(KwArg('a', Catalog.object_a)) @inject(KwArg('a', Catalog.object_a_factory))
@inject(KwArg('b', Catalog.object_b)) @inject(KwArg('b', Catalog.object_b_factory))
@inject(KwArg('database', Catalog.database)) @inject(KwArg('database', Catalog.database))
def example(a, b, database): def example(a, b, database):
assert a.db is b.db is database is Catalog.database() assert a.db is b.db is database is Catalog.database()
@ -137,7 +151,8 @@ Examples
example() example()
You can get more **Objects** examples in ``/examples`` directory on
You can get more *Objects* examples in ``/examples`` directory on
GitHub: GitHub:
https://github.com/rmk135/objects https://github.com/rmk135/objects
@ -147,7 +162,7 @@ Feedback
-------- --------
Feel free to post questions, bugs, feature requests, proposals etc. on Feel free to post questions, bugs, feature requests, proposals etc. on
**Objects** GitHub Issues: *Objects* GitHub Issues:
https://github.com/rmk135/objects/issues https://github.com/rmk135/objects/issues
@ -157,3 +172,7 @@ Your feedback is quite important!
.. _PyPi: https://pypi.python.org/pypi/Objects .. _PyPi: https://pypi.python.org/pypi/Objects
.. _Stable version: http://objects.readthedocs.org/en/stable/ .. _Stable version: http://objects.readthedocs.org/en/stable/
.. _Latest version: http://objects.readthedocs.org/en/latest/ .. _Latest version: http://objects.readthedocs.org/en/latest/
.. _SLOC: http://en.wikipedia.org/wiki/Source_lines_of_code
.. _SOLID: http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29
.. _IoC: http://en.wikipedia.org/wiki/Inversion_of_control
.. _dependency injection: http://en.wikipedia.org/wiki/Dependency_injection

View File

@ -1 +1 @@
0.7.5 0.7.6

83
docs/_providers.rst Normal file
View File

@ -0,0 +1,83 @@
Providers
=========
Providers delegation
--------------------
Overriding of providers
-----------------------
Any provider can be overridden by another provider.
Example:
.. code-block:: python
"""Providers overriding example."""
import sqlite3
from objects.providers import Factory
from objects.providers import Singleton
from objects.injections import KwArg
from objects.injections import Attribute
class ObjectA(object):
"""ObjectA has dependency on database."""
def __init__(self, database):
"""Initializer.
Database dependency need to be injected via init arg."""
self.database = database
def get_one(self):
"""Select one from database and return it."""
return self.database.execute('SELECT 1')
class ObjectAMock(ObjectA):
"""Mock of ObjectA.
Has no dependency on database.
"""
def __init__(self):
"""Initializer."""
def get_one(self):
"""Select one from database and return it.
Mock makes no database queries and always returns two instead of one.
"""
return 2
# Database and `ObjectA` providers.
database = Singleton(sqlite3.Connection,
KwArg('database', ':memory:'),
KwArg('timeout', 30),
KwArg('detect_types', True),
KwArg('isolation_level', 'EXCLUSIVE'),
Attribute('row_factory', sqlite3.Row))
object_a_factory = Factory(ObjectA,
KwArg('database', database))
# Overriding `ObjectA` provider with `ObjectAMock` provider.
object_a_factory.override(Factory(ObjectAMock))
# Creating several `ObjectA` instances.
object_a_1 = object_a_factory()
object_a_2 = object_a_factory()
# Making some asserts.
assert object_a_1 is not object_a_2
assert object_a_1.get_one() == object_a_2.get_one() == 2

View File

@ -1,13 +1,13 @@
Advanced usage Advanced usage
============== ==============
Below you can find some variants of advanced usage of **Objects**. Below you can find some variants of advanced usage of *Objects*.
@inject decorator @inject decorator
----------------- -----------------
``@inject`` decorator could be used for patching any callable with injection. ``@inject`` decorator could be used for patching any callable with injection.
Any Python object will be injected *as is*, except **Objects** providers, Any Python object will be injected *as is*, except *Objects* providers,
that will be called to provide injectable value. that will be called to provide injectable value.
.. code-block:: python .. code-block:: python

2
docs/catalogs.rst Normal file
View File

@ -0,0 +1,2 @@
Catalogs
========

51
docs/decorators.rst Normal file
View File

@ -0,0 +1,51 @@
Decorators
==========
Current section of *Objects* documentation describes several useful decorators.
@inject decorator
-----------------
``@inject`` decorator can be used for making *inline* dependency injections.
It *patches* decorated callable in such way that dependency injection will be
done before every call of decorated callable.
``@inject`` decorator takes only argument that is supposed to be an
``objects.injections.Injection`` instance.
Any Python object will be injected *as is*, except *Objects* providers,
that will be called to provide injectable value.
Below is an example of how Flask's view could be patched using ``@inject``
decorator:
.. code-block:: python
"""`@inject` decorator example."""
from objects.providers import NewInstance
from objects.injections import KwArg
from objects.injections import inject
new_object = NewInstance(object)
@inject(KwArg('object_a', new_object))
@inject(KwArg('some_setting', 1334))
def example_callback(object_a, some_setting):
"""This function has dependencies on object a and b.
Dependencies are injected using `@inject` decorator.
"""
assert isinstance(object_a, object)
assert some_setting == 1334
example_callback()
example_callback()
@override decorator
-------------------

View File

@ -1,7 +1,7 @@
Entities Entities
======== ========
Current section describes main **Objects** entities and their interaction. Current section describes main *Objects* entities and their interaction.
Providers Providers
--------- ---------
@ -45,10 +45,10 @@ dependencies of objects.
Objects can take dependencies in various forms. Some objects take init Objects can take dependencies in various forms. Some objects take init
arguments, other are using attributes or methods to be initialized. Injection, arguments, other are using attributes or methods to be initialized. Injection,
in terms of **Objects**, is an instruction how to provide dependency for the in terms of *Objects*, is an instruction how to provide dependency for the
particular object. particular object.
Every Python object could be an injection's value. Special case is a **Objects** Every Python object could be an injection's value. Special case is a *Objects*
provider as an injection's value. In such case, injection value is a result of provider as an injection's value. In such case, injection value is a result of
injectable provider call (every time injection is done). injectable provider call (every time injection is done).
@ -107,7 +107,7 @@ Catalogs
Catalogs are named set of providers. Catalogs are named set of providers.
**Objects** catalogs can be used for grouping of providers by some *Objects* catalogs can be used for grouping of providers by some
kind of rules. In example below, there are two catalogs: kind of rules. In example below, there are two catalogs:
``Resources`` and ``Models``. ``Resources`` and ``Models``.

View File

@ -1,7 +1,7 @@
Examples Examples
======== ========
You can get more **Objects** examples in ``/examples`` directory on You can get more *Objects* examples in ``/examples`` directory on
GitHub: GitHub:
https://github.com/rmk135/objects https://github.com/rmk135/objects

View File

@ -2,7 +2,7 @@ Feedback
======== ========
Feel free to post questions, bugs, feature requests, proposals etc. on Feel free to post questions, bugs, feature requests, proposals etc. on
**Objects** GitHub Issues: *Objects* GitHub Issues:
https://github.com/rmk135/objects/issues https://github.com/rmk135/objects/issues

BIN
docs/images/callable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/images/factory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/images/singleton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,27 +1,27 @@
Objects Objects
======= =======
Dependency management tool for Python projects. Dependency injection framework for Python projects.
+---------------------------------------+-------------------------------------------------------------------+ +---------------------------------------+-------------------------------------------------------------------+
| *PyPi* | .. image:: https://pypip.in/version/Objects/badge.svg | | *PyPi* | .. image:: https://img.shields.io/pypi/v/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Latest Version | | | :alt: Latest Version |
| | .. image:: https://pypip.in/download/Objects/badge.svg | | | .. image:: https://img.shields.io/pypi/dm/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Downloads | | | :alt: Downloads |
| | .. image:: https://pypip.in/license/Objects/badge.svg | | | .. image:: https://img.shields.io/pypi/l/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: License | | | :alt: License |
+---------------------------------------+-------------------------------------------------------------------+ +---------------------------------------+-------------------------------------------------------------------+
| *Python versions and implementations* | .. image:: https://pypip.in/py_versions/Objects/badge.svg | | *Python versions and implementations* | .. image:: https://img.shields.io/pypi/pyversions/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Supported Python versions | | | :alt: Supported Python versions |
| | .. image:: https://pypip.in/implementation/Objects/badge.svg | | | .. image:: https://img.shields.io/pypi/implementation/Objects.svg |
| | :target: https://pypi.python.org/pypi/Objects/ | | | :target: https://pypi.python.org/pypi/Objects/ |
| | :alt: Supported Python implementations | | | :alt: Supported Python implementations |
+---------------------------------------+-------------------------------------------------------------------+ +---------------------------------------+-------------------------------------------------------------------+
| *Builds and test coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master | | *Builds and tests coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master |
| | :target: https://travis-ci.org/rmk135/objects | | | :target: https://travis-ci.org/rmk135/objects |
| | :alt: Build Status | | | :alt: Build Status |
| | .. image:: https://coveralls.io/repos/rmk135/objects/badge.svg | | | .. image:: https://coveralls.io/repos/rmk135/objects/badge.svg |
@ -38,7 +38,9 @@ Contents
introduction introduction
installation installation
entities providers/index
advanced_usage injections
catalogs
decorators
examples examples
feedback feedback

37
docs/injections.rst Normal file
View File

@ -0,0 +1,37 @@
Injections
==========
Injections are *Objects* entities that are used for specification of dependency
injection types.
Different functions, classes and objects can take dependency injections in
various forms. Some of them take dependencies like keyword arguments during
call time, other require setting of attributes or calling of specialized
methods for doing dependency injections.
So, when you are doing dependency injection you need to specify its type and
that is the place where *Injections* need to be used.
Some key points of *Objects* injections:
- Every *Objects* injection always takes injectable value as an
``injectable`` param. Every Python object could be an injectable.
- Every *Objects* injection always has ``value`` property that returns
injection's injectable. ``value`` property is calculated every time it is
accessed. Every Python object, except of *Objects* providers, that was
provided as and ``injectable`` will be returned by ``value`` property
*"as is"*. *Objects* providers will be called every time during ``value``
accessing and result of such calls will be returned.
- Every *Objects* *Injection* can have additional params that are needed
for doing particular type of injection.
There are several types of *Injections*:
- ``KwArg`` - is used for making keyword argument injections for any kind
of callables (functions, methods, objects instantiation and so on). Takes
keyword argument name as string and injectable.
- ``Attribute`` - is used for making injections by setting of injection's
value to a particular attribute. Takes attribute name as string and
injectable.
- ``Method`` - is used for making injections by calling of method with
injectable value. Takes method name as string and injectable.

View File

@ -1,15 +1,19 @@
Installation Installation
============ ============
Latest stable version of **Objects** library can be installed from PyPi_:: Latest stable version of *Objects* framework can be installed from PyPi_:
.. code-block:: bash
pip install objects pip install objects
Sources can be cloned from GitHub_:: Sources can be cloned from GitHub_:
.. code-block:: bash
git clone https://github.com/rmk135/objects.git git clone https://github.com/rmk135/objects.git
All **Objects** releases can be found on GitHub: https://github.com/rmk135/objects/releases All *Objects* releases can be found on GitHub: https://github.com/rmk135/objects/releases
.. _PyPi: https://pypi.python.org/pypi/Objects .. _PyPi: https://pypi.python.org/pypi/Objects
.. _GitHub: https://github.com/rmk135/objects .. _GitHub: https://github.com/rmk135/objects

View File

@ -1,16 +1,36 @@
Introduction Introduction
============ ============
Python ecosystem consists of a big amount of various classes, functions and Python ecosystem consists of a big amount of various libraries that contain
objects that could be used for applications development. Each of them has its different classes and functions that could be used for applications
own role. development. Each of them has its own role.
Modern Python applications are mostly the composition of well-known open Modern Python applications are mostly the composition of well-known open
source systems, frameworks, libraries and some turnkey functionality. source systems / frameworks / libraries and some turnkey functionality.
When application goes bigger, its amount of objects and their dependencies When application goes bigger, its complexity and SLOC_ are also increased.
also increased extremely fast and became hard to maintain. Being driven by SOLID_ (for example), developers often start to split
application's sources into not so big classes, functions and modules. It
always helps, but there is another problem on the horizon.
**Objects** is designed to be developer's friendly tool for managing objects It sounds like "I have so many classes and functions! They are great, now I can
and their dependencies in formal, pretty way. Main idea of **Objects** is to understand each of them, but it is so hard to see the whole picture! How are
keep dependencies under control. they linked with each other? What dependencies does this class have?". And
this is a key question: "What dependencies do certain class / function have?".
To resolve this issues developers have to go inside with IoC_ principles and
implementation patterns.
One of such IoC_ implementation patterns is called `dependency injection`_.
*Objects* is a dependency injection framework for Python projects.
It was designed to be developer's friendly tool for managing any kind of
Python objects and their dependencies in formal, pretty way.
Main idea of *Objects* is to keep dependencies under control.
.. _SLOC: http://en.wikipedia.org/wiki/Source_lines_of_code
.. _SOLID: http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29
.. _IoC: http://en.wikipedia.org/wiki/Inversion_of_control
.. _dependency injection: http://en.wikipedia.org/wiki/Dependency_injection

View File

@ -0,0 +1,73 @@
Callable providers
------------------
``Callable`` provider is a provider that wraps particular callable with
some injections. Every call of this provider returns result of call of initial
callable.
Callable providers and injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Callable`` provider uses ``KwArg`` injections. ``KwArg`` injections are
done by passing injectable values as keyword arguments during call time.
Context keyword arguments have higher priority than ``KwArg`` injections.
Example:
.. image:: /images/callable.png
:width: 100%
:align: center
.. code-block:: python
"""`Callable` providers example."""
from passlib.hash import sha256_crypt
from objects.providers import Callable
from objects.injections import KwArg
# Password hasher and verifier providers (hash function could be changed
# anytime (for example, to sha512) without any changes in client's code):
password_hasher = Callable(sha256_crypt.encrypt,
KwArg('salt_size', 16),
KwArg('rounds', 10000))
password_verifier = Callable(sha256_crypt.verify)
# Making some asserts (client's code):
hashed_password = password_hasher('super secret')
assert password_verifier('super secret', hashed_password)
Callable providers delegation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Callable`` provider could be delegated to any other provider via any kind of
injection. Delegation of ``Callable`` providers is the same as ``Factory`` and
``Singleton`` providers delegation, please follow *Factory providers
delegation* section for example.
``Callable`` delegate could be created obviously using
``Delegate(Callable())`` or by calling ``Callable.delegate()`` method.
Example:
.. code-block:: python
"""`Callable` providers delegation example."""
import sys
from objects.providers import Callable
from objects.providers import Delegate
# Some callable provider and few delegates of it:
callable_provider = Callable(sys.exit)
callable_provider_delegate1 = callable_provider.delegate()
callable_provider_delegate2 = Delegate(callable_provider)
# Making some asserts:
assert callable_provider_delegate1() is callable_provider
assert callable_provider_delegate2() is callable_provider

View File

@ -0,0 +1,2 @@
Extending of providers
----------------------

View File

@ -0,0 +1,121 @@
External dependency providers
-----------------------------
``ExternalDependency`` provider can be useful for development of
self-sufficient libraries / modules / applications that has required external
dependencies.
For example, you have created self-sufficient library / module / application,
that has dependency on *database connection*.
Second step you want to do is to make this software component to be easy
reusable by wide amount of developers and to be easily integrated into many
applications.
It may be good idea, to move all external dependencies (like
*database connection*) to the top level and make them to be injected on your
software component's initialization. It will make third party developers feel
themselves free about integration of yours component in their applications,
because they would be able to find right place / right way for doing this
in their application's architectures.
At the same time, you can be sure, that your external dependency will be
satisfied with appropriate instance.
Example:
.. note::
Class ``UserService`` is a part of some library. ``UserService`` has
dependency on database connection, which can be satisfied with any
DBAPI 2.0 database connection. Being a self-sufficient library,
``UserService`` doesn't hardcode any kind of database management logic.
Instead of this, ``UserService`` has external dependency, that has to
be satisfied by cleint's code, out of library's scope.
.. image:: /images/external_dependency.png
.. code-block:: python
"""`ExternalDependency` providers example."""
from objects.providers import ExternalDependency
from objects.providers import Factory
from objects.providers import Singleton
from objects.injections import KwArg
from objects.injections import Attribute
# Importing SQLITE3 and contextlib.closing for working with cursors:
import sqlite3
from contextlib import closing
# Definition of example UserService:
class UserService(object):
"""Example class UserService.
UserService has dependency on DBAPI 2.0 database connection.
"""
def __init__(self, database):
"""Initializer.
Database dependency need to be injected via init arg.
"""
self.database = database
def init_database(self):
"""Initialize database, if it has not been initialized yet."""
with closing(self.database.cursor()) as cursor:
cursor.execute("""
CREATE TABLE IF NOT EXISTS users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(32)
)
""")
def create(self, name):
"""Create user with provided name and return his id."""
with closing(self.database.cursor()) as cursor:
cursor.execute('INSERT INTO users(name) VALUES (?)', (name,))
return cursor.lastrowid
def get_by_id(self, id):
"""Return user info by user id."""
with closing(self.database.cursor()) as cursor:
cursor.execute('SELECT id, name FROM users WHERE id=?', (id,))
return cursor.fetchone()
# Database and UserService providers:
database = ExternalDependency(instance_of=sqlite3.dbapi2.Connection)
users_service_factory = Factory(UserService,
KwArg('database', database))
# Out of library's scope.
#
# Setting database provider:
database.provided_by(Singleton(sqlite3.dbapi2.Connection,
KwArg('database', ':memory:'),
KwArg('timeout', 30),
KwArg('detect_types', True),
KwArg('isolation_level', 'EXCLUSIVE'),
Attribute('row_factory', sqlite3.Row)))
# Creating UserService instance:
users_service = users_service_factory()
# Initializing UserService database:
users_service.init_database()
# Creating test user and retrieving full information about him:
test_user_id = users_service.create(name='test_user')
test_user = users_service.get_by_id(test_user_id)
# Making some asserts:
assert test_user['id'] == 1
assert test_user['name'] == 'test_user'

430
docs/providers/factory.rst Normal file
View File

@ -0,0 +1,430 @@
Factory providers
-----------------
``Factory`` provider creates new instance of specified class on every call.
Nothing could be better than brief example:
.. image:: /images/factory.png
:width: 80%
:align: center
.. code-block:: python
"""`Factory` providers example."""
from objects.providers import Factory
class User(object):
"""Example class User."""
# Factory provider creates new instance of specified class on every call.
users_factory = Factory(User)
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert user1 is not user2
assert isinstance(user1, User) and isinstance(user2, User)
Factory providers and injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Objects can take dependencies in different forms. Some objects take init
arguments, other are using attributes setting or method calls to be
initialized. It affects how such objects need to be created and initialized,
and that is the place where ``objects.injections`` need to be used.
``Factory`` provider takes various number of positional arguments, that define
what kind of dependency injections need to be done.
All of those instructions are defined in ``objects.injections`` module and are
subclasses of ``objects.injections.Injection``. There are several types of
injections that are used by ``Factory`` provider:
- ``KwArg`` - injection is done by passing injectable value in object's
``__init__()`` method in time of object's creation via keyword argument.
Takes keyword name of ``__init__()`` argument and injectable value.
- ``Attribute`` - injection is done by setting specified attribute with
injectable value right after object's creation. Takes attribute's name
and injectable value.
- ``Method`` - injection is done by calling of specified method with
injectable value right after object's creation and attribute injections
are done. Takes method name and injectable value.
All ``Injection``'s injectable values are provided *"as is"*, except of
providers. Providers will be called every time, when injection needs to be
done.
Factory providers and __init__ injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Example below shows how to create ``Factory`` of particular class with
``__init__`` keyword argument injections which injectable values are also
provided by another factories:
.. image:: /images/factory_init_injections.png
.. code-block:: python
"""`Factory` providers with init injections example."""
from objects.providers import Factory
from objects.injections import KwArg
class User(object):
"""Example class User."""
def __init__(self, main_photo):
"""Initializer.
:param main_photo: Photo
:return:
"""
self.main_photo = main_photo
super(User, self).__init__()
class Photo(object):
"""Example class Photo."""
# User and Photo factories:
photos_factory = Factory(Photo)
users_factory = Factory(User,
KwArg('main_photo', photos_factory))
# Creating several User objects:
user1 = users_factory() # Same as: user1 = User(main_photo=Photo())
user2 = users_factory() # Same as: user2 = User(main_photo=Photo())
# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user1.main_photo, Photo)
assert isinstance(user2, User)
assert isinstance(user2.main_photo, Photo)
assert user1 is not user2
assert user1.main_photo is not user2.main_photo
Next example shows how ``Factory`` provider deals with positional and keyword
``__init__`` context arguments. In few words, ``Factory`` provider fully
passes positional context arguments to class's ``__init__`` method, but
keyword context arguments have priority on ``KwArg`` injections (this could be
useful for testing).
So, please, follow the example below:
.. image:: /images/factory_init_injections_and_contexts.png
.. code-block:: python
"""`Factory` providers with init injections and context arguments example."""
from objects.providers import Factory
from objects.injections import KwArg
class User(object):
"""Example class User.
Class User has to be provided with user id.
Also Class User has dependencies on class Photo and class CreditCard
objects.
All of the dependencies have to be provided like __init__ arguments.
"""
def __init__(self, id, main_photo, credit_card):
"""Initializer.
:param id: int
:param main_photo: Photo
:param credit_card: CreditCard
:return:
"""
self.id = id
self.main_photo = main_photo
self.credit_card = credit_card
super(User, self).__init__()
class Photo(object):
"""Example class Photo."""
class CreditCard(object):
"""Example class CreditCard."""
# User, Photo and CreditCard factories:
credit_cards_factory = Factory(CreditCard)
photos_factory = Factory(Photo)
users_factory = Factory(User,
KwArg('main_photo', photos_factory),
KwArg('credit_card', credit_cards_factory))
# Creating several User objects:
user1 = users_factory(1)
# Same as: user1 = User(1,
# main_photo=Photo(),
# credit_card=CreditCard())
user2 = users_factory(2)
# Same as: user2 = User(2,
# main_photo=Photo(),
# credit_card=CreditCard())
# Making some asserts:
assert user1.id == 1
assert isinstance(user1.main_photo, Photo)
assert isinstance(user1.credit_card, CreditCard)
assert user2.id == 2
assert isinstance(user2.main_photo, Photo)
assert isinstance(user2.credit_card, CreditCard)
assert user1.main_photo is not user2.main_photo
assert user1.credit_card is not user2.credit_card
# Context keyword arguments have priority on KwArg injections priority:
main_photo_mock = Photo()
credit_card_mock = CreditCard()
user3 = users_factory(3, main_photo=main_photo_mock,
credit_card=credit_card_mock)
assert user3.id == 3
assert user3.main_photo is main_photo_mock
assert user3.credit_card is credit_card_mock
Factory providers and attribute injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Example below shows how to create ``Factory`` of particular class with
attribute injections. Those injections are done by setting specified attributes
with injectable values right after object's creation.
Example:
.. image:: /images/factory_attribute_injections.png
.. code-block:: python
"""`Factory` providers with attribute injections example."""
from objects.providers import Factory
from objects.injections import Attribute
class User(object):
"""Example class User."""
def __init__(self):
"""Initializer."""
self.main_photo = None
self.credit_card = None
class Photo(object):
"""Example class Photo."""
class CreditCard(object):
"""Example class CreditCard."""
# User, Photo and CreditCard factories:
credit_cards_factory = Factory(CreditCard)
photos_factory = Factory(Photo)
users_factory = Factory(User,
Attribute('main_photo', photos_factory),
Attribute('credit_card', credit_cards_factory))
# Creating several User objects:
user1 = users_factory()
# Same as: user1 = User()
# user1.main_photo = Photo()
# user1.credit_card = CreditCard()
user2 = users_factory()
# Same as: user2 = User()
# user2.main_photo = Photo()
# user2.credit_card = CreditCard()
# Making some asserts:
assert user1 is not user2
assert isinstance(user1.main_photo, Photo)
assert isinstance(user1.credit_card, CreditCard)
assert isinstance(user2.main_photo, Photo)
assert isinstance(user2.credit_card, CreditCard)
assert user1.main_photo is not user2.main_photo
assert user1.credit_card is not user2.credit_card
Factory providers and method injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Current example shows how to create ``Factory`` of particular class with
method injections. Those injections are done by calling of specified method
with injectable value right after object's creation and attribute injections
are done.
Method injections are not very popular in Python due Python best practices
(usage of public attributes instead of setter methods), but it may appear in
some cases.
Example:
.. image:: /images/factory_method_injections.png
.. code-block:: python
"""`Factory` providers with method injections example."""
from objects.providers import Factory
from objects.injections import Method
class User(object):
"""Example class User."""
def __init__(self):
"""Initializer."""
self.main_photo = None
self.credit_card = None
def set_main_photo(self, photo):
"""Set user's main photo."""
self.main_photo = photo
def set_credit_card(self, credit_card):
"""Set user's credit card."""
self.credit_card = credit_card
class Photo(object):
"""Example class Photo."""
class CreditCard(object):
"""Example class CreditCard."""
# User, Photo and CreditCard factories:
credit_cards_factory = Factory(CreditCard)
photos_factory = Factory(Photo)
users_factory = Factory(User,
Method('set_main_photo', photos_factory),
Method('set_credit_card', credit_cards_factory))
# Creating several User objects:
user1 = users_factory()
# Same as: user1 = User()
# user1.set_main_photo(Photo())
# user1.set_credit_card(CreditCard())
user2 = users_factory()
# Same as: user2 = User()
# user2.set_main_photo(Photo())
# user2.set_credit_card(CreditCard())
# Making some asserts:
assert user1 is not user2
assert isinstance(user1.main_photo, Photo)
assert isinstance(user1.credit_card, CreditCard)
assert isinstance(user2.main_photo, Photo)
assert isinstance(user2.credit_card, CreditCard)
assert user1.main_photo is not user2.main_photo
assert user1.credit_card is not user2.credit_card
Factory providers delegation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Factory`` provider could be delegated to any other provider via any kind of
injection. Saying in other words, delegation of factories - is a way to inject
factories themselves, instead of results of their calls.
As it was mentioned earlier, ``Injection`` calls ``Factory`` if ``Factory`` is
injectable value. ``Factory`` delegation is performed by wrapping delegated
``Factory`` into special provider type - ``Delegate``, that just returns
``Factory`` itself.
Another one, more *convenient*, method of creating ``Delegate`` for ``Factory``
is just calling ``Factory.delegate()`` method that returns delegate for current
factory.
Example:
.. image:: /images/factory_delegation.png
.. code-block:: python
"""`Factory` providers delegation example."""
from objects.providers import Factory
from objects.injections import KwArg
class User(object):
"""Example class User."""
def __init__(self, photos_factory):
"""Initializer.
:param photos_factory: objects.providers.Factory
:return:
"""
self.photos_factory = photos_factory
self._main_photo = None
super(User, self).__init__()
@property
def main_photo(self):
"""Return user's main photo."""
if not self._main_photo:
self._main_photo = self.photos_factory()
return self._main_photo
class Photo(object):
"""Example class Photo."""
# User and Photo factories:
photos_factory = Factory(Photo)
users_factory = Factory(User,
KwArg('photos_factory', photos_factory.delegate()))
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user1.main_photo, Photo)
assert isinstance(user2, User)
assert isinstance(user2.main_photo, Photo)
assert user1 is not user2
assert user1.main_photo is not user2.main_photo

17
docs/providers/index.rst Normal file
View File

@ -0,0 +1,17 @@
Providers
=========
Providers are strategies of accessing objects.
All providers are callable. They describe how particular objects are provided.
.. toctree::
:maxdepth: 2
factory
singleton
static
callable
external_dependency
overriding
extending

View File

@ -0,0 +1,4 @@
Overriding of providers
-----------------------
Every provider could be overridden by another provider.

View File

@ -0,0 +1,134 @@
Singleton providers
-------------------
``Singleton`` provider creates new instance of specified class on first call
and returns same instance on every next call.
Example:
.. image:: /images/singleton.png
:width: 80%
:align: center
.. code-block:: python
"""`Singleton` providers example."""
from objects.providers import Singleton
class UserService(object):
"""Example class UserService."""
# Singleton provider creates new instance of specified class on first call and
# returns same instance on every next call.
users_service_provider = Singleton(UserService)
# Retrieving several UserService objects:
user_service1 = users_service_provider()
user_service2 = users_service_provider()
# Making some asserts:
assert user_service1 is user_service2
assert isinstance(user_service1, UserService)
assert isinstance(user_service2, UserService)
Singleton providers and injections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Singleton`` providers use ``Factory`` providers for first creation of
specified class instance, so, all of the rules about injections are the same,
as for ``Factory`` providers.
.. image:: /images/singleton_internals.png
:width: 80%
:align: center
.. note::
Due that ``Singleton`` provider creates specified class instance only on
the first call, all injections are done once, during the first call, also.
Every next call, while instance has been already created and memorized, no
injections are done, ``Singleton`` provider just returns memorized earlier
instance.
This may cause some problems, for example, in case of trying to bind
``Factory`` provider with ``Singleton`` provider (provided by dependent
``Factory`` instance will be injected only once, during the first call).
Be aware that such behaviour was made with opened eyes and is not a bug.
By the way, in such case, ``Delegate`` provider can be useful. It makes
possible to inject providers *as is*. Please check out full example in
*Providers delegation* section.
Singleton providers resetting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Created and memorized by ``Singleton`` instance can be reset. Reset of
``Singleton``'s memorized instance is done by clearing reference to it. Further
lifecycle of memorized instance is out of ``Singleton`` provider's control.
Example:
.. code-block:: python
"""`Singleton` providers resetting example."""
from objects.providers import Singleton
class UserService(object):
"""Example class UserService."""
# Users service singleton provider:
users_service_provider = Singleton(UserService)
# Retrieving several UserService objects:
user_service1 = users_service_provider()
user_service2 = users_service_provider()
# Making some asserts:
assert user_service1 is user_service2
assert isinstance(user_service1, UserService)
assert isinstance(user_service2, UserService)
# Resetting of memorized instance:
users_service_provider.reset()
# Retrieving one more UserService object:
user_service3 = users_service_provider()
# Making some asserts:
assert user_service3 is not user_service1
Singleton providers delegation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Singleton`` provider could be delegated to any other provider via any kind of
injection. Delegation of ``Singleton`` providers is the same as ``Factory``
providers delegation, please follow *Factory providers delegation* section for
example.
``Singleton`` delegate could be created obviously using
``Delegate(Singleton())`` or by calling ``Singleton.delegate()`` method.
Example:
.. code-block:: python
"""`Singleton` providers delegation example."""
from objects.providers import Singleton
from objects.providers import Delegate
# Some singleton provider and few delegates of it:
singleton_provider = Singleton(object)
singleton_provider_delegate1 = singleton_provider.delegate()
singleton_provider_delegate2 = Delegate(singleton_provider)
# Making some asserts:
assert singleton_provider_delegate1() is singleton_provider
assert singleton_provider_delegate2() is singleton_provider

42
docs/providers/static.rst Normal file
View File

@ -0,0 +1,42 @@
Static providers
----------------
Static providers are family of providers that return their values "as is".
There are four types of static providers:
- ``Class``
- ``Object``
- ``Function``
- ``Value``
All of them have the same behaviour, but usage of anyone is predicted by
readability and providing object's type.
Example:
.. code-block:: python
"""`Static` providers example."""
from objects.providers import Class
from objects.providers import Object
from objects.providers import Function
from objects.providers import Value
# Provides class - `object`:
cls_provider = Class(object)
assert cls_provider() is object
# Provides object - `object()`:
object_provider = Object(object())
assert isinstance(object_provider(), object)
# Provides function - `len`:
function_provider = Function(len)
assert function_provider() is len
# Provides value - `123`:
value_provider = Value(123)
assert value_provider() == 123

View File

@ -1,42 +0,0 @@
"""Callable provider examples."""
from objects.catalog import AbstractCatalog
from objects.providers import Singleton
from objects.providers import Callable
from objects.injections import KwArg
from objects.injections import Attribute
import sqlite3
def consuming_function(arg, db):
"""Example function that has input arg and dependency on database."""
return arg, db
class Catalog(AbstractCatalog):
"""Catalog of objects providers."""
database = Singleton(sqlite3.Connection,
KwArg('database', ':memory:'),
Attribute('row_factory', sqlite3.Row))
""":type: (objects.Provider) -> sqlite3.Connection"""
consuming_function = Callable(consuming_function,
KwArg('db', database))
""":type: (objects.Provider) -> consuming_function"""
# Some calls.
arg1, db1 = Catalog.consuming_function(1)
arg2, db2 = Catalog.consuming_function(2)
arg3, db3 = Catalog.consuming_function(3)
# Some asserts.
assert db1 is db2 is db3
assert arg1 == 1
assert arg2 == 2
assert arg3 == 3

View File

@ -2,12 +2,12 @@
from objects.catalog import AbstractCatalog from objects.catalog import AbstractCatalog
from objects.providers import Factory
from objects.providers import Singleton from objects.providers import Singleton
from objects.providers import NewInstance
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import Attribute from objects.injections import Attribute
from objects.injections import inject from objects.decorators import inject
import sqlite3 import sqlite3
@ -40,19 +40,19 @@ class Catalog(AbstractCatalog):
Attribute('row_factory', sqlite3.Row)) Attribute('row_factory', sqlite3.Row))
""":type: (objects.Provider) -> sqlite3.Connection""" """:type: (objects.Provider) -> sqlite3.Connection"""
object_a = NewInstance(ObjectA, object_a_factory = Factory(ObjectA,
KwArg('db', database)) KwArg('db', database))
""":type: (objects.Provider) -> ObjectA""" """:type: (objects.Provider) -> ObjectA"""
object_b = NewInstance(ObjectB, object_b_factory = Factory(ObjectB,
KwArg('a', object_a), KwArg('a', object_a_factory),
KwArg('db', database)) KwArg('db', database))
""":type: (objects.Provider) -> ObjectB""" """:type: (objects.Provider) -> ObjectB"""
# Catalog static provides. # Catalog static provides.
a1, a2 = Catalog.object_a(), Catalog.object_a() a1, a2 = Catalog.object_a_factory(), Catalog.object_a_factory()
b1, b2 = Catalog.object_b(), Catalog.object_b() b1, b2 = Catalog.object_b_factory(), Catalog.object_b_factory()
assert a1 is not a2 assert a1 is not a2
assert b1 is not b2 assert b1 is not b2
@ -60,8 +60,8 @@ assert a1.db is a2.db is b1.db is b2.db is Catalog.database()
# Example of inline injections. # Example of inline injections.
@inject(KwArg('a', Catalog.object_a)) @inject(KwArg('a', Catalog.object_a_factory))
@inject(KwArg('b', Catalog.object_b)) @inject(KwArg('b', Catalog.object_b_factory))
@inject(KwArg('database', Catalog.database)) @inject(KwArg('database', Catalog.database))
def example(a, b, database): def example(a, b, database):
assert a.db is b.db is database is Catalog.database() assert a.db is b.db is database is Catalog.database()

View File

@ -1,56 +0,0 @@
"""Provider delegation example."""
from objects.catalog import AbstractCatalog
from objects.providers import Singleton
from objects.providers import NewInstance
from objects.injections import KwArg
from objects.injections import Attribute
import sqlite3
class ObjectA(object):
"""Example class ObjectA, that has dependency on database."""
def __init__(self, db):
"""Initializer."""
self.db = db
class ObjectB(object):
"""Example class ObjectB, that has dependency on ObjectA provider."""
def __init__(self, a_provider):
"""Initializer."""
self.a_provider = a_provider
class Catalog(AbstractCatalog):
"""Catalog of objects providers."""
database = Singleton(sqlite3.Connection,
KwArg('database', ':memory:'),
Attribute('row_factory', sqlite3.Row))
""":type: (objects.Provider) -> sqlite3.Connection"""
object_a = NewInstance(ObjectA,
KwArg('db', database))
""":type: (objects.Provider) -> ObjectA"""
object_b = Singleton(ObjectB,
KwArg('a_provider', object_a.delegate()))
""":type: (objects.Provider) -> ObjectB"""
# Catalog static provides.
b = Catalog.object_b()
a1, a2 = b.a_provider(), b.a_provider()
# Some asserts.
assert a1 is not a2
assert a1.db is a2.db is Catalog.database()

View File

@ -1,63 +0,0 @@
"""External dependency example."""
from objects.catalog import AbstractCatalog
from objects.providers import Singleton
from objects.providers import NewInstance
from objects.providers import ExternalDependency
from objects.injections import KwArg
from objects.injections import Attribute
import sqlite3
class ObjectA(object):
"""Example class ObjectA, that has dependency on database."""
def __init__(self, db):
"""Initializer."""
self.db = db
class ObjectB(object):
"""Example class ObjectB, that has dependencies on ObjectA and database."""
def __init__(self, a, db):
"""Initializer."""
self.a = a
self.db = db
class Catalog(AbstractCatalog):
"""Catalog of objects providers."""
database = ExternalDependency(instance_of=sqlite3.Connection)
""":type: (objects.Provider) -> sqlite3.Connection"""
object_a = NewInstance(ObjectA,
KwArg('db', database))
""":type: (objects.Provider) -> ObjectA"""
object_b = NewInstance(ObjectB,
KwArg('a', object_a),
KwArg('db', database))
""":type: (objects.Provider) -> ObjectB"""
# Satisfaction of external dependency.
Catalog.database.override(Singleton(sqlite3.Connection,
KwArg('database', ':memory:'),
Attribute('row_factory', sqlite3.Row)))
# Catalog static provides.
a1, a2 = Catalog.object_a(), Catalog.object_a()
b1, b2 = Catalog.object_b(), Catalog.object_b()
# Some asserts.
assert a1 is not a2
assert b1 is not b2
assert a1.db is a2.db is b1.db is b2.db is Catalog.database()

View File

@ -1,7 +1,6 @@
"""Override example.""" """Override example."""
from objects.catalog import AbstractCatalog from objects.catalog import AbstractCatalog
from objects.catalog import override
from objects.providers import Singleton from objects.providers import Singleton
from objects.providers import NewInstance from objects.providers import NewInstance
@ -9,6 +8,8 @@ from objects.providers import NewInstance
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import Attribute from objects.injections import Attribute
from objects.decorators import override
import sqlite3 import sqlite3

View File

@ -0,0 +1,16 @@
"""`Callable` providers delegation example."""
import sys
from objects.providers import Callable
from objects.providers import Delegate
# Some callable provider and few delegates of it:
callable_provider = Callable(sys.exit)
callable_provider_delegate1 = callable_provider.delegate()
callable_provider_delegate2 = Delegate(callable_provider)
# Making some asserts:
assert callable_provider_delegate1() is callable_provider
assert callable_provider_delegate2() is callable_provider

View File

@ -0,0 +1,18 @@
"""`Callable` providers example."""
from passlib.hash import sha256_crypt
from objects.providers import Callable
from objects.injections import KwArg
# Password hasher and verifier providers (hash function could be changed
# anytime (for example, to sha512) without any changes in client's code):
password_hasher = Callable(sha256_crypt.encrypt,
KwArg('salt_size', 16),
KwArg('rounds', 10000))
password_verifier = Callable(sha256_crypt.verify)
# Making some asserts (client's code):
hashed_password = password_hasher('super secret')
assert password_verifier('super secret', hashed_password)

View File

@ -0,0 +1,80 @@
"""`ExternalDependency` providers example."""
from objects.providers import ExternalDependency
from objects.providers import Factory
from objects.providers import Singleton
from objects.injections import KwArg
from objects.injections import Attribute
# Importing SQLITE3 and contextlib.closing for working with cursors:
import sqlite3
from contextlib import closing
# Definition of example UserService:
class UserService(object):
"""Example class UserService.
UserService has dependency on DBAPI 2.0 database connection.
"""
def __init__(self, database):
"""Initializer.
Database dependency need to be injected via init arg.
"""
self.database = database
def init_database(self):
"""Initialize database, if it has not been initialized yet."""
with closing(self.database.cursor()) as cursor:
cursor.execute("""
CREATE TABLE IF NOT EXISTS users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(32)
)
""")
def create(self, name):
"""Create user with provided name and return his id."""
with closing(self.database.cursor()) as cursor:
cursor.execute('INSERT INTO users(name) VALUES (?)', (name,))
return cursor.lastrowid
def get_by_id(self, id):
"""Return user info by user id."""
with closing(self.database.cursor()) as cursor:
cursor.execute('SELECT id, name FROM users WHERE id=?', (id,))
return cursor.fetchone()
# Database and UserService providers:
database = ExternalDependency(instance_of=sqlite3.dbapi2.Connection)
users_service_factory = Factory(UserService,
KwArg('database', database))
# Out of library's scope.
#
# Setting database provider:
database.provided_by(Singleton(sqlite3.dbapi2.Connection,
KwArg('database', ':memory:'),
KwArg('timeout', 30),
KwArg('detect_types', True),
KwArg('isolation_level', 'EXCLUSIVE'),
Attribute('row_factory', sqlite3.Row)))
# Creating UserService instance:
users_service = users_service_factory()
# Initializing UserService database:
users_service.init_database()
# Creating test user and retrieving full information about him:
test_user_id = users_service.create(name='test_user')
test_user = users_service.get_by_id(test_user_id)
# Making some asserts:
assert test_user['id'] == 1
assert test_user['name'] == 'test_user'

View File

@ -0,0 +1,19 @@
"""`Factory` providers example."""
from objects.providers import Factory
class User(object):
"""Example class User."""
# Factory provider creates new instance of specified class on every call.
users_factory = Factory(User)
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert user1 is not user2
assert isinstance(user1, User) and isinstance(user2, User)

View File

@ -0,0 +1,53 @@
"""`Factory` providers with attribute injections example."""
from objects.providers import Factory
from objects.injections import Attribute
class User(object):
"""Example class User."""
def __init__(self):
"""Initializer."""
self.main_photo = None
self.credit_card = None
class Photo(object):
"""Example class Photo."""
class CreditCard(object):
"""Example class CreditCard."""
# User, Photo and CreditCard factories:
credit_cards_factory = Factory(CreditCard)
photos_factory = Factory(Photo)
users_factory = Factory(User,
Attribute('main_photo', photos_factory),
Attribute('credit_card', credit_cards_factory))
# Creating several User objects:
user1 = users_factory()
# Same as: user1 = User()
# user1.main_photo = Photo()
# user1.credit_card = CreditCard()
user2 = users_factory()
# Same as: user2 = User()
# user2.main_photo = Photo()
# user2.credit_card = CreditCard()
# Making some asserts:
assert user1 is not user2
assert isinstance(user1.main_photo, Photo)
assert isinstance(user1.credit_card, CreditCard)
assert isinstance(user2.main_photo, Photo)
assert isinstance(user2.credit_card, CreditCard)
assert user1.main_photo is not user2.main_photo
assert user1.credit_card is not user2.credit_card

View File

@ -0,0 +1,50 @@
"""`Factory` providers delegation example."""
from objects.providers import Factory
from objects.injections import KwArg
class User(object):
"""Example class User."""
def __init__(self, photos_factory):
"""Initializer.
:param photos_factory: objects.providers.Factory
:return:
"""
self.photos_factory = photos_factory
self._main_photo = None
super(User, self).__init__()
@property
def main_photo(self):
"""Return user's main photo."""
if not self._main_photo:
self._main_photo = self.photos_factory()
return self._main_photo
class Photo(object):
"""Example class Photo."""
# User and Photo factories:
photos_factory = Factory(Photo)
users_factory = Factory(User,
KwArg('photos_factory', photos_factory.delegate()))
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user1.main_photo, Photo)
assert isinstance(user2, User)
assert isinstance(user2.main_photo, Photo)
assert user1 is not user2
assert user1.main_photo is not user2.main_photo

View File

@ -0,0 +1,42 @@
"""`Factory` providers with init injections example."""
from objects.providers import Factory
from objects.injections import KwArg
class User(object):
"""Example class User."""
def __init__(self, main_photo):
"""Initializer.
:param main_photo: Photo
:return:
"""
self.main_photo = main_photo
super(User, self).__init__()
class Photo(object):
"""Example class Photo."""
# User and Photo factories:
photos_factory = Factory(Photo)
users_factory = Factory(User,
KwArg('main_photo', photos_factory))
# Creating several User objects:
user1 = users_factory() # Same as: user1 = User(main_photo=Photo())
user2 = users_factory() # Same as: user2 = User(main_photo=Photo())
# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user1.main_photo, Photo)
assert isinstance(user2, User)
assert isinstance(user2.main_photo, Photo)
assert user1 is not user2
assert user1.main_photo is not user2.main_photo

View File

@ -0,0 +1,80 @@
"""`Factory` providers with init injections and context arguments example."""
from objects.providers import Factory
from objects.injections import KwArg
class User(object):
"""Example class User.
Class User has to be provided with user id.
Also Class User has dependencies on class Photo and class CreditCard
objects.
All of the dependencies have to be provided like __init__ arguments.
"""
def __init__(self, id, main_photo, credit_card):
"""Initializer.
:param id: int
:param main_photo: Photo
:param credit_card: CreditCard
:return:
"""
self.id = id
self.main_photo = main_photo
self.credit_card = credit_card
super(User, self).__init__()
class Photo(object):
"""Example class Photo."""
class CreditCard(object):
"""Example class CreditCard."""
# User, Photo and CreditCard factories:
credit_cards_factory = Factory(CreditCard)
photos_factory = Factory(Photo)
users_factory = Factory(User,
KwArg('main_photo', photos_factory),
KwArg('credit_card', credit_cards_factory))
# Creating several User objects:
user1 = users_factory(1)
# Same as: user1 = User(1,
# main_photo=Photo(),
# credit_card=CreditCard())
user2 = users_factory(2)
# Same as: user2 = User(2,
# main_photo=Photo(),
# credit_card=CreditCard())
# Making some asserts:
assert user1.id == 1
assert isinstance(user1.main_photo, Photo)
assert isinstance(user1.credit_card, CreditCard)
assert user2.id == 2
assert isinstance(user2.main_photo, Photo)
assert isinstance(user2.credit_card, CreditCard)
assert user1.main_photo is not user2.main_photo
assert user1.credit_card is not user2.credit_card
# Context keyword arguments have priority on KwArg injections priority:
main_photo_mock = Photo()
credit_card_mock = CreditCard()
user3 = users_factory(3, main_photo=main_photo_mock,
credit_card=credit_card_mock)
assert user3.id == 3
assert user3.main_photo is main_photo_mock
assert user3.credit_card is credit_card_mock

View File

@ -0,0 +1,61 @@
"""`Factory` providers with method injections example."""
from objects.providers import Factory
from objects.injections import Method
class User(object):
"""Example class User."""
def __init__(self):
"""Initializer."""
self.main_photo = None
self.credit_card = None
def set_main_photo(self, photo):
"""Set user's main photo."""
self.main_photo = photo
def set_credit_card(self, credit_card):
"""Set user's credit card."""
self.credit_card = credit_card
class Photo(object):
"""Example class Photo."""
class CreditCard(object):
"""Example class CreditCard."""
# User, Photo and CreditCard factories:
credit_cards_factory = Factory(CreditCard)
photos_factory = Factory(Photo)
users_factory = Factory(User,
Method('set_main_photo', photos_factory),
Method('set_credit_card', credit_cards_factory))
# Creating several User objects:
user1 = users_factory()
# Same as: user1 = User()
# user1.set_main_photo(Photo())
# user1.set_credit_card(CreditCard())
user2 = users_factory()
# Same as: user2 = User()
# user2.set_main_photo(Photo())
# user2.set_credit_card(CreditCard())
# Making some asserts:
assert user1 is not user2
assert isinstance(user1.main_photo, Photo)
assert isinstance(user1.credit_card, CreditCard)
assert isinstance(user2.main_photo, Photo)
assert isinstance(user2.credit_card, CreditCard)
assert user1.main_photo is not user2.main_photo
assert user1.credit_card is not user2.credit_card

View File

@ -0,0 +1,21 @@
"""`Singleton` providers example."""
from objects.providers import Singleton
class UserService(object):
"""Example class UserService."""
# Singleton provider creates new instance of specified class on first call and
# returns same instance on every next call.
users_service_provider = Singleton(UserService)
# Retrieving several UserService objects:
user_service1 = users_service_provider()
user_service2 = users_service_provider()
# Making some asserts:
assert user_service1 is user_service2
assert isinstance(user_service1, UserService)
assert isinstance(user_service2, UserService)

View File

@ -0,0 +1,14 @@
"""`Singleton` providers delegation example."""
from objects.providers import Singleton
from objects.providers import Delegate
# Some singleton provider and few delegates of it:
singleton_provider = Singleton(object)
singleton_provider_delegate1 = singleton_provider.delegate()
singleton_provider_delegate2 = Delegate(singleton_provider)
# Making some asserts:
assert singleton_provider_delegate1() is singleton_provider
assert singleton_provider_delegate2() is singleton_provider

View File

@ -0,0 +1,29 @@
"""`Singleton` providers resetting example."""
from objects.providers import Singleton
class UserService(object):
"""Example class UserService."""
# Users service singleton provider:
users_service_provider = Singleton(UserService)
# Retrieving several UserService objects:
user_service1 = users_service_provider()
user_service2 = users_service_provider()
# Making some asserts:
assert user_service1 is user_service2
assert isinstance(user_service1, UserService)
assert isinstance(user_service2, UserService)
# Resetting of memorized instance:
users_service_provider.reset()
# Retrieving one more UserService object:
user_service3 = users_service_provider()
# Making some asserts:
assert user_service3 is not user_service1

View File

@ -0,0 +1,23 @@
"""`Static` providers example."""
from objects.providers import Class
from objects.providers import Object
from objects.providers import Function
from objects.providers import Value
# Provides class - `object`:
cls_provider = Class(object)
assert cls_provider() is object
# Provides object - `object()`:
object_provider = Object(object())
assert isinstance(object_provider(), object)
# Provides function - `len`:
function_provider = Function(len)
assert function_provider() is len
# Provides value - `123`:
value_provider = Value(123)
assert value_provider() == 123

View File

@ -1,9 +1,8 @@
"""`@inject` decorator example.""" """`@inject` decorator example."""
from objects.providers import NewInstance from objects.providers import NewInstance
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import inject from objects.decorators import inject
new_object = NewInstance(object) new_object = NewInstance(object)

View File

@ -3,7 +3,6 @@
import sqlite3 import sqlite3
from objects.catalog import AbstractCatalog from objects.catalog import AbstractCatalog
from objects.catalog import override
from objects.providers import Singleton from objects.providers import Singleton
from objects.providers import NewInstance from objects.providers import NewInstance
@ -11,6 +10,8 @@ from objects.providers import NewInstance
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import Attribute from objects.injections import Attribute
from objects.decorators import override
class ObjectA(object): class ObjectA(object):

View File

@ -1,66 +0,0 @@
"""Provider overriding example."""
import sqlite3
from objects.providers import Singleton
from objects.providers import NewInstance
from objects.injections import KwArg
from objects.injections import Attribute
class ObjectA(object):
"""ObjectA has dependency on database."""
def __init__(self, database):
"""Initializer.
Database dependency need to be injected via init arg."""
self.database = database
def get_one(self):
"""Select one from database and return it."""
return self.database.execute('SELECT 1')
class ObjectAMock(ObjectA):
"""Mock of ObjectA.
Has no dependency on database.
"""
def __init__(self):
"""Initializer."""
def get_one(self):
"""Select one from database and return it.
Mock makes no database queries and always returns two instead of one.
"""
return 2
# Database and `ObjectA` providers.
database = Singleton(sqlite3.Connection,
KwArg('database', ':memory:'),
KwArg('timeout', 30),
KwArg('detect_types', True),
KwArg('isolation_level', 'EXCLUSIVE'),
Attribute('row_factory', sqlite3.Row))
object_a = NewInstance(ObjectA,
KwArg('database', database))
# Overriding `ObjectA` provider with `ObjectAMock` provider.
object_a.override(NewInstance(ObjectAMock))
# Creating several `ObjectA` instances.
object_a_1 = object_a()
object_a_2 = object_a()
# Making some asserts.
assert object_a_1 is not object_a_2
assert object_a_1.get_one() == object_a_2.get_one() == 2

View File

@ -1,23 +0,0 @@
"""`NewInstance` and `Singleton` providers example."""
from objects.providers import NewInstance
from objects.providers import Singleton
# NewInstance provider will create new instance of specified class
# on every call.
new_object = NewInstance(object)
object_1 = new_object()
object_2 = new_object()
assert object_1 is not object_2
# Singleton provider will create new instance of specified class on first call,
# and return same instance on every next call.
single_object = Singleton(object)
single_object_1 = single_object()
single_object_2 = single_object()
assert single_object_1 is single_object_2

View File

@ -1,12 +1,17 @@
"""`KwArg` and `Attribute` injections example.""" """`@inject` decorator example.
Flask is required to make this example work.
"""
import sqlite3 import sqlite3
from objects.providers import Singleton from flask import Flask
from objects.providers import NewInstance
from objects.providers import Factory
from objects.providers import Singleton
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import Attribute from objects.injections import Attribute
from objects.decorators import inject
class ObjectA(object): class ObjectA(object):
@ -32,14 +37,25 @@ database = Singleton(sqlite3.Connection,
KwArg('isolation_level', 'EXCLUSIVE'), KwArg('isolation_level', 'EXCLUSIVE'),
Attribute('row_factory', sqlite3.Row)) Attribute('row_factory', sqlite3.Row))
object_a = NewInstance(ObjectA, object_a_factory = Factory(ObjectA,
KwArg('database', database)) KwArg('database', database))
# Creating several `ObjectA` instances.
object_a_1 = object_a()
object_a_2 = object_a()
# Making some asserts. # Flask application.
assert object_a_1 is not object_a_2 app = Flask(__name__)
assert object_a_1.database is object_a_2.database
assert object_a_1.get_one() == object_a_2.get_one() == 1
# Flask view with inject decorator.
@app.route('/')
@inject(KwArg('database', database))
@inject(KwArg('object_a', object_a_factory))
def hello(database):
one = database.execute('SELECT 1').fetchone()[0]
return 'Query returned {0}, db connection {1}'.format(one, database)
if __name__ == '__main__':
app.run()
# Example output of "GET / HTTP/1.1" is:
# Query returned 1, db connection <sqlite3.Connection object at 0x1057e4030>

View File

@ -4,11 +4,10 @@ Dependency management tool for Python projects.
""" """
from .catalog import AbstractCatalog from .catalog import AbstractCatalog
from .catalog import override
from .providers import Provider from .providers import Provider
from .providers import Delegate from .providers import Delegate
from .providers import NewInstance from .providers import Factory
from .providers import Singleton from .providers import Singleton
from .providers import ExternalDependency from .providers import ExternalDependency
from .providers import Class from .providers import Class
@ -22,16 +21,18 @@ from .injections import KwArg
from .injections import Attribute from .injections import Attribute
from .injections import Method from .injections import Method
from .decorators import override
from .decorators import inject
from .errors import Error from .errors import Error
__all__ = ('AbstractCatalog', __all__ = ('AbstractCatalog',
'override',
# Providers # Providers
'Provider', 'Provider',
'Delegate', 'Delegate',
'NewInstance', 'Factory',
'Singleton', 'Singleton',
'ExternalDependency', 'ExternalDependency',
'Class', 'Class',
@ -46,5 +47,9 @@ __all__ = ('AbstractCatalog',
'Attribute', 'Attribute',
'Method', 'Method',
# Decorators
'override',
'inject',
# Errors # Errors
'Error') 'Error')

View File

@ -1,58 +1,67 @@
"""Catalog module.""" """Catalog module."""
from .providers import Provider from six import iteritems
from six import add_metaclass
from .errors import Error from .errors import Error
from .utils import is_provider
class CatalogMetaClass(type):
"""Providers catalog meta class."""
def __new__(mcs, class_name, bases, attributes):
"""Meta class factory."""
providers = dict()
new_attributes = dict()
for name, value in iteritems(attributes):
if is_provider(value):
providers[name] = value
new_attributes[name] = value
cls = type.__new__(mcs, class_name, bases, new_attributes)
cls.providers = cls.providers.copy()
cls.providers.update(providers)
return cls
@add_metaclass(CatalogMetaClass)
class AbstractCatalog(object): class AbstractCatalog(object):
"""Abstract object provides catalog.""" """Abstract providers catalog."""
__slots__ = ('__used_providers__',) providers = dict()
__slots__ = ('_used_providers',)
def __init__(self, *used_providers): def __init__(self, *used_providers):
"""Initializer.""" """Initializer."""
self.__used_providers__ = set(used_providers) self._used_providers = set(used_providers)
def __getattribute__(self, item): def __getattribute__(self, item):
"""Return providers.""" """Return providers."""
attribute = super(AbstractCatalog, self).__getattribute__(item) attribute = super(AbstractCatalog, self).__getattribute__(item)
if item in ('__used_providers__',): if item in ('providers', '_used_providers',):
return attribute return attribute
if attribute not in self.__used_providers__: if attribute not in self._used_providers:
raise Error('Provider \'{0}\' '.format(item) + raise Error('Provider \'{0}\' '.format(item) +
'is not listed in dependencies') 'is not listed in dependencies')
return attribute return attribute
@classmethod @classmethod
def all_providers(cls, provider_type=Provider): def filter(cls, provider_type):
"""Return set of all class providers.""" """Return dict of providers, that are instance of provided type."""
providers = set() return dict([(name, provider)
for attr_name in set(dir(cls)) - set(dir(AbstractCatalog)): for name, provider in iteritems(cls.providers)
provider = getattr(cls, attr_name) if isinstance(provider, provider_type)])
if not isinstance(provider, provider_type):
continue
providers.add((attr_name, provider))
return providers
@classmethod @classmethod
def override(cls, overriding): def override(cls, overriding):
"""Override current catalog providers by overriding catalog providers.
:type overriding: AbstractCatalog
""" """
Override current catalog providers by overriding catalog providers. for name, provider in iteritems(overriding.providers):
cls.providers[name].override(provider)
:param overriding: AbstractCatalog
"""
overridden = overriding.all_providers() - cls.all_providers()
for name, provider in overridden:
overridden_provider = getattr(cls, name)
overridden_provider.override(provider)
def override(catalog):
"""Catalog overriding decorator."""
def decorator(overriding_catalog):
"""Overriding decorator."""
catalog.override(overriding_catalog)
return overriding_catalog
return decorator

41
objects/decorators.py Normal file
View File

@ -0,0 +1,41 @@
"""Decorators module."""
from six import wraps
from .utils import ensure_is_injection
def override(catalog):
"""Catalog overriding decorator."""
def decorator(overriding_catalog):
"""Overriding decorator."""
catalog.override(overriding_catalog)
return overriding_catalog
return decorator
def inject(injection):
"""Dependency injection decorator.
:type injection: Injection
:return: (callable) -> (callable)
"""
injection = ensure_is_injection(injection)
def decorator(callback):
"""Dependency injection decorator."""
if hasattr(callback, '_injections'):
callback._injections += (injection,)
@wraps(callback)
def decorated(*args, **kwargs):
"""Decorated with dependency injection callback."""
for injection in getattr(decorated, '_injections'):
if injection.name not in kwargs:
kwargs[injection.name] = injection.value
return callback(*args, **kwargs)
setattr(decorated, '_injections', (injection,))
return decorated
return decorator

View File

@ -1,16 +1,13 @@
"""Injections module.""" """Injections module."""
from six import wraps
from .utils import is_provider from .utils import is_provider
from .utils import ensure_is_injection
class Injection(object): class Injection(object):
"""Base injection class.""" """Base injection class."""
__IS_OBJECTS_INJECTION__ = True __IS_INJECTION__ = True
__slots__ = ('name', 'injectable') __slots__ = ('name', 'injectable')
def __init__(self, name, injectable): def __init__(self, name, injectable):
@ -30,38 +27,18 @@ class KwArg(Injection):
"""Keyword argument injection.""" """Keyword argument injection."""
__IS_OBJECTS_KWARG_INJECTION__ = True __IS_KWARG_INJECTION__ = True
class Attribute(Injection): class Attribute(Injection):
"""Attribute injection.""" """Attribute injection."""
__IS_OBJECTS_ATTRIBUTE_INJECTION__ = True __IS_ATTRIBUTE_INJECTION__ = True
class Method(Injection): class Method(Injection):
"""Method injection.""" """Method injection."""
__IS_OBJECTS_METHOD_INJECTION__ = True __IS_METHOD_INJECTION__ = True
def inject(injection):
"""Inject decorator.
:type injection: Injection
:return: (callable) -> (callable)
"""
injection = ensure_is_injection(injection)
def decorator(callback):
"""Decorator."""
@wraps(callback)
def decorated(*args, **kwargs):
"""Decorated."""
if injection.name not in kwargs:
kwargs[injection.name] = injection.value
return callback(*args, **kwargs)
return decorated
return decorator

View File

@ -14,15 +14,26 @@ class Provider(object):
"""Base provider class.""" """Base provider class."""
__IS_OBJECTS_PROVIDER__ = True __IS_PROVIDER__ = True
__slots__ = ('overridden',) __slots__ = ('_overridden',)
def __init__(self): def __init__(self):
"""Initializer.""" """Initializer."""
self.overridden = None self._overridden = None
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
if self._overridden:
return self.last_overriding(*args, **kwargs)
return self._provide(*args, **kwargs)
def _provide(self, *args, **kwargs):
"""Providing strategy implementation.
Abstract protected method that implements providing strategy of
particular provider. Current method is called every time when not
overridden provider is called. Need to be overridden in subclasses.
"""
raise NotImplementedError() raise NotImplementedError()
def delegate(self): def delegate(self):
@ -31,112 +42,130 @@ class Provider(object):
def override(self, provider): def override(self, provider):
"""Override provider with another provider.""" """Override provider with another provider."""
if not self.overridden: if not self._overridden:
self.overridden = (ensure_is_provider(provider),) self._overridden = (ensure_is_provider(provider),)
else: else:
self.overridden = self.overridden + (ensure_is_provider(provider),) self._overridden += (ensure_is_provider(provider),)
def reset_override(self): @property
"""Reset all overriding providers.""" def is_overridden(self):
self.overridden = None """Check if provider is overridden by another provider."""
return bool(self._overridden)
@property @property
def last_overriding(self): def last_overriding(self):
"""Return last overriding provider.""" """Return last overriding provider."""
try: try:
return self.overridden[-1] return self._overridden[-1]
except (TypeError, IndexError): except (TypeError, IndexError):
raise Error('Provider {0} '.format(str(self)) + raise Error('Provider {0} is not overridden'.format(str(self)))
'is not overridden')
def reset_last_overriding(self):
"""Reset last overriding provider."""
if not self._overridden:
raise Error('Provider {0} is not overridden'.format(str(self)))
self._overridden = self._overridden[:-1]
def reset_override(self):
"""Reset all overriding providers."""
self._overridden = None
class Delegate(Provider): class Delegate(Provider):
"""Provider's delegate.""" """Provider's delegate."""
__slots__ = ('delegated',) __slots__ = ('_delegated',)
def __init__(self, delegated): def __init__(self, delegated):
"""Initializer. """Initializer.
:type delegated: Provider :type delegated: Provider
""" """
self.delegated = ensure_is_provider(delegated) self._delegated = ensure_is_provider(delegated)
super(Delegate, self).__init__() super(Delegate, self).__init__()
def __call__(self): def _provide(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
return self.delegated return self._delegated
class NewInstance(Provider): class Factory(Provider):
"""New instance provider. """Factory provider.
New instance providers will create and return new instance on every call. Factory provider creates new instance of specified class on every call.
""" """
__slots__ = ('provides', 'kwargs', 'attributes', 'methods') __slots__ = ('_provides', '_kwargs', '_attributes', '_methods')
def __init__(self, provides, *injections): def __init__(self, provides, *injections):
"""Initializer.""" """Initializer."""
if not isinstance(provides, class_types): if not callable(provides):
raise Error('NewInstance provider expects to get class, ' + raise Error('Factory provider expects to get callable, ' +
'got {0} instead'.format(str(provides))) 'got {0} instead'.format(str(provides)))
self.provides = provides self._provides = provides
self.kwargs = tuple((injection self._kwargs = tuple((injection
for injection in injections for injection in injections
if is_kwarg_injection(injection))) if is_kwarg_injection(injection)))
self.attributes = tuple((injection self._attributes = tuple((injection
for injection in injections for injection in injections
if is_attribute_injection(injection))) if is_attribute_injection(injection)))
self.methods = tuple((injection self._methods = tuple((injection
for injection in injections for injection in injections
if is_method_injection(injection))) if is_method_injection(injection)))
super(NewInstance, self).__init__() super(Factory, self).__init__()
def __call__(self, *args, **kwargs): def _provide(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
if self.overridden:
return self.last_overriding(*args, **kwargs)
init_kwargs = dict(((injection.name, injection.value) init_kwargs = dict(((injection.name, injection.value)
for injection in self.kwargs)) for injection in self._kwargs))
init_kwargs.update(kwargs) init_kwargs.update(kwargs)
instance = self.provides(*args, **init_kwargs) instance = self._provides(*args, **init_kwargs)
for attribute in self.attributes: for attribute in self._attributes:
setattr(instance, attribute.name, attribute.value) setattr(instance, attribute.name, attribute.value)
for method in self.methods: for method in self._methods:
getattr(instance, method.name)(method.value) getattr(instance, method.name)(method.value)
return instance return instance
class Singleton(NewInstance): class NewInstance(Factory):
"""NewInstance provider.
It is synonym of Factory provider. NewInstance provider is considered to
be deprecated, but will be able to use for further backward
compatibility.
"""
class Singleton(Provider):
"""Singleton provider. """Singleton provider.
Singleton provider will create instance once and return it on every call. Singleton provider will create instance once and return it on every call.
""" """
__slots__ = ('instance',) __slots__ = ('_instance', '_factory')
def __init__(self, *args, **kwargs): def __init__(self, provides, *injections):
"""Initializer.""" """Initializer."""
self.instance = None self._instance = None
super(Singleton, self).__init__(*args, **kwargs) self._factory = Factory(provides, *injections)
super(Singleton, self).__init__()
def __call__(self, *args, **kwargs): def _provide(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
if not self.instance: if not self._instance:
self.instance = super(Singleton, self).__call__(*args, **kwargs) self._instance = self._factory(*args, **kwargs)
return self.instance return self._instance
def reset(self): def reset(self):
"""Reset instance.""" """Reset instance."""
self.instance = None self._instance = None
class ExternalDependency(Provider): class ExternalDependency(Provider):
@ -147,29 +176,33 @@ class ExternalDependency(Provider):
the client's code, but it's interface is known. the client's code, but it's interface is known.
""" """
__slots__ = ('instance_of',) __slots__ = ('_instance_of',)
def __init__(self, instance_of): def __init__(self, instance_of):
"""Initializer.""" """Initializer."""
if not isinstance(instance_of, class_types): if not isinstance(instance_of, class_types):
raise Error('ExternalDependency provider expects to get class, ' + raise Error('ExternalDependency provider expects to get class, ' +
'got {0} instead'.format(str(instance_of))) 'got {0} instead'.format(str(instance_of)))
self.instance_of = instance_of self._instance_of = instance_of
super(ExternalDependency, self).__init__() super(ExternalDependency, self).__init__()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
if not self.overridden: if not self._overridden:
raise Error('Dependency is not defined') raise Error('Dependency is not defined')
instance = self.last_overriding(*args, **kwargs) instance = self.last_overriding(*args, **kwargs)
if not isinstance(instance, self.instance_of): if not isinstance(instance, self._instance_of):
raise Error('{0} is not an '.format(instance) + raise Error('{0} is not an '.format(instance) +
'instance of {0}'.format(self.instance_of)) 'instance of {0}'.format(self._instance_of))
return instance return instance
def provided_by(self, provider):
"""Set external dependency provider."""
return self.override(provider)
class _StaticProvider(Provider): class _StaticProvider(Provider):
@ -179,18 +212,16 @@ class _StaticProvider(Provider):
it got on input. it got on input.
""" """
__slots__ = ('provides',) __slots__ = ('_provides',)
def __init__(self, provides): def __init__(self, provides):
"""Initializer.""" """Initializer."""
self.provides = provides self._provides = provides
super(_StaticProvider, self).__init__() super(_StaticProvider, self).__init__()
def __call__(self): def _provide(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
if self.overridden: return self._provides
return self.last_overriding()
return self.provides
class Class(_StaticProvider): class Class(_StaticProvider):
@ -221,28 +252,25 @@ class Callable(Provider):
with some predefined dependency injections. with some predefined dependency injections.
""" """
__slots__ = ('callback', 'injections') __slots__ = ('_callback', '_injections')
def __init__(self, callback, *injections): def __init__(self, callback, *injections):
"""Initializer.""" """Initializer."""
if not callable(callback): if not callable(callback):
raise Error('Callable expected, got {0}'.format(str(callback))) raise Error('Callable expected, got {0}'.format(str(callback)))
self.callback = callback self._callback = callback
self.injections = tuple((injection self._injections = tuple((injection
for injection in injections for injection in injections
if is_kwarg_injection(injection))) if is_kwarg_injection(injection)))
super(Callable, self).__init__() super(Callable, self).__init__()
def __call__(self, *args, **kwargs): def _provide(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
if self.overridden:
return self.last_overriding()
injections = dict(((injection.name, injection.value) injections = dict(((injection.name, injection.value)
for injection in self.injections)) for injection in self._injections))
injections.update(kwargs) injections.update(kwargs)
return self.callback(*args, **injections) return self._callback(*args, **injections)
class Config(Provider): class Config(Provider):
@ -254,18 +282,22 @@ class Config(Provider):
to create deferred config value provider. to create deferred config value provider.
""" """
__slots__ = ('value',) __slots__ = ('_value',)
def __init__(self, value=None): def __init__(self, value=None):
"""Initializer.""" """Initializer."""
if not value: if not value:
value = dict() value = dict()
self.value = value self._value = value
super(Config, self).__init__() super(Config, self).__init__()
def __call__(self, paths=None): def __getattr__(self, item):
"""Return instance of deferred config."""
return _ChildConfig(parents=(item,), root_config=self)
def _provide(self, paths=None):
"""Return provided instance.""" """Return provided instance."""
value = self.value value = self._value
if paths: if paths:
for path in paths: for path in paths:
try: try:
@ -275,13 +307,9 @@ class Config(Provider):
'"{0}" is undefined'.format('.'.join(paths))) '"{0}" is undefined'.format('.'.join(paths)))
return value return value
def __getattr__(self, item):
"""Return instance of deferred config."""
return _ChildConfig(parents=(item,), root_config=self)
def update_from(self, value): def update_from(self, value):
"""Update current value from another one.""" """Update current value from another one."""
self.value.update(value) self._value.update(value)
class _ChildConfig(Provider): class _ChildConfig(Provider):
@ -292,19 +320,19 @@ class _ChildConfig(Provider):
the current path in the config tree. the current path in the config tree.
""" """
__slots__ = ('parents', 'root_config') __slots__ = ('_parents', '_root_config')
def __init__(self, parents, root_config): def __init__(self, parents, root_config):
"""Initializer.""" """Initializer."""
self.parents = parents self._parents = parents
self.root_config = root_config self._root_config = root_config
super(_ChildConfig, self).__init__() super(_ChildConfig, self).__init__()
def __call__(self, *args, **kwargs):
"""Return provided instance."""
return self.root_config(self.parents)
def __getattr__(self, item): def __getattr__(self, item):
"""Return instance of deferred config.""" """Return instance of deferred config."""
return _ChildConfig(parents=self.parents + (item,), return _ChildConfig(parents=self._parents + (item,),
root_config=self.root_config) root_config=self._root_config)
def _provide(self, *args, **kwargs):
"""Return provided instance."""
return self._root_config(self._parents)

View File

@ -8,7 +8,7 @@ from .errors import Error
def is_provider(instance): def is_provider(instance):
"""Check if instance is provider instance.""" """Check if instance is provider instance."""
return (not isinstance(instance, class_types) and return (not isinstance(instance, class_types) and
hasattr(instance, '__IS_OBJECTS_PROVIDER__')) getattr(instance, '__IS_PROVIDER__', False) is True)
def ensure_is_provider(instance): def ensure_is_provider(instance):
@ -22,7 +22,7 @@ def ensure_is_provider(instance):
def is_injection(instance): def is_injection(instance):
"""Check if instance is injection instance.""" """Check if instance is injection instance."""
return (not isinstance(instance, class_types) and return (not isinstance(instance, class_types) and
hasattr(instance, '__IS_OBJECTS_INJECTION__')) getattr(instance, '__IS_INJECTION__', False) is True)
def ensure_is_injection(instance): def ensure_is_injection(instance):
@ -36,16 +36,16 @@ def ensure_is_injection(instance):
def is_kwarg_injection(instance): def is_kwarg_injection(instance):
"""Check if instance is keyword argument injection instance.""" """Check if instance is keyword argument injection instance."""
return (not isinstance(instance, class_types) and return (not isinstance(instance, class_types) and
hasattr(instance, '__IS_OBJECTS_KWARG_INJECTION__')) getattr(instance, '__IS_KWARG_INJECTION__', False) is True)
def is_attribute_injection(instance): def is_attribute_injection(instance):
"""Check if instance is attribute injection instance.""" """Check if instance is attribute injection instance."""
return (not isinstance(instance, class_types) and return (not isinstance(instance, class_types) and
hasattr(instance, '__IS_OBJECTS_ATTRIBUTE_INJECTION__')) getattr(instance, '__IS_ATTRIBUTE_INJECTION__', False) is True)
def is_method_injection(instance): def is_method_injection(instance):
"""Check if instance is method injection instance.""" """Check if instance is method injection instance."""
return (not isinstance(instance, class_types) and return (not isinstance(instance, class_types) and
hasattr(instance, '__IS_OBJECTS_METHOD_INJECTION__')) getattr(instance, '__IS_METHOD_INJECTION__', False) is True)

View File

@ -7,7 +7,7 @@ from setuptools import setup
from setuptools import Command from setuptools import Command
SHORT_DESCRIPTION = 'Dependency management tool for Python projects' SHORT_DESCRIPTION = 'Dependency injection framework for Python projects'
# Getting description. # Getting description.
@ -47,8 +47,6 @@ class PublishCommand(Command):
self.run_command('upload') self.run_command('upload')
os.system('git tag -a {0} -m \'version {0}\''.format(version)) os.system('git tag -a {0} -m \'version {0}\''.format(version))
os.system('git push --tags') os.system('git push --tags')
setup(name='Objects', setup(name='Objects',
version=version, version=version,
description=SHORT_DESCRIPTION, description=SHORT_DESCRIPTION,
@ -69,9 +67,9 @@ setup(name='Objects',
'Dependency management', 'Dependency management',
'Dependency injection', 'Dependency injection',
'Dependency injection container', 'Dependency injection container',
'Dependency injector',
'DI', 'DI',
'DIC', 'DIC',
'Dependency injector',
'Inversion of Control', 'Inversion of Control',
'Inversion of Control container', 'Inversion of Control container',
'IoC', 'IoC',

View File

@ -3,7 +3,6 @@
import unittest2 as unittest import unittest2 as unittest
from objects.catalog import AbstractCatalog from objects.catalog import AbstractCatalog
from objects.catalog import override
from objects.providers import Object from objects.providers import Object
from objects.providers import Value from objects.providers import Value
@ -34,32 +33,38 @@ class CatalogTests(unittest.TestCase):
def test_all_providers(self): def test_all_providers(self):
"""Test getting of all catalog providers.""" """Test getting of all catalog providers."""
all_providers = self.Catalog.all_providers() self.assertTrue(len(self.Catalog.providers) == 2)
all_providers_dict = dict(all_providers)
self.assertIsInstance(all_providers, set) self.assertIn('obj', self.Catalog.providers)
self.assertTrue(len(all_providers) == 2) self.assertIn(self.Catalog.obj, self.Catalog.providers.values())
self.assertIn('obj', all_providers_dict) self.assertIn('another_obj', self.Catalog.providers)
self.assertIn(self.Catalog.obj, all_providers_dict.values()) self.assertIn(self.Catalog.another_obj,
self.Catalog.providers.values())
self.assertIn('another_obj', all_providers_dict)
self.assertIn(self.Catalog.another_obj, all_providers_dict.values())
def test_all_providers_by_type(self): def test_all_providers_by_type(self):
"""Test getting of all catalog providers of specific type.""" """Test getting of all catalog providers of specific type."""
self.assertTrue(len(self.Catalog.all_providers(Object)) == 2) self.assertTrue(len(self.Catalog.filter(Object)) == 2)
self.assertTrue(len(self.Catalog.all_providers(Value)) == 0) self.assertTrue(len(self.Catalog.filter(Value)) == 0)
def test_overriding(self): def test_metaclass_with_several_catalogs(self):
"""Test catalog overriding with another catalog.""" """Test that metaclass work well with several catalogs."""
@override(self.Catalog) class Catalog1(AbstractCatalog):
class OverridingCatalog(self.Catalog):
"""Overriding catalog.""" """Catalog1."""
obj = Value(1) provider = Object(object())
another_obj = Value(2)
self.assertEqual(self.Catalog.obj(), 1) class Catalog2(AbstractCatalog):
self.assertEqual(self.Catalog.another_obj(), 2)
"""Catalog2."""
provider = Object(object())
self.assertTrue(len(Catalog1.providers) == 1)
self.assertIs(Catalog1.provider, Catalog1.providers['provider'])
self.assertTrue(len(Catalog2.providers) == 1)
self.assertIs(Catalog2.provider, Catalog2.providers['provider'])
self.assertIsNot(Catalog1.provider, Catalog2.provider)

115
tests/test_decorators.py Normal file
View File

@ -0,0 +1,115 @@
"""Objects decorators unittests."""
import unittest2 as unittest
from objects.decorators import override
from objects.decorators import inject
from objects.catalog import AbstractCatalog
from objects.providers import Factory
from objects.providers import Object
from objects.providers import Value
from objects.injections import KwArg
from objects.errors import Error
class OverrideTests(unittest.TestCase):
"""Override decorator test cases."""
class Catalog(AbstractCatalog):
"""Test catalog."""
obj = Object(object())
another_obj = Object(object())
def test_overriding(self):
"""Test catalog overriding with another catalog."""
@override(self.Catalog)
class OverridingCatalog(self.Catalog):
"""Overriding catalog."""
obj = Value(1)
another_obj = Value(2)
self.assertEqual(self.Catalog.obj(), 1)
self.assertEqual(self.Catalog.another_obj(), 2)
class InjectTests(unittest.TestCase):
"""Inject decorator test cases."""
def test_decorated(self):
"""Test `inject()` decorated callback."""
provider1 = Factory(object)
provider2 = Factory(list)
@inject(KwArg('a', provider1))
@inject(KwArg('b', provider2))
def test(a, b):
return a, b
a1, b1 = test()
a2, b2 = test()
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIsNot(a1, a2)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_kwargs_priority(self):
"""Test `inject()` decorated callback kwargs priority."""
provider1 = Factory(object)
provider2 = Factory(list)
object_a = object()
@inject(KwArg('a', provider1))
@inject(KwArg('b', provider2))
def test(a, b):
return a, b
a1, b1 = test(a=object_a)
a2, b2 = test(a=object_a)
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIs(a1, object_a)
self.assertIs(a2, object_a)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_with_args(self):
"""Test `inject()` decorated callback with args."""
provider = Factory(list)
object_a = object()
@inject(KwArg('b', provider))
def test(a, b):
return a, b
a1, b1 = test(object_a)
a2, b2 = test(object_a)
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIs(a1, object_a)
self.assertIs(a2, object_a)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorate_with_not_injection(self):
"""Test `inject()` decorator with not an injection instance."""
self.assertRaises(Error, inject, object)

View File

@ -6,11 +6,8 @@ from objects.injections import Injection
from objects.injections import KwArg from objects.injections import KwArg
from objects.injections import Attribute from objects.injections import Attribute
from objects.injections import Method from objects.injections import Method
from objects.injections import inject
from objects.providers import NewInstance from objects.providers import Factory
from objects.providers import Error
class InjectionTests(unittest.TestCase): class InjectionTests(unittest.TestCase):
@ -30,7 +27,7 @@ class InjectionTests(unittest.TestCase):
def test_value_with_provider_injectable(self): def test_value_with_provider_injectable(self):
"""Test Injection value property with provider.""" """Test Injection value property with provider."""
injection = Injection('some_arg_name', NewInstance(object)) injection = Injection('some_arg_name', Factory(object))
self.assertIsInstance(injection.value, object) self.assertIsInstance(injection.value, object)
@ -65,77 +62,3 @@ class MethodTests(unittest.TestCase):
injection = Method('some_arg_name', 'some_value') injection = Method('some_arg_name', 'some_value')
self.assertEqual(injection.name, 'some_arg_name') self.assertEqual(injection.name, 'some_arg_name')
self.assertEqual(injection.injectable, 'some_value') self.assertEqual(injection.injectable, 'some_value')
class InjectTests(unittest.TestCase):
"""Inject decorator test cases."""
def test_decorated(self):
"""Test `inject()` decorated callback."""
provider1 = NewInstance(object)
provider2 = NewInstance(list)
@inject(KwArg('a', provider1))
@inject(KwArg('b', provider2))
def test(a, b):
return a, b
a1, b1 = test()
a2, b2 = test()
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIsNot(a1, a2)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_kwargs_priority(self):
"""Test `inject()` decorated callback kwargs priority."""
provider1 = NewInstance(object)
provider2 = NewInstance(list)
object_a = object()
@inject(KwArg('a', provider1))
@inject(KwArg('b', provider2))
def test(a, b):
return a, b
a1, b1 = test(a=object_a)
a2, b2 = test(a=object_a)
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIs(a1, object_a)
self.assertIs(a2, object_a)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_with_args(self):
"""Test `inject()` decorated callback with args."""
provider = NewInstance(list)
object_a = object()
@inject(KwArg('b', provider))
def test(a, b):
return a, b
a1, b1 = test(object_a)
a2, b2 = test(object_a)
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIs(a1, object_a)
self.assertIs(a2, object_a)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorate_with_not_injection(self):
"""Test `inject()` decorator with not an injection instance."""
self.assertRaises(Error, inject, object)

View File

@ -4,7 +4,7 @@ import unittest2 as unittest
from objects.providers import Provider from objects.providers import Provider
from objects.providers import Delegate from objects.providers import Delegate
from objects.providers import NewInstance from objects.providers import Factory
from objects.providers import Singleton from objects.providers import Singleton
from objects.providers import ExternalDependency from objects.providers import ExternalDependency
from objects.providers import Class from objects.providers import Class
@ -44,12 +44,12 @@ class ProviderTests(unittest.TestCase):
delegate1 = self.provider.delegate() delegate1 = self.provider.delegate()
self.assertIsInstance(delegate1, Delegate) self.assertIsInstance(delegate1, Delegate)
self.assertIs(delegate1.delegated, self.provider) self.assertIs(delegate1(), self.provider)
delegate2 = self.provider.delegate() delegate2 = self.provider.delegate()
self.assertIsInstance(delegate2, Delegate) self.assertIsInstance(delegate2, Delegate)
self.assertIs(delegate2.delegated, self.provider) self.assertIs(delegate2(), self.provider)
self.assertIsNot(delegate1, delegate2) self.assertIsNot(delegate1, delegate2)
@ -57,31 +57,12 @@ class ProviderTests(unittest.TestCase):
"""Test provider overriding.""" """Test provider overriding."""
overriding_provider = Provider() overriding_provider = Provider()
self.provider.override(overriding_provider) self.provider.override(overriding_provider)
self.assertTrue(self.provider.overridden) self.assertTrue(self.provider.is_overridden)
def test_override_with_not_provider(self): def test_override_with_not_provider(self):
"""Test provider overriding with not provider instance.""" """Test provider overriding with not provider instance."""
self.assertRaises(Error, self.provider.override, object()) self.assertRaises(Error, self.provider.override, object())
def test_reset_override(self):
"""Test reset of provider's override."""
overriding_provider = Provider()
self.provider.override(overriding_provider)
self.assertTrue(self.provider.overridden)
self.assertIs(self.provider.last_overriding, overriding_provider)
self.provider.reset_override()
self.assertFalse(self.provider.overridden)
try:
self.provider.last_overriding
except Error:
pass
else:
self.fail('Got en error in {}'.format(
str(self.test_last_overriding_of_not_overridden_provider)))
def test_last_overriding(self): def test_last_overriding(self):
"""Test getting last overriding provider.""" """Test getting last overriding provider."""
overriding_provider1 = Provider() overriding_provider1 = Provider()
@ -103,6 +84,45 @@ class ProviderTests(unittest.TestCase):
self.fail('Got en error in {}'.format( self.fail('Got en error in {}'.format(
str(self.test_last_overriding_of_not_overridden_provider))) str(self.test_last_overriding_of_not_overridden_provider)))
def test_reset_last_overriding(self):
"""Test reseting of last overriding provider."""
overriding_provider1 = Provider()
overriding_provider2 = Provider()
self.provider.override(overriding_provider1)
self.provider.override(overriding_provider2)
self.assertIs(self.provider.last_overriding, overriding_provider2)
self.provider.reset_last_overriding()
self.assertIs(self.provider.last_overriding, overriding_provider1)
self.provider.reset_last_overriding()
self.assertFalse(self.provider.is_overridden)
def test_reset_last_overriding_of_not_overridden_provider(self):
"""Test resetting of last overriding on not overridden provier."""
self.assertRaises(Error, self.provider.reset_last_overriding)
def test_reset_override(self):
"""Test reset of provider's override."""
overriding_provider = Provider()
self.provider.override(overriding_provider)
self.assertTrue(self.provider.is_overridden)
self.assertIs(self.provider.last_overriding, overriding_provider)
self.provider.reset_override()
self.assertFalse(self.provider.is_overridden)
try:
self.provider.last_overriding
except Error:
pass
else:
self.fail('Got en error in {}'.format(
str(self.test_last_overriding_of_not_overridden_provider)))
class DelegateTests(unittest.TestCase): class DelegateTests(unittest.TestCase):
@ -130,13 +150,13 @@ class DelegateTests(unittest.TestCase):
self.assertIs(delegated2, self.delegated) self.assertIs(delegated2, self.delegated)
class NewInstanceTests(unittest.TestCase): class FactoryTests(unittest.TestCase):
"""NewInstance test cases.""" """Factory test cases."""
class Example(object): class Example(object):
"""Example class for NewInstance provider tests.""" """Example class for Factory provider tests."""
def __init__(self, init_arg1=None, init_arg2=None): def __init__(self, init_arg1=None, init_arg2=None):
"""Initializer. """Initializer.
@ -164,15 +184,19 @@ class NewInstanceTests(unittest.TestCase):
def test_is_provider(self): def test_is_provider(self):
"""Test `is_provider` check.""" """Test `is_provider` check."""
self.assertTrue(is_provider(NewInstance(self.Example))) self.assertTrue(is_provider(Factory(self.Example)))
def test_init_with_not_class(self): def test_init_with_callable(self):
"""Test creation of provider with not a class.""" """Test creation of provider with a callable."""
self.assertRaises(Error, NewInstance, 123) self.assertTrue(Factory(credits))
def test_init_with_not_callable(self):
"""Test creation of provider with not a callable."""
self.assertRaises(Error, Factory, 123)
def test_call(self): def test_call(self):
"""Test creation of new instances.""" """Test creation of new instances."""
provider = NewInstance(self.Example) provider = Factory(self.Example)
instance1 = provider() instance1 = provider()
instance2 = provider() instance2 = provider()
@ -182,7 +206,7 @@ class NewInstanceTests(unittest.TestCase):
def test_call_with_init_args(self): def test_call_with_init_args(self):
"""Test creation of new instances with init args injections.""" """Test creation of new instances with init args injections."""
provider = NewInstance(self.Example, provider = Factory(self.Example,
KwArg('init_arg1', 'i1'), KwArg('init_arg1', 'i1'),
KwArg('init_arg2', 'i2')) KwArg('init_arg2', 'i2'))
@ -201,7 +225,7 @@ class NewInstanceTests(unittest.TestCase):
def test_call_with_attributes(self): def test_call_with_attributes(self):
"""Test creation of new instances with attribute injections.""" """Test creation of new instances with attribute injections."""
provider = NewInstance(self.Example, provider = Factory(self.Example,
Attribute('attribute1', 'a1'), Attribute('attribute1', 'a1'),
Attribute('attribute2', 'a2')) Attribute('attribute2', 'a2'))
@ -220,7 +244,7 @@ class NewInstanceTests(unittest.TestCase):
def test_call_with_methods(self): def test_call_with_methods(self):
"""Test creation of new instances with method injections.""" """Test creation of new instances with method injections."""
provider = NewInstance(self.Example, provider = Factory(self.Example,
Method('method1', 'm1'), Method('method1', 'm1'),
Method('method2', 'm2')) Method('method2', 'm2'))
@ -239,7 +263,7 @@ class NewInstanceTests(unittest.TestCase):
def test_call_with_context_args(self): def test_call_with_context_args(self):
"""Test creation of new instances with context args.""" """Test creation of new instances with context args."""
provider = NewInstance(self.Example) provider = Factory(self.Example)
instance = provider(11, 22) instance = provider(11, 22)
self.assertEqual(instance.init_arg1, 11) self.assertEqual(instance.init_arg1, 11)
@ -247,7 +271,7 @@ class NewInstanceTests(unittest.TestCase):
def test_call_with_context_kwargs(self): def test_call_with_context_kwargs(self):
"""Test creation of new instances with context kwargs.""" """Test creation of new instances with context kwargs."""
provider = NewInstance(self.Example, provider = Factory(self.Example,
KwArg('init_arg1', 1)) KwArg('init_arg1', 1))
instance1 = provider(init_arg2=22) instance1 = provider(init_arg2=22)
@ -260,9 +284,9 @@ class NewInstanceTests(unittest.TestCase):
def test_call_overridden(self): def test_call_overridden(self):
"""Test creation of new instances on overridden provider.""" """Test creation of new instances on overridden provider."""
provider = NewInstance(self.Example) provider = Factory(self.Example)
overriding_provider1 = NewInstance(dict) overriding_provider1 = Factory(dict)
overriding_provider2 = NewInstance(list) overriding_provider2 = Factory(list)
provider.override(overriding_provider1) provider.override(overriding_provider1)
provider.override(overriding_provider2) provider.override(overriding_provider2)
@ -323,12 +347,12 @@ class ExternalDependencyTests(unittest.TestCase):
def test_call_overridden(self): def test_call_overridden(self):
"""Test call of overridden external dependency.""" """Test call of overridden external dependency."""
self.provider.override(NewInstance(list)) self.provider.provided_by(Factory(list))
self.assertIsInstance(self.provider(), list) self.assertIsInstance(self.provider(), list)
def test_call_overridden_but_not_instance_of(self): def test_call_overridden_but_not_instance_of(self):
"""Test call of overridden external dependency, but not instance of.""" """Test call of overridden external dependency, but not instance of."""
self.provider.override(NewInstance(dict)) self.provider.provided_by(Factory(dict))
self.assertRaises(Error, self.provider) self.assertRaises(Error, self.provider)
def test_call_not_overridden(self): def test_call_not_overridden(self):

View File

@ -40,6 +40,26 @@ class IsProviderTests(unittest.TestCase):
"""Test with object.""" """Test with object."""
self.assertFalse(is_provider(object())) self.assertFalse(is_provider(object()))
def test_with_subclass_instance(self):
"""Test with subclass of provider instance."""
class SomeProvider(Provider):
"""Some provider for test."""
self.assertTrue(is_provider(SomeProvider()))
def test_with_class_with_getattr(self):
"""Test with class that has __getattr__() method implementation."""
class SomeClass(object):
"""Some test class with __getattr__() method implementation."""
def __getattr__(self, _):
"""Test implementation that just returns False."""
return False
self.assertFalse(is_provider(SomeClass()))
class EnsureIsProviderTests(unittest.TestCase): class EnsureIsProviderTests(unittest.TestCase):

View File

@ -18,6 +18,7 @@ commands=
[testenv:coveralls] [testenv:coveralls]
basepython=python2.7 basepython=python2.7
passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
deps= deps=
{[testenv]deps} {[testenv]deps}
coverage coverage
@ -39,14 +40,14 @@ basepython=python2.7
deps= deps=
flake8 flake8
commands= commands=
flake8 --max-complexity=8 objects flake8 --max-complexity=8 objects/
[testenv:pep257] [testenv:pep257]
basepython=python2.7 basepython=python2.7
deps= deps=
pep257 pep257
commands= commands=
pep257 objects pep257 objects/
[testenv:py26] [testenv:py26]
basepython=python2.6 basepython=python2.6