GithubHelp home page GithubHelp logo

aspenweb / state_chain.py Goto Github PK

View Code? Open in Web Editor NEW
6.0 6.0 4.0 140 KB

Model an algorithm as a list of functions operating on a shared state dict.

Home Page: https://state-chain-py.readthedocs.io/

License: MIT License

Python 100.00%

state_chain.py's People

Contributors

chadwhitacre avatar changaco avatar pjz avatar techtonik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

state_chain.py's Issues

extend algorithm.debug w/ no arguments to mean set_trace

You can add pdb.set_trace to an algorithm list and it will work as expected (you get dropped into the run function when it's hit). However, you have to import pdb separately. We should have an algorithm.set_trace method that aliases pdb.set_trace.

event-like algorithm chains

algorithm.on("key", algorithm_func) 

should add algorithm_func to a chain that gets executed after the "key" shows up in the state dict for the first time.

upstream some decorators

Aspen defined decorators for algorithm functions. Seems like those really belong over here. I'm inlining them here for anyone who wants to copy/paste them to their own projects until we get them fully ported over here.

# Filters
# =======
# These are decorators for algorithm functions.

def by_lambda(filter_lambda):
    """
    """
    def wrap(function):
        def wrapped_function_by_lambda(*args,**kwargs):
            if filter_lambda():
                return function(*args,**kwargs)
        algorithm._transfer_func_name(wrapped_function_by_lambda, function)
        return wrapped_function_by_lambda
    return wrap


def by_regex(regex_tuples, default=True):
    """Only call function if

    regex_tuples is a list of (regex, filter?) where if the regex matches the
    requested URI, then the flow is applied or not based on if filter? is True
    or False.

    For example::

        from aspen.flows.filter import by_regex

        @by_regex( ( ("/secret/agenda", True), ( "/secret.*", False ) ) )
        def use_public_formatting(request):
            ...

    would call the 'use_public_formatting' flow step only on /secret/agenda
    and any other URLs not starting with /secret.

    """
    regex_res = [ (re.compile(regex), disposition) \
                           for regex, disposition in regex_tuples.items() ]
    def filter_function(function):
        def function_filter(request, *args):
            for regex, disposition in regex_res:
                if regex.matches(request.line.uri):
                    if disposition:
                        return function(*args)
            if default:
                return function(*args)
        algorithm._transfer_func_name(function_filter, function)
        return function_filter
    return filter_function


def by_dict(truthdict, default=True):
    """Filter for hooks

    truthdict is a mapping of URI -> filter? where if the requested URI is a
    key in the dict, then the hook is applied based on the filter? value.

    """
    def filter_function(function):
        def function_filter(request, *args):
            do_hook = truthdict.get(request.line.uri, default)
            if do_hook:
                return function(*args)
        algorithm._transfer_func_name(function_filter, function)
        return function_filter
    return filter_function

Can't run the chain with an existing state dict

The StateChain.run() method takes the initial state as keyword arguments, so it's not possible to use an existing dict, because Python creates a copy:

>>> d = {}
>>> def run(**state):
...     return state is d
... 
>>> run(**d)
False

State chains 2.0: static type checking and better performance

In addition to being somewhat inefficient, the current state chains don't facilitate static type checking. I'm thinking we could solve both problems with a 2.0 version of state chains based on optionally-typed state objects instead of dicts.

Below is a simplified example of what this 2.0 version could look like for a web framework like Pando:

class State:
    website: Website
    environ: dict
    request: Optional[Request] = None
    response: Optional[Response] = None
    exception: Optional[Exception] = None

chain = StateChain(State)

@chain.add
def parse_environ_into_request(state: State):
    state.request = Request.from_wsgi(state.website, state.environ)

@chain.add(exception='required')
def handle_negotiation_exception(state: State):
    if isinstance(state.exception, NotFound):
        state.response = Response(404)
    elif isinstance(state.exception, NegotiationFailure):
        state.response = Response(406, state.exception.message)
    else:
        return
    state.exception = None

state = chain.run(State(website, environ))

The differences between this hypothetical 2.0 and the current 1.x are:

  • No more dependency injection, the chain functions now only take one argument: the state. This facilitates static typing and improves performance.
  • A new chain.add decorator replaces the clunky StateChain.from_dotted_name constructor.
  • The chain functions that handle exceptions are explicitly marked.

implement error handling differently

Error handling as currently implemented is not entirely intuitive. Essentially we're folding a branching tree into a linear flow, and it's hard to reason about that. What if instead we provided for a list of lists?

normal = [foo, bar, baz, buz]
fallback = [blah, buz]
last_ditch = [bloo]

algo = Algorithm(*normal, _cascade=[fallback, last_ditch])

Test with relative imports

Relative imports are now a thing. Does this module work given a 'dotted name' of .. ? or .algorithm ? etc, etc.

insert_{before,after} and remove should use all args

Right now insert_{before,after} and remove only take one function argument. They should take multiple function arguments. This way of calling the insert_* variants should result in an algorithm with the additional functions in the order they are passed to the insert_* method. E.g.:

>>> alg.insert_before('foo', bar, baz)
>>> alg.functions
[bar, baz, foo]
>>> alg.insert_after('foo', bar, baz)
>>> alg.functions
[foo, bar, baz]

algorithm.{START,END}

The insert_{before,after} functions should take special markers for their first argument, to indicate that the functions should be inserted at the start or end of the algorithm.

require algorithm functions to be decorated as such

Here's an instance of me being confused because it wasn't obvious to me that a function was meant to be used in an algorithm. We could have a decorator that adds a __for_algorithm__ to the func or some such:

@algorithm.function
def foo_bar(baz, buz):
    pass

We could also rework from_dotted_name to use this to whitelist instead of blacklisting _foo.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.