A quick way to verify function signatures in Python
Project description
This small library allows you to quickly check whether a callable can be called in an expected way. This can be useful, for example, if you write libraries that accept callbacks.
Table of contents
Installation
Install sigmatch with pip:
pip install sigmatch
You can also use instld to quickly try out this package and others without installing them.
Usage
To check the signature of a callable, you first need to «bake» your expectations into a special object. Here's how to do it:
from sigmatch import PossibleCallMatcher
expectation = PossibleCallMatcher('.., c')
You see, we passed a strange string to the PossibleCallMatcher constructor. What does it mean? It is a short description of the expected signature in a special micro-language. Let's learn how to compose short expressions in it! Here are a few rules:
- You list the expected arguments of the function separated by commas, like this:
a, b, c. The spaces are recommended, but not required - You indicate positional arguments using dots, like this:
., ., .. The points do not necessarily have to be separated by commas, so a completely equivalent expression would be.... - If you specify a name, it means that the argument will be passed to the function by name (rather than by position). For example, the expression
x, ymeans that the function will be called like this:function(x=1, y=2)(notfunction(1, 2)!). - If you use unpacking when calling a function, use
*for iterable unpacking and**for mapping unpacking. - The arguments in the expression must be in the following order: first positional, then keyword, then usual unpacking, then dictionary unpacking. Do not violate this!
- If the function does not accept any arguments, do not pass anything to the
PossibleCallMatcherconstructor.
Here are some examples of expressions:
..means «the function will be called with 2 positional arguments».., first, secondmeans «the function will be called with 1 positional argument and 2 keyword arguments:firstandsecond»..., *means «the function will be called with 2 positional arguments, and a list can also be unpacked when calling», like this:function(1, 2, *[3, 4, 5])..., first, **means «the function will be called with 2 positional arguments, the argumentfirstwill be passed by name, and a dictionary can be unpacked when calling», like this:function(1, 2, first=3, **{'second': 4, 'third': 5}).
Now let's use this matcher on a few functions:
def first_suitable_function(a, b, c):
...
def second_suitable_function(a, b, c=None, d=None): # This function also suits us, because the argument "d" does not have to be passed.
...
def not_suitable_function(a, b): # Attention!
...
print(expectation.match(first_suitable_function))
#> True
print(expectation.match(second_suitable_function))
#> True
print(expectation.match(not_suitable_function))
#> False
⚠️ Some built-in functions, such as
next, are written in C and therefore cannot be extracted from them. But if you wrote the function yourself and it is in Python, it should work fine.
As you can see, the same expression can correspond to functions with different signatures. This is because our expressions describe not the signature of the function, but how it will be called. Python allows the same function to be called in different ways.
If you want an exception to be raised when the signature does not match expectations, pass the argument raise_exception=True when calling the match() method:
expectation.match(not_suitable_function, raise_exception=True)
#> ...
#> sigmatch.errors.SignatureMismatchError: The signature of the callable object does not match the expected one.
In this case, an exception will also be raised if the signature cannot be extracted from the passed object.
Combining different expectations
Sometimes the same function can be called differently in different parts of a program, and that's perfectly normal. But how can you express this situation concisely in terms of sigmatch? Just combine several expectation objects with +:
expectation = PossibleCallMatcher('...') + PossibleCallMatcher('.., c') + PossibleCallMatcher('.., d')
⚠️ The current variation selection algorithm has one known flaw: it ignores the presence of default values for positional-only parameters. However, this problem rarely occurs in real code.
The resulting object behaves just like a regular matcher object, i.e., it will also have a match() method. However, it will check several signatures, and if at least one of them matches the callable, it will return True, otherwise False:
def now_its_suitable(a, b): # This one matches now.
...
print(expectation.match(now_its_suitable))
#> True
You can treat the combined matcher as a regular collection: iterate over it, get its length, and test membership.
Comparing functions with each other
Sometimes you may not know in advance what callable interface you expect, but you need it to match the signature of some other function so that they are compatible with each other. How can you do this?
To generate all valid ways to call a function, use the from_callable() method of the PossibleCallMatcher class:
def function(a, b):
...
possible_arguments = PossibleCallMatcher.from_callable(function)
print(possible_arguments)
#> SignatureSeriesMatcher(PossibleCallMatcher('., a'), PossibleCallMatcher('., b'), PossibleCallMatcher('..'), PossibleCallMatcher('a, b'))
This is the same combined matcher described above.
If you need to make sure that the signatures of two functions are completely identical, simply compare the resulting matchers:
def function_1(a, b):
...
def function_2(a, b):
...
def different_function(a, b, c):
...
print(PossibleCallMatcher.from_callable(function_1) == PossibleCallMatcher.from_callable(function_2))
#> True
print(PossibleCallMatcher.from_callable(function_2) == PossibleCallMatcher.from_callable(different_function))
#> False
But sometimes, the set of valid calls for one function is a subset of the set of valid calls for another, even though the signatures are not completely equal. And you want to make sure that any way you can call the first function, you can also call the second. How can you do this? Use the in operator:
def function_1(a, b):
...
def function_2(a, b, c=None):
...
print(PossibleCallMatcher.from_callable(function_1) in PossibleCallMatcher.from_callable(function_2))
#> True
And finally, the least strict check: sometimes you need to make sure that two different functions have at least one valid call in common. To do this, you can compute the intersection of their valid calls using the & operator:
def function_1(a, b, d=None):
...
def function_2(a, b, c=None):
...
print(bool(PossibleCallMatcher.from_callable(function_1) & PossibleCallMatcher.from_callable(function_2)))
#> True
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sigmatch-0.0.10.tar.gz.
File metadata
- Download URL: sigmatch-0.0.10.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac1e3233cedf19bdfb6e4c42a032b3a381b48c46f597fc1ab613512f75ffb03c
|
|
| MD5 |
89863be6976025b55201c6a7d00b0b79
|
|
| BLAKE2b-256 |
d78be974978e19e05e78a1926cf776dfca188b599354f8276be91bb6002f6a93
|
Provenance
The following attestation bundles were made for sigmatch-0.0.10.tar.gz:
Publisher:
release.yml on mutating/sigmatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sigmatch-0.0.10.tar.gz -
Subject digest:
ac1e3233cedf19bdfb6e4c42a032b3a381b48c46f597fc1ab613512f75ffb03c - Sigstore transparency entry: 1352624224
- Sigstore integration time:
-
Permalink:
mutating/sigmatch@4fff3f7591af914554c8e3a7cfd9a71a0c41cfdc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mutating
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4fff3f7591af914554c8e3a7cfd9a71a0c41cfdc -
Trigger Event:
push
-
Statement type:
File details
Details for the file sigmatch-0.0.10-py3-none-any.whl.
File metadata
- Download URL: sigmatch-0.0.10-py3-none-any.whl
- Upload date:
- Size: 11.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
849dadb7c60b1dbab5396331fb27b4b11bb498483def23427358f31d60a8f9b2
|
|
| MD5 |
8f070f76c01a0b0d20fc8e612f2eb006
|
|
| BLAKE2b-256 |
2567a583292179b52cb70dcdb871e033793d98cae5b0d45b0457bc000b6332a7
|
Provenance
The following attestation bundles were made for sigmatch-0.0.10-py3-none-any.whl:
Publisher:
release.yml on mutating/sigmatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sigmatch-0.0.10-py3-none-any.whl -
Subject digest:
849dadb7c60b1dbab5396331fb27b4b11bb498483def23427358f31d60a8f9b2 - Sigstore transparency entry: 1352624339
- Sigstore integration time:
-
Permalink:
mutating/sigmatch@4fff3f7591af914554c8e3a7cfd9a71a0c41cfdc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mutating
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4fff3f7591af914554c8e3a7cfd9a71a0c41cfdc -
Trigger Event:
push
-
Statement type: