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.

Files for python-forge, version 18.5.0
Filename, size File type Python version Upload date Hashes
Filename, size python_forge-18.5.0-0-py35-none-any.whl (20.8 kB) File type Wheel Python version py35 Upload date Hashes View hashes

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page