Skip to content

Commit 8113b81

Browse files
committed
fixes #577
1 parent 02d98f6 commit 8113b81

File tree

3 files changed

+210
-1
lines changed

3 files changed

+210
-1
lines changed

fastcore/_modidx.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,11 +639,14 @@
639639
'fastcore.xtras.loads': ('xtras.html#loads', 'fastcore/xtras.py'),
640640
'fastcore.xtras.loads_multi': ('xtras.html#loads_multi', 'fastcore/xtras.py'),
641641
'fastcore.xtras.local2utc': ('xtras.html#local2utc', 'fastcore/xtras.py'),
642+
'fastcore.xtras.make_nullable': ('xtras.html#make_nullable', 'fastcore/xtras.py'),
642643
'fastcore.xtras.mapped': ('xtras.html#mapped', 'fastcore/xtras.py'),
643644
'fastcore.xtras.maybe_open': ('xtras.html#maybe_open', 'fastcore/xtras.py'),
645+
'fastcore.xtras.mk_dataclass': ('xtras.html#mk_dataclass', 'fastcore/xtras.py'),
644646
'fastcore.xtras.mkdir': ('xtras.html#mkdir', 'fastcore/xtras.py'),
645647
'fastcore.xtras.modified_env': ('xtras.html#modified_env', 'fastcore/xtras.py'),
646648
'fastcore.xtras.modify_exception': ('xtras.html#modify_exception', 'fastcore/xtras.py'),
649+
'fastcore.xtras.nullable_dc': ('xtras.html#nullable_dc', 'fastcore/xtras.py'),
647650
'fastcore.xtras.obj2dict': ('xtras.html#obj2dict', 'fastcore/xtras.py'),
648651
'fastcore.xtras.open_file': ('xtras.html#open_file', 'fastcore/xtras.py'),
649652
'fastcore.xtras.parse_env': ('xtras.html#parse_env', 'fastcore/xtras.py'),

fastcore/xtras.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'ReindexCollection', 'get_source_link', 'truncstr', 'sparkline', 'modify_exception', 'round_multiple',
1111
'set_num_threads', 'join_path_file', 'autostart', 'EventTimer', 'stringfmt_names', 'PartialFormatter',
1212
'partial_format', 'utc2local', 'local2utc', 'trace', 'modified_env', 'ContextManagers', 'shufflish',
13-
'console_help', 'hl_md', 'type2str', 'dataclass_src']
13+
'console_help', 'hl_md', 'type2str', 'dataclass_src', 'nullable_dc', 'make_nullable', 'mk_dataclass']
1414

