Skip to main content

Interact with arguments passed to a function by their parameter name or position

Project description

Argbox

When defining Python decorators we sometimes want to interact with the inputted arguments by their respective parameter name or position in the function's signature. This is where argbox comes in handy:

import argbox

def decorator(func):
    def wrapper(*args, **kwargs):
        ctx = argbox.Context(func, args, kwargs) # (1)!
        param_0_arg = ctx.get_arg(position=0) # (2)!
        param_y_arg = ctx.get_arg(name="y") # (3)!
        print(f"Parameter 0={param_0_arg} y={param_y_arg}")
        return func(*ctx.args, **ctx.kwargs)
    return wrapper

@decorator
def func(x, y):
    pass

This decorator will work no matter if arguments are inputted as positional or keyword arguments; and no matter the ordering of the keyword arguments:

>>> func(1, 2)
Parameter 0=1 y=2
>>> func(1, y=2)
Parameter 0=1 y=2
>>> func(x=1, y=2)
Parameter 0=1 y=2
>>> func(y=2, x=1)
Parameter 0=1 y=2

Example 1

Say we want a decorator that validates the types of the inputted arguments before running the wrapped function. Without argbox we can make a simple version, but which will only work when arguments are passed as positional arguments:

def check_types(*types):
    def wrapper(func):
        def wrapped(*args, **kwargs):
            if len(args) < len(types):
                raise ValueError(f"Expected at least {len(types)} positional arguments, got {len(args)}")
            for i, type_ in enumerate(types):
                if not isinstance(args[i], type_):
                    raise TypeError(f"Argument {i} is not of type {type_}")
            return func(*args, **kwargs)
        return wrapped
    return wrapper

@check_types(str, int)
def add(x, y):
    return x + str(y)
>>> add("hello", 1)
hello1
>>> add("hello", y=1)
# raise ValueError
>>> add(x="hello", y=1)
# raises ValueError
>>> add(y=1, x="hello")
# raises ValueError

With argbox we can make a version that works with both positional and keyword arguments; also no matter the ordering of the keyword arguments:

from argbox import Context

def check_types(*types):
    def wrapper(func):
        def wrapped(*args, **kwargs):
            ctx = Context(func, args, kwargs)
            for i, type_ in enumerate(types):
                # get argument at parameter position i
                arg = ctx.get_arg(position=i)
                if not isinstance(arg, type_):
                    raise TypeError(f"Argument {i} is not of type {type_}")
            return func(*args, **kwargs)
        return wrapped
    return wrapper

@check_types(int, str)
def add(x, y):
    return str(x) + y
>>> add("hello", 1)
hello1
>>> add("hello", y=1)
hello1
>>> add(x="hello", y=1)
hello1
>>> add(y=1, x="hello")
hello1

Example 2

Say we want a decorator that validates if the specified backend exists before running the wrapped function. Without argbox we can make a simple version, but which will only work when the backend parameter is passed as a keyword argument:

def check_backend_exists(func):
    def wrapped(*args, **kwargs):
        ctx = Context(func, args, kwargs)
        if not "backend" in kwargs:
            raise ValueError("Parameter `backend` must be specified as a keyword argument")
        backend = kwargs["backend"]
        if backend == "numpy":
            if importlib.util.find_spec("numpy") is None:
                raise ImportError("Backend 'numpy' is not installed")
        return func(*ctx.args, **ctx.kwargs)
    return wrapped

@check_backend_exists
def random_array(size: int, backend="python"):
    if backend == "numpy":
        import numpy as np
        return np.random.rand(size)
    else:
        return [random.random() for _ in range(size)]
>>> random_array(3, backend="numpy")
array([0.23532788, 0.880924  , 0.77030806])
>>> random_array(3, "numpy")
# raises ValueError

With argbox we can make a version, which works with backend both as a positional and keyword argument:

def check_backend_exists(func):
    def wrapped(*args, **kwargs):
        ctx = Context(func, args, kwargs)
        backend = ctx.get_arg(name="backend")
        if backend == "numpy":
            if importlib.util.find_spec("numpy") is None:
                raise ImportError("Backend 'numpy' is not installed")
        return func(*ctx.args, **ctx.kwargs)
    return wrapped

@check_backend_exists
def random_array(size: int, backend="python"):
    if backend == "numpy":
        import numpy as np
        return np.random.rand(size)
    else:
        return [random.random() for _ in range(size)]
>>> random_array(3, backend="numpy")
array([0.49507765, 0.99207232, 0.26754601])
>>> random_array(3, "numpy")
array([0.99186762, 0.96713066, 0.58288705])

Project details


Download files

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

Source Distribution

argbox-0.3.0.tar.gz (7.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

argbox-0.3.0-py3-none-any.whl (7.0 kB view details)

Uploaded Python 3

File details

Details for the file argbox-0.3.0.tar.gz.

File metadata

  • Download URL: argbox-0.3.0.tar.gz
  • Upload date:
  • Size: 7.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for argbox-0.3.0.tar.gz
Algorithm Hash digest
SHA256 d7c86027cc12256929b49c8b1f1757f3b06bb11b44234c0fa529a808eb347b2c
MD5 b2e6cd6025171b15ec275280ff4a2d57
BLAKE2b-256 86463ddb79a951044c1b8f853f9c1c144f17c11e8877e2a86fe5a0b07bd2f013

See more details on using hashes here.

File details

Details for the file argbox-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: argbox-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 7.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for argbox-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5197b47f71dfa18699131cd556264938f7a43110d54c3b9ce3592da81b0b5373
MD5 3d527f1b4b0fad503403da9ad74fa436
BLAKE2b-256 8b4068b1fc498d61bfc6d8c4b3cdb52af08dd977a4112f525ef274e96b23c369

See more details on using hashes here.

Supported by

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