Skip to content

Commit 2fa2a63

Browse files
authored
Merge pull request #421 from seeM/union-annots
use py310 style union annotations
2 parents 079d5b1 + a9dcfc3 commit 2fa2a63

11 files changed

+90
-43
lines changed

fastcore/_nbdev.py

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"type_hints": "01_basics.ipynb",
5151
"annotations": "01_basics.ipynb",
5252
"anno_ret": "01_basics.ipynb",
53+
"union2tuple": "01_basics.ipynb",
5354
"argnames": "01_basics.ipynb",
5455
"with_cast": "01_basics.ipynb",
5556
"store_attr": "01_basics.ipynb",

fastcore/basics.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
'null', 'tonull', 'get_class', 'mk_class', 'wrap_class', 'ignore_exceptions', 'exec_local', 'risinstance',
55
'Inf', 'in_', 'lt', 'gt', 'le', 'ge', 'eq', 'ne', 'add', 'sub', 'mul', 'truediv', 'is_', 'is_not', 'in_',
66
'true', 'stop', 'gen', 'chunked', 'otherwise', 'custom_dir', 'AttrDict', 'get_annotations_ex', 'eval_type',
7-
'type_hints', 'annotations', 'anno_ret', 'argnames', 'with_cast', 'store_attr', 'attrdict', 'properties',
8-
'camel2words', 'camel2snake', 'snake2camel', 'class2attr', 'getcallable', 'getattrs', 'hasattrs', 'setattrs',
9-
'try_attrs', 'GetAttrBase', 'GetAttr', 'delegate_attr', 'ShowPrint', 'Int', 'Str', 'Float', 'flatten',
10-
'concat', 'strcat', 'detuplify', 'replicate', 'setify', 'merge', 'range_of', 'groupby', 'last_index',
11-
'filter_dict', 'filter_keys', 'filter_values', 'cycle', 'zip_cycle', 'sorted_ex', 'not_', 'argwhere',
12-
'filter_ex', 'range_of', 'renumerate', 'first', 'nested_attr', 'nested_callable', 'nested_idx',
7+
'type_hints', 'annotations', 'anno_ret', 'union2tuple', 'argnames', 'with_cast', 'store_attr', 'attrdict',
8+
'properties', 'camel2words', 'camel2snake', 'snake2camel', 'class2attr', 'getcallable', 'getattrs',
9+
'hasattrs', 'setattrs', 'try_attrs', 'GetAttrBase', 'GetAttr', 'delegate_attr', 'ShowPrint', 'Int', 'Str',
10+
'Float', 'flatten', 'concat', 'strcat', 'detuplify', 'replicate', 'setify', 'merge', 'range_of', 'groupby',
11+
'last_index', 'filter_dict', 'filter_keys', 'filter_values', 'cycle', 'zip_cycle', 'sorted_ex', 'not_',
12+
'argwhere', 'filter_ex', 'range_of', 'renumerate', 'first', 'nested_attr', 'nested_callable', 'nested_idx',
1313
'set_nested_idx', 'val2idx', 'uniqueify', 'loop_first_last', 'loop_first', 'loop_last', 'num_methods',
1414
'rnum_methods', 'inum_methods', 'fastuple', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'bind', 'mapt', 'map_ex',
1515
'compose', 'maps', 'partialler', 'instantiate', 'using_attr', 'Self', 'Self', 'copy_func', 'patch_to',
@@ -20,6 +20,8 @@
2020
from .imports import *
2121
import builtins,types
2222
import pprint
23+
try: from types import UnionType
24+
except ImportError: UnionType = None
2325

