Skip to content

Commit 2322c7b

Browse files
authored
Merge pull request #186 from apoorvdarshan/fix-138-return-annotation
Fix FunctionMaker.create() with return type annotation (#138)
2 parents 16d2563 + b94262e commit 2322c7b

3 files changed

Lines changed: 32 additions & 5 deletions

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ HISTORY
33

44
## Unreleased
55

6+
Fixed `FunctionMaker.create` raising a `SyntaxError` when the signature
7+
string contains a return annotation, e.g.
8+
`create("f(a) -> int", "return a")` (issue #138).
9+
610
## 5.3.1 (2026-05-18)
711

812
Added license SPDX identifier to pyproject.toml (reported by

src/decorator/__init__.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,22 +253,28 @@ def create(cls, obj, body, evaldict, defaults=None,
253253
attribute __source__ is added to the result. The attributes attrs
254254
are added, if any.
255255
"""
256-
if isinstance(obj, str): # "name(signature)"
256+
if isinstance(obj, str): # "name(signature)" or "name(signature) -> ret"
257257
name, rest = obj.strip().split('(', 1)
258-
signature = rest[:-1] # strip a right parens
258+
# split the argument list from an optional return annotation;
259+
# the argument list ends at the last right parens
260+
signature, _, return_annotation = rest.rpartition(')')
259261
func = None
260262
else: # a function
261263
name = None
262264
signature = None
265+
return_annotation = ''
263266
func = obj
264267
self = cls(func, name, signature, defaults, doc, module)
268+
self.return_annotation = return_annotation # e.g. " -> int"
265269
ibody = '\n'.join(' ' + line for line in body.splitlines())
266270
caller = evaldict.get('_call_') # when called from `decorate`
267271
if caller and iscoroutinefunction(caller):
268-
body = ('async def %(name)s(%(signature)s):\n' + ibody)
272+
body = ('async def %(name)s(%(signature)s)'
273+
'%(return_annotation)s:\n' + ibody)
269274
body = re.sub(r'\breturn\b', 'return await', body)
270275
else:
271-
body = 'def %(name)s(%(signature)s):\n' + ibody
276+
body = ('def %(name)s(%(signature)s)'
277+
'%(return_annotation)s:\n' + ibody)
272278
return self.make(body, evaldict, addsource, **attrs)
273279

274280

tests/test.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import functools
77
import asyncio
88
from collections import defaultdict, ChainMap, abc as c
9-
from decorator import dispatch_on, contextmanager, decorator
9+
from decorator import dispatch_on, contextmanager, decorator, FunctionMaker
1010
try:
1111
from . import documentation as doc # good with pytest
1212
except ImportError:
@@ -114,6 +114,23 @@ def test_signature(self):
114114
sig = inspect.signature(doc.f1)
115115
self.assertEqual(str(sig), '(x)')
116116

117+
def test_return_annotation(self):
118+
# see https://github.com/micheles/decorator/issues/138
119+
def add(a, b):
120+
return a + b
121+
122+
# a signature string without a return annotation still works
123+
add2 = FunctionMaker.create(
124+
'add2(a: int, b: int)', 'return add(a, b)',
125+
evaldict={'add': add})
126+
self.assertEqual(add2(6, 8), 14)
127+
128+
# a signature string with a return annotation must not raise
129+
add3 = FunctionMaker.create(
130+
'add3(a: int, b: int) -> int', 'return add(a, b)',
131+
evaldict={'add': add})
132+
self.assertEqual(add3(6, 8), 14)
133+
117134
def test_unique_filenames(self):
118135
@decorator
119136
def d1(f, *args, **kwargs):

0 commit comments

Comments
 (0)