Skip to content

Commit 517b37c

Browse files
author
Sylvain MARIE
committed
Updated documentation and tests
1 parent 398a5fa commit 517b37c

File tree

2 files changed

+47
-38
lines changed

2 files changed

+47
-38
lines changed

docs/index.md

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The typical use cases are:
2121

2222
It currently supports three ways to define the signature of the created function
2323

24-
- from a given reference function, e.g. `foo`
24+
- from a given reference function, e.g. `foo`.
2525
- from strings, e.g. `'foo(a, b=1)'`
2626
- from `Signature` objects, either manually created, or obtained by using the `inspect.signature` (or its backport `funcsigs.signature`) method.
2727

@@ -69,7 +69,8 @@ func_impl called !
6969

7070
You can also:
7171

72-
* override the function name, docstring and module name if you pass a non-None `func_name`, `doc` and `modulename` argument
72+
* remove the name from the signature string (e.g. `'(b, a=0)'`) to directly use the function name of `func_impl`.
73+
* override the function name, docstring, qualname and module name if you pass a non-None `func_name`, `doc`, `qualname` and `module_name` argument
7374
* add other attributes on the generated function if you pass additional keyword arguments
7475

7576
See `help(create_function)` for details.
@@ -96,7 +97,7 @@ def gen_func(*args, **kwargs):
9697
return args, kwargs
9798
```
9899

99-
It also has the capability to take `None` as a signature, if you just want to update the metadata (`func_name`, `doc`, `modulename`) without creating any function:
100+
It also has the capability to take `None` as a signature, if you just want to update the metadata (`func_name`, `doc`, `qualname`, `module_name`) without creating any function:
100101

101102
```python
102103
@with_signature(None, func_name='f')
@@ -164,11 +165,11 @@ In many real-world applications we want to reuse "as is", or slightly modify, an
164165

165166
#### Copying a signature
166167

167-
The easiest way to copy the signature from another function is to directly pass this function as the first argument in `@with_signature` or `create_function`. That way you do not have to use `inspect`/`funcsigs`.
168+
If you just want to expose the same signature as a reference function (and not wrap it nor appear like it), the easiest way to copy the signature from another function `f` is to use `signature(f)` from `inspect`/`funcsigs`.
168169

169170
#### Signature-preserving function wrappers
170171

171-
[`@functools.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps) is a famous decorator to create "signature-preserving" function wrappers. However it does not actually preserve the signature, it just uses a trick (setting the `__wrapped__` attribute) to trigger special dedicated behaviour in `stdlib`'s `help()` and `signature()` methods.
172+
[`@functools.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps) is a famous decorator to create "signature-preserving" function wrappers. However it does not actually preserve the signature, it just uses a trick (setting the `__wrapped__` attribute) to trigger special dedicated behaviour in `stdlib`'s `help()` and `signature()` methods. See [here](https://stackoverflow.com/questions/308999/what-does-functools-wraps-do/55102697#55102697).
172173

173174
This has two major limitations:
174175

@@ -224,30 +225,29 @@ Finally note that a `create_wrapper` function is also provided for convenience ;
224225

225226
#### Editing a signature
226227

227-
Below we show how to add a parameter to a function. We first capture its `Signature` using `inspect.signature(f)`, we modify it to add a parameter, and finally we use it in `create_function` to create our final function:
228+
Below we show how to add a parameter to a function. We first capture its `Signature` using `inspect.signature(f)`, we modify it to add a parameter, and finally we use it in `wraps` to create our final function:
228229

229230
```python
230-
from makefun import with_signature
231+
from makefun import wraps
231232
from inspect import signature, Parameter
232233

233234
# (0) the reference function
234235
def foo(b, a=0):
235236
print("foo called: b=%s, a=%s" % (b, a))
236237
return b, a
237238

238-
# (1a) capture the name and signature of reference function `foo`
239-
func_name = foo.__name__
240-
original_func_sig = signature(foo)
241-
print("Original Signature: %s" % original_func_sig)
239+
# (1a) capture the signature of reference function `foo`
240+
foo_sig = signature(foo)
241+
print("Original Signature: %s" % foo_sig)
242242

243243
# (1b) modify the signature to add a new parameter 'z' as first argument
244-
params = list(original_func_sig.parameters.values())
244+
params = list(foo_sig.parameters.values())
245245
params.insert(0, Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD))
246-
func_sig = original_func_sig.replace(parameters=params)
247-
print("New Signature: %s" % func_sig)
246+
new_sig = foo_sig.replace(parameters=params)
247+
print("New Signature: %s" % new_sig)
248248

249249
# (2) define the wrapper implementation
250-
@with_signature(func_sig, func_name=func_name)
250+
@wraps(foo, new_sig=new_sig)
251251
def foo_wrapper(z, *args, **kwargs):
252252
print("foo_wrapper called ! z=%s" % z)
253253
# call the foo function
@@ -283,23 +283,24 @@ def foo(b, c, a=0):
283283
pass
284284

285285
# original signature
286-
original_func_sig = signature(foo)
287-
print("original signature: %s%s" % (foo.__name__, original_func_sig))
286+
foo_sig = signature(foo)
287+
print("original signature: %s" % foo_sig)
288288

289289
# let's modify it
290-
func_sig = add_signature_parameters(original_func_sig,
291-
first=(Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD),),
292-
last=(Parameter('o', kind=Parameter.POSITIONAL_OR_KEYWORD,
293-
default=True),))
294-
func_sig = remove_signature_parameters(func_sig, 'b', 'a')
295-
print("modified signature: %s%s" % (foo.__name__, original_func_sig))
290+
new_sig = add_signature_parameters(foo_sig,
291+
first=Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD),
292+
last=Parameter('o', kind=Parameter.POSITIONAL_OR_KEYWORD,
293+
default=True)
294+
)
295+
new_sig = remove_signature_parameters(new_sig, 'b', 'a')
296+
print("modified signature: %s" % new_sig)
296297
```
297298

