Advanced Usage¶
Forge enables some new, interesting software development patterns. Some of those patterns are documented below.
Supplied arguments¶
The supplied arguments
pattern is useful when you want to eliminate parameters from a signature.
With this pattern, the caller has no ability to override default
values, making it useful for scripts and generated code.
import logging
import inspect
from uuid import uuid4
import forge
TOKEN = 'token_{}'.format(uuid4().hex)
def get_nonce():
return uuid4().hex
@forge.sign(
forge.pos('token', default=TOKEN, bound=True),
forge.pos('nonce', factory=get_nonce, bound=True),
forge.arg('endpoint'),
)
def execute_api_request(token, nonce, endpoint):
logging.info(
'calling %s with token: %s and nonce %s',
endpoint, token, nonce,
)
# notice that `execute_api_request` now has only one parameter, `endpoint`
assert forge.stringify_callable(execute_api_request) == \
'execute_api_request(endpoint)'
# the argument value for `token` is a bound constant, and
# the argument value for `none` is a bound factory
execute_api_request('http://dummy.api/1')
# INFO:root:calling http://dummy.api/1 with token: token_4eb9da44e3f244959572535b8d47d34a and nonce c11f018894154a248dd336de1da98e71
Var-keyword precision¶
The var-keyword precision
pattern is useful when you want to explicitly define which keyword-only parameters a callable takes.
This is a useful alternative to provided a generic var-keyword and white-listing or black-listing parameters within the callable’s code.
import inspect
import forge
import requests
defaults = {
k: forge.kwarg(default=None) for k in (
'method', 'url', 'params', 'data', 'json', 'headers', 'cookies',
'files', 'auth', 'timeout', 'allow_redirects', 'proxies', 'verify',
'stream', 'cert',
)
}
request = forge.sign(
forge.arg('method'),
forge.arg('url'),
**{k: v for k, v in defaults.items() if k not in ('method', 'url')},
)(requests.request)
head = forge.sign(
forge.arg('url'),
**{k: v for k, v in defaults.items() if k not in ('url')},
)(requests.request)
get = forge.sign(
forge.arg('url'),
forge.arg('params', default=None),
**{k: v for k, v in defaults.items() if k not in ('url', 'params')},
)(requests.get)
post = forge.sign(
forge.arg('url'),
forge.arg('data', default=None),
forge.arg('json', default=None),
**{k: v for k, v in defaults.items() if k not in ('url', 'data', 'json')},
)
# `requests.request` looks like this (notice the var-keyword **kwargs)
assert forge.stringify_callable(requests.request) == \
'request(method, url, **kwargs)'
# our wrapped `request` looks like this
assert forge.stringify_callable(request) == (
'request('
'method, url, *, params=None, data=None, json=None, headers=None, '
'cookies=None, files=None, auth=None, timeout=None, '
'allow_redirects=None, proxies=None, verify=None, stream=None, '
'cert=None'
')'
)
Transmutating parameters¶
The transmutating-parameters
pattern is useful when you want to convert (or manifest) an argument value to a different argument value.
This pattern is especially helpful you are passing object-ids, as for example with an ORM.
import forge
class User:
__repo__ = {}
@classmethod
def get(cls, user_id):
return cls.__repo__.get(user_id)
def __init__(self, id, name, email_address):
self.id = id
self.name = name
self.email_address = email_address
user_arg = forge.arg(
'user_id',
'user',
converter=lambda ctx, name, value: User.get(value),
)
def create_user(name, email_address):
user = User(
id=len(User.__repo__),
name=name,
email_address=email_address,
)
user.__repo__[user.id] = user
return user.id
@forge.sign(user_arg, forge.arg('name'))
def update_name(user, name):
user.name = name
# Notice that `user_id` is converted into a `user` object
assert forge.stringify_callable(update_name) == \
'update_name(user_id, name)'
user_id = create_user('John London', 'john@email.com')
update_name(user_id, 'Jack London')
assert User.get(user_id).name == 'Jack London'
Void arguments¶
The void-arguments
pattern allows quick-collection and filtering of input values for processing.
This is useful when multiple parameters can optionally be provided, and None is a valid argument value.
This code makes use of forge.void
.
import datetime
import forge
class Book:
__repo__ = {}
def __init__(self, id, title, author, publication_date):
self.id = id
self.title = title
self.author = author
self.publication_date = publication_date
@classmethod
def get(cls, book_id):
return cls.__repo__.get(book_id)
@classmethod
def create(cls, title, author, publication_date):
ins = cls(
id=len(cls.__repo__),
title=title,
author=author,
publication_date=publication_date,
)
cls.__repo__[ins.id] = ins
return ins.id
@classmethod
@forge.sign(
forge.cls,
forge.arg('book_id', 'book', converter=lambda ctx, name, value: ctx.get(value)),
forge.kwarg('title', default=forge.void),
forge.kwarg('author', default=forge.void),
forge.kwarg('publication_date', default=forge.void),
)
def update(cls, book, **kwargs):
for k, v in kwargs.items():
if v is not forge.void:
setattr(book, k, v)
assert forge.stringify_callable(Book.update) == \
'update(book_id, *, title=<void>, author=<void>, publication_date=<void>)'
book_id = Book.create(
'Call of the Wild',
'John London',
datetime.date(1903, 8, 1),
)
Book.update(book_id, author='Jack London')
assert Book.get(book_id).author == 'Jack London'
Chameleon function¶
The chameleon function
pattern demonstrates the powerful functionality of forge
.
With this pattern, you gain the ability to dynamically revise a function’s signature on demand.
This could be useful for auto-discovered dependency injection.
import forge
@forge.sign(
*forge.args('remove'),
**forge.kwargs,
)
def chameleon(*remove, **kwargs):
forge.resign(
*forge.args('remove'),
**{
k: forge.kwarg(default=v) for k, v in kwargs.items()
if k not in remove
},
**forge.kwargs,
)(chameleon)
return kwargs
# Initial use
assert forge.stringify_callable(chameleon) == 'chameleon(*remove, **kwargs)'
# Empty call preserves signature
assert chameleon() == {}
assert forge.stringify_callable(chameleon) == 'chameleon(*remove, **kwargs)'
# Var-keyword arguments add keyword-only parameters
assert chameleon(a=1) == dict(a=1)
assert forge.stringify_callable(chameleon) == 'chameleon(*remove, a=1, **kwargs)'
# Empty call preserves signature
assert chameleon() == dict(a=1)
# Var-positional arguments remove keyword-only parameters
assert chameleon('a') == dict(a=1)
assert forge.stringify_callable(chameleon) == 'chameleon(*remove, **kwargs)'