Skip to content

Commit

Permalink
Merge pull request #55 from teald/v0.4.0
Browse files Browse the repository at this point in the history
v0.4.0
  • Loading branch information
teald authored Dec 10, 2022
2 parents ed1811f + 892bd7e commit d6ebd10
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 13 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<img src="docs/source/porchlight_logo.gif" width="200" height="200" alt="porchlight logo. A snake's head erupts from the bottom of a porchlight casing, reaching towards a spinning triangular pyramid. The pyramid radiates bright, saturated, multicolored light." style="float:left" />

[porchlight](https://porchlight.readthedocs.io/en/latest/)
==========

Expand Down Expand Up @@ -47,9 +49,6 @@ neighborhood.add_function(increase_x)
print(neighborhood)
```

Although this is the current extent of documentation, there should be some more
complete documentation within the next couple weeks/months.

Documentation
-----------

Expand Down
16 changes: 15 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = "D J Teal"

# The full version, including alpha/beta/rc tags
release = "0.3.1"
release = "0.4.0"


# -- General configuration ---------------------------------------------------
Expand Down Expand Up @@ -68,3 +68,17 @@
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["source/_static"]
# html_css_files = ["porchlight.css"]


# Include initialization and call special methods to be documented. From
# https://stackoverflow.com/questions/5599254/how-to-use-sphinxs-autodoc
# -to-document-a-classs-init-self-method
def skip(app, what, name, obj, would_skip, options):
if name in ["__init__", "__call__"]:
return False

return would_skip


def setup(app):
app.connect("autodoc-skip-member", skip)
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ Welcome to |porchlight|'s documentation!
coupling Python functions and managing shared data.

.. toctree::
:maxdepth: 2
:maxdepth: 3
:caption: Contents:

other/about
other/quickstart
source/neighborhood
source/door
source/param
source/utils

Indices and tables
==================
Expand Down
6 changes: 6 additions & 0 deletions docs/source/door.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
`door` module
=============

The `door` module contains classes that represent interfaces to python
functions. They are primarily used on functions that have been defined in pure
python, but can be extended to include arbitrary callables.

.. automodule:: porchlight.door
:members:
:show-inheritance:
:private-members:
8 changes: 8 additions & 0 deletions docs/source/neighborhood.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
`neighborhood` module
=====================

`neighborhood` contains the |Neighborhood| class, which acts as a mediator
class between different functions that have been re-cast as
:py:class:`~porchlight.door.Door` objects.

.. automodule:: porchlight.neighborhood
:members:
:show-inheritance:
:private-members:

.. |Neighborhood| replace:: :py:class:`~porchlight.neighborhood.Neighborhood`
2 changes: 2 additions & 0 deletions docs/source/param.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@

.. automodule:: porchlight.param
:members:
:show-inheritance:
:private-members:
29 changes: 29 additions & 0 deletions docs/source/utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
`utils` module
==============

The `utils` module contains utility functions and classes to assist in
|porchlight|'s internal functioning.

.. automodule:: porchlight.utils
:members:
:show-inheritance:
:private-members:

Inspection utilities
--------------------

.. automodule:: porchlight.utils.inspect_functions
:members:
:show-inheritance:
:private-members:

Typing utilities
----------------

.. automodule:: porchlight.utils.typing_functions
:members:
:show-inheritance:
:private-members:


.. |porchlight| replace:: **porchlight**
124 changes: 119 additions & 5 deletions porchlight/door.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,121 @@ class Door(BaseDoor):
"""Inherits from and extends :class:`~porchlight.door.BaseDoor`"""

def __init__(
self, function: Callable = None, *, argument_mapping: dict = {}
self,
function: Callable = None,
*,
argument_mapping: dict = {},
wrapped: bool = False,
arguments: dict = {},
keyword_args: dict = {},
return_vals: List = [],
name: str = "",
typecheck: bool = False,
):
"""Initializes the :py:class:`~porchlight.door.Door` object using a
callable.
Arguments
---------
function : Callable
A callable object to be parsed by :py:class:`~BaseDoor`.
argument_mapping : dict, keyword-only, optional
Maps parameters automatically by name. For example, to have a Door
accept "a" and "b" ans arguments instead of "x" and "y", one could
use
.. code-block:: python
def fxn(x):
y = 2 * x
return y
my_door = Door(fxn, argument_mapping={'x': 'a', 'y': 'b'})
to accomplish what would otherwise require wrapping the function
yourself.
wrapped : bool, keyword-only, optional
If `True`, will not parse the function using
:py:class:`~porchlight.door.BaseDoor`. Instead, it will take user
arguments and generate a function wrapper using the following
keyword-only arguments:
- arguments
- keyword_args
- return_vals
And this wrapper will be used to initialize the
:py:class:`~porchlight.door.BaseDoor` properties.
arguments : dict, keyword-only, optional
Arguments to be passed to the function if it is wrapped. Does not
override :py:class:`~porchlight.door.BaseDoor` if ``wrapped`` is
``False``.
keyword_args : dict, keyword-only, optional
Corresponds to keyword arguments that may be passed positionally.
Only used when ``wrapped`` is ``True``.
name : str, keyword-only, optional
Overrides the default name for the Door if provided.
typecheck : :py:obj:`bool`, optional
If `True`, the `Door` object will assert that arguments passed
to `__call__` (when the `Door` itself is called like a
function) have the type expected by type annotations and any user
specifications. By default, this is `True`.
"""
self.argmap = argument_mapping
self.name = name
self.wrapped = wrapped
self.typecheck = typecheck

if name:
self.__name__ = name

# For wrapped functions, circumvent normal initialization.
if self.wrapped:
self._initialize_wrapped_function(
arguments, keyword_args, return_vals
)

# In these cases, there's no reason to use a decorator.
if function is None:
msg = "Auto-wrapped functions must be passed directly."

logger.error(msg)
raise DoorError(msg)

self._base_function = function

return

self.function_initialized = False
if function is None:
return

self.__call__(function)

def _initialize_wrapped_function(
self, arguments, keyword_args, return_vals
):
"""Initializes a function that is auto-wrapped by
:py:class:`~porchlight.door.Door` instead of being passed to
:py:class:`~porchlight.door.BaseDoor`
"""
if not self.name:
self.name = "AutoWrappedFunctionDoor"
self.__name__ = self.name

self.arguments = arguments
self.keyword_args = keyword_args
self.return_vals = return_vals

self.function_initialized = True

def __call__(self, *args, **kwargs):
if not self.function_initialized:
# Need to recieve the function.
Expand All @@ -449,14 +554,20 @@ def __call__(self, *args, **kwargs):
raise TypeError(msg)

function = args[0]
super().__init__(function)

super().__init__(function, typecheck=self.typecheck)

self.function_initialized = True

# Perform any necessary argument mapping.
self.map_arguments()

return self

if self.wrapped:
result = self._base_function(*args, **kwargs)
return result

# Check argument mappings.
if not self.argmap:
# Just pass arguments normally
Expand Down Expand Up @@ -508,13 +619,16 @@ def map_arguments(self):
logger.error(msg)
raise DoorError(msg)

if old_name not in self.arguments:
if old_name not in self.arguments and not any(
old_name in retvals for retvals in self.return_vals
):
msg = f"{old_name} is not a valid argument for {self.name}"
logger.error(msg)
raise DoorError(msg)

self.arguments[mapped_name] = self.arguments[old_name]
del self.arguments[old_name]
if old_name in self.arguments:
self.arguments[mapped_name] = self.arguments[old_name]
del self.arguments[old_name]

# Change keyword arguments as well.
if old_name in self.keyword_args:
Expand Down
Loading

0 comments on commit d6ebd10

Please sign in to comment.