298299
yields
299300

300301
```bash
301-
original signature: foo(b, c, a=0)
302-
modified signature: foo(z, c, o=True)
302+
original signature: (b, c, a=0)
303+
modified signature: (z, c, o=True)
303304
```
304305

305306
They might save you a few lines of code if your use-case is not too specific.
@@ -372,11 +373,11 @@ prints the following source code:
372373

373374
```python
374375
def foo(b, a=0):
375-
return _call_handler_(b=b, a=a)
376+
return _func_impl_(b=b, a=a)
376377

377378
```
378379

379-
The `_call_handler_` symbol represents your implementation. As [already mentioned](#arguments_mapping), you see that the variables are passed to it *as keyword arguments* when possible (`_call_handler_(b=b)`, not simply `_call_handler_(b)`). Of course if it is not possible it adapts:
380+
The `_func_impl_` symbol represents your implementation. As [already mentioned](#arguments_mapping), you see that the variables are passed to it *as keyword arguments* when possible (`_func_impl_(b=b)`, not simply `_func_impl_(b)`). Of course if it is not possible it adapts:
380381

381382
```python
382383
gen_func = create_function("foo(a=0, *args, **kwargs)", func_impl)
@@ -387,7 +388,7 @@ prints the following source code:
387388

388389
```python
389390
def foo(a=0, *args, **kwargs):
390-
return _call_handler_(a=a, *args, **kwargs)
391+
return _func_impl_(a=a, *args, **kwargs)
391392
```
392393

393394
#### Function reference injection

makefun/tests/test_doc.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from funcsigs import signature, Signature, Parameter
88

99

10-
from makefun import create_function, add_signature_parameters, remove_signature_parameters, with_signature, wraps
10+
from makefun import create_function, add_signature_parameters, remove_signature_parameters, with_signature, wraps, \
11+
create_wrapper
1112

1213
python_version = sys.version_info.major
1314

@@ -67,7 +68,8 @@ def func_impl(*args, **kwargs):
6768
assert gen_func.__source__ == ref_src
6869

6970

70-
def test_from_sig_wrapper():
71+
@pytest.mark.parametrize('use_decorator', [False, True], ids="use_decorator={}".format)
72+
def test_from_sig_wrapper(use_decorator):
7173
""" Tests that we can create a function from a Signature object """
7274

7375
def foo(b, a=0):
@@ -94,7 +96,10 @@ def func_impl(z, *args, **kwargs):
9496
return z, output
9597

9698
# create the dynamic function
97-
gen_func = create_function(func_sig, func_impl, func_name=func_name)
99+
if use_decorator:
100+
gen_func = wraps(foo, new_sig=func_sig)(func_impl)
101+
else:
102+
gen_func = create_wrapper(foo, func_impl, new_sig=func_sig)
98103

99104
# check the source code
100105
ref_src = "def foo(z, b, a=0):\n return _func_impl_(z=z, b=b, a=a)\n"
@@ -111,15 +116,18 @@ def foo(b, c, a=0):
111116
pass
112117

113118
# original signature
114-
original_func_sig = signature(foo)
115-
assert str(original_func_sig) == '(b, c, a=0)'
119+
foo_sig = signature(foo)
120+
print("original signature: %s" % foo_sig)
116121

117122
# let's modify it
118-
func_sig = add_signature_parameters(original_func_sig,
119-
first=Parameter('z', Parameter.POSITIONAL_OR_KEYWORD),
120-
last=(Parameter('o', Parameter.POSITIONAL_OR_KEYWORD, default=True),))
121-
func_sig = remove_signature_parameters(func_sig, 'b', 'a')
122-
assert str(func_sig) == '(z, c, o=True)'
123+
new_sig = add_signature_parameters(foo_sig,
124+
first=Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD),
125+
last=Parameter('o', kind=Parameter.POSITIONAL_OR_KEYWORD,
126+
default=True)
127+
)
128+
new_sig = remove_signature_parameters(new_sig, 'b', 'a')
129+
print("modified signature: %s" % new_sig)
130+
assert str(new_sig) == '(z, c, o=True)'
123131

124132

125133
def test_injection():

0 commit comments

Comments
 (0)