2426
# Cell
2527
defaults = SimpleNamespace()
@@ -289,7 +291,7 @@ def get_annotations_ex(obj, *, globals=None, locals=None):
289291
def eval_type(t, glb, loc):
290292
"`eval` a type or collection of types, if needed, for annotations in py3.10+"
291293
if isinstance(t,str):
292-
if '|' in t: return eval_type(tuple(t.split('|')), glb, loc)
294+
if '|' in t: return Union[eval_type(tuple(t.split('|')), glb, loc)]
293295
return eval(t, glb, loc)
294296
if isinstance(t,(tuple,list)): return type(t)([eval_type(c, glb, loc) for c in t])
295297
return t
@@ -320,6 +322,12 @@ def anno_ret(func):
320322
"Get the return annotation of `func`"
321323
return annotations(func).get('return', None) if func else None
322324

325+
# Cell
326+
def union2tuple(t):
327+
if (getattr(t, '__origin__', None) is Union
328+
or (UnionType and isinstance(t, UnionType))): return t.__args__
329+
return t
330+
323331
# Cell
324332
def argnames(f, frame=False):
325333
"Names of arguments to function or frame `f`"
@@ -906,7 +914,7 @@ def patch(f=None, *, as_prop=False, cls_method=False):
906914
"Decorator: add `f` to the first parameter's class (based on f's type annotations)"
907915
if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method)
908916
ann,glb,loc = get_annotations_ex(f)
909-
cls = eval_type(ann.pop('cls') if cls_method else next(iter(ann.values())), glb, loc)
917+
cls = union2tuple(eval_type(ann.pop('cls') if cls_method else next(iter(ann.values())), glb, loc))
910918
return patch_to(cls, as_prop=as_prop, cls_method=cls_method)(f)
911919

912920
# Cell

fastcore/dispatch.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def _reset(self):
6060

6161
def add(self, t, f):
6262
"Add type `t` and function `f`"
63-
if not isinstance(t,tuple): t=tuple(L(t))
63+
if not isinstance(t, tuple): t = tuple(L(union2tuple(t)))
6464
for t_ in t: self.d[t_] = f
6565
self._reset()
6666

fastcore/imports.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from operator import itemgetter,attrgetter
44
from warnings import warn
5-
from typing import Iterable,Generator,Sequence,Iterator,List,Set,Dict,Union,Optional
5+
from typing import Iterable,Generator,Sequence,Iterator,List,Set,Dict,Union,Optional,Tuple
66
from functools import partial,reduce
77
from pathlib import Path
88

fastcore/transform.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(self, enc=None, dec=None, split_idx=None, order=None):
6464
if enc:
6565
self.encodes.add(enc)
6666
self.order = getattr(enc,'order',self.order)
67-
if len(type_hints(enc)) > 0: self.input_types = first(type_hints(enc).values())
67+
if len(type_hints(enc)) > 0: self.input_types = union2tuple(first(type_hints(enc).values()))
6868
self._name = _get_name(enc)
6969
if dec: self.decodes.add(dec)
7070

fastcore/xtras.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def __setstate__(self, s): self.coll,self.idxs,self.cache,self.tfm = s['coll'],s
104104

