Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

49 Commits

Repository files navigation

argcheck-python3

Automated checking of Python3 function-call argument types & values using the @validate_call decorator.

Use of argcheck enables you to remove verbose, repetitive argument-checking boilerplate code from within the function, by instead annotating per-parameter constraints.

Example usage

importargcheckasacfromtypingimportSequence@ac.validate_calldefconstruct_conv_net(
in_channels: ac.Annotated[int, ac.isPositive] =3,
out_channels: ac.Annotated[int, ac.isPositive] =1,
num_features_per_scale: ac.Annotated[
Sequence[int],
[ac.isNotEmpty, ac.isMonotonicIncr, ac.eachAll(ac.isPositive)]
] = [64, 128, 256, 512],
):
# Construct a Convolutional Neural Network...

Currently-implemented argument checks

  • isTypeEqualTo(specified_type): check for equality to declared parameter type
  • isTypeOfSequence(specified_type): check for a sequence type
  • isNotEmpty: verify that a container of values is NOT empty
  • isMonotonicIncr: verify that a sequence of values is monotonic increasing
  • isPositive: verify that a numeric value is positive
  • eachAll(check): apply check to each value in a container; expect ALL true

Syntax

(The syntax is demonstrated in the "Example usage" above.)

The constraints upon argument types are specified per-parameter using PEP-484 type hint annotations (parameter_name: type_annotation), extended using the PEP-593 Annotated type annotation (Annotated[T, metadata]) for argument-value constraints as metadata.

Only annotated parameters are checked; you don't need to annotate parameters if you don't care about checking their arguments. Without the @validate_call decorator, argcheck annotations are merely inert type hint annotations that conform to PEP-484 or PEP-593.

What argcheck does / does not

Arguments are only checked when the decorated function is called. argcheck does not perform any static analysis of code.

If an argument check is violated when the decorated function is called, an exception derived from argcheck.ArgCheckException will be raised.

argcheck is optimised for detailed error reporting rather than for speed of checking. argcheck is well-suited to large, infrequently-called functions where it's important to validate arguments before expensive mistakes occur. argcheck might not be well-suited to frequently-called functions if speed of execution is important.

A function decorated by @validate_call will present the same signature as the original function, so other signature-introspection functionality should also still work.

Use of generic Sequence type typing.Sequence[T]

This code was originally written on Python 3.5, so it uses typing.Sequence rather than collections.abc.Sequence as its "generic Sequence" type:

Why?

Gather round, kids, while grampa tells you a story about the Before Time...

Before Python3.9, typing.Sequence was a distinct type from collections.abc.Sequence:

# in file `/usr/lib/python3.5/typing.py`:ifhasattr(collections_abc, 'Reversible'):
classSequence(Sized, Reversible[T_co], Container[T_co],
extra=collections_abc.Sequence):
passelse:
classSequence(Sized, Iterable[T_co], Container[T_co],
extra=collections_abc.Sequence):
pass

But from Python3.9 onwards: typing.Sequence is now merely an alias for collections.abc.Sequence:

# in file `/usr/lib/python3.10/typing.py`:Sequence=_alias(collections.abc.Sequence, 1)

Also, before Python3.9, collections.abc.Sequence didn't support subscripting ([]), which is needed to enable a generic alias type in annotations. Only typing.Sequence supported subscripting:

>>>importsys; sys.version'3.5.2 (default, Jan 26 2021, 13:30:48) \n[GCC 5.4.0 20160609]'>>>fromtypingimportSequenceastyp_Seq>>>fromcollections.abcimportSequenceasabc_Seq>>>typ_Seq[int]
typing.Sequence<+T_co>[int]
>>>abc_Seq[int]
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>TypeError: 'ABCMeta'objectisnotsubscriptable>>>

But from Python3.9 onwards:

>>>importsys; sys.version'3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]'>>>fromtypingimportSequenceastyp_Seq>>>fromcollections.abcimportSequenceasabc_Seq>>>typ_Seq[int]
typing.Sequence[int]
>>>abc_Seq[int]
collections.abc.Sequence[int]
>>>

Also, before Python 3.7, you could use isinstance(value, type) to check generic types:

>>>some_list= [1, 2, 3]
>>>isinstance(some_list, typ_Seq[int])
True

But from Python3.7 onwards:

>>>some_list= [1, 2, 3]
>>>isinstance(some_list, typ_Seq[int])
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"/usr/lib/python3.10/typing.py", line994, in__instancecheck__returnself.__subclasscheck__(type(obj))
File"/usr/lib/python3.10/typing.py", line997, in__subclasscheck__raiseTypeError("Subscripted generics cannot be used with"TypeError: Subscriptedgenericscannotbeusedwithclassandinstancechecks>>>isinstance(some_list, abc_Seq[int])
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>TypeError: isinstance() argument2cannotbeaparameterizedgeneric

Here is a Stack Overflow question about this, from someone with the same problem: https://stackoverflow.com/questions/53854463/python-3-7-check-if-type-annotation-is-subclass-of-generic

So this caused the hasty definition of a new class checks.isTypeOfSequence when I encountered this problem on a deployment to a Python3.10 system!

About

A Python3 decorator for automated checking of function-call argument types/values using std-lib ‘inspect'.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages