From 0ebb2be3167ebfe18055d3bd5465c94c100a5ae9 Mon Sep 17 00:00:00 2001 From: Douglas Daly Date: Sun, 14 Jul 2019 12:07:01 -0400 Subject: [PATCH 1/4] Update README --- .travis.yml | 2 -- README.md | 35 +++++++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3dd7555..c4e86c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,5 +27,3 @@ jobs: python: "3.7" dist: xenial sudo: required - - python: "3.6" - - python: "3.5" diff --git a/README.md b/README.md index 6b8d41e..0297498 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,27 @@ - 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``: components providing global application configuration settings + management and storage. + +- ``messaging``: the foundations for building custom messaging frameworks. + +- ``repository``: base class (and exception classes) for implementing the + repository pattern for back-end agnostic object storage. + +- ``singleton``: metaclass for creating singleton classes. + +- ``unit_of_work``: 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 @@ -33,16 +54,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 From ffb4d87dae70b0a7afaabf89ef3cbc9424ef3128 Mon Sep 17 00:00:00 2001 From: Douglas Daly Date: Sun, 14 Jul 2019 13:17:24 -0400 Subject: [PATCH 2/4] Minor fixes to docs --- README.md | 19 +++++++++++-------- docs/index.rst | 6 +++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0297498..548d8aa 100644 --- a/README.md +++ b/README.md @@ -20,18 +20,21 @@ 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``: components providing global application configuration settings - management and storage. +- [``config``](./src/frequent/config.py): components providing global + application configuration settings management and storage. -- ``messaging``: the foundations for building custom messaging frameworks. +- [``messaging``](./src/frequent/messaging.py): the foundations for building + custom messaging frameworks. -- ``repository``: base class (and exception classes) for implementing the - repository pattern for back-end agnostic object storage. +- [``repository``](./src/frequent/repository.py): base class (and exception + classes) for implementing the repository pattern for back-end agnostic object + storage. -- ``singleton``: metaclass for creating singleton classes. +- [``singleton``](./src/frequent/singleton.py): metaclass for creating + singleton classes. -- ``unit_of_work``: base classes for implementing the unit of work pattern for - transactional blocks. +- [``unit_of_work``](./src/frequent/unit_of_work.py): base classes for + implementing the unit of work pattern for transactional blocks. ## Installation diff --git a/docs/index.rst b/docs/index.rst index 870076f..fe1e8e2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -111,10 +111,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/ @@ -126,5 +126,5 @@ Indices and tables :target: https://coveralls.io/github/douglasdaly/frequent-py :alt: Coverage .. |nbsp| unicode:: 0xA0 - :trim: + :trim: .. |copy| unicode:: 0xA9 .. copyright sign From 4724fe6cf1edc2af243ae7f1eb6edbc7107203d9 Mon Sep 17 00:00:00 2001 From: Douglas Daly Date: Fri, 2 Aug 2019 19:42:58 -0400 Subject: [PATCH 3/4] Added chaining to messaging framework, doc fixes. --- .envrc | 1 - Makefile | 4 +- docs/index.rst | 7 -- docs/usage/foundation.messaging.rst | 75 ++++++++++++++---- docs/usage/pattern.unit_of_work.rst | 36 ++------- src/frequent/messaging.py | 117 ++++++++++++++++++++++------ tests/test_messaging.py | 84 ++++++++++++++++---- todos/done.txt | 2 + todos/todo.txt | 2 - 9 files changed, 234 insertions(+), 94 deletions(-) diff --git a/.envrc b/.envrc index e71b81b..d6e22d9 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1 @@ export TODO_DIR=./todos -export PIP_USE_PEP517=false diff --git a/Makefile b/Makefile index 86b8eac..4f9c9ac 100644 --- a/Makefile +++ b/Makefile @@ -145,12 +145,12 @@ 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_DEPS) diff --git a/docs/index.rst b/docs/index.rst index fe1e8e2..b323ecd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 ============ diff --git a/docs/usage/foundation.messaging.rst b/docs/usage/foundation.messaging.rst index bd91af5..0fd1a87 100644 --- a/docs/usage/foundation.messaging.rst +++ b/docs/usage/foundation.messaging.rst @@ -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). @@ -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 ` and +:obj:`MessageHandler ` classes as well as +the components needed to facilitate message passing via the +:obj:`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 @@ -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 `_ 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. @@ -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 -------------------------- @@ -130,6 +169,9 @@ Module Decorators :obj:`message ` +Functions + :obj:`chain ` + Abstract Classes :obj:`Message `, :obj:`MessageHandler ` @@ -141,3 +183,6 @@ Classes Exceptions :obj:`MessagingException `, :obj:`NoHandlersFoundException ` + +Type Hints + :obj:`T_Handler ` diff --git a/docs/usage/pattern.unit_of_work.rst b/docs/usage/pattern.unit_of_work.rst index d2a1d1f..677f93d 100644 --- a/docs/usage/pattern.unit_of_work.rst +++ b/docs/usage/pattern.unit_of_work.rst @@ -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 ` subclass (provided it - can support staging changes to be made, persisting those changes and - deleting those changes) all work performed inside a - :obj:`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 ` subclass (provided it can +support staging changes to be made, persisting those changes and deleting those +changes) all work performed inside a +:obj:`UnitOfWork ` context block is a +transaction. .. note:: @@ -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 ` utilizing any type of -storage system **without requiring any changes** to our ``create_new_user`` -(or any other business) logic. The -:obj:`UnitOfWork ` provides a standardized -way of interacting with our persistence layer. - Useful Links ============ diff --git a/src/frequent/messaging.py b/src/frequent/messaging.py index e090432..227a8ac 100644 --- a/src/frequent/messaging.py +++ b/src/frequent/messaging.py @@ -19,16 +19,22 @@ from abc import abstractmethod from collections.abc import Mapping from dataclasses import dataclass +from functools import partial from typing import Any +from typing import Callable from typing import Iterator from typing import Optional from typing import Sequence from typing import Tuple from typing import Type +from typing import Union from uuid import UUID from uuid import uuid1 +T_Handler = Union[Callable[['Message'], None], 'MessageHandler'] + + class Message(ABC): """ Base class for all message objects. @@ -95,8 +101,8 @@ def convert_to_message(cls: type, target_cls: Type[Message]) -> Type[Message]: """ new_cls = dataclass(cls, frozen=True) - if not isinstance(cls, Message): - new_cls = type(cls.__name__, (new_cls, Message), {}) + if not isinstance(cls, target_cls): + new_cls = type(cls.__name__, (new_cls, target_cls), {}) return new_cls @@ -153,17 +159,27 @@ def bus(self) -> 'MessageBus': """ return self._bus - def __call__(self, msg: Message) -> None: - return self.handle(msg) + def __call__( + self, + msg: Message, + successor: Optional[T_Handler] = None + ) -> None: + return self.handle(msg, successor=successor) @abstractmethod - def handle(self, msg: Message) -> None: + def handle( + self, + msg: Message, + successor: Optional[T_Handler] = None + ) -> None: """Handles the given message object. Parameters ---------- msg : Message The message object to handle. + successor : :obj:`MessageHandler` or :obj:`Callable`, optional + The next handler in the chain. """ pass @@ -184,8 +200,7 @@ class HandlerRegistry(Mapping): >>> registry + }> """ @@ -202,9 +217,9 @@ def __repr__(self) -> str: if len(v) > 1: ret += 's' ret += "]," - return ret + "\n }\n>" + return ret + "\n}>" - def __getitem__(self, key: Type[Message]) -> Sequence[MessageHandler]: + def __getitem__(self, key: Type[Message]) -> Sequence[T_Handler]: ret = self._store.get(key) if not ret: raise NoHandlersFoundException(key) @@ -219,8 +234,8 @@ def __len__(self) -> int: def add( self, msg_cls: Type[Message], - handler: MessageHandler, - *handlers: Tuple[MessageHandler, ...] + handler: T_Handler, + *handlers: Tuple[T_Handler, ...] ) -> None: """Adds message handler(s) for the specified message type. @@ -228,12 +243,11 @@ def add( ---------- msg_cls : :obj:`type` of :obj:`Message` The message class to add handler mappings for. - handler : MessageHandler - The message handler instance to map to the given `msg_cls` - type. + handler : :obj:`MessageHandler` or :obj:`Callable` + The message handler to map to the given ``msg_cls`` type. handlers : optional - Additional message handler instance(s) to map to the given - `msg_cls` type. + Additional message handler(s) to map to the given + ``msg_cls`` type. """ if msg_cls not in self._store: @@ -249,7 +263,7 @@ def get( self, key: Type[Message], default: Any = None - ) -> Sequence[MessageHandler]: + ) -> Sequence[T_Handler]: """Gets the message handler(s) associated with the given type. Parameters @@ -261,8 +275,8 @@ def get( Returns ------- - :obj:`Sequence` of :obj:`MessageHandler` or :obj:`object` - The message handlers found (if any) or the `default` value + :obj:`Sequence` of :obj:`Callable` or :obj:`object` + The message handlers found (if any) or the ``default`` value given if no message handlers are found. """ @@ -271,7 +285,7 @@ def get( except NoHandlersFoundException: return default - def remove(self, msg_cls: Type[Message]) -> Sequence[MessageHandler]: + def remove(self, msg_cls: Type[Message]) -> Sequence[T_Handler]: """Removes all mappings for the specified message type. Parameters @@ -281,14 +295,15 @@ def remove(self, msg_cls: Type[Message]) -> Sequence[MessageHandler]: Returns ------- - :obj:`list` of :obj:`MessageHandler` - The message handler implementations which were mapped to the - specified `msg_cls` type. + :obj:`Sequence` of :obj:`Callable` + The message handlers which were mapped to the specified + ``msg_cls`` type. Raises ------ NoHandlersFoundException - If no handlers were mapped to the specified `msg_cls` type. + If no handlers were mapped to the specified ``msg_cls`` + type. """ handlers = self._store.pop(msg_cls) @@ -344,9 +359,61 @@ def handle(self, msg: Message) -> None: return +def chain(*handlers: Tuple[T_Handler, ...]) -> T_Handler: + """Chains multiple handlers together. + + Parameters + ---------- + handlers : :obj:`MessageHandler` or :obj:`Callable` + The handlers to chain together, with each being passed the next + handler via the ``successor`` keyword-argument. + + Returns + ------- + Callable + The chained callable to use for message handling. + + Examples + -------- + To chain together two handlers into one: + + >>> class MessageIdLogger(MessageHandler): + ... def handle(self, msg, successor=None): + ... # Print our message's ID + ... print(msg.id) + ... # ... then call the next handler. + ... return successor(msg) + ... + >>> def my_next_handler(msg): + ... if msg.code == 42: + ... print('The answer!') + ... else: + ... print('Not the answer...') + ... return + >>> chained_handler = chain(MessageIdLogger(), my_next_handler) + >>> ans_msg = MyMessage(42) + >>> not_ans_msg = MyMessage(41) + >>> chained_handler(answer_msg) + 5a47c192-a50b-11e9-bc30-a434d9ba8632 + The answer! + >>> chained_handler(not_ans_msg) + 9a47c651-a52b-11f9-bc80-a484d9ba8633 + Not the answer + + """ + def _chain(head, tail): + if not tail: + return head + nxt_head, *nxt_tail = tail + return partial(head, successor=_chain(nxt_head, nxt_tail)) + + head, *tail = handlers + return _chain(head, tail) + + class MessagingException(Exception): """ - Messaging framework exception. + Messaging framework base exception. """ pass diff --git a/tests/test_messaging.py b/tests/test_messaging.py index 9c78db8..a8ed354 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -82,8 +82,6 @@ class TestMessageBus(object): def test_create(self) -> None: t_bus = messaging.MessageBus() - assert repr(t_bus) == "" - t_msg = DecoratedMessage('Liz', 'Doug', 'Hi!') try: t_bus.handle(t_msg) @@ -106,7 +104,6 @@ class TestHandlerRegistry(object): def test_create(self) -> None: registry = messaging.HandlerRegistry() assert len(registry) == 0 - assert repr(registry) == "" return def test_usage(self) -> None: @@ -119,11 +116,6 @@ def test_usage(self) -> None: assert len(registry) == 1 assert registry.get(DecoratedMessage) == [t_handler_a] assert registry.get(BasicMessage) is None - assert repr(registry) == ( - "" - ) thl = registry.remove(DecoratedMessage) assert len(thl) == 1 @@ -136,11 +128,6 @@ def test_usage(self) -> None: assert len(registry) == 1 assert len(registry[DecoratedMessage]) == 2 assert registry.get(DecoratedMessage) == [t_handler_b, t_handler_a] - assert repr(registry) == ( - "" - ) registry.clear() assert len(registry) == 0 @@ -150,3 +137,74 @@ def test_usage(self) -> None: except messaging.NoHandlersFoundException: assert True return + + +def test_chain(): + """Tests the :obj:`messaging.chain` method.""" + msg_q = BasicMessage('DeepThought', -1) + msg_a = BasicMessage('The Mice', 42) + + log_ids = [] + log_res = [] + + class LogMsgHandler(messaging.MessageHandler): + def handle( + self, msg: BasicMessage, successor: messaging.T_Handler = None + ) -> None: + log_ids.append(msg.id) + if successor: + successor(msg) + return + + def save_result(msg: BasicMessage) -> None: + log_res.append(f"{msg.target}: {msg.code}") + if msg.code == 42: + log_res.append("You're welcome.") + return + + bus = messaging.MessageBus() + log_handler = LogMsgHandler(bus) + + chained = messaging.chain(log_handler, save_result) + chained(msg_q) + chained(msg_a) + + assert len(log_ids) == 2 + assert log_ids[0] == msg_q.id + assert log_ids[1] == msg_a.id + + assert len(log_res) == 3 + assert log_res[0] == f"{msg_q.target}: {msg_q.code}" + assert log_res[1] == f"{msg_a.target}: {msg_a.code}" + assert log_res[2] == "You're welcome." + + log_ids.clear() + log_res.clear() + + def check_target( + msg: BasicMessage, successor: messaging.T_Handler + ) -> None: + if msg.target == 'The Mice': + return successor(msg) + return + + chained_two = messaging.chain(log_handler, check_target, save_result) + chained_two(msg_q) + chained_two(msg_a) + + assert len(log_ids) == 2 + assert log_ids[0] == msg_q.id + assert log_ids[1] == msg_a.id + + assert len(log_res) == 2 + assert log_res[0] == f"{msg_a.target}: {msg_a.code}" + assert log_res[1] == "You're welcome." + + chained_break = messaging.chain(save_result, check_target) + try: + chained_break(msg_a) + assert False + except TypeError: + assert True + + return diff --git a/todos/done.txt b/todos/done.txt index e69de29..89d2046 100644 --- a/todos/done.txt +++ b/todos/done.txt @@ -0,0 +1,2 @@ +x 2019-08-02 Allow handlers to be chained in succession @messaging +change +x 2019-08-02 Allow handlers to be simple functions @messaging +change diff --git a/todos/todo.txt b/todos/todo.txt index e3de84b..3f77a34 100644 --- a/todos/todo.txt +++ b/todos/todo.txt @@ -1,6 +1,4 @@ Fix messaging.message decorator to pass MyPy tests -Compatibility with py3.6 +change -Compatibility with py3.5 +change Unit tests for messaging module +add Unit tests for unit_of_work module +add Unit tests for repository module +add From c42bd484526dd2545d19e11d88ada254cad1e171 Mon Sep 17 00:00:00 2001 From: Douglas Daly Date: Sun, 4 Aug 2019 16:18:21 -0400 Subject: [PATCH 4/4] Updates for release --- .gitignore | 4 ++- CHANGELOG.md | 10 +++++++- Makefile | 2 +- docs/changelogs/changelog_1.rst | 4 +-- docs/changelogs/changelog_3.rst | 10 ++++++++ src/frequent/__version__.py | 2 +- tasks/develop.py | 43 --------------------------------- tasks/docs.py | 3 --- tasks/generate.py | 4 --- tasks/helpers.py | 3 --- tasks/release.py | 3 --- todos/done.txt | 2 -- todos/report.txt | 1 + 13 files changed, 27 insertions(+), 64 deletions(-) create mode 100644 docs/changelogs/changelog_3.rst delete mode 100644 tasks/develop.py diff --git a/.gitignore b/.gitignore index 2268ecb..ceea745 100644 --- a/.gitignore +++ b/.gitignore @@ -70,9 +70,11 @@ target/ # Pycharm .idea +# Neovim +.vim + # VS Code .vscode -XDG_CACHE_HOME # Jupyter NB Checkpoints .ipynb_checkpoints/ diff --git a/CHANGELOG.md b/CHANGELOG.md index cdc6e3f..c843a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/Makefile b/Makefile index 4f9c9ac..92ea7ad 100644 --- a/Makefile +++ b/Makefile @@ -152,7 +152,7 @@ update-requirements: -update-requirements-actual generate-requirements ## Update -update-requirements-actual: $(UPDATE_DEPS) -generate-requirements: +generate-requirements: ## Generates the requirements.txt files $(GENERATE_DEPS) # Documentation diff --git a/docs/changelogs/changelog_1.rst b/docs/changelogs/changelog_1.rst index f4fd98e..e36172d 100644 --- a/docs/changelogs/changelog_1.rst +++ b/docs/changelogs/changelog_1.rst @@ -1,5 +1,5 @@ -v0.0.1 -====== +0.0.1 +===== :Release: June 09, 2019 diff --git a/docs/changelogs/changelog_3.rst b/docs/changelogs/changelog_3.rst new file mode 100644 index 0000000..e358f32 --- /dev/null +++ b/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 diff --git a/src/frequent/__version__.py b/src/frequent/__version__.py index a8d2807..8671cd3 100644 --- a/src/frequent/__version__.py +++ b/src/frequent/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '0.1.0' +__version__ = '0.1.1' diff --git a/tasks/develop.py b/tasks/develop.py deleted file mode 100644 index c5fc60e..0000000 --- a/tasks/develop.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tasks related to project development. -""" -# -# Imports -# -import glob - -import invoke - -from .helpers import log as hlog - - -# -# Helpers -# - -def log(msg, level=None): - hlog(msg, name='develop', level=level) - - -# -# Script functions -# - -@invoke.task -def update_requirements(ctx): - """Updates the requirements.txt files""" - for r_file in glob.glob('./requirements*.txt'): - log('Processing %s' % r_file) - with open(r_file, 'r') as fin: - contents = fin.readlines() - out = [] - for ln in contents: - ln = ln.strip() - if '-e .' in ln and '--no-use-pep517' not in ln: - ln = '%s %s' % ('--no-use-pep517', ln) - out.append(ln) - with open(r_file, 'w') as fout: - fout.write('\n'.join(out)) - return - diff --git a/tasks/docs.py b/tasks/docs.py index 4f4819c..7b3481b 100644 --- a/tasks/docs.py +++ b/tasks/docs.py @@ -2,9 +2,6 @@ """ Documentation related tasks. """ -# -# Imports -# from glob import glob import os import re diff --git a/tasks/generate.py b/tasks/generate.py index d53cb6c..2d4166f 100644 --- a/tasks/generate.py +++ b/tasks/generate.py @@ -9,9 +9,6 @@ https://github.com/pypa/pip/blob/master/tasks/generate.py """ -# -# Imports -# import datetime import glob import os @@ -289,4 +286,3 @@ def changelog(ctx, draft=False): ctx.run('git add %s' % doc_file) return - diff --git a/tasks/helpers.py b/tasks/helpers.py index bdde91c..92d7180 100644 --- a/tasks/helpers.py +++ b/tasks/helpers.py @@ -2,9 +2,6 @@ """ Helper functions for tasks. """ -# -# Imports -# import os import subprocess diff --git a/tasks/release.py b/tasks/release.py index eba463f..575763f 100644 --- a/tasks/release.py +++ b/tasks/release.py @@ -2,9 +2,6 @@ """ Tasks related to releases. """ -# -# Imports -# from .helpers import log as hlog diff --git a/todos/done.txt b/todos/done.txt index 89d2046..e69de29 100644 --- a/todos/done.txt +++ b/todos/done.txt @@ -1,2 +0,0 @@ -x 2019-08-02 Allow handlers to be chained in succession @messaging +change -x 2019-08-02 Allow handlers to be simple functions @messaging +change diff --git a/todos/report.txt b/todos/report.txt index 22b7416..9b3d2ae 100644 --- a/todos/report.txt +++ b/todos/report.txt @@ -1,2 +1,3 @@ 2019-06-09T23:18:24 0 2 2019-07-13T19:22:14 6 6 +2019-08-04T16:06:58 4 2