Package: python-redux
🚀 Python Redux🌟 OverviewPython Redux is a Redux implementation for Python, bringing Redux's state management
architecture to Python applications.⚙️ FeaturesRedux API for Python developers.Reduce boilerplate by droppingtypeproperty, payload classes and action creators:Each action is a subclass ofBaseAction.Its type is checked by utilizingisinstance(no need fortypeproperty).Its payload are its direct properties (no need forpayloadproperty).Its creator is its auto-generated constructor.➡️Sample usageUse type annotations for all its API.Immutable state management for predictable state updates usingpython-immutable.Offers a streamlined, nativeAPIfor handling
side-effects asynchronously, eliminating the necessity for more intricate utilities
such as redux-thunk or redux-saga.Incorporates theautorun decorator, inspired
by the mobx framework, to better integrate with elements of the software following
procedural patterns.Supports middlewares.📦 InstallationThe package handle in PyPI ispython-reduxPippipinstallpython-reduxPoetrypoetryaddpython-redux🛠 UsageHandling Side Effects with EventsPython-redux introduces a powerful concept for managing side effects:Events.
This approach allows reducers to remain pure while still signaling the need for
side effects.Why Events?Separation of Concerns: By returning events, reducers stay pure and focused
solely on state changes, delegating side effects to other parts of the software.Flexibility: Events allow asynchronous operations like API calls to be handled
separately, enhancing scalability and maintainability.How to Use EventsReducers: Reducers primarily return a new state. They can optionally return
actions and events, maintaining their purity as these do not enact side effects
themselves.Dispatch Function: Besides actions, dispatch function can now accept events,
enabling a more integrated flow of state and side effects.Event Listeners: Implement listeners for specific events. These listeners
handle the side effects (e.g., API calls) asynchronously.Best PracticesDefine Clear Events: Create well-defined events that represent specific side
effects.Use Asynchronously: Design event listeners to operate asynchronously, keeping
your application responsive. Note that python-redux, by default, runs all event
handler functions in new threads.This concept fills the gap in handling side effects within Redux's ecosystem, offering
a more nuanced and integrated approach to state and side effect management.See todo sample below or check thedemoto see it in action.Autorun DecoratorInspired by MobX'sautorunandreaction, python-redux introduces
the autorun decorator. This decorator requires a selector function as an argument.
The selector is a function that accepts the store instance and returns a derived
object from the store's state. The primary function of autorun is to establish a
subscription to the store. Whenever the store is changed, autorun executes the
selector with the updated store.
Importantly, the decorated function is triggered only if there is a change in the
selector's return value. This mechanism ensures that the decorated function runs
in response to relevant state changes, enhancing efficiency and responsiveness in
the application.See todo sample below or check thedemoto see it in action.Combining reducers -combine_reducersYou can compose high level reducers by combining smaller reducers usingcombine_reducersutility function. This works mostly the same as the JS redux library version except
that it provides a mechanism to dynamically add/remove reducers to/from it.
This is done by generating an id and returning it along the generated reducer.
This id is used to refer to this reducer in the future. Let's assume you composed
a reducer like this:reducer,reducer_id=combine_reducers(state_type=StateType,first=straight_reducer,second=second_reducer,)You can then add a new reducer to it using thereducer_idlike this:store.dispatch(CombineReducerRegisterAction(_id=reducer_id,key='third',third=third_reducer,),)You can also remove a reducer from it like this:store.dispatch(CombineReducerRegisterAction(_id=reducer_id,key='second',),)Without this id, all the combined reducers in the store tree would registerthirdreducer and unregistersecondreducer, but thanks to thisreducer_id, these
actions will only target the desired combined reducer.🔎 Sample UsageMinimal todo application store implemented using python-redux:# state:classToDoItem(Immutable):id:strcontent:strtimestamp:floatclassToDoState(Immutable):items:Sequence[ToDoItem]# actions:classAddTodoItemAction(BaseAction):content:strtimestamp:floatclassRemoveTodoItemAction(BaseAction):id:str# events:classCallApi(BaseEvent):parameters:object# reducer:defreducer(state:ToDoState|None,action:BaseAction,)->ReducerResult[ToDoState,BaseAction,BaseEvent]:ifstateisNone:returnToDoState(items=[ToDoItem(id=uuid.uuid4().hex,content='Initial Item',timestamp=time.time(),),],)ifisinstance(action,AddTodoItemAction):returnreplace(state,items=[*state.items,ToDoItem(id=uuid.uuid4().hex,content=action.content,timestamp=action.timestamp,),],)ifisinstance(action,RemoveTodoItemAction):returnCompleteReducerResult(state=replace(state,actions=[itemforiteminstate.itemsifitem.id!=action.id],),events=[CallApi(parameters={})],)returnstatestore=create_store(reducer)# subscription:dummy_render=printstore.subscribe(dummy_render)# autorun:@store.autorun(lambdastate:state.items[0].contentiflen(state.items)>0elseNone,)defreaction(content:str|None)->None:print(content)# event listener, note that this will run async in a separate thread, so it can include# io operations like network calls, etc:dummy_api_call=printstore.subscribe_event(CallApi,lambdaevent:dummy_api_call(event.parameters))# dispatch:store.dispatch(AddTodoItemAction(content='New Item',timestamp=time.time()))store.dispatch(FinishAction())🎉 DemoFor a detailed example, seedemo.pyin theGitHub repository.🤝 ContributingContributions following Python best practices are welcome.🔒 LicenseRefer to the repository for license details.