Skip to content

Commit

Permalink
Merge branch 'release/0.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasdaly committed Aug 4, 2019
2 parents a0cb960 + c42bd48 commit 91775ca
Show file tree
Hide file tree
Showing 21 changed files with 290 additions and 171 deletions.
1 change: 0 additions & 1 deletion .envrc
@@ -1,2 +1 @@
export TODO_DIR=./todos
export PIP_USE_PEP517=false
4 changes: 3 additions & 1 deletion .gitignore
Expand Up @@ -70,9 +70,11 @@ target/
# Pycharm
.idea

# Neovim
.vim

# VS Code
.vscode
XDG_CACHE_HOME

# Jupyter NB Checkpoints
.ipynb_checkpoints/
Expand Down
2 changes: 0 additions & 2 deletions .travis.yml
Expand Up @@ -27,5 +27,3 @@ jobs:
python: "3.7"
dist: xenial
sudo: required
- python: "3.6"
- python: "3.5"
10 changes: 9 additions & 1 deletion CHANGELOG.md
Expand Up @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

[//]: # (BEGIN)

## \[0.1.1\] - 2019-08-04

### Changed

- Allow handlers to be chained in succession
- Allow handlers to be simple functions


## \[0.1.0\] - 2019-07-13

### Added
Expand All @@ -17,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Unit of work base components


## \[v0.0.1\] - 2019-06-09
## \[0.0.1\] - 2019-06-09

### Added

Expand Down
6 changes: 3 additions & 3 deletions Makefile
Expand Up @@ -145,14 +145,14 @@ clean-py: ## Delete all compiled Python files or temp files
# Requirements

requirements: ## Installs Python dependencies
(export PIP_USE_PEP517=false; $(INSTALL_DEPS))
$(INSTALL_DEPS)

update-requirements: -update-requirements-actual generate-requirements ## Updates the project's dependencies

-update-requirements-actual:
(export PIP_USE_PEP517=false; $(UPDATE_DEPS))
$(UPDATE_DEPS)

generate-requirements:
generate-requirements: ## Generates the requirements.txt files
$(GENERATE_DEPS)

# Documentation
Expand Down
38 changes: 28 additions & 10 deletions README.md
Expand Up @@ -13,6 +13,30 @@
- Documentation: https://frequent-py.readthedocs.io/


## Features

All of the components in ``frequent`` have extensive code documentation (as
well as extensive usage documentation and examples) and unit tests. The
modules (and their associated unit tests) are entirely self-contained and
depend solely on the standard library.

- [``config``](./src/frequent/config.py): components providing global
application configuration settings management and storage.

- [``messaging``](./src/frequent/messaging.py): the foundations for building
custom messaging frameworks.

- [``repository``](./src/frequent/repository.py): base class (and exception
classes) for implementing the repository pattern for back-end agnostic object
storage.

- [``singleton``](./src/frequent/singleton.py): metaclass for creating
singleton classes.

- [``unit_of_work``](./src/frequent/unit_of_work.py): base classes for
implementing the unit of work pattern for transactional blocks.


## Installation

You have a few options for installing/using `frequent`. The first is to
Expand All @@ -33,16 +57,10 @@ for the component(s) that you need (same goes for the unit tests).

## About

I found myself copying/re-writing certain components for my projects
over and over again. This library is an attempt to take some of the
components I find myself needing frequently and package them up in a
convenient and easy-to-use manner.

### Features

- ``config``: for global configuration/settings management.
- ``messaging``: components for creating custom messaging frameworks.
- ``singleton``: for singleton classes.
I found myself copying/re-writing certain components for my projects over and
over again. This library is an attempt to take some of these components I find
myself needing frequently (and re-writing *too frequently*) and package them up
in a convenient and easy-to-use format.


## License
Expand Down
4 changes: 2 additions & 2 deletions docs/changelogs/changelog_1.rst
@@ -1,5 +1,5 @@
v0.0.1
======
0.0.1
=====

:Release: June 09, 2019

Expand Down
10 changes: 10 additions & 0 deletions docs/changelogs/changelog_3.rst
@@ -0,0 +1,10 @@
0.1.1
=====

:Release: August 04, 2019

Changed
-------

- Allow handlers to be chained in succession
- Allow handlers to be simple functions
13 changes: 3 additions & 10 deletions docs/index.rst
Expand Up @@ -65,13 +65,6 @@ depend solely on the standard library.
implementing the unit of work pattern for transactional blocks.


.. warning::

This library is *currently* only compatible with Python 3.7, efforts are
being made to make as much of it (as possible) compatible with 3.6 and 3.5.
It will **not** be made compatible with Python 2.


Installation
============

Expand Down Expand Up @@ -111,10 +104,10 @@ Indices and tables
* :ref:`search`

.. |pyvers| image:: https://img.shields.io/pypi/pyversions/frequent.svg
:target: https://pypi.org/projects/frequent/
:target: https://pypi.org/project/frequent/
:alt: Supported Python Versions
.. |pypi| image:: https://img.shields.io/pypi/v/frequent.svg
:target: https://pypi.org/projects/frequent/
:target: https://pypi.org/project/frequent/
:alt: PyPI Page
.. |docs| image:: https://readthedocs.org/projects/frequent-py/badge/?version=latest
:target: https://frequent-py.readthedocs.io/en/latest/
Expand All @@ -126,5 +119,5 @@ Indices and tables
:target: https://coveralls.io/github/douglasdaly/frequent-py
:alt: Coverage
.. |nbsp| unicode:: 0xA0
:trim:
:trim:
.. |copy| unicode:: 0xA9 .. copyright sign
75 changes: 60 additions & 15 deletions docs/usage/foundation.messaging.rst
Expand Up @@ -9,8 +9,12 @@ applications which implement the :doc:`pattern.repository` and/or the
advantages of using this kind of system are:

- It **decouples** :obj:`Message` objects from the business-logic which handles
them (:obj:`MessageHandlers`).
- It allows **multiple** handlers to act on a single :obj:`Message` object.
them (:obj:`MessageHandler` or any :obj:`Callable` taking a message object as
its first argument).
- It allows **chaining** of handlers together in order to call subsequent
handlers on a message in a sequential way via a simple :obj:`chain` function.
- It allows **broadcasting** a single message to multiple handlers (different
from sequential chaining).
- It uses a **centralized** :obj:`MessageBus` to shuttle messages about (you
*may* want to create your own instance with the :doc:`utility.singleton`
module to make it a singleton object).
Expand All @@ -22,20 +26,20 @@ advantages of using this kind of system are:
Usage
=====

The :py:mod:`messaging` module provides all the pieces needed to create custom
:obj:`Message` and :obj:`MessageHandler` classes as well as the components
needed to facilitate message passing via the :obj:`MessageBus` and
:obj:`HandlerRegistry` classes. It also provides a special decorator,
:obj:`message`, which is the simplest way to create your own :obj:`Message`
objects. In the examples below we'll create a *very basic* messaging framework
to deliver messages to the appropriate user's mailbox.
The :obj:`frequent.messaging` module provides all the pieces needed to create
custom :obj:`Message <frequent.messaging.Message>` and
:obj:`MessageHandler <frequent.messaging.MessageHandler>` classes as well as
the components needed to facilitate message passing via the
:obj:`MessageBus <frequent.messaging.MessageBus>`. In the examples below
we'll create a *very basic* messaging framework to deliver messages to the
appropriate user's mailbox to show some of these features.


Creating Message Classes
------------------------

To start you'll need to create your own :obj:`Message` classes, which can be
done using the ``@message`` decorator:
To start you'll need to create your own ``Message`` classes, which can be done
using the ``@message`` decorator:

.. code-block:: python
Expand All @@ -48,14 +52,14 @@ done using the ``@message`` decorator:
text: str
The decorator will automatically add the :obj:`Message` class to the base
The decorator will automatically add the ``Message`` class to the base
classes (``__bases__``) of the ``MyMessage`` class and cast the class as a
`dataclass <https://docs.python.org/3/library/dataclasses.html>`_ via the new
(as of Python 3.7) standard library.

.. note::

Each instance of :obj:`Message` has an auto-generated ``id`` attribute (a
Each instance of ``Message`` has an auto-generated ``id`` attribute (a
:obj:`UUID`) generated using ``uuid.uuid1()`` from the standard library.


Expand All @@ -69,16 +73,51 @@ Now let's create a message handler for sending messages by subclassing the
from frequent.messaging import MessageHandler
def MyMessageHandler(MessageHandler):
class MyMessageHandler(MessageHandler):
def __init__(self, bus, mailboxes):
self._mailboxes = mailboxes
return super().__init__(bus)
def handle(self, msg):
def handle(self, msg, successor=None):
self._mailboxes[msg.recipient].append(msg)
return
We can create the instance now with:

>>> bus = MessageBus()
>>> mailboxes = []
>>> my_message_handler = MyMessageHandler(bus, mailboxes)

.. note::

Handlers can also be functions which take the first argument as the message
object and an (optional) keyword-argument ``successor`` for the next
handler to call (if chaining handlers together). The advantage of the
:obj:`MessageHandler` object is it's reference to a :obj:`MessageBus` which
it can use to transmit additional messages (if needed).


Chaining Handlers Together
--------------------------

Suppose we want to first log a message prior to handling it, we can create a
function to do that which will then call the next function in the chain:

.. code-block:: python
from frequent.messaging import chain
def log_message_handler(msg, successor):
print(f"{msg.sender}->{msg.recipient}: '{msg.text}'")
return successor(msg)
Now we chain this one together with the previous ``MyMessageHandler``:

>>> chained_handler = chain(log_message_handler, my_message_handler)
>>> chained_handler(MyMessage('Doug', 'Liz', 'Hello!'))
Doug->Liz: 'Hello!'


Configuring the MessageBus
--------------------------
Expand Down Expand Up @@ -130,6 +169,9 @@ Module
Decorators
:obj:`message <frequent.messaging.message>`

Functions
:obj:`chain <frequent.messaging.chain>`

Abstract Classes
:obj:`Message <frequent.messaging.Message>`,
:obj:`MessageHandler <frequent.messaging.MessageHandler>`
Expand All @@ -141,3 +183,6 @@ Classes
Exceptions
:obj:`MessagingException <frequent.messaging.MessagingException>`,
:obj:`NoHandlersFoundException <frequent.messaging.NoHandlersFoundException>`

Type Hints
:obj:`T_Handler <frequent.messaging.T_Handler>`
36 changes: 7 additions & 29 deletions docs/usage/pattern.unit_of_work.rst
Expand Up @@ -5,26 +5,13 @@ Unit of Work
The :doc:`frequent.unit_of_work <../api/frequent.unit_of_work>` module provides
base classes for an implementation of the unit of work pattern. This pattern
is (sometimes) used when working with object persistence/storage (e.g. ORMs).
The advantages of using this pattern when working with object
persistence/storage are:

.. _advantages:

- The **transactional**-nature of units of work:

Regardless of the storage back-end implemented with the
:obj:`UnitOfWork <frequent.unit_of_work.UnitOfWork>` subclass (provided it
can support staging changes to be made, persisting those changes and
deleting those changes) all work performed inside a
:obj:`UnitOfWork <frequent.unit_of_work.UnitOfWork>` context block is a
transaction.

- The **decoupling** of business-logic from storage-system details:

Regardless of the implementation details for working with the storage
system, the way units of work are used remains constant. This allows you
to swap-out (or inject) storage back-ends as needed *without changing the
business-logic* of the application.
The primary advantage of this pattern is the **transactional** nature of units
of work. Regardless of the storage back-end implemented with the
:obj:`UnitOfWork <frequent.unit_of_work.UnitOfWork>` subclass (provided it can
support staging changes to be made, persisting those changes and deleting those
changes) all work performed inside a
:obj:`UnitOfWork <frequent.unit_of_work.UnitOfWork>` context block is a
transaction.

.. note::

Expand Down Expand Up @@ -254,15 +241,6 @@ Thus we have only two possible outcomes when adding ``User`` objects:
The point is, we won't wind up in some in-between state where the user is added
but the associated profile is not (or vice-versa).

This also allows us to de-couple the details of our storage back-end from the
business-logic of the application. We can easily swap out the SQLAlchemy-based
``MyUnitOfWork`` for any other
:obj:`UnitOfWork <frequent.unit_of_work.UnitOfWork>` utilizing any type of
storage system **without requiring any changes** to our ``create_new_user``
(or any other business) logic. The
:obj:`UnitOfWork <frequent.unit_of_work.UnitOfWork>` provides a standardized
way of interacting with our persistence layer.


Useful Links
============
Expand Down
2 changes: 1 addition & 1 deletion src/frequent/__version__.py
@@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__version__ = '0.1.0'
__version__ = '0.1.1'

0 comments on commit 91775ca

Please sign in to comment.