Baluster 0.2.1
Provides hierarchical factory
License: MIT
Requires Python: >=3.6
Keywords: context,context manager,async

==================================
Baluster - Python Composition tool
==================================

.. image:: https://travis-ci.org/palankai/baluster.svg?branch=master
    :target: https://travis-ci.org/palankai/baluster

| Project homepage: `<https://github.com/palankai/baluster>`_
| Issues: `<https://github.com/palankai/baluster/issues>`_
|

What is this package for
------------------------

This package provides a simple way to build back structure of application.
Can be used building composite root as acting a factory for resources,
building a fixture factory for tests.

Features
--------

  - Lazy initialisation
  - Simple composition and dependency handling


Example - composie root
-----------------------

.. code:: python

    from baluster import Baluster, placeholders
    import psycopg2

    class ApplicationRoot(Baluster):
        @placeholders.factory
        def db(self, root):
            # Will be called at the first use
            # Will be cached so won't be called again
            return psycopg2.connect("dbname=test user=postgres")

        @db.close
        def _close_db(self, root, db):
            db.close()

        @placeholders.factory
        def cr(self, root):
            return self.db.cursor()

        @cr.close
        def _close_cr(self, root, cr):
            cr.close()


    def main():
        approot = ApplicationRoot()
        with approot:
            approot.cr.execute('SELECT * FROM user')

        # at this point the connection and the cursor has already been closed


Example - async composie root
-----------------------------

.. code:: python

    from baluster import Baluster, placeholders

    class AsyncApplicationRoot(Baluster):

        @placeholders.factory
        async def resource(self, root):
            # Will be called at the first use
            # Will be cached so won't be called again
            return await some_aync_resource()

        @db.close
        async def _close_resource(self, root, resource):
            await resource.close()


    def main():
        approot = AsyncApplicationRoot()
        async with approot:
            conn = await approot.resource
            await conn.operation(...)

        # at this point the resource has already been closed


Example - fixture factory for tests
-----------------------------------

.. code:: python

    from baluster import Baluster, placeholders
    import psycopg2

    class Fixtures(Baluster):

        @placeholders.factory
        def cr(self, root):
            conn = psycopg2.connect("dbname=test user=postgres")
            return conn.cursor()

        class users(Baluster):

            @placeholders.factory
            def user(self, root):
                root.cr.execute('SELECT * FROM user WHERE id=1')
                return User(root.cr.fetchone())

            @placeholders.factory
            def customer(self, root):
                root.cr.execute('SELECT * FROM customer WHERE id=1')
                return Customer(root.cr.fetchone())

        class orders(Baluster):

            @placeholders.factory
            def amount(self, root):
                return 100

            @placeholders.factory
            def quantity(self, root):
                return 1

            @placeholders.factory
            def order(self, root):
                customer = root.users.customer
                created_by = root.users.user
                amount = self.amount
                # Fictive order object...
                return Order(
                    customer=customer, created_by=created_by,
                    amount=amount, quantity=quantity
                )

            @placeholders.factory
            def shipped_order(self, root):
                order = self.order
                order.mark_shipped()
                return order


    def test_order():
        # Demonstrate a few use fictive usecase

        # Creating order with defaults
        f = Fixtures()
        assert f.order.calculated_total_value == 100
        assert f.order.shipping_address == f.users.customer.address

        # Create new fixtures, but keep some cached data
        f2 = f.copy('cr', 'users')

        # Set some value
        f2.order.amount = 50
        f2.order.quantity = 3
        assert f2.order.calculated_total_value == 150

        # Manage different stage of object life
        f3 = f.copy('cr', 'users')
        order = f3.shipped_order

        with pytest.raises(OrderException):
            order.cancel()
            # as it is shipped


Installation
------------

Python target: >=3.6

.. code::

    $ pip install baluster

Dependencies
------------

The package is independent, using only the python standard library.


Development
-----------

.. code::

   pip install -r requirements-dev.txt

This installs the package in development mode (`setup.py develop`)
and the testing packages.
I would like to achive nearly 100% test coverage.

Test
~~~~
.. code::

   pytest


Contribution
------------

I really welcome any comments!
I would be happy if you fork my code and create pull requests.
For an approved pull request flake8 have to pass just as all of tests.