Skip to main content

A quick way to verify function signatures in Python

Project description

Downloads Downloads Coverage Status Lines of code Hits-of-Code Test-Package Python versions PyPI version Checked with mypy Ruff DeepWiki

logo

This small library allows you to quickly check whether any called object matches the signature you expect. This may be useful to you, for example, if you write libraries that work with callbacks.

Table of contents

Installation

You can install sigmatch using pip:

pip install sigmatch

You can also quickly try out this and other packages without having to install using instld.

Usage

To check the signature of a called object, 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, y means that the function will be called something like this: function(x=1, y=2) (not function(1, 2)!).
  • If you use unpacking when calling a function, use * for usual unpacking and ** for dictionary one.
  • 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 PossibleCallMatcher constructor.

Here are some examples of expressions:

  • .. means «the function will be called with 2 positional arguments».
  • ., first, second means «the function will be called with 1 positional argument and 2 named arguments: first and second».
  • .., * 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 argument first will be passed by name, and a dictionary can be unpacked when calling», like this: function(1, 2, first=3, **{'second': 4, 'third': 5}).

Well, let's go back to the object we created above and try to apply it to the functions to see if they suit us or not:

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, there should be no problem.

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 list several expectation objects using plus signs:

expectation = PossibleCallMatcher('...') + PossibleCallMatcher('.., c'),  + PossibleCallMatcher('.., d')

⚠️ The current variation selection algorithm has one known flaw: it ignores the presence of default values for strictly positional function parameters. However, this problem rarely occurs in real code.

The resulting object will be completely identical to a regular object of the expected signature, i.e., it will also have a match() method. However, it will check several signatures, and if at least one of them matches your object, it will return True, otherwise False:

def now_its_suitable(a, b):  # Let me remind you that last time a function with the same signature didn't suit us, but now it does!
    ...
    
print(expectation.match(now_its_suitable))
#> True

You can treat the sum of such objects as a regular collection: iterate through them, find out their number, check for containing.

Comparing functions with each other

Sometimes you may not know in advance what specific function signature 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 it?

To calculate all possible combinations of function arguments, 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'))

Yes, this is the sum of expected signatures that you may have read about above!

If you need to make sure that the signatures of two functions are completely identical, simply compare these combinations with each other:

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(function_3))
#> False

But sometimes, the signature of one function is a subset of 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 weakest check: sometimes you need to make sure that two different functions have at least one common calling way. To do this, you can calculate the intersection of signatures 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


Download files

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

Source Distribution

sigmatch-0.0.6.tar.gz (13.2 kB view details)

Uploaded Source

Built Distribution

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

sigmatch-0.0.6-py3-none-any.whl (11.1 kB view details)

Uploaded Python 3

File details

Details for the file sigmatch-0.0.6.tar.gz.

File metadata

  • Download URL: sigmatch-0.0.6.tar.gz
  • Upload date:
  • Size: 13.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sigmatch-0.0.6.tar.gz
Algorithm Hash digest
SHA256 1c728d2c6379fd970ca719269c80dc6b135f75870e8cbc0a49717b5f178d2951
MD5 9798e0d7fcab8feb453fecd66c99736f
BLAKE2b-256 55725d3d82c6b17188292cbad5d26f1e61a660ac6ed0af6424d6ecc5fbd57b36

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigmatch-0.0.6.tar.gz:

Publisher: release.yml on mutating/sigmatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sigmatch-0.0.6-py3-none-any.whl.

File metadata

  • Download URL: sigmatch-0.0.6-py3-none-any.whl
  • Upload date:
  • Size: 11.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sigmatch-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 8e6323a3de550934bdb3a2dab7c4d33b06c762aacd33e61eccce2103000e265f
MD5 a19cb3f3bb021115457aca0f036f46b8
BLAKE2b-256 88221a2692ce3e87536df42b23cbfa5a2bb789813a0eee404cf26e60e6f5eaf0

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigmatch-0.0.6-py3-none-any.whl:

Publisher: release.yml on mutating/sigmatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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