From ab66e74482c938acbd087f33d465c862b6c655d5 Mon Sep 17 00:00:00 2001 From: Lorenzo Berni Date: Tue, 14 Feb 2017 15:51:02 +0100 Subject: [PATCH 1/6] Refactoring of store documentation --- docs/api/action.rst | 2 +- docs/api/reducer.rst | 2 +- docs/api/store.rst | 2 +- docs/conf.py | 7 +- requirements.txt | 5 +- revived/action.py | 9 ++- revived/store.py | 178 ++++++++++++++++++++++++++----------------- 7 files changed, 127 insertions(+), 78 deletions(-) diff --git a/docs/api/action.rst b/docs/api/action.rst index 7bbf34f907..dba1dcc294 100644 --- a/docs/api/action.rst +++ b/docs/api/action.rst @@ -1,6 +1,6 @@ -======================================= ``revived.action`` module documentation ======================================= .. automodule:: revived.action :members: + :special-members: diff --git a/docs/api/reducer.rst b/docs/api/reducer.rst index a116ab7b17..519208a4a6 100644 --- a/docs/api/reducer.rst +++ b/docs/api/reducer.rst @@ -1,6 +1,6 @@ -======================================== ``revived.reducer`` module documentation ======================================== .. automodule:: revived.reducer :members: + :special-members: diff --git a/docs/api/store.rst b/docs/api/store.rst index be68306c0f..ee157681f4 100644 --- a/docs/api/store.rst +++ b/docs/api/store.rst @@ -1,6 +1,6 @@ -====================================== ``revived.store`` module documentation ====================================== .. automodule:: revived.store :members: + :special-members: diff --git a/docs/conf.py b/docs/conf.py index af9d9387cb..72feee5dc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,8 +33,11 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.viewcode'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx_autodoc_typehints' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/requirements.txt b/requirements.txt index dbffee2bff..701ad94728 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ mypy pytest -sphinx-autobuild Sphinx -sphinx_rtd_theme \ No newline at end of file +sphinx_rtd_theme +sphinx-autobuild +sphinx-autodoc-typehints \ No newline at end of file diff --git a/revived/action.py b/revived/action.py index 02b1c180a3..824457d206 100644 --- a/revived/action.py +++ b/revived/action.py @@ -1,4 +1,6 @@ -"""Action module. +""" +Action module +============= This module implements helper functions and classes that can be used to define actions and action creators, in the same fashion of redux ones, but using @@ -21,7 +23,10 @@ class ActionType(str, Enum): for each module. Usually there would be no need for such a feature-less class, but it is pretty handy while using type hints. """ - pass + # FIXME: this method is added to avoid sphinx_autodoc_typehints errors: + # see https://github.com/agronholm/sphinx-autodoc-typehints/issues/12 + def __init__(*args, **kwargs): + pass class Action(dict): diff --git a/revived/store.py b/revived/store.py index 5fb76c000e..ee0b975f54 100644 --- a/revived/store.py +++ b/revived/store.py @@ -1,7 +1,66 @@ -"""Store module. +""" +Store module +============ -This module implements the global state store, and the init action and +This module implements the **global state store**, and the ``init`` action and action_creator. This is the entry point of the revived module. + +Dispatch actions +---------------- + +To dispatch actions the :any:`revived.store.Store.dispatch` method should be +used, passing as parameter the result of an action_creator. See more in +:any:`revived.action.action` and :any:`revived.action.Action`. + +.. code:: python + + # create the store object + store = Store(root_reducer) + + # register subscribers + # ... + + # dispatch an action using the action_creator + store.dispatch(an_action_creator(a_parameter, another_parameter)) + +Subscribe and unsubscribe to state changes +------------------------------------------ + +There are two ways to **subscribe** and **usubscribe** to store changes: + +#. using the :any:`revived.store.Store.subscribe` method: + + .. code:: python + + # create the store object + store = Store(root_reducer) + + # define the function + def a_subscriber(): + # do something! + pass + + # subscribe the function + unsubscribe = store.subscribe(a_subscriber) + + # unsubscribe the function + unsubscribe() + +#. using the :any:`revived.store.Store.subscriber` decorator: + + .. code:: python + + # create the store object + store = Store(root_reducer) + + # define and subscribe the function + @store.subscriber + def a_subscriber(): + # do something! + pass + + # unsubscribe the function + a_subscriber.unsubscribe() """ from .action import action from .action import Action @@ -18,7 +77,7 @@ class ActionType(BaseActionType): """Action types for the store module. - Basically the only type here is the **INIT** one. Reducers should wait for + Basically the only type here is the ``INIT`` one. Reducers should wait for this action to create the initial state for the state subpath they are responsible of. """ @@ -27,95 +86,72 @@ class ActionType(BaseActionType): @action(ActionType.INIT) def init(): - """Action creator for the init aciton. + """Action creator for the init action. """ pass class DispatchInReducerError(Exception): - """Exception raised when a dispatch is called inside a reducer. + """Raised when :any:`revived.store.Store.dispatch` is called in a reducer. """ - pass + # FIXME: this method is added to avoid sphinx_autodoc_typehints errors: + # see https://github.com/agronholm/sphinx-autodoc-typehints/issues/12 + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) class Subscriber: - """Wrapper around a subscriber function with unsubscribe property. + """Wrapper around a subscriber function with the ``unsubscribe`` property. - Creating a subscriber via decorator it is not possible to return the - unsubscribe function. So a Subscriber is created around the callback, that - contains the unsubscribe function to be used to properly unregister the - subscriber. + While creating a subscriber using the decorator it is not possible to return + the ``unsubscribe`` function. So a ``Subscriber`` is created wrapping the + callback, that contains the :any:`revived.store.Subscriber.unsubscribe` + function to be used to properly unregister the subscriber. """ def __init__(self, callback: Callable[[], None], unsubscribe: Callable[[], None]) -> None: """Constructor. Creates a subscriber wrapper around a callback, with the provided - unsubscribe function. + ``unsubscribe`` function. :param callback: The callback to be wrapped into the subscriber. :param unsubscribe: The unsubscribe function for the subscriber. """ self.callback = callback - self.unsubscribe = unsubscribe + self._unsubscribe = unsubscribe def __call__(self) -> None: """Calls the wrapped subscriber. """ self.callback() + @property + def unsubscribe(self) -> Callable[[], None]: + """Property containing the ``unsubscribe`` function. + + :returns: The ``unsubscribe`` function for the subscriber. + """ + return self._unsubscribe + class Store: """Container object of the global state. This object is responsible of the global state. Its main responsibilities are: - * Keeping track of all the subscribers, and call them on state changes. - * Keeping reference to the reducer to be used and call it to proprly handle - state changes. - - There are two ways to subscribe and usubscribe to store changes: - - #. using the ``Store.subscribe`` method: - - .. code:: python - - # create the store object - store = Store(root_reducer) - # define the function - def a_subscriber(): - # do something! - pass - - # subscribe the function - unsubscribe = store.subscribe(a_subscriber) - - # unsubscribe the function - unsubscribe() - - #. using the ``Store.subscriber`` decorator: - - .. code:: python - - # create the store object - store = Store(root_reducer) - - # define and subscribe the function - @store.subscriber - def a_subscriber(): - # do something! - pass - - # unsubscribe the function - a_subscriber.unsubscribe() + * Keeping track of all the *subscribers*, and call them on **state + changes**. + * Keeping reference to the *reducer* to be used and call it to properly + handle **state changes**. """ def __init__(self, reducer: Union[Reducer, Module]) -> None: """Constructor. - Creates the store, using the given function as reducer. At the beginning - no callback is subscribed to store changes. It is possible to add - subscribers later, while there is no way - at the moment - to replace - the reducer. + Creates the store, using the given function as ``reducer``. At the + beginning no callback is subscribed to *store changes*. It is possible + to add subscribers later, while there is no way - *at the moment* - to + replace the reducer. :param reducer: The root reducer. """ @@ -128,32 +164,34 @@ def __init__(self, reducer: Union[Reducer, Module]) -> None: self.dispatch(init()) def subscribe(self, callback: Callable[[], None]) -> Callable[[], None]: - """Subscribes a callback to state changes. + """Subscribes a callback to *state changes*. Every time the state changes, the callback is called. No parameters are - passed to the callback. It is responsibility of the store handler to - actually connect the store with the caller. The returned function can be - called without arguments to unsubscribe the callback. + passed to the callback. It is responsibility of the callback to actually + connect the store with the caller. The returned function can be called + without arguments to unsubscribe the callback. :param callback: The callback to be subscribed. - :returns: The unsubscribe functions. + :returns: The unsubscribe function. """ key = uuid.uuid1() self._subscribers[key] = callback - def unsubscribe(): + def unsubscribe() -> None: self._subscribers.pop(key, None) return unsubscribe def subscriber(self, callback: Callable[[], None]) -> Subscriber: - """Decorator function to subscribe a function to store changes. + """Decorator function to subscribe a function to *store changes*. The subscribed function will be called every time the internal state of the store changes. + **NOTE: The decorator function will return the function itself**. To - unsubscribe the callback the user should use the *unsubscribe* function - attached into the callback. + unsubscribe the callback the user should use the + :any:`revived.store.Subscriber.unsubscribe` function attached into the + callback. :param callback: The callback to be subscribed. :returns: The callback itself. @@ -164,14 +202,16 @@ def subscriber(self, callback: Callable[[], None]) -> Subscriber: return s def dispatch(self, action: Action) -> None: - """Dispatches an action. + """Dispatches an *action*. + + This is the only piece of code responsible of *dispatching actions*. + When an action is dispatched, the state is changed according to the + defined root reducer and all the subscribers are called. - This is the only piece of code responsible of dispatching actions. When - an action is dispatched, the state is changed according to the defined - root reducer and all the subscribers are called. **The calling order is - not guaranteed**. + **The calling order is not guaranteed**. :param action: The action that should be dispatched. + :raises: :class:`revived.store.DispatchInReducerError` """ if self._is_reducing: raise DispatchInReducerError From 73c479e4d7cb4ba86739ce861f3bb18655409504 Mon Sep 17 00:00:00 2001 From: Lorenzo Berni Date: Wed, 15 Feb 2017 18:26:36 +0100 Subject: [PATCH 2/6] Update action documentation --- docs/api/action.rst | 1 - revived/action.py | 89 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/docs/api/action.rst b/docs/api/action.rst index dba1dcc294..d764eb7d1e 100644 --- a/docs/api/action.rst +++ b/docs/api/action.rst @@ -3,4 +3,3 @@ .. automodule:: revived.action :members: - :special-members: diff --git a/revived/action.py b/revived/action.py index 824457d206..bb654f6df6 100644 --- a/revived/action.py +++ b/revived/action.py @@ -3,8 +3,60 @@ ============= This module implements helper functions and classes that can be used to define -actions and action creators, in the same fashion of redux ones, but using -decorators instead of anonymous functions. +``actions`` and ``action creators``, in the same fashion of redux ones, but +using decorators instead of anonymous functions. + +Actions and action creators +--------------------------- + +Actions are payloads of information that send data from your application to your +``store``. They are the only source of information for the ``store``. You send +them to the store using :any:`revived.store.Store.dispatch`. + +Actions are instances of :any:`revived.action.Action`. They have a type +property. Types should be defined in an enum inheriting +:any:`revived.action.ActionType`. Once your app is large enough, you may want to +move them into a separate module. + +Action creators are exactly that: functions that create ``actions``. It's easy +to conflate the terms ``action`` and ``action creator``, so do your best to use +the *proper term*. + +Define action types +------------------- + +While you are free to define the action type enum as he prefers, it is +**strongly suggested** to write them down in this way: + +.. code:: python + + from revived.actions import ActionType as BaseActionType + + # custom action types enum + class ActionType(BaseActionType): + AN_ACTION_TYPE = 'an_action_type' + + +Define action creators +---------------------- + +While it is possible to explicitly build :any:`revived.action.Action` instances +directly, it is **strongly suggested** to create ``actions`` using ``action +creators``. + +Assuming you are in the same module of the ``action types`` defined previously, +you can define ``action creators`` in this way: + +.. code:: python + + # define the action creator that takes two arguments and returns a + # dictionary with those arguments in an arbitrary way. + @action(ActionTypes.AN_ACTION_TYPE) + def an_action_type_with_parameters(param1, param2): + return {'1': param1, '2': param2} + + # create the action object + action_obj = an_action_type_with_parameters(1, 2) """ from enum import Enum from enum import unique @@ -34,36 +86,33 @@ class Action(dict): Redux actions are plain objects - ie: python dicts - but having a specific class here helps for type hinting. The rationale behind this is that we - store the type as *metadata* instead of part of the action data itself. - """ + store the type as ``metadata`` instead of part of the action data itself. - def __init__(self, action_type: ActionType, data: Optional[Dict[str, Any]]=None) -> None: - """Constructor. + While ``action_type`` is going to be stored as ``metadata``, the + :any:`revived.action.Action` instance itself is going to behave exactly as a + dict, with all the action data inside. - Builds an action using the specified action type and optional data. - While action_type is going to be stored as *metadata*, the Action object - itself is going to behave exactly as a dict, with all the action data - inside. + :param action_type: The type of the action. + :param data: An optional dict containing data. No restriction on depth + and members type, as long as the keys are strings. + """ - :param action_type: The type of the action. - :param data: An optional dict containing data. No restriction on depth - and members type, as long as the keys are strings. - """ + def __init__(self, action_type: ActionType, data: Optional[Dict[str, Any]]=None) -> None: super().__init__(**(data or {})) self.type = action_type def action(action_type: ActionType) -> Callable[[Callable], Callable]: - """Decorator function to use as an *action creator* factory. + """Decorator function to use as an ``action creator`` factory. This helper function is used to create action creators. The idea behind this - is that we just want to define the relevant data as a dict, instead of - complex objects. This decorator will take care of simple-dict-returing - functions preparing the proper Action object that is needed by the revived - API. + is that we just want to define the relevant data as a ``dict``, instead of + complex objects. This decorator will take care of simple-dict-returning + functions preparing the proper :any:`revived.action.Action` instance that + is needed by the revived API. :param action_type: The type of the action. - :returns: The action creator. + :returns: The ``action creator``. """ def wrap(f: Callable[..., Dict]) -> Callable[..., Action]: @wraps(f) From f0dea04fd6a715e0029a4cebff12f07f8d0ce6a6 Mon Sep 17 00:00:00 2001 From: Lorenzo Berni Date: Wed, 15 Feb 2017 18:37:59 +0100 Subject: [PATCH 3/6] Update store documentation. --- docs/api/store.rst | 1 - revived/store.py | 61 ++++++++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/docs/api/store.rst b/docs/api/store.rst index ee157681f4..0337ec86cf 100644 --- a/docs/api/store.rst +++ b/docs/api/store.rst @@ -3,4 +3,3 @@ .. automodule:: revived.store :members: - :special-members: diff --git a/revived/store.py b/revived/store.py index ee0b975f54..f56bdc788c 100644 --- a/revived/store.py +++ b/revived/store.py @@ -5,6 +5,23 @@ This module implements the **global state store**, and the ``init`` action and action_creator. This is the entry point of the revived module. +:any:`revived.store.Store` is the object that brings ``actions`` and +``reducers``. The store has the following responsibilities: + +* Holds application state; +* Allows access to state via :any:`revived.store.Store.get_state`; +* Allows state to be updated via :any:`revived.store.Store.dispatch`; +* Registers listeners via :any:`revived.store.Store.subscribe` or + :any:`revived.store.Store.subscriber` decorator; +* Handles unregistering of listeners via the function returned by + :any:`revived.store.Store.subscribe` or via the property + :any:`revived.store.Subscriber.unsubscribe` of the + :any:`revived.store.Subscriber` instance. + +It's important to note that you'll only have a single store in your application. +When you want to split your data handling logic, you'll use ``reducer`` +composition instead of many stores. + Dispatch actions ---------------- @@ -104,19 +121,16 @@ class Subscriber: """Wrapper around a subscriber function with the ``unsubscribe`` property. While creating a subscriber using the decorator it is not possible to return - the ``unsubscribe`` function. So a ``Subscriber`` is created wrapping the - callback, that contains the :any:`revived.store.Subscriber.unsubscribe` - function to be used to properly unregister the subscriber. - """ - def __init__(self, callback: Callable[[], None], unsubscribe: Callable[[], None]) -> None: - """Constructor. + the ``unsubscribe`` function. So a :any:`revived.store.Subscriber` is + created wrapping the callback, that contains the + :any:`revived.store.Subscriber.unsubscribe` function to be used to properly + unregister the subscriber. - Creates a subscriber wrapper around a callback, with the provided - ``unsubscribe`` function. + :param callback: The callback to be wrapped into the subscriber. + :param unsubscribe: The unsubscribe function for the subscriber. + """ - :param callback: The callback to be wrapped into the subscriber. - :param unsubscribe: The unsubscribe function for the subscriber. - """ + def __init__(self, callback: Callable[[], None], unsubscribe: Callable[[], None]) -> None: self.callback = callback self._unsubscribe = unsubscribe @@ -135,7 +149,7 @@ def unsubscribe(self) -> Callable[[], None]: class Store: - """Container object of the global state. + """Container object for the global state. This object is responsible of the global state. Its main responsibilities are: @@ -144,17 +158,16 @@ class Store: changes**. * Keeping reference to the *reducer* to be used and call it to properly handle **state changes**. - """ - def __init__(self, reducer: Union[Reducer, Module]) -> None: - """Constructor. - Creates the store, using the given function as ``reducer``. At the - beginning no callback is subscribed to *store changes*. It is possible - to add subscribers later, while there is no way - *at the moment* - to - replace the reducer. + Creates the store, using the given function as ``reducer``. At the + beginning no callback is subscribed to *store changes*. It is possible + to add subscribers later, while there is no way - *at the moment* - to + replace the reducer. - :param reducer: The root reducer. - """ + :param reducer: The root reducer. + """ + + def __init__(self, reducer: Union[Reducer, Module]) -> None: self._reducer = reducer self._state = None # type: Any @@ -202,15 +215,15 @@ def subscriber(self, callback: Callable[[], None]) -> Subscriber: return s def dispatch(self, action: Action) -> None: - """Dispatches an *action*. + """Dispatches an ``action``. This is the only piece of code responsible of *dispatching actions*. - When an action is dispatched, the state is changed according to the + When an ``action`` is dispatched, the state is changed according to the defined root reducer and all the subscribers are called. **The calling order is not guaranteed**. - :param action: The action that should be dispatched. + :param action: The ``action`` that should be dispatched. :raises: :class:`revived.store.DispatchInReducerError` """ if self._is_reducing: From be7cf6e036068d87c94627bcef7e1b0b29ef9cb7 Mon Sep 17 00:00:00 2001 From: Lorenzo Berni Date: Thu, 16 Feb 2017 12:18:35 +0100 Subject: [PATCH 4/6] Update reducer module documentation --- docs/api/reducer.rst | 5 +- revived/reducer.py | 209 +++++++++++++++++++++++++++++++++---------- 2 files changed, 166 insertions(+), 48 deletions(-) diff --git a/docs/api/reducer.rst b/docs/api/reducer.rst index 519208a4a6..a8df0a9ac3 100644 --- a/docs/api/reducer.rst +++ b/docs/api/reducer.rst @@ -1,6 +1,5 @@ -``revived.reducer`` module documentation -======================================== +Reducer (``revived.reducer``) module documentation +================================================== .. automodule:: revived.reducer :members: - :special-members: diff --git a/revived/reducer.py b/revived/reducer.py index 92893c5a43..aece0309e0 100644 --- a/revived/reducer.py +++ b/revived/reducer.py @@ -1,8 +1,169 @@ -"""Reducer module. - +""" This module implements helper functions and classes that can be used to define reducers in the same fashion of redux ones, but using decorators instead of anonymous functions. + +Things you **should never do** inside a reducer: + +* Mutate its arguments; +* Perform side effects like API calls and routing transitions; +* Call **non-pure** functions. + +**Given the same arguments, it should calculate the next state and return it. No +surprises. No side effects. No API calls. No mutations. Just a calculation.** + +Create a reducer +================ + +A reducer is a function that looks like this: + +.. code:: python + + def dummy(prev, action): + next = prev + if action.type == ActionType.DUMMY_ACTION_TYPE: + # Do something + return next + +In order to decrease the amount of required boilerplate ``revived`` makes use of +a lot of python goodies, especially **decorators**. + +While every function can be used as ``reducer`` (as long as it takes the proper +parameters), the easiest way to create a ``reducer`` that handles a specific +type of ``actions`` is to use the :any:`revived.reducer.reducer` decorator. + +.. code:: python + + @reducer(ActionType.DUMMY_ACTION_TYPE) + def dummy(prev, action): + next = prev + # Do something + return next + + +Combine reducers +================ + +You can naively combine several ``reducers`` in this way: + +.. code:: python + + def dummy(prev, action): + next = prev + if action.type == ActionType.DUMMY_ACTION_TYPE1: + # Do something + return next + elif action.type == ActionType.DUMMY_ACTION_TYPE2: + # Do something different + return next + else: + return next + +but this is going to make your ``reducer`` function huge and barely readable. +:any:`revived.reducer` contains utility functions that allows you to create much +more readable ``reducers``. + +Reducers can (*and should*) be combined. You can easily do this combination +using :any:`revived.reducer.combine_reducers`. + +The following example will produce a ``combined reducer`` where both the +``reducers`` will handle the whole subtree passed to it: exactly the same result of +the previous snippet of code! + +.. code:: python + + @reducer(ActionType.DUMMY_ACTION_TYPE1) + def dummy1(prev, action): + next = prev + # Do something + return next + + @reducer(ActionType.DUMMY_ACTION_TYPE2) + def dummy2(prev, action): + next = prev + # Do something + return next + + combined_reducer = combine_reducers(dummy1, dummy2) + +**Note**: a ``combined reducer`` is a ``reducer`` and can be combined again with +other reducers allowing you to creare every structure you will ever need in your +app. + +Pass a subtree of the state +--------------------------- + +If you want it is possible to pass to a reducer only a subtree of the state +passed to the ``combined reducer``. To do this you should use keyword arguments +in this way: + +.. code:: python + + @reducer(ActionType.DUMMY_ACTION_TYPE1) + def dummy1(prev, action): + next = prev + # Do something + return next + + @reducer(ActionType.DUMMY_ACTION_TYPE2) + def dummy2(prev, action): + next = prev + # Do something + return next + + combined_reducer = combine_reducers(dummy1, dummy_subtree=dummy2) + +In this example ``dummy1`` will receive the whole subtree passed to the +``combined_reducer`` while ``dummy2`` will only receive the ``dummy_subtree`` +subtree. + +Create a reducer module +======================= + +A ``reducer module`` is an utility object that behave exactly like a single +``reducer``, but permits to register more ``reducers`` into it. You will use it +to define a bunch of ``reducers`` that are all handling the same subtree of the +``state``. + +Note that this is *only a helper construct*, because the following snippet of +code: + +.. code:: python + + mod = Module() + + @mod.reduder(ActionType.DUMMY_ACTION_TYPE1) + def dummy1(prev, action): + next = prev + # Do something + return next + + @mod.reduder(ActionType.DUMMY_ACTION_TYPE2) + def dummy2(prev, action): + next = prev + # Do something + return next + +has exactly the same result of: + +.. code:: python + + @reducer(ActionType.DUMMY_ACTION_TYPE1) + def dummy1(prev, action): + next = prev + # Do something + return next + + @reducer(ActionType.DUMMY_ACTION_TYPE2) + def dummy2(prev, action): + next = prev + # Do something + return next + + module_reducer = combine_reducers(dummy1, dummy2) + +And of course **you can combine** a ``reducer module`` with other ``reducers`` +and ``reducer modules``. """ from .action import Action from .action import ActionType @@ -23,52 +184,10 @@ class Module: reducer decorator and then combining all the defined reducers as top-level reducers. The module instance will work exactly as a reducer function, but will call all the registered reducers. The call order is not guaranteed. - - This snippet: - - .. code:: python - - module_reducer = Module() - - @mod.reduder(ActionType.DUMMY_ACTION_TYPE1) - def dummy1(prev, action): - next = prev - # Do something - return next - - @mod.reduder(ActionType.DUMMY_ACTION_TYPE2) - def dummy2(prev, action): - next = prev - # Do something - return next - - has exactly the same result of: - - .. code:: python - - @reducer(ActionType.DUMMY_ACTION_TYPE1) - def dummy1(prev, action): - next = prev - # Do something - return next - - @reducer(ActionType.DUMMY_ACTION_TYPE2) - def dummy2(prev, action): - next = prev - # Do something - return next - - module_reducer = combine_reducers(dummy1, dummy2) """ def __init__(self) -> None: - """Constructor. - - Creates a module - ie. an aggregate of reducers - that works like a - single reducer on a specific state. - """ - self._reducers = [] # type: List - ReducerList + self._reducers = [] # type: ReducerList def __call__(self, prev: Any, action: Action): """Lets the module work like a reducer. From 9c33e4d2258c2fd7b3d7183b78abc1c3566bf466 Mon Sep 17 00:00:00 2001 From: Lorenzo Berni Date: Thu, 16 Feb 2017 12:34:09 +0100 Subject: [PATCH 5/6] Final update of documentation --- docs/api/action.rst | 4 +-- docs/api/store.rst | 4 +-- revived/action.py | 9 +++---- revived/store.py | 65 ++++++++++++++++++++++++--------------------- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/api/action.rst b/docs/api/action.rst index d764eb7d1e..35d13bd814 100644 --- a/docs/api/action.rst +++ b/docs/api/action.rst @@ -1,5 +1,5 @@ -``revived.action`` module documentation -======================================= +Action (``revived.action``) module documentation +================================================ .. automodule:: revived.action :members: diff --git a/docs/api/store.rst b/docs/api/store.rst index 0337ec86cf..d9d3b77d0b 100644 --- a/docs/api/store.rst +++ b/docs/api/store.rst @@ -1,5 +1,5 @@ -``revived.store`` module documentation -====================================== +Store (``revived.store``) module documentation +============================================== .. automodule:: revived.store :members: diff --git a/revived/action.py b/revived/action.py index bb654f6df6..61f99f4d32 100644 --- a/revived/action.py +++ b/revived/action.py @@ -1,13 +1,10 @@ """ -Action module -============= - This module implements helper functions and classes that can be used to define ``actions`` and ``action creators``, in the same fashion of redux ones, but using decorators instead of anonymous functions. Actions and action creators ---------------------------- +=========================== Actions are payloads of information that send data from your application to your ``store``. They are the only source of information for the ``store``. You send @@ -23,7 +20,7 @@ the *proper term*. Define action types -------------------- +=================== While you are free to define the action type enum as he prefers, it is **strongly suggested** to write them down in this way: @@ -38,7 +35,7 @@ class ActionType(BaseActionType): Define action creators ----------------------- +====================== While it is possible to explicitly build :any:`revived.action.Action` instances directly, it is **strongly suggested** to create ``actions`` using ``action diff --git a/revived/store.py b/revived/store.py index f56bdc788c..d566b2a5c2 100644 --- a/revived/store.py +++ b/revived/store.py @@ -1,10 +1,10 @@ """ -Store module -============ - -This module implements the **global state store**, and the ``init`` action and +This module implements the **global state store**, and the ``INIT`` action and action_creator. This is the entry point of the revived module. +Rationale behind the ``Store`` +============================== + :any:`revived.store.Store` is the object that brings ``actions`` and ``reducers``. The store has the following responsibilities: @@ -23,7 +23,7 @@ composition instead of many stores. Dispatch actions ----------------- +================ To dispatch actions the :any:`revived.store.Store.dispatch` method should be used, passing as parameter the result of an action_creator. See more in @@ -41,43 +41,48 @@ store.dispatch(an_action_creator(a_parameter, another_parameter)) Subscribe and unsubscribe to state changes ------------------------------------------- +========================================== -There are two ways to **subscribe** and **usubscribe** to store changes: +There are two ways to **subscribe** and **usubscribe** to store changes: using +the :any:`revived.store.Store.subscribe` method or the +:any:`revived.store.Store.subscriber` decorator. Both approaches are equivalent +and the choice should be just made based on your taste. -#. using the :any:`revived.store.Store.subscribe` method: +Subscribe using :any:`revived.store.Store.subscribe` +---------------------------------------------------- - .. code:: python +.. code:: python - # create the store object - store = Store(root_reducer) + # create the store object + store = Store(root_reducer) - # define the function - def a_subscriber(): - # do something! - pass + # define the function + def a_subscriber(): + # do something! + pass - # subscribe the function - unsubscribe = store.subscribe(a_subscriber) + # subscribe the function + unsubscribe = store.subscribe(a_subscriber) - # unsubscribe the function - unsubscribe() + # unsubscribe the function + unsubscribe() -#. using the :any:`revived.store.Store.subscriber` decorator: +Subscribe using :any:`revived.store.Store.subscriber` +----------------------------------------------------- - .. code:: python +.. code:: python - # create the store object - store = Store(root_reducer) + # create the store object + store = Store(root_reducer) - # define and subscribe the function - @store.subscriber - def a_subscriber(): - # do something! - pass + # define and subscribe the function + @store.subscriber + def a_subscriber(): + # do something! + pass - # unsubscribe the function - a_subscriber.unsubscribe() + # unsubscribe the function + a_subscriber.unsubscribe() """ from .action import action from .action import Action From ff1202166093226324c2193c17684b81b51fe58b Mon Sep 17 00:00:00 2001 From: Lorenzo Berni Date: Thu, 16 Feb 2017 13:05:15 +0100 Subject: [PATCH 6/6] Update readme --- README.rst | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 0e6ae94f84..65b63f9015 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,10 @@ Revived ======= +.. image:: https://readthedocs.org/projects/revived/badge/?version=latest + :target: http://revived.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + --------------------------------------------------------------------- A predictable state container for python *heavily* inspired by Redux_ --------------------------------------------------------------------- @@ -14,10 +18,17 @@ do pretty much the same job in the most pythonic way possible. Contents -------- +* Documentation_ * Installation_ * Examples_ * Contribute_ -* Documentation_ + +Documentation +------------- + +Currently the documentation is not buliding into ReadTheDocs (see +`issue #11 `_). You can +build the documentation locally. Check out Contribute_ section. Installation ------------ @@ -71,11 +82,6 @@ Contribute #. Create a pull request. #. Profit :) -Documentation -------------- - -More detailed documentation is **coming soon**. - .. _Redux: http://redux.js.org/ .. _`Redux API`: Redux_ .. _virtualenv: https://virtualenv.pypa.io/en/stable/