Skip to content

Commit

Permalink
fixes #607
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Aug 15, 2024
1 parent ce7ab4a commit 0951105
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 68 deletions.
10 changes: 9 additions & 1 deletion fastcore/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@
'fastcore.xml.Safe.__html__': ('xml.html#safe.__html__', 'fastcore/xml.py'),
'fastcore.xml.__getattr__': ('xml.html#__getattr__', 'fastcore/xml.py'),
'fastcore.xml._escape': ('xml.html#_escape', 'fastcore/xml.py'),
'fastcore.xml._flatten_tuple': ('xml.html#_flatten_tuple', 'fastcore/xml.py'),
'fastcore.xml._preproc': ('xml.html#_preproc', 'fastcore/xml.py'),
'fastcore.xml._to_attr': ('xml.html#_to_attr', 'fastcore/xml.py'),
'fastcore.xml._to_xml': ('xml.html#_to_xml', 'fastcore/xml.py'),
Expand Down Expand Up @@ -624,6 +625,10 @@
'fastcore/xtras.py'),
'fastcore.xtras.ReindexCollection.reindex': ('xtras.html#reindexcollection.reindex', 'fastcore/xtras.py'),
'fastcore.xtras.ReindexCollection.shuffle': ('xtras.html#reindexcollection.shuffle', 'fastcore/xtras.py'),
'fastcore.xtras.Unset': ('xtras.html#unset', 'fastcore/xtras.py'),
'fastcore.xtras.Unset.__bool__': ('xtras.html#unset.__bool__', 'fastcore/xtras.py'),
'fastcore.xtras.Unset.__repr__': ('xtras.html#unset.__repr__', 'fastcore/xtras.py'),
'fastcore.xtras.Unset.__str__': ('xtras.html#unset.__str__', 'fastcore/xtras.py'),
'fastcore.xtras._ceil': ('xtras.html#_ceil', 'fastcore/xtras.py'),
'fastcore.xtras._has_property_getter': ('xtras.html#_has_property_getter', 'fastcore/xtras.py'),
'fastcore.xtras._is_property': ('xtras.html#_is_property', 'fastcore/xtras.py'),
Expand All @@ -635,6 +640,7 @@
'fastcore.xtras._unwrapped_func': ('xtras.html#_unwrapped_func', 'fastcore/xtras.py'),
'fastcore.xtras._unwrapped_type_dispatch_func': ( 'xtras.html#_unwrapped_type_dispatch_func',
'fastcore/xtras.py'),
'fastcore.xtras.asdict': ('xtras.html#asdict', 'fastcore/xtras.py'),
'fastcore.xtras.autostart': ('xtras.html#autostart', 'fastcore/xtras.py'),
'fastcore.xtras.bunzip': ('xtras.html#bunzip', 'fastcore/xtras.py'),
'fastcore.xtras.console_help': ('xtras.html#console_help', 'fastcore/xtras.py'),
Expand All @@ -643,11 +649,14 @@
'fastcore.xtras.dumps': ('xtras.html#dumps', 'fastcore/xtras.py'),
'fastcore.xtras.expand_wildcards': ('xtras.html#expand_wildcards', 'fastcore/xtras.py'),
'fastcore.xtras.flexicache': ('xtras.html#flexicache', 'fastcore/xtras.py'),
'fastcore.xtras.flexiclass': ('xtras.html#flexiclass', 'fastcore/xtras.py'),
'fastcore.xtras.get_source_link': ('xtras.html#get_source_link', 'fastcore/xtras.py'),
'fastcore.xtras.globtastic': ('xtras.html#globtastic', 'fastcore/xtras.py'),
'fastcore.xtras.hl_md': ('xtras.html#hl_md', 'fastcore/xtras.py'),
'fastcore.xtras.image_size': ('xtras.html#image_size', 'fastcore/xtras.py'),
'fastcore.xtras.is_listy': ('xtras.html#is_listy', 'fastcore/xtras.py'),
'fastcore.xtras.is_namedtuple': ('xtras.html#is_namedtuple', 'fastcore/xtras.py'),
'fastcore.xtras.is_typeddict': ('xtras.html#is_typeddict', 'fastcore/xtras.py'),
'fastcore.xtras.join_path_file': ('xtras.html#join_path_file', 'fastcore/xtras.py'),
'fastcore.xtras.load_pickle': ('xtras.html#load_pickle', 'fastcore/xtras.py'),
'fastcore.xtras.loads': ('xtras.html#loads', 'fastcore/xtras.py'),
Expand All @@ -656,7 +665,6 @@
'fastcore.xtras.make_nullable': ('xtras.html#make_nullable', 'fastcore/xtras.py'),
'fastcore.xtras.mapped': ('xtras.html#mapped', 'fastcore/xtras.py'),
'fastcore.xtras.maybe_open': ('xtras.html#maybe_open', 'fastcore/xtras.py'),
'fastcore.xtras.mk_dataclass': ('xtras.html#mk_dataclass', 'fastcore/xtras.py'),
'fastcore.xtras.mkdir': ('xtras.html#mkdir', 'fastcore/xtras.py'),
'fastcore.xtras.modified_env': ('xtras.html#modified_env', 'fastcore/xtras.py'),
'fastcore.xtras.modify_exception': ('xtras.html#modify_exception', 'fastcore/xtras.py'),
Expand Down
13 changes: 11 additions & 2 deletions fastcore/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,20 @@ def valmap(o):
if isinstance(o, dict): return '; '.join(f"{k}:{v}" for k,v in o.items()) if o else None
return o

