Skip to main content

warned-dataclasses

pypi codecov

This package adds functionality to Python's dataclasses feature to emit a warning or raise an exception if an explicit value for an attribute was used to initialize a dataclass but some user-specified condition that that attribute logically relies on was not met.

Motivation

The primary use case for this package is for tools (such as 🤗 Transformers) that use dataclasses for command-line parsing, where different command-line parameters make sense in different scenarios.

In the simple case, a programmer can just emit a warning or raise an exception if an explicit value was passed to one of these parameters in a context where it is not appropriate. However, the programmer may also want to set sensible defaults for such parameters when they are appropriate.

One approach to this problem is to compare the runtime value against the default value, and assume an explicit value was passed if they do not match. However, this approach presents two concerns:

  1. complex and difficult-to-introspect default_factory objects may be used in the dataclass's fields
  2. the programmer may want to warn the user even if they explicitly pass the default value

This package presents a solution to both of these problems.

Installation

This package is on PyPI for Python >=3.8:

pip install warned-dataclasses

Usage

There are two simple usage paradigms for this package. The following (contrived) examples should illustrate them.

1. warn_for_condition

With this approach, warnings are emitted as unmet conditions are discovered. This could be useful for emitting relevant warnings amidst other logging statements describing program flow, or (when using error=True) if an unmet condition in a critical point in the program should terminate the program immediately.

import json

from dataclasses import dataclass, field

from warned_dataclasses import Warned, warned, warn_for_condition


@warned
@dataclass
class User:
    id: int
    admin_level: Warned[int, 'admin_only'] = field(default=1)


def check_admin(user: User):
    with open('admins.json', 'r') as admins_fd:
        admins = json.load(admins_fd)

    if user.id not in admins:
        # uh-oh, user is not an admin
        warn_for_condition(user, 'admin_only')


if __name__ == '__main__':
    user = User(123, admin_level=2)
    check_admin(user)

2. satisfy and warn_all

With this approach, warnings are suppressed as conditions are judged to be met and marked as satisfied, and all warnings for a dataclass are emitted at once when warn_all is called.

import json

from dataclasses import dataclass, field

from warned_dataclasses import Warned, warned, warn_all, satisfy


@warned
@dataclass
class User:
    id: int
    admin_level: Warned[int, 'admin_only'] = field(default=1)
    fileshare_home_dir: Warned[str, 'db_access'] = field(default='~')


def check_admin(user: User):
    with open('admins.json', 'r') as admins_fd:
        admins = json.load(admins_fd)

    if user.id in admins:
        # user is an admin; no warning
        satisfy(user, 'admin_only')


def check_db_access(user: User):
    with open('db_users.json', 'r') as db_users_fd:
        db_users = json.load(db_users_fd)

    if user.id in db_users:
        # user is a db user; no warning
        satisfy(user, 'db_access')


if __name__ == '__main__':
    user = User(123, admin_level=2, fileshare_home_dir='/')
    check_admin(user)
    check_db_access(user)

    # now emit all unsatisfied errors
    warn_all(user)

Advanced Usage

1. Decorator options

  • By default, a warned dataclass will emit a warning to the current logging logger. To raise an exception instead, use @warned(error=True).

  • By default, a warned dataclass will only emit a warning once for each condition; future calls to warn_for_condition or warn_all will treat that condition as satisfied. To disable this behavior and emit a warning every time it is invoked, use @warned(satisfy_on_warn=False).

  • By default, a warned dataclass will emit a warning if a value equal to the default value for a field is passed explicitly. Sometimes this is undesirable, and explicitly-passed default values should be ignored. To disable warnings for explicit default values, use @warned(warn_on_default=False).

  • A plain @warned can be used with or without parentheses.

2. Multiple warned dataclasses and ConditionSet

If your code uses multiple warned dataclasses that share some conditions, the methods illustrated above become clunky, as you end up having to call warn_for_condition(obj, condition) for every warned dataclass object that uses condition. Instead, you can collect conditions immediately after instantiation using ConditionSet, then call any of the usual functions, either with the ConditionSet object as the first parameter or as a method on ConditionSet:

from dataclasses import dataclass, field

from warned_dataclasses import warned, Warned, ConditionSet, warn_for_condition, warn_all, satisfy


@warned
@dataclass
class One:
    ...
    some_attr: Warned[int, 'abc'] = field(default=32)
    other_attr: Warned[str, 'shared_condition'] = field(default='')
    ...


@warned
@dataclass
class Two:
    ...
    some_attr: Warned[int, 'shared_condition'] = field(default=5)
    other_attr: Warned[float, '123'] = field(default=0.0)
    ...


def main():
    ...
    one = One(...)
    two = Two(...)
    
    conditions = ConditionSet(one, two)
    
    ...
    
    conditions.warn_for_condition("shared_condition")
    conditions.satisfy("123")
    conditions.warn_all()
    
    # or:
    
    warn_for_condition(conditions, "shared_condition")
    satisfy(conditions, "123")
    warn_all(conditions)

Metadata

Release files for warned-dataclasses 0.5.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 warned-dataclasses 0.5.1
File Size Uploaded Provenance
warned-dataclasses-0.5.1.tar.gz 14.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for warned-dataclasses 0.5.1
File Interpreter ABI Platform
warned_dataclasses-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 27.8 kB

Release files / warned-dataclasses-0.5.1.tar.gz

Download URL warned-dataclasses-0.5.1.tar.gz
Size 14.6 kB
Tags Source
SHA-256 checksum
How to use checksums
86a1543abad9bba8a93def2af23db9039259ba5ea30323ee58398a602b7308ce
BLAKE2b-256 checksum
How to use checksums
2af3687851465bc28d50b65fd7d4c0bdf3a197e20c0de670807985692325f807
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.9.15

Release files / warned_dataclasses-0.5.1-py3-none-any.whl

Download URL warned_dataclasses-0.5.1-py3-none-any.whl
Size 13.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2455620a403dcc147894892256ebaa984280d2b4f4c0255992412e16e9c94e3c
BLAKE2b-256 checksum
How to use checksums
4ccb6f98780aa142169d9374fec3f96843cfdcbfe451db821f3894db3d0f53e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.9.15

Security for warned-dataclasses 0.5.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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