Skip to main content

Art Deco

Python package CI powered_by_alpha_build Python 3.8+ Checked with mypy pylint Score pre-commit

Description

Art Deco is a library that, via decorators, makes complex processing of function/method arguments effortless. Use cases for art_deco include, but are not limited to:

  • data validation (e.g., validating the inputs of a function)
  • input conversion (e.g., type coercion based on type annotations)
  • deserializing / parsing (e.g., parse JSONs or decode MessagePack into Python objects based on type annotations)
  • caching

That is, this library shines when one needs to build a decorator that processes inputs based on: the inspected signature of the function arguments (e.g., needs access to argument names, type annotations, default values), or runtime information like discerning whether an argument was called positionally or as a keyword, as part of a variable argument (i.e., *args, **kwargs) etc.

Therefore, it works like a charm in conjunction with any data validation library (e.g., cerberus, pandera) or any library that consumes data from the external world - network, Excel etc. (e.g., flask, plotly dash, pyxll, xlwings).

Moreover, art_deco handles several Python language constructs:

  • functions, regular methods, static methods, class methods
  • sync and async
  • metaclasses and classes (regular classes, dataclasses, attrs classes, pydantic models and data classes)
  • variable arguments (i.e., *args, **kwargs)
  • positional only, keyword only arguments

and provides:

  • art_deco.core: a fundamental layer based on which one can implement any argument processing logic.
  • art_deco.hack_args: a convenient, pre-packaged argument processor implemented on top of art_deco.core.

Getting Started

Installation

pip install art_deco

Hacking function arguments

To illustrate what can be achieved, let's start with a few toy usages of art_deco.hack_args.

from __future__ import annotations

from art_deco.hack_args import marks
from art_deco.hack_args.processor import hack_args

@marks.arg_hacker
def hack_x(x: int | float | str) -> str:
    """Function used for data validation and type coercion."""
    assert float(x) < 100, f'Validation failed: {x} must be < 100'
    return str(x) if isinstance(x, (int, float)) else x

@hack_args({'x': hack_x})
def func(x):
    """Function with a processed argument."""
    return x

assert func('1') == func(1) == '1'
# func(200) ----> Errors with AssertionError, thanks to data validation

the same thing can be achieved via the Annotated type hint:

from typing_extensions import Annotated

@hack_args()
def func(x: Annotated[str, hack_x]) -> str:
    """Function with a processed argument."""
    return x

To validate more than one argument at a time:

from art_deco.core.arg_processors.api import Context


@marks.wide_validator
def validate_x_y(_: Context, x: str, y: str) -> None:
  """Validate 2 arguments."""
  # Context is an internal concept that allows users to use static information like the inspected signature or
  # default values, as well as call runtime information like what arguments were called positionally.
  if x.startswith('1'):
    assert y.startswith('2'), f'Validation failed: {x} starts with 1, but {y} does not start with 2'


@hack_args({'x': hack_x, 'y': hack_x, ('x', 'y'): validate_x_y})
def func(x, y):
  """Function with 2 processed arguments."""
  return f'x={x}, y={y}'


# Similarly we could use `Annotated`
@hack_args()
def func(x: Annotated[str, hack_x, validate_x_y], y: Annotated[str, hack_x, validate_x_y]) -> str:
  """Function with 2 processed arguments."""
  return f'x={x}, y={y}'

Implementing a new argument processor

In art_deco.core, there are 2 types of argument processors:

  • static: initialized once, at function definition time, arguments to be processed are determined once as well, while the actual argument processing happens every time the decorated function is called.
  • dynamic: initialized every time the decorated function is called, arguments to be processed and the actual argument processing happen every time the decorated functions is called.

Let's look at a static example, since a dynamic processor would have a nearly identical interface.

from __future__ import annotations

from typing import Any, Mapping

from art_deco.core.arg_processors.api import ArgNames, Context, MultiArgValidateFunc
from art_deco.core.arg_processors.static_decorator import static_process_args
from art_deco.core.specs.static_arg_specs import StaticArgSpecs
from art_deco.core.specs.dynamic_arg_specs import DynamicArgSpec

class SimpleCastingProcessor:
    def __init__(self, static_args: StaticArgSpecs) -> None:
        self.static_args = static_args
        self.hacks: dict[str, Any] = {}
        for arg in static_args.args:
            if arg.has_annotation:
                self.hacks[arg.name] = arg.annotation

    def should_process_arg(self, arg: str) -> bool:
        """Hook function called to determine whether to process an argument or not, always by name."""
        return arg in self.hacks

    def process_arg(self, arg: DynamicArgSpec, _: Context) -> Any:
        """This function specifies how to actually process the argument."""
        if arg.is_default:  # If the value is the default one, do not run any casting
            return arg.value
        return self.hacks[arg.name](arg.value)  # Get the annotation for the argument name and coerce the supplied value

    def get_wide_checks(self) -> Mapping[ArgNames, MultiArgValidateFunc]:  # No logic for "wide" checks.
        return {}


@static_process_args(SimpleCastingProcessor)
def coercion(x: int, y: float) -> float:
    return x + y

assert coercion('1', '2') == 3
assert coercion('1', 2) == 3
assert coercion(1, 2) == 3

Development

Read CONTRIBUTING.md.

Release files for art-deco 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for art-deco 0.0.1
File Size Uploaded
art_deco-0.0.1.tar.gz 15.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for art-deco 0.0.1
File Interpreter ABI Platform
art_deco-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 33.4 kB

Release files / art_deco-0.0.1.tar.gz

Download URL art_deco-0.0.1.tar.gz
Size 15.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0222e3983bd1df0aa59d8d1048b6f2e59ab0e589d6b0172ba94865f691b3fd9e
BLAKE2b-256 checksum
How to use checksums
e6ca50b242d2a8f9a9862618bcfe89cf6ad9e91721ebaa7764c7be9003a012e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.7.15

Release files / art_deco-0.0.1-py3-none-any.whl

Download URL art_deco-0.0.1-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d78d5c309d95f36bdf4a168637b66afeb5ec7e922f95238db59292e3f869e781
BLAKE2b-256 checksum
How to use checksums
c0f1838375ef818871f3c52172040ac6798f706d1486d5f4ce0cb39d8e9c576c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.7.15

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page