# %% ../nbs/11_xml.ipynb
def _flatten_tuple(tup):
if not any(isinstance(item, tuple) for item in tup): return tup
result = []
for item in tup:
if isinstance(item, tuple): result.extend(item)
else: result.append(item)
return tuple(result)

# %% ../nbs/11_xml.ipynb
def _preproc(c, kw, attrmap=attrmap, valmap=valmap):
if len(c)==1 and isinstance(c[0], (types.GeneratorType, map, filter)): c = tuple(c[0])
attrs = {attrmap(k.lower()):valmap(v) for k,v in kw.items()}
return c,filter_values(attrs, noop)
attrs = {attrmap(k.lower()):valmap(v) for k,v in kw.items() if v is not None}
return _flatten_tuple(c),attrs

# %% ../nbs/11_xml.ipynb
def ft(tag:str, *c, void_:bool=False, attrmap:callable=attrmap, valmap:callable=valmap, **kw):
Expand Down
61 changes: 46 additions & 15 deletions fastcore/xtras.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@
from __future__ import annotations

# %% auto 0
__all__ = ['spark_chars', 'walk', 'globtastic', 'maybe_open', 'mkdir', 'image_size', 'bunzip', 'loads', 'loads_multi', 'dumps',
'untar_dir', 'repo_details', 'run', 'open_file', 'save_pickle', 'load_pickle', 'parse_env',
__all__ = ['spark_chars', 'UNSET', 'walk', 'globtastic', 'maybe_open', 'mkdir', 'image_size', 'bunzip', 'loads', 'loads_multi',
'dumps', 'untar_dir', 'repo_details', 'run', 'open_file', 'save_pickle', 'load_pickle', 'parse_env',
'expand_wildcards', 'dict2obj', 'obj2dict', 'repr_dict', 'is_listy', 'mapped', 'IterLen',
'ReindexCollection', 'get_source_link', 'truncstr', 'sparkline', 'modify_exception', 'round_multiple',
'set_num_threads', 'join_path_file', 'autostart', 'EventTimer', 'stringfmt_names', 'PartialFormatter',
'partial_format', 'utc2local', 'local2utc', 'trace', 'modified_env', 'ContextManagers', 'shufflish',
'console_help', 'hl_md', 'type2str', 'dataclass_src', 'nullable_dc', 'make_nullable', 'mk_dataclass',
'flexicache', 'time_policy', 'mtime_policy', 'timed_cache']
'console_help', 'hl_md', 'type2str', 'dataclass_src', 'Unset', 'nullable_dc', 'make_nullable', 'flexiclass',
'asdict', 'is_typeddict', 'is_namedtuple', 'flexicache', 'time_policy', 'mtime_policy', 'timed_cache']

