Skip to content

Commit

Permalink
Add new "uses" parameter to ActionFactory methods, allowing additiona…
Browse files Browse the repository at this point in the history
…l fixtures to be provided for specific controllers (#946)

* Add uses argument to ActionFactory calls for adding fixtures

* document ActionFactory better in "convenience decorators" section

* fix gitinore for apps folders

"It is not possible to re-include a file if a parent directory of that file is excluded." caused the !apps/... re-includes not to work

* example convenience decorator comment

---------

Co-authored-by: Laurin Schmidt <[email protected]>
  • Loading branch information
laundmo and Laurin Schmidt authored Nov 21, 2024
1 parent 3b4d7d1 commit 3752e12
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Thumbs.db
./*.zip
apps/_documentation/static/*/*.pdf
apps/_documentation/static/*/*.epub
apps*/*
apps*/*/*
!apps/todo/*
!apps/showcase/*
!apps/_dashboard/*
Expand Down
2 changes: 2 additions & 0 deletions apps/_scaffold/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def download(filename):
# Define convenience decorators
# They can be used instead of @action and @action.uses
# They should NEVER BE MIXED with @action and @action.uses
# If you need to provide extra fixtures for a specific controller
# add them like this: @authenticated(uses=[extra_fixture])
# #######################################################
unauthenticated = ActionFactory(db, session, T, flash, auth)
authenticated = ActionFactory(db, session, T, flash, auth.user)
2 changes: 2 additions & 0 deletions apps/fadebook/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def download(filename):
# Define convenience decorators
# They can be used instead of @action and @action.uses
# They should NEVER BE MIXED with @action and @action.uses
# If you need to provide extra fixtures for a specific controller
# add them like this: @authenticated(uses=[extra_fixture])
# #######################################################
unauthenticated = ActionFactory(db, session, T, flash, auth)
authenticated = ActionFactory(db, session, T, flash, auth.user)
2 changes: 2 additions & 0 deletions apps/tagged_posts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def download(filename):
# Define convenience decorators
# They can be used instead of @action and @action.uses
# They should NEVER BE MIXED with @action and @action.uses
# If you need to provide extra fixtures for a specific controller
# add them like this: @authenticated(uses=[extra_fixture])
# #######################################################
unauthenticated = ActionFactory(db, session, T, flash, auth)
authenticated = ActionFactory(db, session, T, flash, auth.user)
51 changes: 45 additions & 6 deletions docs/chapter-06.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1122,17 +1122,17 @@ Convenience Decorators
----------------------
The ``_scaffold`` application, in ``common.py`` defines two special
convenience decorators:
convenience decorators using ActionFactory:
::
.. code:: python
@unauthenticated()
def index():
return dict()
and
::
.. code:: python
@authenticated()
def index():
Expand All @@ -1146,6 +1146,45 @@ arguments of the action separated by a slash (/).
- @unauthenticated does not require the user to be logged in.
- @authenticated required the user to be logged in.
They can be combined with (and precede) other ``@action.uses(...)`` but
they should not be combined with ``@action(...)`` because they perform
that function automatically.
.. warning::
ActionFactory decorators like these cannot be combined
with @action or @action.uses
The decorators can be used directly as shown above, which enables
all HTTP methods (GET, POST, PUT, ...) but you can also create separate
controllers for each HTTP method:
.. code:: python
@authenticated.get()
def index():
# only handle GET requests
return dict()
@authenticated.post(path="index")
def index_form():
# only handle POST requests
return dict()
The both decorator and its HTTP method calls have the following arguments:
- ``path`` overwrites the path built from the function name
with the given string. Does not automatically handle arguments.
- ``template`` specifies the template name, instead of using
the function name.
- ``uses`` specify extra fixtures for this specific controllers.
.. code:: python
@authenticated(
path="test",
template="generic.html",
uses=[Inject(message="Hello World")])
def example():
return dict()
As manual ordering of fixtures isn't possible with ``uses``,
make sure the fixtures define their dependencies.
See: :ref:`Fixtures with dependencies`
38 changes: 22 additions & 16 deletions py4web/utils/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,38 @@ class ActionFactory:
def __init__(self, *fixtures):
self.fixtures = fixtures

def get(self, path=None, template=None):
return self._action_maker("GET", path, template)
def get(self, path=None, template=None, uses=None):
return self._action_maker("GET", path, template, uses)

def post(self, path=None, template=None):
return self._action_maker("POST", path, template)
def post(self, path=None, template=None, uses=None):
return self._action_maker("POST", path, template, uses)

def put(self, path=None, template=None):
return self._action_maker("PUT", path, template)
def put(self, path=None, template=None, uses=None):
return self._action_maker("PUT", path, template, uses)

def delete(self, path=None, template=None):
return self._action_maker("DELETE", path, template)
def delete(self, path=None, template=None, uses=None):
return self._action_maker("DELETE", path, template, uses)

def head(self, path=None, template=None):
return self._action_maker("HEAD", path, template)
def head(self, path=None, template=None, uses=None):
return self._action_maker("HEAD", path, template, uses)

def __call__(
self, path=None, template=None, method=["GET", "POST", "PUT", "HEAD", "DELETE"]
self,
path=None,
template=None,
method=["GET", "POST", "PUT", "HEAD", "DELETE"],
uses=None
):
return self._action_maker(method, path, template)
return self._action_maker(method, path, template, uses)

def _action_maker(self, method, path, template):
def _action_maker(self, method, path, template, uses):
uses = uses or [] # handle uses=None
def make_action(func, path=path, method=method, template=template):
if not path:
path = func.__name__
for name in func.__code__.co_varnames[: func.__code__.co_argcount]:
path += "/<%s>" % name
fixtures = [f for f in self.fixtures]
fixtures = [*self.fixtures, *uses]
if template is None:
template = func.__name__ + ".html"
if template:
Expand All @@ -59,8 +64,9 @@ def make_action(func, path=path, method=method, template=template):

return make_action

def callback(self, path=None):
return CallbackFactory(path, self.fixtures)
def callback(self, path=None, uses=None):
uses = uses or [] # handle uses=None
return CallbackFactory(path, [*self.fixtures, *uses])


class CallbackFactory:
Expand Down

0 comments on commit 3752e12

Please sign in to comment.