From 6bfb71bdfa1eeaa1e11f8010fa41db5392fce00c Mon Sep 17 00:00:00 2001 From: Lawrence Vanderpool Date: Thu, 24 Dec 2020 17:18:51 -0600 Subject: [PATCH 1/2] Fixes to SocketpuppetConsumer for handling dotfiles 1. Update requirements_dev.txt to include base requirements, as these are necessary for starting the server and performing any tests. 2. Use Reflex.__subclasses__() rather than name checking for reflex subclass detection, which will be more consistent and allow users to name their reflexes whatever they want. 3. Clear falsey values from the filepaths detected in the load_reflexes_from_config function, which is a tenuous solution to not including vim-generated dotfiles in the module loading process. undo style changes --- requirements_dev.txt | 1 + sockpuppet/consumer.py | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 076465f..c536592 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,5 @@ -r requirements_test.txt +-r requirements.txt django-extensions invoke diff --git a/sockpuppet/consumer.py b/sockpuppet/consumer.py index 8ad6521..ed9eee0 100644 --- a/sockpuppet/consumer.py +++ b/sockpuppet/consumer.py @@ -16,7 +16,7 @@ from django.conf import settings from .channel import Channel -from .reflex import PROTECTED_VARIABLES +from .reflex import PROTECTED_VARIABLES, Reflex from .element import Element from .utils import classify @@ -109,12 +109,13 @@ def group_send(self, recipient, message): send(recipient, message) def load_reflexes_from_config(self, config): - def append_reflex(module): - # TODO only import classes that are actually that inherits Reflex - for classname in dir(module): - if 'reflex' in classname.lower(): - ReflexClass = getattr(module, classname) - self.reflexes[ReflexClass.__name__] = ReflexClass + def append_reflex(): + self.reflexes.update( + { + ReflexClass.__name__: ReflexClass + for ReflexClass in Reflex.__subclasses__() + } + ) modpath = config.module.__path__[0] @@ -122,21 +123,22 @@ def append_reflex(module): if dirpath == modpath and 'reflexes.py' in filenames: # classes in reflexes.py import_path = '{}.reflexes'.format(config.name) - module = import_module(import_path) + import_module(import_path) + append_reflex() - append_reflex(module) elif dirpath == path.join(modpath, 'reflexes'): # assumes reflexes folder is placed directly in app. import_path = '{config_name}.reflexes.{reflex_file}' for filename in filenames: - name = filename.split('.')[0] + # eliminates empty values in the filename before getting the + # module name from the filename. + name = [file for file in filename.split('.') if file][0] full_import_path = import_path.format( config_name=config.name, reflex_file=name ) - module = import_module(full_import_path) - - append_reflex(module) + import_module(full_import_path) + append_reflex() def reflex_message(self, data, **kwargs): logger.debug('Json: %s', data) From 92df5ae27bbfc4210e5fdecdda8774bab0f94b7a Mon Sep 17 00:00:00 2001 From: Lawrence Vanderpool Date: Fri, 25 Dec 2020 10:45:54 -0600 Subject: [PATCH 2/2] Require reflexes to be defined in a reflexes.py module or a reflexes package for ease of discovery. flake8 errors --- docs/quickstart-django.md | 4 +++- docs/reflexes.md | 2 +- sockpuppet/consumer.py | 30 +++++++----------------------- tests/example/reflexes/__init__.py | 7 +++++++ 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/docs/quickstart-django.md b/docs/quickstart-django.md index c44e232..d5d584e 100644 --- a/docs/quickstart-django.md +++ b/docs/quickstart-django.md @@ -90,7 +90,9 @@ class CounterReflex(Reflex): ``` {% endcode %} -Sockpuppet maps your requests to Reflex classes that live in your `your_app/reflexes` folder or reflexes that exist in the file `your_app/reflex.py`. In this example, the increment method is executed and the count is incremented by 1. The `self.count` instance variable is passed to the template when it is re-rendered. +Sockpuppet maps your requests to Reflex classes that live in your `your_app/reflexes.py` module, or in a `your_app/reflexes` package with an `__init__.py` file that imports all the Reflex subclasses in that directory (similar to [organizing Django models in a package](https://docs.djangoproject.com/en/3.1/topics/db/models/#organizing-models-in-a-package)). + +In this example, the increment method is executed and the count is incremented by 1. The `self.count` instance variable is passed to the template when it is re-rendered. Yes, it really is that simple. diff --git a/docs/reflexes.md b/docs/reflexes.md index a105a2a..5d87aaa 100644 --- a/docs/reflexes.md +++ b/docs/reflexes.md @@ -11,7 +11,7 @@ Server side reflexes inherit from `sockpuppet.Reflex`. They hold logic responsib * Sockpuppet: the name of this project, which has a JS websocket client and a django based server component, which is based on django-channels. * Stimulus: an incredibly simple yet powerful JS framework by the creators of Rails * "a Reflex": used to describe the full, round-trip life-cycle of a Sockpuppet operation, from client to server and back again -* Reflex class: a python class that inherits from `sockpuppet.Reflex` and lives in your `reflexes` folder or `reflex.py`, this is where your Reflex actions are implemented. +* Reflex class: a python class that inherits from `sockpuppet.Reflex` and lives in your `your_app/reflexes` package or `your_app/reflexes.py` file. This is where your Reflex actions are implemented. * Reflex action: a method in a Reflex class, called in response to activity in the browser. It has access to several special accessors containing all of the Reflex controller element's attributes * Reflex controller: a Stimulus controller that imports the StimulusReflex client library. It has a `stimulate` method for triggering Reflexes and like all Stimulus controllers, it's aware of the element it is attached to - as well as any Stimulus [targets](https://stimulusjs.org/reference/targets) in its DOM hierarchy * Reflex controller element: the DOM element upon which the `data-reflex` attribute is placed, which often has data attributes intended to be delivered to the server during a Reflex action diff --git a/sockpuppet/consumer.py b/sockpuppet/consumer.py index ed9eee0..9c3d3db 100644 --- a/sockpuppet/consumer.py +++ b/sockpuppet/consumer.py @@ -4,7 +4,6 @@ from importlib import import_module from functools import wraps import inspect -from os import walk, path import sys from urllib.parse import urlparse @@ -117,28 +116,13 @@ def append_reflex(): } ) - modpath = config.module.__path__[0] - - for dirpath, dirnames, filenames in walk(modpath): - if dirpath == modpath and 'reflexes.py' in filenames: - # classes in reflexes.py - import_path = '{}.reflexes'.format(config.name) - import_module(import_path) - append_reflex() - - elif dirpath == path.join(modpath, 'reflexes'): - # assumes reflexes folder is placed directly in app. - import_path = '{config_name}.reflexes.{reflex_file}' - - for filename in filenames: - # eliminates empty values in the filename before getting the - # module name from the filename. - name = [file for file in filename.split('.') if file][0] - full_import_path = import_path.format( - config_name=config.name, reflex_file=name - ) - import_module(full_import_path) - append_reflex() + reflex_module_path = f'{config.name}.reflexes' + try: + import_module(reflex_module_path) + append_reflex() + except ModuleNotFoundError: + # No reflexes.py or reflexes module was found in the app + pass def reflex_message(self, data, **kwargs): logger.debug('Json: %s', data) diff --git a/tests/example/reflexes/__init__.py b/tests/example/reflexes/__init__.py index e69de29..4f2e279 100644 --- a/tests/example/reflexes/__init__.py +++ b/tests/example/reflexes/__init__.py @@ -0,0 +1,7 @@ +from .example_reflex import ExampleReflex, DecrementReflex, ParamReflex + +__all__ = [ + 'ExampleReflex', + 'DecrementReflex', + 'ParamReflex' +]