Skip to main content

Some useful Python decorators for cleaner software development.

Project description

Pedantic

Coverage Status PyPI version Conda Version Last Commit Stars Open Issues Open PRs

A collection of useful decorators in mixins for Python development.

GenericMixin

Do you need a way to figure out to which type a type variable is bound? With GenericMixin you can do exactly this:

from pedantic import GenericMixin

class Foo[T, U](GenericMixin):
    values: list[T]
    value: U

f = Foo[str, int]()
print(f.type_vars)  # {T: <class 'str'>, U: <class 'int'>}

@frozen_dataclass

With @frozen_dataclass you can create immutable data classes with provides a copy_with() instance method. So you can write

from pedantic import frozen_dataclass

@frozen_dataclass
class Foo:
    a: int
    b: str

foo = Foo(a=6, b='hi')
bar = foo.copy_with(a=42)

instead of

from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Foo:
    a: int
    b: str

foo = Foo(a=6, b='hi')
bar = replace(foo, a=42)

You also can enforce run-time type checks for you dataclasses with @frozen_dataclass(type_safe=True).

@in_subprocess

If you have an asynchronous service, that should perform some long-running calculation without blocking the event loop to keep the service responsive, you can use @in_subprocess to run the calculation in a separate process.

import time
from pedantic import in_subprocess

@in_subprocess
def f() -> int:
    time.sleep(10)  # a long-taking synchronous operation, e.g., a calculation
    return 42

await f() == 42  # calculation is done in a separate process => event loop is not blocked

WithDecoratedMethods

You want to register instance methods of a class as callbacks with a decorator? Easy!

from pedantic import DecoratorType, create_decorator, WithDecoratedMethods

class Decorators(DecoratorType):
    ON_SUBJECT = 'on_subject'

on_subject = create_decorator(decorator_type=Decorators.ON_SUBJECT)

class MyClass(WithDecoratedMethods[Decorators]):
    message_broker_client = None
    
    @on_subject("msg_received")
    def on_new_message(self, msg) -> None:
        print(msg)

    def subscribe(self) -> None:
        to_subscribe = self.get_decorated_functions()[Decorators.ON_SUBJECT]
        
        for callback, subject in to_subscribe.items():
            self.message_broker_client.subscribe(subject=subject, on_new_message=callback)        

@pedantic

The @pedantic decorator enforces type annotations and check that passed arguments and returned values match those type annotations.

from pedantic import pedantic

@pedantic
class MyClass:
    def print(self, s: str) -> None: pass

m = MyClass()
m.calc(b=42)
m.print(s='Hi')
m.calc(s=45.0)  # raises PedanticTypeCheckException

Since this is type checking at runtime, it might be slow. So it is recommended to use it only in development mode. This is also not compatible with compiled source code (e.g., with Nuitka).

@validate

This is an alternative to the flask-request-validator that allows you to make parsing arguments from requests and validate them easy.

from flask import Flask, Response, jsonify

from pedantic import (
    FlaskJsonParameter,
    NotEmpty,
    ParameterException,
    ReturnAs,
    TooManyArguments,
    validate,
)

app = Flask(__name__)

@app.route('/')
@validate(
    FlaskJsonParameter(name='key', validators=[NotEmpty()]),
)
def hello_world(key: str) -> Response:
    return jsonify(key)


@app.route('/required')
@validate(
    FlaskJsonParameter(name='required', required=True),
    FlaskJsonParameter(name='not_required', required=False),
    FlaskJsonParameter(name='not_required_with_default', required=False, default=42),
)
def required_params(required, not_required, not_required_with_default) -> Response:
    return jsonify({
        'required': required,
        'not_required': not_required,
        'not_required_with_default': not_required_with_default,
    })


@app.route('/types')
@validate(
    FlaskJsonParameter(name='bool_param', value_type=bool),
    FlaskJsonParameter(name='int_param', value_type=int),
    FlaskJsonParameter(name='float_param', value_type=float),
    FlaskJsonParameter(name='str_param', value_type=str),
    FlaskJsonParameter(name='list_param', value_type=list),
    FlaskJsonParameter(name='dict_param', value_type=dict),
)
def different_types(  # noqa: PLR0913
        bool_param,
        int_param,
        float_param,
        str_param,
        list_param,
        dict_param,
) -> Response:
    return jsonify({
        'bool_param': bool_param,
        'int_param': int_param,
        'float_param': float_param,
        'str_param': str_param,
        'list_param': list_param,
        'dict_param': dict_param,
    })


@app.route('/args')
@validate(
    FlaskJsonParameter(name='a', validators=[NotEmpty()]),
    FlaskJsonParameter(name='b', validators=[NotEmpty()]),
    return_as=ReturnAs.ARGS,
)
def names_do_not_need_to_match(my_key: str, another: str) -> Response:
    return jsonify({
        'my_key': my_key,
        'another': another,
    })


@app.errorhandler(ParameterException)
def handle_parameter_exception(exception: ParameterException) -> Response:
    response = jsonify(exception.to_dict)
    response.status_code = 422
    return response


@app.errorhandler(TooManyArguments)
def handle_too_many_arguments(exception: TooManyArguments) -> Response:
    response = jsonify(str(exception))
    response.status_code = 400
    return response

And it is not only for flask! The implementation is fully generic.

Content of the package

Decorators

Mixins

Helper Functions

Contributing

This project is based on poetry and taskfile. Tip: Run task validate before making commits.

Project details


Release history Release notifications | RSS feed

Download files

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

Source Distribution

pedantic-3.0.1.tar.gz (77.2 kB view details)

Uploaded Source

Built Distribution

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

pedantic-3.0.1-py3-none-any.whl (107.0 kB view details)

Uploaded Python 3

File details

Details for the file pedantic-3.0.1.tar.gz.

File metadata

  • Download URL: pedantic-3.0.1.tar.gz
  • Upload date:
  • Size: 77.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for pedantic-3.0.1.tar.gz
Algorithm Hash digest
SHA256 b6029c36d35f43f9f7e81d32c5a6a9c610a4dea9438a3d55aa0ed6649961846a
MD5 4691002420f03d9e1e258dc1a3f48a79
BLAKE2b-256 71af3def5ee702f19030055d33a042e7612f507d33e921cc338b4b86bb372c9e

See more details on using hashes here.

File details

Details for the file pedantic-3.0.1-py3-none-any.whl.

File metadata

  • Download URL: pedantic-3.0.1-py3-none-any.whl
  • Upload date:
  • Size: 107.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for pedantic-3.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b88b10f6b14f3420504df606cd3b72a2b9cc3d7e5ae4caac13d5894c9d26b382
MD5 b6af24edad0ee8bdb2919d5d5f6f0fb6
BLAKE2b-256 7ccd9bf6d68bd1698183e596556cbf53f2d379ddf77e68b5b795eeec5582ea1f

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