# %% ../nbs/03_xtras.ipynb
from .imports import *
from .foundation import *
from .basics import *
from importlib import import_module
from functools import wraps
import string,time
import string,time,dataclasses
from enum import Enum
from contextlib import contextmanager,ExitStack
from datetime import datetime, timezone
from time import sleep,time,perf_counter
from os.path import getmtime
from dataclasses import dataclass, field, fields, is_dataclass, MISSING, make_dataclass

# %% ../nbs/03_xtras.ipynb
def walk(
Expand Down Expand Up @@ -639,24 +641,29 @@ def type2str(typ:type)->str:

# %% ../nbs/03_xtras.ipynb
def dataclass_src(cls):
import dataclasses
src = f"@dataclass\nclass {cls.__name__}:\n"
for f in dataclasses.fields(cls):
d = "" if f.default is dataclasses.MISSING else f" = {f.default!r}"
src += f" {f.name}: {type2str(f.type)}{d}\n"
return src

# %% ../nbs/03_xtras.ipynb
class Unset(Enum):
_Unset=''
def __repr__(self): return 'UNSET'
def __str__ (self): return 'UNSET'
def __bool__(self): return False
UNSET = Unset._Unset

# %% ../nbs/03_xtras.ipynb
def nullable_dc(cls):
"Like `dataclass`, but default of `None` added to fields without defaults"
from dataclasses import dataclass, field
"Like `dataclass`, but default of `UNSET` added to fields without defaults"
for k,v in get_annotations_ex(cls)[0].items():
if not hasattr(cls,k): setattr(cls, k, field(default=None))
if not hasattr(cls,k): setattr(cls, k, field(default=UNSET))
return dataclass(cls)

# %% ../nbs/03_xtras.ipynb
def make_nullable(clas):
from dataclasses import dataclass, fields, MISSING
if hasattr(clas, '_nullable'): return
clas._nullable = True

Expand All @@ -667,7 +674,7 @@ def __init__(self, *args, **kwargs):
for f in flds:
nm = f.name
if nm not in dargs and nm not in kwargs and f.default is None and f.default_factory is MISSING:
kwargs[nm] = None
kwargs[nm] = UNSET
original_init(self, *args, **kwargs)

clas.__init__ = __init__
Expand All @@ -678,13 +685,37 @@ def __init__(self, *args, **kwargs):
return clas

# %% ../nbs/03_xtras.ipynb
def mk_dataclass(cls):
from dataclasses import dataclass, field, is_dataclass, MISSING
def flexiclass(cls):
"Convert `cls` into a `dataclass` like `make_nullable`"
if is_dataclass(cls): return make_nullable(cls)
for k,v in get_annotations_ex(cls)[0].items():
if not hasattr(cls,k) or getattr(cls,k) is MISSING:
setattr(cls, k, field(default=None))
dataclass(cls, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
setattr(cls, k, field(default=UNSET))
return dataclass(cls, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

# %% ../nbs/03_xtras.ipynb
def asdict(o)->dict:
"Convert `o` to a `dict`, supporting dataclasses, namedtuples, iterables, and `__dict__` attrs."
if isinstance(o, dict): return o
if is_dataclass(o): r = dataclasses.asdict(o)
elif hasattr(o, '_asdict'): r = o._asdict()
elif hasattr(o, '__iter__'):
try: r = dict(o)
except TypeError: pass
elif hasattr(o, '__dict__'): r = o.__dict__
else: raise TypeError(f'Can not convert {o} to a dict')
return {k:v for k,v in r.items() if v not in (UNSET,MISSING)}

# %% ../nbs/03_xtras.ipynb
def is_typeddict(cls:type)->bool:
"Check if `cls` is a `TypedDict`"
attrs = 'annotations', 'required_keys', 'optional_keys'
return isinstance(cls, type) and all(hasattr(cls, f'__{attr}__') for attr in attrs)

# %% ../nbs/03_xtras.ipynb
def is_namedtuple(cls):
"`True` if `cls` is a namedtuple type"
return issubclass(cls, tuple) and hasattr(cls, '_fields')

# %% ../nbs/03_xtras.ipynb
def flexicache(*funcs, maxsize=128):
Expand Down
Loading

0 comments on commit 0951105

Please sign in to comment.