Skip to content

Commit 7f43595

Browse files
author
Tony Crisci
committed
support PEP563 annotations
fixes #3
1 parent 1dc2077 commit 7f43595

2 files changed

Lines changed: 47 additions & 24 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ format:
1313
python3 -m yapf -rip $(source_dirs)
1414

1515
test:
16-
for py in python3.10 python3.6 python3.7 python3.8 python3.9 ; do \
16+
for py in python3.6 python3.7 python3.9 python3.10 python3.8 ; do \
1717
if hash $$py; then \
1818
dbus-run-session $$py -m pytest -sv --cov=dbus_next || exit 1 ; \
1919
fi \

dbus_next/service.py

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,34 @@
88
import inspect
99
from typing import no_type_check_decorator, Dict, List, Any
1010
import copy
11-
12-
# TODO: if the user uses `from __future__ import annotations` in their code,
13-
# the annotation inspection will not work because of PEP 563. We will get
14-
# something that needs to be evaled because type hints will become "forward
15-
# definitions". You can do this eval automatically with
16-
# typing.get_type_hints(). This fails without the __future__ import on
17-
# python 3.7 but will always succeed on python4. I don't know how to tell if
18-
# the user has imported the future annotation feature. We might just not
19-
# support the future import on python3 for now and do a check for python4
20-
# later. I really hope they keep supporting this use case.
11+
import ast
12+
13+
14+
def _parse_annotation(annotation: str) -> str:
15+
'''
16+
Because of PEP 563, if `from __future__ import annotations` is used in code
17+
or on Python version >=3.10 where this is the default, return annotations
18+
from the `inspect` module will return annotations as "forward definitions".
19+
In this case, we must eval the result which we do only when given a string
20+
constant.
21+
'''
22+
def raise_value_error():
23+
raise ValueError(f'service annotations must be a string constant (got {annotation})')
24+
25+
if not annotation or annotation is inspect.Signature.empty:
26+
return ''
27+
if type(annotation) is not str:
28+
raise_value_error()
29+
try:
30+
body = ast.parse(annotation).body
31+
if len(body) == 1 and type(body[0].value) is ast.Constant:
32+
if type(body[0].value.value) is not str:
33+
raise_value_error()
34+
return body[0].value.value
35+
except SyntaxError:
36+
pass
37+
38+
return annotation
2139

2240

2341
class _Method:
@@ -32,16 +50,17 @@ def __init__(self, fn, name, disabled=False):
3250
if i == 0:
3351
# first is self
3452
continue
35-
if param.annotation is inspect.Signature.empty:
53+
annotation = _parse_annotation(param.annotation)
54+
if not annotation:
3655
raise ValueError(
3756
'method parameters must specify the dbus type string as an annotation')
38-
in_args.append(intr.Arg(param.annotation, intr.ArgDirection.IN, param.name))
39-
in_signature += param.annotation
57+
in_args.append(intr.Arg(annotation, intr.ArgDirection.IN, param.name))
58+
in_signature += annotation
4059

4160
out_args = []
42-
if inspection.return_annotation is not inspect.Signature.empty:
43-
out_signature = inspection.return_annotation
44-
for type_ in SignatureTree._get(inspection.return_annotation).types:
61+
out_signature = _parse_annotation(inspection.return_annotation)
62+
if out_signature:
63+
for type_ in SignatureTree._get(out_signature).types:
4564
out_args.append(intr.Arg(type_, intr.ArgDirection.OUT))
4665

4766
self.name = name
@@ -114,8 +133,10 @@ def __init__(self, fn, name, disabled=False):
114133
signature = ''
115134
signature_tree = None
116135

117-
if inspection.return_annotation is not inspect.Signature.empty:
118-
signature = inspection.return_annotation
136+
return_annotation = _parse_annotation(inspection.return_annotation)
137+
138+
if return_annotation:
139+
signature = return_annotation
119140
signature_tree = SignatureTree._get(signature)
120141
for type_ in signature_tree.types:
121142
args.append(intr.Arg(type_, intr.ArgDirection.OUT))
@@ -214,16 +235,18 @@ def __init__(self, fn, *args, **kwargs):
214235
self.prop_getter = fn
215236
self.prop_setter = None
216237

217-
sig = inspect.signature(fn)
218-
if len(sig.parameters) != 1:
238+
inspection = inspect.signature(fn)
239+
if len(inspection.parameters) != 1:
219240
raise ValueError('the property must only have the "self" input parameter')
220241

221-
if sig.return_annotation is inspect.Signature.empty:
242+
return_annotation = _parse_annotation(inspection.return_annotation)
243+
244+
if not return_annotation:
222245
raise ValueError(
223246
'the property must specify the dbus type string as a return annotation string')
224247

225-
self.signature = sig.return_annotation
226-
tree = SignatureTree._get(sig.return_annotation)
248+
self.signature = return_annotation
249+
tree = SignatureTree._get(return_annotation)
227250

228251
if len(tree.types) != 1:
229252
raise ValueError('the property signature must be a single complete type')

0 commit comments

Comments
 (0)