105105
# Cell
106106
def walk(
107-
path:(Path,str), # path to start searching
107+
path:Path|str, # path to start searching
108108
symlinks:bool=True, # follow symlinks?
109109
keep_file:callable=noop, # function that returns True for wanted files
110110
keep_folder:callable=noop, # function that returns True for folders to enter
@@ -118,7 +118,7 @@ def walk(
118118

119119
# Cell
120120
def globtastic(
121-
path:Union[Path,str], # path to start searching
121+
path:Path|str, # path to start searching
122122
recursive:bool=True, # search subfolders
123123
symlinks:bool=True, # follow symlinks?
124124
file_glob:str=None, # Only include files matching glob

nbs/01_basics.ipynb

+36-8
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"#export\n",
1919
"from fastcore.imports import *\n",
2020
"import builtins,types\n",
21-
"import pprint"
21+
"import pprint\n",
22+
"try: from types import UnionType\n",
23+
"except ImportError: UnionType = None"
2224
]
2325
},
2426
{
@@ -840,7 +842,7 @@
840842
"text/markdown": [
841843
"<h4 id=\"noops\" class=\"doc_header\"><code>noops</code><a href=\"https://github.com/fastai/fastcore/tree/master/fastcore/imports.py#L39\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
842844
"\n",
843-
"> <code>noops</code>(**`x`**=*`None`*, **\\*`args`**, **\\*\\*`kwargs`**)\n",
845+
"> <code>noops</code>(**`self`**, **`x`**=*`None`*, **\\*`args`**, **\\*\\*`kwargs`**)\n",
844846
"\n",
845847
"Do nothing (method)"
846848
],
@@ -1447,7 +1449,7 @@
14471449
"def eval_type(t, glb, loc):\n",
14481450
" \"`eval` a type or collection of types, if needed, for annotations in py3.10+\"\n",
14491451
" if isinstance(t,str):\n",
1450-
" if '|' in t: return eval_type(tuple(t.split('|')), glb, loc)\n",
1452+
" if '|' in t: return Union[eval_type(tuple(t.split('|')), glb, loc)]\n",
14511453
" return eval(t, glb, loc)\n",
14521454
" if isinstance(t,(tuple,list)): return type(t)([eval_type(c, glb, loc) for c in t])\n",
14531455
" return t"
@@ -1488,7 +1490,7 @@
14881490
"cell_type": "markdown",
14891491
"metadata": {},
14901492
"source": [
1491-
"`|` is supported in types when using `eval_type` even for python versions prior to 3.9, by returning a tuple of types:"
1493+
"`|` is supported for defining `Union` types when using `eval_type` even for python versions prior to 3.9:"
14921494
]
14931495
},
14941496
{
@@ -1499,7 +1501,7 @@
14991501
{
15001502
"data": {
15011503
"text/plain": [
1502-
"(__main__._T2a, __main__._T2b)"
1504+
"typing.Union[__main__._T2a, __main__._T2b]"
15031505
]
15041506
},
15051507
"execution_count": null,
@@ -1701,6 +1703,32 @@
17011703
"test_eq(anno_ret(None), None) # instead of passing in a func, pass in None"
17021704
]
17031705
},
1706+
{
1707+
"cell_type": "code",
1708+
"execution_count": null,
1709+
"metadata": {},
1710+
"outputs": [],
1711+
"source": [
1712+
"#export\n",
1713+
"def union2tuple(t):\n",
1714+
" if (getattr(t, '__origin__', None) is Union\n",
1715+
" or (UnionType and isinstance(t, UnionType))): return t.__args__\n",
1716+
" return t"
1717+
]
1718+
},
1719+
{
1720+
"cell_type": "code",
1721+
"execution_count": null,
1722+
"metadata": {},
1723+
"outputs": [],
1724+
"source": [
1725+
"test_eq(union2tuple(Union[int,str]), (int,str))\n",
1726+
"test_eq(union2tuple(int), int)\n",
1727+
"test_eq(union2tuple(Tuple[int,str]), Tuple[int,str])\n",
1728+
"test_eq(union2tuple((int,str)), (int,str))\n",
1729+
"if UnionType: test_eq(union2tuple(int|str), (int,str))"
1730+
]
1731+
},
17041732
{
17051733
"cell_type": "code",
17061734
"execution_count": null,
@@ -4850,7 +4878,7 @@
48504878
" \"Decorator: add `f` to the first parameter's class (based on f's type annotations)\"\n",
48514879
" if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method)\n",
48524880
" ann,glb,loc = get_annotations_ex(f)\n",
4853-
" cls = eval_type(ann.pop('cls') if cls_method else next(iter(ann.values())), glb, loc)\n",
4881+
" cls = union2tuple(eval_type(ann.pop('cls') if cls_method else next(iter(ann.values())), glb, loc))\n",
48544882
" return patch_to(cls, as_prop=as_prop, cls_method=cls_method)(f)"
48554883
]
48564884
},
@@ -5377,7 +5405,7 @@
53775405
{
53785406
"data": {
53795407
"text/plain": [
5380-
"8"
5408+
"4"
53815409
]
53825410
},
53835411
"execution_count": null,
@@ -5504,7 +5532,7 @@
55045532
"metadata": {},
55055533
"outputs": [],
55065534
"source": [
5507-
"def discount(price:(int,float), pct:float): \n",
5535+
"def discount(price:int|float, pct:float): \n",
55085536
" return (1-pct) * price\n",
55095537
"\n",
55105538
"assert 90.0 == discount(100.0, .1)"

nbs/03_xtras.ipynb

+13-13
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@
562562
{
563563
"data": {
564564
"text/plain": [
565-
"['b', 'f', 'a', 'c', 'g', 'd', 'e', 'h']"
565+
"['f', 'b', 'a', 'c', 'h', 'd', 'g', 'e']"
566566
]
567567
},
568568
"execution_count": null,
@@ -630,7 +630,7 @@
630630
"source": [
631631
"#export\n",
632632
"def walk(\n",
633-
" path:(Path,str), # path to start searching\n",
633+
" path:Path|str, # path to start searching\n",
634634
" symlinks:bool=True, # follow symlinks?\n",
635635
" keep_file:callable=noop, # function that returns True for wanted files\n",
636636
" keep_folder:callable=noop, # function that returns True for folders to enter\n",
@@ -651,7 +651,7 @@
651651
"source": [
652652
"#export\n",
653653
"def globtastic(\n",
654-
" path:Union[Path,str], # path to start searching\n",
654+
" path:Path|str, # path to start searching\n",
655655
" recursive:bool=True, # search subfolders\n",
656656
" symlinks:bool=True, # follow symlinks?\n",
657657
" file_glob:str=None, # Only include files matching glob\n",
@@ -686,7 +686,7 @@
686686
{
687687
"data": {
688688
"text/plain": [
689-
"(#9) ['./06_docments.ipynb','./08_script.ipynb','./04_dispatch.ipynb','./01_basics.ipynb','./fastcore/docments.py','./fastcore/docscrape.py','./fastcore/script.py','./fastcore/basics.py','./fastcore/dispatch.py']"
689+
"(#9) ['./08_script.ipynb','./04_dispatch.ipynb','./06_docments.ipynb','./01_basics.ipynb','./fastcore/docments.py','./fastcore/dispatch.py','./fastcore/basics.py','./fastcore/docscrape.py','./fastcore/script.py']"
690690
]
691691
},
692692
"execution_count": null,
@@ -1193,7 +1193,7 @@
11931193
{
11941194
"data": {
11951195
"text/plain": [
1196-
"'pip 22.0.3 from /opt/conda/lib/python3.9/site-packages/pip (python 3.9)'"
1196+
"'pip 22.1.2 from /Users/seem/.pyenv/versions/3.9.7/lib/python3.9/site-packages/pip (python 3.9)'"
11971197
]
11981198
},
11991199
"execution_count": null,
@@ -1395,7 +1395,7 @@
13951395
{
13961396
"data": {
13971397
"text/plain": [
1398-
"Path('/home/jovyan/local/zach/fastcore/fastcore')"
1398+
"Path('/Users/seem/code/fastcore/fastcore')"
13991399
]
14001400
},
14011401
"execution_count": null,
@@ -1461,7 +1461,7 @@
14611461
{
14621462
"data": {
14631463
"text/plain": [
1464-
"Path('05_transform.ipynb')"
1464+
"Path('parallel_test.py')"
14651465
]
14661466
},
14671467
"execution_count": null,
@@ -1495,7 +1495,7 @@
14951495
{
14961496
"data": {
14971497
"text/plain": [
1498-
"(Path('../fastcore/shutil.py'), Path('05_transform.ipynb'))"
1498+
"(Path('../fastcore/shutil.py'), Path('parallel_win.ipynb'))"
14991499
]
15001500
},
15011501
"execution_count": null,
@@ -1661,7 +1661,7 @@
16611661
{
16621662
"data": {
16631663
"text/plain": [
1664-
"'nbformat/converter.py#L10'"
1664+
"'nbformat/converter.py#L11'"
16651665
]
16661666
},
16671667
"execution_count": null,
@@ -1879,8 +1879,8 @@
18791879
"name": "stdout",
18801880
"output_type": "stream",
18811881
"text": [
1882-
"Num Events: 8, Freq/sec: 386.5\n",
1883-
"Most recent: ▇▇▁▁▁ 315.1 316.3 246.7 262.8 264.1\n"
1882+
"Num Events: 1, Freq/sec: 728.9\n",
1883+
"Most recent: ▇▅▂▅▁ 338.0 292.7 266.0 299.7 228.3\n"
18841884
]
18851885
}
18861886
],
@@ -2029,7 +2029,7 @@
20292029
"name": "stdout",
20302030
"output_type": "stream",
20312031
"text": [
2032-
"2000-01-01 12:00:00 UTC is 2000-01-01 12:00:00+00:00 local time\n"
2032+
"2000-01-01 12:00:00 UTC is 2000-01-01 23:00:00+11:00 local time\n"
20332033
]
20342034
}
20352035
],
@@ -2059,7 +2059,7 @@
20592059
"name": "stdout",
20602060
"output_type": "stream",
20612061
"text": [
2062-
"2000-01-01 12:00:00 local is 2000-01-01 12:00:00+00:00 UTC time\n"
2062+
"2000-01-01 12:00:00 local is 2000-01-01 01:00:00+00:00 UTC time\n"
20632063
]
20642064
}
20652065
],

nbs/04_dispatch.ipynb

+3-3
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@
259259
"\n",
260260
" def add(self, t, f):\n",
261261
" \"Add type `t` and function `f`\"\n",
262-
" if not isinstance(t,tuple): t=tuple(L(t))\n",
262+
" if not isinstance(t, tuple): t = tuple(L(union2tuple(t)))\n",
263263
" for t_ in t: self.d[t_] = f\n",
264264
" self._reset()\n",
265265
"\n",
@@ -363,7 +363,7 @@
363363
"def f2(x:int, y:float): return x+y #int and float for 2nd arg\n",
364364
"def f_nin(x:numbers.Integral)->int: return x+1 #integral numeric\n",
365365
"def f_ni2(x:int): return x #integer\n",
366-
"def f_bll(x:(bool,list)): return x #bool or list\n",
366+
"def f_bll(x:bool|list): return x #bool or list\n",
367367
"def f_num(x:numbers.Number): return x #Number (root of numerics) "
368368
]
369369
},
@@ -949,7 +949,7 @@
949949
"metadata": {},
950950
"outputs": [],
951951
"source": [
952-
"def m_nin(self, x:(str,numbers.Integral)): return str(x)+'1'\n",
952+
"def m_nin(self, x:str|numbers.Integral): return str(x)+'1'\n",
953953
"def m_bll(self, x:bool): self.foo='a'\n",
954954
"def m_num(self, x:numbers.Number): return x*2\n",
955955
"\n",

nbs/05_transform.ipynb

+9-6
Large diffs are not rendered by default.

nbs/06_docments.ipynb

+7
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,13 @@
10901090
"from nbdev.export import notebook2script\n",
10911091
"notebook2script()"
10921092
]
1093+
},
1094+
{
1095+
"cell_type": "code",
1096+
"execution_count": null,
1097+
"metadata": {},
1098+
"outputs": [],
1099+
"source": []
10931100
}
10941101
],
10951102
"metadata": {

0 commit comments

Comments
 (0)