1515
# %% ../nbs/03_xtras.ipynb 2
1616
from .imports import *
@@ -642,3 +642,41 @@ def dataclass_src(cls):
642642
d = "" if f.default is dataclasses.MISSING else f" = {f.default!r}"
643643
src += f" {f.name}: {type2str(f.type)}{d}\n"
644644
return src
645+
646+
# %% ../nbs/03_xtras.ipynb 171
647+
def nullable_dc(cls):
648+
"Like `dataclass`, but default of `None` added to fields without defaults"
649+
from dataclasses import dataclass, field
650+
from inspect import get_annotations
651+
for k,v in get_annotations(cls).items():
652+
if not hasattr(cls,k): setattr(cls, k, field(default=None))
653+
return dataclass(cls)
654+
655+
# %% ../nbs/03_xtras.ipynb 173
656+
def make_nullable(cls):
657+
from dataclasses import dataclass, fields, MISSING
658+
if hasattr(cls, '_nullable'): return
659+
cls._nullable = True
660+
661+
@patch
662+
def __init__(self:cls, *args, **kwargs):
663+
flds = fields(cls)
664+
dargs = {k.name:v for k,v in zip(flds, args)}
665+
for f in flds:
666+
nm = f.name
667+
if nm not in dargs and nm not in kwargs and f.default is None and f.default_factory is MISSING:
668+
kwargs[nm] = None
669+
self._orig___init__(*args, **kwargs)
670+
671+
for f in fields(cls):
672+
if f.default is MISSING and f.default_factory is MISSING: f.default = None
673+
674+
# %% ../nbs/03_xtras.ipynb 177
675+
def mk_dataclass(cls):
676+
from dataclasses import dataclass, field, is_dataclass, MISSING
677+
from inspect import get_annotations
678+
if is_dataclass(cls): return make_nullable(cls)
679+
for k,v in get_annotations(cls).items():
680+
if not hasattr(cls,k) or getattr(cls,k) is MISSING:
681+
setattr(cls, k, field(default=None))
682+
dataclass(cls, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

nbs/03_xtras.ipynb

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,174 @@
25982598
"print(dataclass_src(DC))"
25992599
]
26002600
},
2601+
{
2602+
"cell_type": "code",
2603+
"execution_count": null,
2604+
"metadata": {},
2605+
"outputs": [],
2606+
"source": [
2607+
"#| export\n",
2608+
"def nullable_dc(cls):\n",
2609+
" \"Like `dataclass`, but default of `None` added to fields without defaults\"\n",
2610+
" from dataclasses import dataclass, field\n",
2611+
" from inspect import get_annotations\n",
2612+
" for k,v in get_annotations(cls).items():\n",
2613+
" if not hasattr(cls,k): setattr(cls, k, field(default=None))\n",
2614+
" return dataclass(cls)"
2615+
]
2616+
},
2617+
{
2618+
"cell_type": "code",
2619+
"execution_count": null,
2620+
"metadata": {},
2621+
"outputs": [
2622+
{
2623+
"data": {
2624+
"text/plain": [
2625+
"Person(name='Bob', age=None, city='Unknown')"
2626+
]
2627+
},
2628+
"execution_count": null,
2629+
"metadata": {},
2630+
"output_type": "execute_result"
2631+
}
2632+
],
2633+
"source": [
2634+
"@nullable_dc\n",
2635+
"class Person: name: str; age: int; city: str = \"Unknown\"\n",
2636+
"Person(name=\"Bob\")"
2637+
]
2638+
},
2639+
{
2640+
"cell_type": "code",
2641+
"execution_count": null,
2642+
"metadata": {},
2643+
"outputs": [],
2644+
"source": [
2645+
"#| export\n",
2646+
"def make_nullable(cls):\n",
2647+
" from dataclasses import dataclass, fields, MISSING\n",
2648+
" if hasattr(cls, '_nullable'): return\n",
2649+
" cls._nullable = True\n",
2650+
"\n",
2651+
" @patch\n",
2652+
" def __init__(self:cls, *args, **kwargs):\n",
2653+
" flds = fields(cls)\n",
2654+
" dargs = {k.name:v for k,v in zip(flds, args)}\n",
2655+
" for f in flds:\n",
2656+
" nm = f.name\n",
2657+
" if nm not in dargs and nm not in kwargs and f.default is None and f.default_factory is MISSING:\n",
2658+
" kwargs[nm] = None\n",
2659+
" self._orig___init__(*args, **kwargs)\n",
2660+
" \n",
2661+
" for f in fields(cls):\n",
2662+
" if f.default is MISSING and f.default_factory is MISSING: f.default = None"
2663+
]
2664+
},
2665+
{
2666+
"cell_type": "code",
2667+
"execution_count": null,
2668+
"metadata": {},
2669+
"outputs": [
2670+
{
2671+
"data": {
2672+
"text/plain": [
2673+
"Person(name='Bob', age=None, city='NY')"
2674+
]
2675+
},
2676+
"execution_count": null,
2677+
"metadata": {},
2678+
"output_type": "execute_result"
2679+
}
2680+
],
2681+
"source": [
2682+
"@dataclass\n",
2683+
"class Person: name: str; age: int; city: str = \"Unknown\"\n",
2684+
"\n",
2685+
"make_nullable(Person)\n",
2686+
"Person(\"Bob\", city='NY')"
2687+
]
2688+
},
2689+
{
2690+
"cell_type": "code",
2691+
"execution_count": null,
2692+
"metadata": {},
2693+
"outputs": [
2694+
{
2695+
"data": {
2696+
"text/plain": [
2697+
"Person(name='Bob', age=None, city='Unknown')"
2698+
]
2699+
},
2700+
"execution_count": null,
2701+
"metadata": {},
2702+
"output_type": "execute_result"
2703+
}
2704+
],
2705+
"source": [
2706+
"Person(name=\"Bob\")"
2707+
]
2708+
},
2709+
{
2710+
"cell_type": "code",
2711+
"execution_count": null,
2712+
"metadata": {},
2713+
"outputs": [
2714+
{
2715+
"data": {
2716+
"text/plain": [
2717+
"Person(name='Bob', age=34, city='Unknown')"
2718+
]
2719+
},
2720+
"execution_count": null,
2721+
"metadata": {},
2722+
"output_type": "execute_result"
2723+
}
2724+
],
2725+
"source": [
2726+
"Person(\"Bob\", 34)"
2727+
]
2728+
},
2729+
{
2730+
"cell_type": "code",
2731+
"execution_count": null,
2732+
"metadata": {},
2733+
"outputs": [],
2734+
"source": [
2735+
"#| export\n",
2736+
"def mk_dataclass(cls):\n",
2737+
" from dataclasses import dataclass, field, is_dataclass, MISSING\n",
2738+
" from inspect import get_annotations\n",
2739+
" if is_dataclass(cls): return make_nullable(cls)\n",
2740+
" for k,v in get_annotations(cls).items():\n",
2741+
" if not hasattr(cls,k) or getattr(cls,k) is MISSING:\n",
2742+
" setattr(cls, k, field(default=None))\n",
2743+
" dataclass(cls, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)"
2744+
]
2745+
},
2746+
{
2747+
"cell_type": "code",
2748+
"execution_count": null,
2749+
"metadata": {},
2750+
"outputs": [
2751+
{
2752+
"data": {
2753+
"text/plain": [
2754+
"Person(name='Bob', age=None, city='Unknown')"
2755+
]
2756+
},
2757+
"execution_count": null,
2758+
"metadata": {},
2759+
"output_type": "execute_result"
2760+
}
2761+
],
2762+
"source": [
2763+
"class Person: name: str; age: int; city: str = \"Unknown\"\n",
2764+
"\n",
2765+
"mk_dataclass(Person)\n",
2766+
"Person(name=\"Bob\")"
2767+
]
2768+
},
26012769
{
26022770
"cell_type": "markdown",
26032771
"metadata": {},

0 commit comments

Comments
 (0)