Skip to main content

forge (python) signatures for fun and profit

Project description

forge logo

forge (python) signatures for fun and profit

pypi project MIT license Python 3.5+ master Travis CI Status master Coveralls Status Documentation Status

forge is an elegant Python package for crafting function signatures. Its aim is to help you write better, more literate code with less boilerplate.

The power of dynamic signatures is finally within grasp – add, remove, or enhance parameters at will!

forge is a Python-only package hosted on PyPI for Python 3.5+.

The recommended installation method is pip-installing into a virtualenv:

$ pip install python-forge

Re-signing a function

The primary purpose of forge is to alter the public signature of functions:

import forge

@forge.sign(
    forge.pos('positional'),
    forge.arg('argument'),
    *forge.args,
    keyword=forge.kwarg(),
    **forge.kwargs,
)
def myfunc(*args, **kwargs):
    return (args, kwargs)

assert forge.stringify_callable(myfunc) == \
    'myfunc(positional, /, argument, *args, keyword, **kwargs)'

args, kwargs = myfunc(1, 2, 3, 4, 5, keyword='abc', extra='xyz')

assert args == (3, 4, 5)
assert kwargs == {
    'positional': 1,
    'argument': 2,
    'keyword': 'abc',
    'extra': 'xyz',
    }

You can re-map a parameter to a different ParameterKind (e.g. positional-only to positional-or-keyword or keyword-only), and optionally supply a default value:

import forge

@forge.sign(forge.kwarg('color', 'colour', default='blue'))
def myfunc(colour):
    return colour

assert forge.stringify_callable(myfunc) == "myfunc(*, color='blue')"
assert myfunc() == 'blue'

You can also supply type annotations for usage with linters like mypy:

import forge

@forge.sign(forge.arg('number', type=int))
@forge.returns(str)
def to_str(number):
    return str(number)

assert forge.stringify_callable(to_str) == 'to_str(number:int) -> str'
assert to_str(3) == '3'

Validating a parameter

You can validate arguments by either passing a validator or an iterable (such as a list or tuple) of validators to your FParameter constructor.

import forge

class Present:
    pass

def validate_gt5(ctx, name, value):
    if value < 5:
        raise TypeError("{name} must be >= 5".format(name=name))

@forge.sign(forge.arg('count', validator=validate_gt5))
def send_presents(count):
    return [Present() for i in range(count)]

assert forge.stringify_callable(send_presents) == 'send_presents(count)'

try:
    send_presents(3)
except TypeError as exc:
    assert exc.args[0] == "count must be >= 5"

sent = send_presents(5)
assert len(sent) == 5
for p in sent:
    assert isinstance(p, Present)

You can optionally provide a context parameter, such as self, cls, or create your own named parameter with forge.ctx('myparam'), and use that alongside validation:

import forge

def validate_color(ctx, name, value):
    if value not in ctx.colors:
        raise TypeError(
            'expected one of {ctx.colors}, received {value}'.\
            format(ctx=ctx, value=value)
        )

class ColorSelector:
    def __init__(self, *colors):
        self.colors = colors
        self.selected = None

    @forge.sign(
        forge.self,
        forge.arg('color', validator=validate_color)
    )
    def select_color(self, color):
        self.selected = color

cs = ColorSelector('red', 'green', 'blue')

try:
    cs.select_color('orange')
except TypeError as exc:
    assert exc.args[0] == \
        "expected one of ('red', 'green', 'blue'), received orange"

cs.select_color('red')
assert cs.selected == 'red'

Converting a parameter

You can convert an argument by passing a conversion function to your FParameter constructor.

import forge

def uppercase(ctx, name, value):
    return value.upper()

@forge.sign(forge.arg('message', converter=uppercase))
def shout(message):
    return message

assert shout('hello over there') == 'HELLO OVER THERE'

You can optionally provide a context parameter, such as self, cls, or create your own named FParameter with forge.ctx('myparam'), and use that alongside conversion:

import forge

def titleize(ctx, name, value):
    return '{ctx.title} {value}'.format(ctx=ctx, value=value)

class RoleAnnouncer:
    def __init__(self, title):
        self.title = title

    @forge.sign(forge.self, forge.arg('name', converter=titleize))
    def announce(self, name):
        return 'Now announcing {name}!'.format(name=name)

doctor_ra = RoleAnnouncer('Doctor')
captain_ra = RoleAnnouncer('Captain')

assert doctor_ra.announce('Strangelove') == \
    "Now announcing Doctor Strangelove!"
assert captain_ra.announce('Lionel Mandrake') == \
    "Now announcing Captain Lionel Mandrake!"

Project information

forge is released under the MIT license, its documentation lives at Read the Docs, the code on GitHub, and the latest release on PyPI. It’s rigorously tested on Python 3.6+ and PyPy 3.5+.

forge is authored by Devin Fee. Other contributors are listed under https://github.com/dfee/forge/graphs/contributors.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

python_forge-18.5.0-0-py35-none-any.whl (20.8 kB view hashes)

Uploaded Python 3.5

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page