From bbadd55413f8655c6fa1a232b711e3ab90a829ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 02:56:46 +0000 Subject: [PATCH] deploy: 7ff70e02160434f9ef7c16375c7bd30c5cec3bc5 --- .nojekyll | 0 .../figure-commonmark/cell-35-output-1.png | Bin 0 -> 33507 bytes .../figure-commonmark/cell-36-output-1.png | Bin 0 -> 2837 bytes .../figure-commonmark/cell-38-output-1.png | Bin 0 -> 49078 bytes .../figure-html/cell-35-output-1.png | Bin 0 -> 33507 bytes .../figure-html/cell-36-output-1.png | Bin 0 -> 2837 bytes .../figure-html/cell-38-output-1.png | Bin 0 -> 49078 bytes .../figure-commonmark/cell-73-output-1.png | Bin 0 -> 137479 bytes .../figure-html/cell-73-output-1.png | Bin 0 -> 137479 bytes CNAME | 1 + apilist.txt | 1356 ++ basics.html | 3644 +++++ basics.html.md | 3622 +++++ dispatch.html | 1283 ++ dispatch.html.md | 722 + docments.html | 1104 ++ docments.html.md | 450 + foundation.html | 1565 ++ foundation.html.md | 1081 ++ images/att_00000.png | Bin 0 -> 35850 bytes images/att_00005.png | Bin 0 -> 8156 bytes images/att_00006.png | Bin 0 -> 8133 bytes images/att_00007.png | Bin 0 -> 9873 bytes index.html | 729 + index.html.md | 54 + llms-ctx-full.txt | 12213 ++++++++++++++++ llms-ctx.txt | 2418 +++ llms.txt | 43 + meta.html | 1309 ++ meta.html.md | 814 + net.html | 1059 ++ net.html.md | 390 + parallel.html | 1014 ++ parallel.html.md | 321 + py2pyi.html | 1236 ++ py2pyi.html.md | 545 + robots.txt | 1 + script.html | 1016 ++ script.html.md | 431 + search.json | 672 + site_libs/bootstrap/bootstrap-icons.css | 2078 +++ site_libs/bootstrap/bootstrap-icons.woff | Bin 0 -> 176200 bytes site_libs/bootstrap/bootstrap.min.css | 12 + site_libs/bootstrap/bootstrap.min.js | 7 + site_libs/clipboard/clipboard.min.js | 7 + site_libs/quarto-html/anchor.min.js | 9 + site_libs/quarto-html/popper.min.js | 6 + .../quarto-syntax-highlighting.css | 205 + site_libs/quarto-html/quarto.js | 908 ++ site_libs/quarto-html/tippy.css | 1 + site_libs/quarto-html/tippy.umd.min.js | 2 + site_libs/quarto-nav/headroom.min.js | 7 + site_libs/quarto-nav/quarto-nav.js | 325 + site_libs/quarto-search/autocomplete.umd.js | 3 + site_libs/quarto-search/fuse.min.js | 9 + site_libs/quarto-search/quarto-search.js | 1290 ++ sitemap.xml | 71 + style.html | 893 ++ style.html.md | 176 + styles.css | 18 + test.html | 1079 ++ test.html.md | 394 + tour.html | 953 ++ tour.html.md | 417 + transform.html | 1450 ++ transform.html.md | 1009 ++ xdg.html | 867 ++ xdg.html.md | 180 + xml.html | 986 ++ xml.html.md | 280 + xtras.html | 2293 +++ xtras.html.md | 1854 +++ 72 files changed, 56882 insertions(+) create mode 100644 .nojekyll create mode 100644 00_test_files/figure-commonmark/cell-35-output-1.png create mode 100644 00_test_files/figure-commonmark/cell-36-output-1.png create mode 100644 00_test_files/figure-commonmark/cell-38-output-1.png create mode 100644 00_test_files/figure-html/cell-35-output-1.png create mode 100644 00_test_files/figure-html/cell-36-output-1.png create mode 100644 00_test_files/figure-html/cell-38-output-1.png create mode 100644 05_transform_files/figure-commonmark/cell-73-output-1.png create mode 100644 05_transform_files/figure-html/cell-73-output-1.png create mode 100644 CNAME create mode 100644 apilist.txt create mode 100644 basics.html create mode 100644 basics.html.md create mode 100644 dispatch.html create mode 100644 dispatch.html.md create mode 100644 docments.html create mode 100644 docments.html.md create mode 100644 foundation.html create mode 100644 foundation.html.md create mode 100644 images/att_00000.png create mode 100644 images/att_00005.png create mode 100644 images/att_00006.png create mode 100644 images/att_00007.png create mode 100644 index.html create mode 100644 index.html.md create mode 100644 llms-ctx-full.txt create mode 100644 llms-ctx.txt create mode 100644 llms.txt create mode 100644 meta.html create mode 100644 meta.html.md create mode 100644 net.html create mode 100644 net.html.md create mode 100644 parallel.html create mode 100644 parallel.html.md create mode 100644 py2pyi.html create mode 100644 py2pyi.html.md create mode 100644 robots.txt create mode 100644 script.html create mode 100644 script.html.md create mode 100644 search.json create mode 100644 site_libs/bootstrap/bootstrap-icons.css create mode 100644 site_libs/bootstrap/bootstrap-icons.woff create mode 100644 site_libs/bootstrap/bootstrap.min.css create mode 100644 site_libs/bootstrap/bootstrap.min.js create mode 100644 site_libs/clipboard/clipboard.min.js create mode 100644 site_libs/quarto-html/anchor.min.js create mode 100644 site_libs/quarto-html/popper.min.js create mode 100644 site_libs/quarto-html/quarto-syntax-highlighting.css create mode 100644 site_libs/quarto-html/quarto.js create mode 100644 site_libs/quarto-html/tippy.css create mode 100644 site_libs/quarto-html/tippy.umd.min.js create mode 100644 site_libs/quarto-nav/headroom.min.js create mode 100644 site_libs/quarto-nav/quarto-nav.js create mode 100644 site_libs/quarto-search/autocomplete.umd.js create mode 100644 site_libs/quarto-search/fuse.min.js create mode 100644 site_libs/quarto-search/quarto-search.js create mode 100644 sitemap.xml create mode 100644 style.html create mode 100644 style.html.md create mode 100644 styles.css create mode 100644 test.html create mode 100644 test.html.md create mode 100644 tour.html create mode 100644 tour.html.md create mode 100644 transform.html create mode 100644 transform.html.md create mode 100644 xdg.html create mode 100644 xdg.html.md create mode 100644 xml.html create mode 100644 xml.html.md create mode 100644 xtras.html create mode 100644 xtras.html.md diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/00_test_files/figure-commonmark/cell-35-output-1.png b/00_test_files/figure-commonmark/cell-35-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7b80ee81fc4070bf694ffc1a480f9ebf924c184b GIT binary patch literal 33507 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 Eg4Agq?f?J) literal 0 HcmV?d00001 diff --git a/00_test_files/figure-commonmark/cell-36-output-1.png b/00_test_files/figure-commonmark/cell-36-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..4ddbcd3eb80b6feb5ed8858efb02a38dbcc4aad2 GIT binary patch literal 2837 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 literal 0 HcmV?d00001 diff --git a/00_test_files/figure-commonmark/cell-38-output-1.png b/00_test_files/figure-commonmark/cell-38-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b8e59c91c32d22ab32dd470ebca63f98c6bb8726 GIT binary patch literal 49078 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 Eg4Agq?f?J) literal 0 HcmV?d00001 diff --git a/00_test_files/figure-html/cell-36-output-1.png b/00_test_files/figure-html/cell-36-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..4ddbcd3eb80b6feb5ed8858efb02a38dbcc4aad2 GIT binary patch literal 2837 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 literal 0 HcmV?d00001 diff --git a/00_test_files/figure-html/cell-38-output-1.png b/00_test_files/figure-html/cell-38-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b8e59c91c32d22ab32dd470ebca63f98c6bb8726 GIT binary patch literal 49078 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 TI7S0T$TO{>`b;&ZgD`{+9*Gb3 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 LNkvXXu0mjfWz?cc literal 0 HcmV?d00001 diff --git a/05_transform_files/figure-html/cell-73-output-1.png b/05_transform_files/figure-html/cell-73-output-1.png new file mode 100644 index 0000000000000000000000000000000000000000..45f2e4cfb25b70b5cecb2c18604a1e594d6e3137 GIT binary patch literal 137479 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 TI7S0T$TO{>`b;&ZgD`{+9*Gb3 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 LNkvXXu0mjfWz?cc literal 0 HcmV?d00001 diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..d9bb41c6 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +fastcore.fast.ai diff --git a/apilist.txt b/apilist.txt new file mode 100644 index 00000000..d56559ee --- /dev/null +++ b/apilist.txt @@ -0,0 +1,1356 @@ +# fastcore Module Documentation + +## fastcore.basics + +> Basic functionality used in the fastai library + +- `def ifnone(a, b)` + `b` if `a` is None else `a` + +- `def maybe_attr(o, attr)` + `getattr(o,attr,o)` + +- `def basic_repr(flds)` + Minimal `__repr__` + +- `class BasicRepr` + Base class for objects needing a basic `__repr__` + + +- `def is_array(x)` + `True` if `x` supports `__array__` or `iloc` + +- `def listify(o, *rest)` + Convert `o` to a `list` + +- `def tuplify(o, use_list, match)` + Make `o` a tuple + +- `def true(x)` + Test whether `x` is truthy; collections with >0 elements are considered `True` + +- `class NullType` + An object that is `False` and can be called, chained, and indexed + + - `def __getattr__(self, *args)` + - `def __call__(self, *args, **kwargs)` + - `def __getitem__(self, *args)` + - `def __bool__(self)` + +- `def tonull(x)` + Convert `None` to `null` + +- `def get_class(nm, *fld_names, **flds)` + Dynamically create a class, optionally inheriting from `sup`, containing `fld_names` + +- `def mk_class(nm, *fld_names, **flds)` + Create a class using `get_class` and add to the caller's module + +- `def wrap_class(nm, *fld_names, **flds)` + Decorator: makes function a method of a new class `nm` passing parameters to `mk_class` + +- `class ignore_exceptions` + Context manager to ignore exceptions + + - `def __enter__(self)` + - `def __exit__(self, *args)` + +- `def exec_local(code, var_name)` + Call `exec` on `code` and return the var `var_name` + +- `def risinstance(types, obj)` + Curried `isinstance` but with args reversed + +- `class Inf` + Infinite lists + + +- `def in_(x, a)` + `True` if `x in a` + +- `def ret_true(*args, **kwargs)` + Predicate: always `True` + +- `def ret_false(*args, **kwargs)` + Predicate: always `False` + +- `def stop(e)` + Raises exception `e` (by default `StopIteration`) + +- `def gen(func, seq, cond)` + Like `(func(o) for o in seq if cond(func(o)))` but handles `StopIteration` + +- `def chunked(it, chunk_sz, drop_last, n_chunks)` + Return batches from iterator `it` of size `chunk_sz` (or return `n_chunks` total) + +- `def otherwise(x, tst, y)` + `y if tst(x) else x` + +- `def custom_dir(c, add)` + Implement custom `__dir__`, adding `add` to `cls` + +- `class AttrDict` + `dict` subclass that also provides access to keys as attrs + + - `def __getattr__(self, k)` + - `def __setattr__(self, k, v)` + - `def __dir__(self)` + - `def copy(self)` + +- `class AttrDictDefault` + `AttrDict` subclass that returns `None` for missing attrs + + - `def __init__(self, *args, **kwargs)` + - `def __getattr__(self, k)` + +- `class NS` + `SimpleNamespace` subclass that also adds `iter` and `dict` support + + - `def __iter__(self)` + - `def __getitem__(self, x)` + - `def __setitem__(self, x, y)` + +- `def get_annotations_ex(obj)` + Backport of py3.10 `get_annotations` that returns globals/locals + +- `def eval_type(t, glb, loc)` + `eval` a type or collection of types, if needed, for annotations in py3.10+ + +- `def type_hints(f)` + Like `typing.get_type_hints` but returns `{}` if not allowed type + +- `def annotations(o)` + Annotations for `o`, or `type(o)` + +- `def anno_ret(func)` + Get the return annotation of `func` + +- `def signature_ex(obj, eval_str)` + Backport of `inspect.signature(..., eval_str=True` to `True`) + +- `def str2int(s)` + Convert `s` to an `int` + +- `def str2float(s)` + Convert `s` to a float + +- `def str2list(s)` + Convert `s` to a list + +- `def str2date(s)` + `date.fromisoformat` with empty string handling + +- `def typed(_func)` + Decorator to check param and return types at runtime, with optional casting + +- `def exec_new(code)` + Execute `code` in a new environment and return it + +- `def exec_import(mod, sym)` + Import `sym` from `mod` in a new environment + +## fastcore.dispatch + +> Basic single and dual parameter dispatch + +- `def lenient_issubclass(cls, types)` + If possible return whether `cls` is a subclass of `types`, otherwise return False. + +- `def sorted_topologically(iterable)` + Return a new list containing all items from the iterable sorted topologically + +- `class TypeDispatch` + Dictionary-like object; `__getitem__` matches keys of types using `issubclass` + + - `def __init__(self, funcs, bases)` + - `def add(self, f)` + Add type `t` and function `f` + + - `def first(self)` + Get first function in ordered dict of type:func. + + - `def returns(self, x)` + Get the return type of annotation of `x`. + + - `def __repr__(self)` + - `def __call__(self, *args, **kwargs)` + - `def __get__(self, inst, owner)` + - `def __getitem__(self, k)` + Find first matching type that is a super-class of `k` + + +- `class DispatchReg` + A global registry for `TypeDispatch` objects keyed by function name + + - `def __init__(self)` + - `def __call__(self, f)` + +- `def retain_meta(x, res, as_copy)` + Call `res.set_meta(x)`, if it exists + +- `def default_set_meta(self, x, as_copy)` + Copy over `_meta` from `x` to `res`, if it's missing + +- `@typedispatch def cast(x, typ)` + cast `x` to type `typ` (may also change `x` inplace) + +- `def retain_type(new, old, typ, as_copy)` + Cast `new` to type of `old` or `typ` if it's a superclass + +- `def retain_types(new, old, typs)` + Cast each item of `new` to type of matching item in `old` if it's a superclass + +- `def explode_types(o)` + Return the type of `o`, potentially in nested dictionaries for thing that are listy + +## fastcore.docments + +> Document parameters using comments. + +- `def docstring(sym)` + Get docstring for `sym` for functions ad classes + +- `def parse_docstring(sym)` + Parse a numpy-style docstring in `sym` + +- `def isdataclass(s)` + Check if `s` is a dataclass but not a dataclass' instance + +- `def get_dataclass_source(s)` + Get source code for dataclass `s` + +- `def get_source(s)` + Get source code for string, function object or dataclass `s` + +- `def get_name(obj)` + Get the name of `obj` + +- `def qual_name(obj)` + Get the qualified name of `obj` + +- `@delegates(_docments) def docments(elt, full, **kwargs)` + Generates a `docment` + +- `def extract_docstrings(code)` + Create a dict from function/class/method names to tuples of docstrings and param lists + +## fastcore.docscrape + +> Parse numpy-style docstrings + +- `def strip_blank_lines(l)` + Remove leading and trailing blank lines from a list of lines + +- `class Reader` + A line-based string reader. + + - `def __init__(self, data)` + - `def __getitem__(self, n)` + - `def reset(self)` + - `def read(self)` + - `def seek_next_non_empty_line(self)` + - `def eof(self)` + - `def read_to_condition(self, condition_func)` + - `def read_to_next_empty_line(self)` + - `def read_to_next_unindented_line(self)` + - `def peek(self, n)` + - `def is_empty(self)` + +- `class ParseError` + - `def __str__(self)` + +- `class NumpyDocString` + Parses a numpydoc string to an abstract representation + + - `def __init__(self, docstring, config)` + - `def __iter__(self)` + - `def __len__(self)` + - `def __getitem__(self, key)` + - `def __setitem__(self, key, val)` + +- `def dedent_lines(lines, split)` + Deindent a list of lines maximally + +## fastcore.foundation + +> The `L` class and helpers for it + +- `@contextmanager def working_directory(path)` + Change working directory to `path` and return to previous on exit. + +- `def add_docs(cls, cls_doc, **docs)` + Copy values from `docs` to `cls` docstrings, and confirm all public methods are documented + +- `def docs(cls)` + Decorator version of `add_docs`, using `_docs` dict + +- `def coll_repr(c, max_n)` + String repr of up to `max_n` items of (possibly lazy) collection `c` + +- `def is_bool(x)` + Check whether `x` is a bool or None + +- `def mask2idxs(mask)` + Convert bool mask or index list to index `L` + +- `def is_indexer(idx)` + Test whether `idx` will index a single item in a list + +- `class CollBase` + Base class for composing a list of `items` + + - `def __init__(self, items)` + - `def __len__(self)` + - `def __getitem__(self, k)` + - `def __setitem__(self, k, v)` + - `def __delitem__(self, i)` + - `def __repr__(self)` + - `def __iter__(self)` + +- `class L` + Behaves like a list of `items` but can also index with list of indices or masks + + - `def __init__(self, items, *rest)` + - `def __getitem__(self, idx)` + - `def copy(self)` + - `def __setitem__(self, idx, o)` + Set `idx` (can be list of indices, or mask, or int) items to `o` (which is broadcast if not iterable) + + - `def __eq__(self, b)` + - `def sorted(self, key, reverse)` + - `def __iter__(self)` + - `def __contains__(self, b)` + - `def __reversed__(self)` + - `def __invert__(self)` + - `def __repr__(self)` + - `def __mul__(a, b)` + - `def __add__(a, b)` + - `def __radd__(a, b)` + - `def __addi__(a, b)` + - `@classmethod def split(cls, s, sep, maxsplit)` + - `@classmethod def range(cls, a, b, step)` + - `def map(self, f, *args, **kwargs)` + - `def argwhere(self, f, negate, **kwargs)` + - `def argfirst(self, f, negate)` + - `def filter(self, f, negate, **kwargs)` + - `def enumerate(self)` + - `def renumerate(self)` + - `def unique(self, sort, bidir, start)` + - `def val2idx(self)` + - `def cycle(self)` + - `def map_dict(self, f, *args, **kwargs)` + - `def map_first(self, f, g, *args, **kwargs)` + - `def itemgot(self, *idxs)` + - `def attrgot(self, k, default)` + - `def starmap(self, f, *args, **kwargs)` + - `def zip(self, cycled)` + - `def zipwith(self, *rest)` + - `def map_zip(self, f, *args, **kwargs)` + - `def map_zipwith(self, f, *rest, **kwargs)` + - `def shuffle(self)` + - `def concat(self)` + - `def reduce(self, f, initial)` + - `def sum(self)` + - `def product(self)` + - `def setattrs(self, attr, val)` + +- `def save_config_file(file, d, **kwargs)` + Write settings dict to a new config file, or overwrite the existing one. + +- `class Config` + Reading and writing `ConfigParser` ini files + + - `def __init__(self, cfg_path, cfg_name, create, save, extra_files, types)` + - `def __repr__(self)` + - `def __setitem__(self, k, v)` + - `def __contains__(self, k)` + - `def save(self)` + - `def __getattr__(self, k)` + - `def __getitem__(self, k)` + - `def get(self, k, default)` + - `def path(self, k, default)` + - `@classmethod def find(cls, cfg_name, cfg_path, **kwargs)` + Search `cfg_path` and its parents to find `cfg_name` + + +## fastcore.imghdr + +> Recognize image file formats based on their first few bytes. + +- `def test_jpeg(h, f)` + JPEG data with JFIF or Exif markers; and raw JPEG + +- `def test_gif(h, f)` + GIF ('87 and '89 variants) + +- `def test_tiff(h, f)` + TIFF (can be in Motorola or Intel byte order) + +- `def test_rgb(h, f)` + SGI image library + +- `def test_pbm(h, f)` + PBM (portable bitmap) + +- `def test_pgm(h, f)` + PGM (portable graymap) + +- `def test_ppm(h, f)` + PPM (portable pixmap) + +- `def test_rast(h, f)` + Sun raster file + +- `def test_xbm(h, f)` + X bitmap (X10 or X11) + +## fastcore.imports + +- `def is_iter(o)` + Test whether `o` can be used in a `for` loop + +- `def is_coll(o)` + Test whether `o` is a collection (i.e. has a usable `len`) + +- `def all_equal(a, b)` + Compares whether `a` and `b` are the same length and have the same contents + +- `def noop(x, *args, **kwargs)` + Do nothing + +- `def noops(self, x, *args, **kwargs)` + Do nothing (method) + +- `def isinstance_str(x, cls_name)` + Like `isinstance`, except takes a type name instead of a type + +- `def equals(a, b)` + Compares `a` and `b` for equality; supports sublists, tensors and arrays too + +- `def ipython_shell()` + Same as `get_ipython` but returns `False` if not in IPython + +- `def in_ipython()` + Check if code is running in some kind of IPython environment + +- `def in_colab()` + Check if the code is running in Google Colaboratory + +- `def in_jupyter()` + Check if the code is running in a jupyter notebook + +- `def in_notebook()` + Check if the code is running in a jupyter notebook + +- `def remove_prefix(text, prefix)` + Temporary until py39 is a prereq + +- `def remove_suffix(text, suffix)` + Temporary until py39 is a prereq + +## fastcore.meta + +> Metaclasses + +- `def test_sig(f, b)` + Test the signature of an object + +- `class FixSigMeta` + A metaclass that fixes the signature on classes that override `__new__` + + - `def __new__(cls, name, bases, dict)` + +- `class PrePostInitMeta` + A metaclass that calls optional `__pre_init__` and `__post_init__` methods + + - `def __call__(cls, *args, **kwargs)` + +- `class AutoInit` + Same as `object`, but no need for subclasses to call `super().__init__` + + - `def __pre_init__(self, *args, **kwargs)` + +- `class NewChkMeta` + Metaclass to avoid recreating object passed to constructor + + - `def __call__(cls, x, *args, **kwargs)` + +- `class BypassNewMeta` + Metaclass: casts `x` to this class if it's of type `cls._bypass_type` + + - `def __call__(cls, x, *args, **kwargs)` + +- `def empty2none(p)` + Replace `Parameter.empty` with `None` + +- `def anno_dict(f)` + `__annotation__ dictionary with `empty` cast to `None`, returning empty if doesn't exist + +- `def use_kwargs_dict(keep, **kwargs)` + Decorator: replace `**kwargs` in signature with `names` params + +- `def use_kwargs(names, keep)` + Decorator: replace `**kwargs` in signature with `names` params + +- `def delegates(to, keep, but)` + Decorator: replace `**kwargs` in signature with params from `to` + +- `def method(f)` + Mark `f` as a method + +- `def funcs_kwargs(as_method)` + Replace methods in `cls._methods` with those from `kwargs` + +## fastcore.net + +> Network, HTTP, and URL functions + +- `def urlquote(url)` + Update url's path with `urllib.parse.quote` + +- `def urlwrap(url, data, headers)` + Wrap `url` in a urllib `Request` with `urlquote` + +- `class HTTP4xxClientError` + Base class for client exceptions (code 4xx) from `url*` functions + + +- `class HTTP5xxServerError` + Base class for server exceptions (code 5xx) from `url*` functions + + +- `def urlopen(url, data, headers, timeout, **kwargs)` + Like `urllib.request.urlopen`, but first `urlwrap` the `url`, and encode `data` + +- `def urlread(url, data, headers, decode, return_json, return_headers, timeout, **kwargs)` + Retrieve `url`, using `data` dict or `kwargs` to `POST` if present + +- `def urljson(url, data, timeout)` + Retrieve `url` and decode json + +- `def urlclean(url)` + Remove fragment, params, and querystring from `url` if present + +- `def urlsave(url, dest, reporthook, headers, timeout)` + Retrieve `url` and save based on its name + +- `def urlvalid(x)` + Test if `x` is a valid URL + +- `def urlrequest(url, verb, headers, route, query, data, json_data)` + `Request` for `url` with optional route params replaced by `route`, plus `query` string, and post `data` + +- `@patch def summary(self, skip)` + Summary containing full_url, headers, method, and data, removing `skip` from headers + +- `def urlsend(url, verb, headers, decode, route, query, data, json_data, return_json, return_headers, debug, timeout)` + Send request with `urlrequest`, converting result to json if `return_json` + +- `def do_request(url, post, headers, **data)` + Call GET or json-encoded POST on `url`, depending on `post` + +- `def start_server(port, host, dgram, reuse_addr, n_queue)` + Create a `socket` server on `port`, with optional `host`, of type `dgram` + +- `def start_client(port, host, dgram)` + Create a `socket` client on `port`, with optional `host`, of type `dgram` + +- `def tobytes(s)` + Convert `s` into HTTP-ready bytes format + +- `def http_response(body, status, hdrs, **kwargs)` + Create an HTTP-ready response, adding `kwargs` to `hdrs` + +- `@threaded def recv_once(host, port)` + Spawn a thread to receive a single HTTP request and store in `d['r']` + +## fastcore.parallel + +> Threading and multiprocessing functions + +- `def threaded(process)` + Run `f` in a `Thread` (or `Process` if `process=True`), and returns it + +- `def startthread(f)` + Like `threaded`, but start thread immediately + +- `def startproc(f)` + Like `threaded(True)`, but start Process immediately + +- `class ThreadPoolExecutor` + Same as Python's ThreadPoolExecutor, except can pass `max_workers==0` for serial execution + + - `def __init__(self, max_workers, on_exc, pause, **kwargs)` + - `def map(self, f, items, *args, **kwargs)` + +- `@delegates() class ProcessPoolExecutor` + Same as Python's ProcessPoolExecutor, except can pass `max_workers==0` for serial execution + + - `def __init__(self, max_workers, on_exc, pause, **kwargs)` + - `def map(self, f, items, *args, **kwargs)` + +- `def parallel(f, items, *args, **kwargs)` + Applies `func` in parallel to `items`, using `n_workers` + +- `def parallel_async(f, items, *args, **kwargs)` + Applies `f` to `items` in parallel using asyncio and a semaphore to limit concurrency. + +- `def run_procs(f, f_done, args)` + Call `f` for each item in `args` in parallel, yielding `f_done` + +- `def parallel_gen(cls, items, n_workers, **kwargs)` + Instantiate `cls` in `n_workers` procs & call each on a subset of `items` in parallel. + +## fastcore.py2pyi + +- `def imp_mod(module_path, package)` + Import dynamically the module referenced in `fn` + +- `def has_deco(node, name)` + Check if a function node `node` has a decorator named `name` + +- `def create_pyi(fn, package)` + Convert `fname.py` to `fname.pyi` by removing function bodies and expanding `delegates` kwargs + +- `@call_parse def py2pyi(fname, package)` + Convert `fname.py` to `fname.pyi` by removing function bodies and expanding `delegates` kwargs + +- `@call_parse def replace_wildcards(path)` + Expand wildcard imports in the specified Python file. + +## fastcore.script + +> A fast way to turn your python function into a script. + +- `def store_true()` + Placeholder to pass to `Param` for `store_true` action + +- `def store_false()` + Placeholder to pass to `Param` for `store_false` action + +- `def bool_arg(v)` + Use as `type` for `Param` to get `bool` behavior + +- `class Param` + A parameter in a function used in `anno_parser` or `call_parse` + + - `def __init__(self, help, type, opt, action, nargs, const, choices, required, default)` + - `def set_default(self, d)` + - `@property def pre(self)` + - `@property def kwargs(self)` + - `def __repr__(self)` + +- `def anno_parser(func, prog)` + Look at params (annotated with `Param`) in func and return an `ArgumentParser` + +- `def args_from_prog(func, prog)` + Extract args from `prog` + +- `def call_parse(func, nested)` + Decorator to create a simple CLI from `func` using `anno_parser` + +## fastcore.style + +> Fast styling for friendly CLIs. + +- `class StyleCode` + An escape sequence for styling terminal text. + + - `def __init__(self, name, code, typ)` + - `def __str__(self)` + +- `class Style` + A minimal terminal text styler. + + - `def __init__(self, codes)` + - `def __dir__(self)` + - `def __getattr__(self, k)` + - `def __call__(self, obj)` + - `def __repr__(self)` + +- `def demo()` + Demonstrate all available styles and their codes. + +## fastcore.test + +> Helper functions to quickly write tests in notebooks + +- `def test_fail(f, msg, contains, args, kwargs)` + Fails with `msg` unless `f()` raises an exception and (optionally) has `contains` in `e.args` + +- `def test(a, b, cmp, cname)` + `assert` that `cmp(a,b)`; display inputs and `cname or cmp.__name__` if it fails + +- `def nequals(a, b)` + Compares `a` and `b` for `not equals` + +- `def test_eq(a, b)` + `test` that `a==b` + +- `def test_eq_type(a, b)` + `test` that `a==b` and are same type + +- `def test_ne(a, b)` + `test` that `a!=b` + +- `def is_close(a, b, eps)` + Is `a` within `eps` of `b` + +- `def test_close(a, b, eps)` + `test` that `a` is within `eps` of `b` + +- `def test_is(a, b)` + `test` that `a is b` + +- `def test_shuffled(a, b)` + `test` that `a` and `b` are shuffled versions of the same sequence of items + +- `def test_stdout(f, exp, regex)` + Test that `f` prints `exp` to stdout, optionally checking as `regex` + +- `def test_fig_exists(ax)` + Test there is a figure displayed in `ax` + +- `class ExceptionExpected` + Context manager that tests if an exception is raised + + - `def __init__(self, ex, regex)` + - `def __enter__(self)` + - `def __exit__(self, type, value, traceback)` + +## fastcore.transform + +> Definition of `Transform` and `Pipeline` + +- `class Transform` + Delegates (`__call__`,`decode`,`setup`) to (encodes,decodes,setups) if `split_idx` matches + + - `def __init__(self, enc, dec, split_idx, order)` + - `@property def name(self)` + - `def __call__(self, x, **kwargs)` + - `def decode(self, x, **kwargs)` + - `def __repr__(self)` + - `def setup(self, items, train_setup)` + +- `class InplaceTransform` + A `Transform` that modifies in-place and just returns whatever it's passed + + +- `class DisplayedTransform` + A transform with a `__repr__` that shows its attrs + + - `@property def name(self)` + +- `class ItemTransform` + A transform that always take tuples as items + + - `def __call__(self, x, **kwargs)` + - `def decode(self, x, **kwargs)` + +- `def get_func(t, name, *args, **kwargs)` + Get the `t.name` (potentially partial-ized with `args` and `kwargs`) or `noop` if not defined + +- `class Func` + Basic wrapper around a `name` with `args` and `kwargs` to call on a given type + + - `def __init__(self, name, *args, **kwargs)` + - `def __repr__(self)` + - `def __call__(self, t)` + +- `def compose_tfms(x, tfms, is_enc, reverse, **kwargs)` + Apply all `func_nm` attribute of `tfms` on `x`, maybe in `reverse` order + +- `def mk_transform(f)` + Convert function `f` to `Transform` if it isn't already one + +- `def gather_attrs(o, k, nm)` + Used in __getattr__ to collect all attrs `k` from `self.{nm}` + +- `def gather_attr_names(o, nm)` + Used in __dir__ to collect all attrs `k` from `self.{nm}` + +- `class Pipeline` + A pipeline of composed (for encode/decode) transforms, setup with types + + - `def __init__(self, funcs, split_idx)` + - `def setup(self, items, train_setup)` + - `def add(self, ts, items, train_setup)` + - `def __call__(self, o)` + - `def __repr__(self)` + - `def __getitem__(self, i)` + - `def __setstate__(self, data)` + - `def __getattr__(self, k)` + - `def __dir__(self)` + - `def decode(self, o, full)` + - `def show(self, o, ctx, **kwargs)` + +## fastcore.xdg + +> XDG Base Directory Specification helpers. + +- `def xdg_cache_home()` + Path corresponding to `XDG_CACHE_HOME` + +- `def xdg_config_dirs()` + Paths corresponding to `XDG_CONFIG_DIRS` + +- `def xdg_config_home()` + Path corresponding to `XDG_CONFIG_HOME` + +- `def xdg_data_dirs()` + Paths corresponding to XDG_DATA_DIRS` + +- `def xdg_data_home()` + Path corresponding to `XDG_DATA_HOME` + +- `def xdg_runtime_dir()` + Path corresponding to `XDG_RUNTIME_DIR` + +- `def xdg_state_home()` + Path corresponding to `XDG_STATE_HOME` + +## fastcore.xml + +> Concise generation of XML. + +- `class FT` + A 'Fast Tag' structure, containing `tag`,`children`,and `attrs` + + - `def __init__(self, tag, cs, attrs, void_, **kwargs)` + - `def on(self, f)` + - `def changed(self)` + - `def __setattr__(self, k, v)` + - `def __getattr__(self, k)` + - `@property def list(self)` + - `def get(self, k, default)` + - `def __repr__(self)` + - `def __iter__(self)` + - `def __getitem__(self, idx)` + - `def __setitem__(self, i, o)` + - `def __call__(self, *c, **kw)` + - `def set(self, *c, **kw)` + Set children and/or attributes (chainable) + + +- `def ft(tag, *c, **kw)` + Create an `FT` structure for `to_xml()` + +- `def Html(*c, **kwargs)` + An HTML tag, optionally preceeded by `!DOCTYPE HTML` + +- `class Safe` + - `def __html__(self)` + +- `def to_xml(elm, lvl, indent, do_escape)` + Convert `ft` element tree into an XML string + +- `def highlight(s, lang)` + Markdown to syntax-highlight `s` in language `lang` + +## fastcore.xtras + +> Utility functions used in the fastai library + +- `def walk(path, symlinks, keep_file, keep_folder, skip_folder, func, ret_folders)` + Generator version of `os.walk`, using functions to filter files and folders + +- `def globtastic(path, recursive, symlinks, file_glob, file_re, folder_re, skip_file_glob, skip_file_re, skip_folder_re, func, ret_folders)` + A more powerful `glob`, including regex matches, symlink handling, and skip parameters + +- `@contextmanager def maybe_open(f, mode, **kwargs)` + Context manager: open `f` if it is a path (and close on exit) + +- `def mkdir(path, exist_ok, parents, overwrite, **kwargs)` + Creates and returns a directory defined by `path`, optionally removing previous existing directory if `overwrite` is `True` + +- `def image_size(fn)` + Tuple of (w,h) for png, gif, or jpg; `None` otherwise + +- `def bunzip(fn)` + bunzip `fn`, raising exception if output already exists + +- `def loads(s, **kw)` + Same as `json.loads`, but handles `None` + +- `def loads_multi(s)` + Generator of >=0 decoded json dicts, possibly with non-json ignored text at start and end + +- `def dumps(obj, **kw)` + Same as `json.dumps`, but uses `ujson` if available + +- `def untar_dir(fname, dest, rename, overwrite)` + untar `file` into `dest`, creating a directory if the root contains more than one item + +- `def repo_details(url)` + Tuple of `owner,name` from ssh or https git repo `url` + +- `def run(cmd, *rest)` + Pass `cmd` (splitting with `shlex` if string) to `subprocess.run`; return `stdout`; raise `IOError` if fails + +- `def open_file(fn, mode, **kwargs)` + Open a file, with optional compression if gz or bz2 suffix + +- `def save_pickle(fn, o)` + Save a pickle file, to a file name or opened file + +- `def load_pickle(fn)` + Load a pickle file from a file name or opened file + +- `def parse_env(s, fn)` + Parse a shell-style environment string or file + +- `def expand_wildcards(code)` + Expand all wildcard imports in the given code string. + +- `def dict2obj(d, list_func, dict_func)` + Convert (possibly nested) dicts (or lists of dicts) to `AttrDict` + +- `def obj2dict(d)` + Convert (possibly nested) AttrDicts (or lists of AttrDicts) to `dict` + +- `def repr_dict(d)` + Print nested dicts and lists, such as returned by `dict2obj` + +- `def is_listy(x)` + `isinstance(x, (tuple,list,L,slice,Generator))` + +- `def mapped(f, it)` + map `f` over `it`, unless it's not listy, in which case return `f(it)` + +- `@patch def readlines(self, hint, encoding)` + Read the content of `self` + +- `@patch def read_json(self, encoding, errors)` + Same as `read_text` followed by `loads` + +- `@patch def mk_write(self, data, encoding, errors, mode)` + Make all parent dirs of `self`, and write `data` + +- `@patch def relpath(self, start)` + Same as `os.path.relpath`, but returns a `Path`, and resolves symlinks + +- `@patch def ls(self, n_max, file_type, file_exts)` + Contents of path as a list + +- `@patch def delete(self)` + Delete a file, symlink, or directory tree + +- `class IterLen` + Base class to add iteration to anything supporting `__len__` and `__getitem__` + + - `def __iter__(self)` + +- `@docs class ReindexCollection` + Reindexes collection `coll` with indices `idxs` and optional LRU cache of size `cache` + + - `def __init__(self, coll, idxs, cache, tfm)` + - `def __getitem__(self, i)` + - `def __len__(self)` + - `def reindex(self, idxs)` + - `def shuffle(self)` + - `def cache_clear(self)` + - `def __getstate__(self)` + - `def __setstate__(self, s)` + +- `def get_source_link(func)` + Return link to `func` in source code + +- `def truncstr(s, maxlen, suf, space)` + Truncate `s` to length `maxlen`, adding suffix `suf` if truncated + +- `def sparkline(data, mn, mx, empty_zero)` + Sparkline for `data`, with `None`s (and zero, if `empty_zero`) shown as empty column + +- `def modify_exception(e, msg, replace)` + Modifies `e` with a custom message attached + +- `def round_multiple(x, mult, round_down)` + Round `x` to nearest multiple of `mult` + +- `def set_num_threads(nt)` + Get numpy (and others) to use `nt` threads + +- `def join_path_file(file, path, ext)` + Return `path/file` if file is a string or a `Path`, file otherwise + +- `def autostart(g)` + Decorator that automatically starts a generator + +- `class EventTimer` + An event timer with history of `store` items of time `span` + + - `def __init__(self, store, span)` + - `def add(self, n)` + Record `n` events + + - `@property def duration(self)` + - `@property def freq(self)` + +- `def stringfmt_names(s)` + Unique brace-delimited names in `s` + +- `class PartialFormatter` + A `string.Formatter` that doesn't error on missing fields, and tracks missing fields and unused args + + - `def __init__(self)` + - `def get_field(self, nm, args, kwargs)` + - `def check_unused_args(self, used, args, kwargs)` + +- `def partial_format(s, **kwargs)` + string format `s`, ignoring missing field errors, returning missing and extra fields + +- `def utc2local(dt)` + Convert `dt` from UTC to local time + +- `def local2utc(dt)` + Convert `dt` from local to UTC time + +- `def trace(f)` + Add `set_trace` to an existing function `f` + +- `@contextmanager def modified_env(*delete, **replace)` + Context manager temporarily modifying `os.environ` by deleting `delete` and replacing `replace` + +- `class ContextManagers` + Wrapper for `contextlib.ExitStack` which enters a collection of context managers + + - `def __init__(self, mgrs)` + - `def __enter__(self)` + - `def __exit__(self, *args, **kwargs)` + +- `def shufflish(x, pct)` + Randomly relocate items of `x` up to `pct` of `len(x)` from their starting location + +- `def console_help(libname)` + Show help for all console scripts from `libname` + +- `def hl_md(s, lang, show)` + Syntax highlight `s` using `lang`. + +- `def type2str(typ)` + Stringify `typ` + +- `class Unset` + - `def __repr__(self)` + - `def __str__(self)` + - `def __bool__(self)` + - `@property def name(self)` + +- `def nullable_dc(cls)` + Like `dataclass`, but default of `UNSET` added to fields without defaults + +- `def flexiclass(cls)` + Convert `cls` into a `dataclass` like `make_nullable`. Converts in place and also returns the result. + +- `def asdict(o)` + Convert `o` to a `dict`, supporting dataclasses, namedtuples, iterables, and `__dict__` attrs. + +- `def is_typeddict(cls)` + Check if `cls` is a `TypedDict` + +- `def is_namedtuple(cls)` + `True` if `cls` is a namedtuple type + +- `def flexicache(*funcs)` + Like `lru_cache`, but customisable with policy `funcs` + +- `def time_policy(seconds)` + A `flexicache` policy that expires cached items after `seconds` have passed + +- `def mtime_policy(filepath)` + A `flexicache` policy that expires cached items after `filepath` modified-time changes + +- `def timed_cache(seconds, maxsize)` + Like `lru_cache`, but also with time-based eviction + diff --git a/basics.html b/basics.html new file mode 100644 index 00000000..9370bc0c --- /dev/null +++ b/basics.html @@ -0,0 +1,3644 @@ + + + + + + + + + + +Basic functionality – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Basic functionality

+
+ +
+
+ Basic functionality used in the fastai library +
+
+ + +
+ + + + +
+ + + +
+ + + +
+

Basics

+
+

source

+
+

ifnone

+
+
 ifnone (a, b)
+
+

b if a is None else a

+

Since b if a is None else a is such a common pattern, we wrap it in a function. However, be careful, because python will evaluate both a and b when calling ifnone (which it doesn’t do if using the if version directly).

+
+
test_eq(ifnone(None,1), 1)
+test_eq(ifnone(2   ,1), 2)
+
+
+

source

+
+
+

maybe_attr

+
+
 maybe_attr (o, attr)
+
+

getattr(o,attr,o)

+

Return the attribute attr for object o. If the attribute doesn’t exist, then return the object o instead.

+
+
class myobj: myattr='foo'
+
+test_eq(maybe_attr(myobj, 'myattr'), 'foo')
+test_eq(maybe_attr(myobj, 'another_attr'), myobj)
+
+
+

source

+
+
+

basic_repr

+
+
 basic_repr (flds=None)
+
+

Minimal __repr__

+

In types which provide rich display functionality in Jupyter, their __repr__ is also called in order to provide a fallback text representation. Unfortunately, this includes a memory address which changes on every invocation, making it non-deterministic. This causes diffs to get messy and creates conflicts in git. To fix this, put __repr__=basic_repr() inside your class.

+
+
class SomeClass: __repr__=basic_repr()
+repr(SomeClass())
+
+
'SomeClass()'
+
+
+

If you pass a list of attributes (flds) of an object, then this will generate a string with the name of each attribute and its corresponding value. The format of this string is key=value, where key is the name of the attribute, and value is the value of the attribute. For each value, attempt to use the __name__ attribute, otherwise fall back to using the value’s __repr__ when constructing the string.

+
+
class SomeClass:
+    a=1
+    b='foo'
+    __repr__=basic_repr('a,b')
+    __name__='some-class'
+
+repr(SomeClass())
+
+
"SomeClass(a=1, b='foo')"
+
+
+

Nested objects work too:

+
+
class AnotherClass:
+    c=SomeClass()
+    d='bar'
+    __repr__=basic_repr(['c', 'd'])
+
+repr(AnotherClass())
+
+
"AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')"
+
+
+

Instance variables (but not class variables) are shown if basic_repr is called with no arguments:

+
+
class SomeClass:
+    def __init__(self, a=1, b='foo'): self.a,self.b = a,b
+    __repr__=basic_repr()
+
+repr(SomeClass())
+
+
"SomeClass(a=1, b='foo')"
+
+
+
+

source

+
+
+

BasicRepr

+
+
 BasicRepr ()
+
+

Base class for objects needing a basic __repr__

+

As a shortcut for creating a __repr__ for instance variables, you can inherit from BasicRepr:

+
+
class SomeClass(BasicRepr):
+    def __init__(self, a=1, b='foo'): self.a,self.b = a,b
+
+repr(SomeClass())
+
+
"SomeClass(a=1, b='foo')"
+
+
+
+

source

+
+
+

is_array

+
+
 is_array (x)
+
+

True if x supports __array__ or iloc

+
+
is_array(np.array(1)),is_array([1])
+
+
(True, False)
+
+
+
+

source

+
+
+

listify

+
+
 listify (o=None, *rest, use_list=False, match=None)
+
+

Convert o to a list

+

Conversion is designed to “do what you mean”, e.g:

+
+
test_eq(listify('hi'), ['hi'])
+test_eq(listify(b'hi'), [b'hi'])
+test_eq(listify(array(1)), [array(1)])
+test_eq(listify(1), [1])
+test_eq(listify([1,2]), [1,2])
+test_eq(listify(range(3)), [0,1,2])
+test_eq(listify(None), [])
+test_eq(listify(1,2), [1,2])
+
+
+
arr = np.arange(9).reshape(3,3)
+listify(arr)
+
+
[array([[0, 1, 2],
+        [3, 4, 5],
+        [6, 7, 8]])]
+
+
+
+
listify(array([1,2]))
+
+
[array([1, 2])]
+
+
+

Generators are turned into lists too:

+
+
gen = (o for o in range(3))
+test_eq(listify(gen), [0,1,2])
+
+

Use match to provide a length to match:

+
+
test_eq(listify(1,match=3), [1,1,1])
+
+

If match is a sequence, it’s length is used:

+
+
test_eq(listify(1,match=range(3)), [1,1,1])
+
+

If the listified item is not of length 1, it must be the same length as match:

+
+
test_eq(listify([1,1,1],match=3), [1,1,1])
+test_fail(lambda: listify([1,1],match=3))
+
+
+

source

+
+
+

tuplify

+
+
 tuplify (o, use_list=False, match=None)
+
+

Make o a tuple

+
+
test_eq(tuplify(None),())
+test_eq(tuplify([1,2,3]),(1,2,3))
+test_eq(tuplify(1,match=[1,2,3]),(1,1,1))
+
+
+

source

+
+
+

true

+
+
 true (x)
+
+

Test whether x is truthy; collections with >0 elements are considered True

+
+
[(o,true(o)) for o in
+ (array(0),array(1),array([0]),array([0,1]),1,0,'',None)]
+
+
[(array(0), False),
+ (array(1), True),
+ (array([0]), True),
+ (array([0, 1]), True),
+ (1, True),
+ (0, False),
+ ('', False),
+ (None, False)]
+
+
+
+

source

+
+
+

NullType

+
+
 NullType ()
+
+

An object that is False and can be called, chained, and indexed

+
+
bool(null.hi().there[3])
+
+
False
+
+
+
+

source

+
+
+

tonull

+
+
 tonull (x)
+
+

Convert None to null

+
+
bool(tonull(None).hi().there[3])
+
+
False
+
+
+
+

source

+
+
+

get_class

+
+
 get_class (nm, *fld_names, sup=None, doc=None, funcs=None, anno=None,
+            **flds)
+
+

Dynamically create a class, optionally inheriting from sup, containing fld_names

+
+
_t = get_class('_t', 'a', b=2, anno={'b':int})
+t = _t()
+test_eq(t.a, None)
+test_eq(t.b, 2)
+t = _t(1, b=3)
+test_eq(t.a, 1)
+test_eq(t.b, 3)
+t = _t(1, 3)
+test_eq(t.a, 1)
+test_eq(t.b, 3)
+test_eq(t, pickle.loads(pickle.dumps(t)))
+test_eq(_t.__annotations__, {'b':int, 'a':typing.Any})
+repr(t)
+
+
'__main__._t(a=1, b=3)'
+
+
+

Most often you’ll want to call mk_class, since it adds the class to your module. See mk_class for more details and examples of use (which also apply to get_class).

+
+

source

+
+
+

mk_class

+
+
 mk_class (nm, *fld_names, sup=None, doc=None, funcs=None, mod=None,
+           anno=None, **flds)
+
+

Create a class using get_class and add to the caller’s module

+

Any kwargs will be added as class attributes, and sup is an optional (tuple of) base classes.

+
+
mk_class('_t', a=1, sup=dict)
+t = _t()
+test_eq(t.a, 1)
+assert(isinstance(t,dict))
+
+

A __init__ is provided that sets attrs for any kwargs, and for any args (matching by position to fields), along with a __repr__ which prints all attrs. The docstring is set to doc. You can pass funcs which will be added as attrs with the function names.

+
+
def foo(self): return 1
+mk_class('_t', 'a', sup=dict, doc='test doc', funcs=foo)
+
+t = _t(3, b=2)
+test_eq(t.a, 3)
+test_eq(t.b, 2)
+test_eq(t.foo(), 1)
+test_eq(t.__doc__, 'test doc')
+t
+
+
{}
+
+
+
+

source

+
+
+

wrap_class

+
+
 wrap_class (nm, *fld_names, sup=None, doc=None, funcs=None, **flds)
+
+

Decorator: makes function a method of a new class nm passing parameters to mk_class

+
+
@wrap_class('_t', a=2)
+def bar(self,x): return x+1
+
+t = _t()
+test_eq(t.a, 2)
+test_eq(t.bar(3), 4)
+
+
+

source

+
+

ignore_exceptions

+
+
 ignore_exceptions ()
+
+

Context manager to ignore exceptions

+
+
with ignore_exceptions(): 
+    # Exception will be ignored
+    raise Exception
+
+
+

source

+
+
+
+

exec_local

+
+
 exec_local (code, var_name)
+
+

Call exec on code and return the var var_name

+
+
test_eq(exec_local("a=1", "a"), 1)
+
+
+

source

+
+
+

risinstance

+
+
 risinstance (types, obj=None)
+
+

Curried isinstance but with args reversed

+
+
assert risinstance(int, 1)
+assert not risinstance(str, 0)
+assert risinstance(int)(1)
+assert not risinstance(int)(None)
+
+

types can also be strings:

+
+
assert risinstance(('str','int'), 'a')
+assert risinstance('str', 'a')
+assert not risinstance('int', 'a')
+
+
+

source

+
+
+

ver2tuple

+
+
 ver2tuple (v:str)
+
+
+
test_eq(ver2tuple('3.8.1'), (3,8,1))
+test_eq(ver2tuple('3.1'), (3,1,0))
+test_eq(ver2tuple('3.'), (3,0,0))
+test_eq(ver2tuple('3'), (3,0,0))
+
+
+
+
+

NoOp

+

These are used when you need a pass-through function.

+
+
+

noop

+
+
 noop (x=None, *args, **kwargs)
+
+

Do nothing

+
+
noop()
+test_eq(noop(1),1)
+
+
+
+
+

noops

+
+
 noops (x=None, *args, **kwargs)
+
+

Do nothing (method)

+
+
class _t: foo=noops
+test_eq(_t().foo(1),1)
+
+
+
+
+

Infinite Lists

+

These lists are useful for things like padding an array or adding index column(s) to arrays.

+

Inf defines the following properties:

+
    +
  • count: itertools.count()
  • +
  • zeros: itertools.cycle([0])
  • +
  • ones : itertools.cycle([1])
  • +
  • nones: itertools.cycle([None])
  • +
+
+
test_eq([o for i,o in zip(range(5), Inf.count)],
+        [0, 1, 2, 3, 4])
+
+test_eq([o for i,o in zip(range(5), Inf.zeros)],
+        [0]*5)
+
+test_eq([o for i,o in zip(range(5), Inf.ones)],
+        [1]*5)
+
+test_eq([o for i,o in zip(range(5), Inf.nones)],
+        [None]*5)
+
+
+
+

Operator Functions

+
+

source

+
+

in_

+
+
 in_ (x, a)
+
+

True if x in a

+
+
# test if element is in another
+assert in_('c', ('b', 'c', 'a'))
+assert in_(4, [2,3,4,5])
+assert in_('t', 'fastai')
+test_fail(in_('h', 'fastai'))
+
+# use in_ as a partial
+assert in_('fastai')('t')
+assert in_([2,3,4,5])(4)
+test_fail(in_('fastai')('h'))
+
+

In addition to in_, the following functions are provided matching the behavior of the equivalent versions in operator: lt gt le ge eq ne add sub mul truediv is_ is_not mod.

+
+
lt(3,5),gt(3,5),is_(None,None),in_(0,[1,2]),mod(3,2)
+
+
(True, False, True, False, 1)
+
+
+

Similarly to _in, they also have additional functionality: if you only pass one param, they return a partial function that passes that param as the second positional parameter.

+
+
lt(5)(3),gt(5)(3),is_(None)(None),in_([1,2])(0),mod(2)(3)
+
+
(True, False, True, False, 1)
+
+
+
+

source

+
+
+

ret_true

+
+
 ret_true (*args, **kwargs)
+
+

Predicate: always True

+
+
assert ret_true(1,2,3)
+assert ret_true(False)
+
+
+

source

+
+
+

ret_false

+
+
 ret_false (*args, **kwargs)
+
+

Predicate: always False

+
+

source

+
+
+

stop

+
+
 stop (e=<class 'StopIteration'>)
+
+

Raises exception e (by default StopIteration)

+
+

source

+
+
+

gen

+
+
 gen (func, seq, cond=<function ret_true>)
+
+

Like (func(o) for o in seq if cond(func(o))) but handles StopIteration

+
+
test_eq(gen(noop, Inf.count, lt(5)),
+        range(5))
+test_eq(gen(operator.neg, Inf.count, gt(-5)),
+        [0,-1,-2,-3,-4])
+test_eq(gen(lambda o:o if o<5 else stop(), Inf.count),
+        range(5))
+
+
+

source

+
+
+

chunked

+
+
 chunked (it, chunk_sz=None, drop_last=False, n_chunks=None)
+
+

Return batches from iterator it of size chunk_sz (or return n_chunks total)

+

Note that you must pass either chunk_sz, or n_chunks, but not both.

+
+
t = list(range(10))
+test_eq(chunked(t,3),      [[0,1,2], [3,4,5], [6,7,8], [9]])
+test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8],    ])
+
+t = map(lambda o:stop() if o==6 else o, Inf.count)
+test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5]])
+t = map(lambda o:stop() if o==7 else o, Inf.count)
+test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5], [6]])
+
+t = np.arange(10)
+test_eq(chunked(t,3),      [[0,1,2], [3,4,5], [6,7,8], [9]])
+test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8],    ])
+
+test_eq(chunked([], 3),          [])
+test_eq(chunked([], n_chunks=3), [])
+
+
+

source

+
+
+

otherwise

+
+
 otherwise (x, tst, y)
+
+

y if tst(x) else x

+
+
test_eq(otherwise(2+1, gt(3), 4), 3)
+test_eq(otherwise(2+1, gt(2), 4), 4)
+
+
+
+
+

Attribute Helpers

+

These functions reduce boilerplate when setting or manipulating attributes or properties of objects.

+
+

source

+
+

custom_dir

+
+
 custom_dir (c, add)
+
+

Implement custom __dir__, adding add to cls

+

custom_dir allows you extract the __dict__ property of a class and appends the list add to it.

+
+
class _T: 
+    def f(): pass
+
+s = custom_dir(_T(), add=['foo', 'bar'])
+assert {'foo', 'bar', 'f'}.issubset(s)
+
+
+

source

+
+
+

AttrDict

+

dict subclass that also provides access to keys as attrs

+
+
d = AttrDict(a=1,b="two")
+test_eq(d.a, 1)
+test_eq(d['b'], 'two')
+test_eq(d.get('c','nope'), 'nope')
+d.b = 2
+test_eq(d.b, 2)
+test_eq(d['b'], 2)
+d['b'] = 3
+test_eq(d['b'], 3)
+test_eq(d.b, 3)
+assert 'a' in dir(d)
+
+

AttrDict will pretty print in Jupyter Notebooks:

+
+
_test_dict = {'a':1, 'b': {'c':1, 'd':2}, 'c': {'c':1, 'd':2}, 'd': {'c':1, 'd':2},
+              'e': {'c':1, 'd':2}, 'f': {'c':1, 'd':2, 'e': 4, 'f':[1,2,3,4,5]}}
+AttrDict(_test_dict)
+
+
{ 'a': 1,
+  'b': {'c': 1, 'd': 2},
+  'c': {'c': 1, 'd': 2},
+  'd': {'c': 1, 'd': 2},
+  'e': {'c': 1, 'd': 2},
+  'f': {'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}}
+
+
+
+

source

+
+
+

AttrDictDefault

+
+
 AttrDictDefault (*args, default_=None, **kwargs)
+
+

AttrDict subclass that returns None for missing attrs

+
+
d = AttrDictDefault(a=1,b="two", default_='nope')
+test_eq(d.a, 1)
+test_eq(d['b'], 'two')
+test_eq(d.c, 'nope')
+
+
+

source

+
+
+

NS

+

SimpleNamespace subclass that also adds iter and dict support

+

This is very similar to AttrDict, but since it starts with SimpleNamespace, it has some differences in behavior. You can use it just like SimpleNamespace:

+
+
d = NS(**_test_dict)
+d
+
+
namespace(a=1,
+          b={'c': 1, 'd': 2},
+          c={'c': 1, 'd': 2},
+          d={'c': 1, 'd': 2},
+          e={'c': 1, 'd': 2},
+          f={'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]})
+
+
+

…but you can also index it to get/set:

+
+
d['a']
+
+
1
+
+
+

…and iterate t:

+
+
list(d)
+
+
['a', 'b', 'c', 'd', 'e', 'f']
+
+
+
+

source

+
+
+

get_annotations_ex

+
+
 get_annotations_ex (obj, globals=None, locals=None)
+
+

Backport of py3.10 get_annotations that returns globals/locals

+

In Python 3.10 inspect.get_annotations was added. However previous versions of Python are unable to evaluate type annotations correctly if from future import __annotations__ is used. Furthermore, all annotations are evaluated, even if only some subset are needed. get_annotations_ex provides the same functionality as inspect.get_annotations, but works on earlier versions of Python, and returns the globals and locals needed to evaluate types.

+
+

source

+
+
+

eval_type

+
+
 eval_type (t, glb, loc)
+
+

eval a type or collection of types, if needed, for annotations in py3.10+

+

In py3.10, or if from future import __annotations__ is used, a is a str:

+
+
class _T2a: pass
+def func(a: _T2a): pass
+ann,glb,loc = get_annotations_ex(func)
+
+eval_type(ann['a'], glb, loc)
+
+
__main__._T2a
+
+
+

| is supported for defining Union types when using eval_type even for python versions prior to 3.9:

+
+
class _T2b: pass
+def func(a: _T2a|_T2b): pass
+ann,glb,loc = get_annotations_ex(func)
+
+eval_type(ann['a'], glb, loc)
+
+
typing.Union[__main__._T2a, __main__._T2b]
+
+
+
+

source

+
+
+

type_hints

+
+
 type_hints (f)
+
+

Like typing.get_type_hints but returns {} if not allowed type

+

Below is a list of allowed types for type hints in python:

+
+
list(typing._allowed_types)
+
+
[function,
+ builtin_function_or_method,
+ method,
+ module,
+ wrapper_descriptor,
+ method-wrapper,
+ method_descriptor]
+
+
+

For example, type func is allowed so type_hints returns the same value as typing.get_hints:

+
+
def f(a:int)->bool: ... # a function with type hints (allowed)
+exp = {'a':int,'return':bool}
+test_eq(type_hints(f), typing.get_type_hints(f))
+test_eq(type_hints(f), exp)
+
+

However, class is not an allowed type, so type_hints returns {}:

+
+
class _T:
+    def __init__(self, a:int=0)->bool: ...
+assert not type_hints(_T)
+
+
+

source

+
+
+

annotations

+
+
 annotations (o)
+
+

Annotations for o, or type(o)

+

This supports a wider range of situations than type_hints, by checking type() and __init__ for annotations too:

+
+
for o in _T,_T(),_T.__init__,f: test_eq(annotations(o), exp)
+assert not annotations(int)
+assert not annotations(print)
+
+
+

source

+
+
+

anno_ret

+
+
 anno_ret (func)
+
+

Get the return annotation of func

+
+
def f(x) -> float: return x
+test_eq(anno_ret(f), float)
+
+def f(x) -> typing.Tuple[float,float]: return x
+assert anno_ret(f)==typing.Tuple[float,float]
+
+

If your return annotation is None, anno_ret will return NoneType (and not None):

+
+
def f(x) -> None: return x
+
+test_eq(anno_ret(f), NoneType)
+assert anno_ret(f) is not None # returns NoneType instead of None
+
+

If your function does not have a return type, or if you pass in None instead of a function, then anno_ret returns None:

+
+
def f(x): return x
+
+test_eq(anno_ret(f), None)
+test_eq(anno_ret(None), None) # instead of passing in a func, pass in None
+
+
+

source

+
+
+

signature_ex

+
+
 signature_ex (obj, eval_str:bool=False)
+
+

Backport of inspect.signature(..., eval_str=True to <py310

+
+

source

+
+
+

union2tuple

+
+
 union2tuple (t)
+
+
+
test_eq(union2tuple(Union[int,str]), (int,str))
+test_eq(union2tuple(int), int)
+assert union2tuple(Tuple[int,str])==Tuple[int,str]
+test_eq(union2tuple((int,str)), (int,str))
+if UnionType: test_eq(union2tuple(int|str), (int,str))
+
+
+

source

+
+
+

argnames

+
+
 argnames (f, frame=False)
+
+

Names of arguments to function or frame f

+
+
test_eq(argnames(f), ['x'])
+
+
+

source

+
+
+

with_cast

+
+
 with_cast (f)
+
+

Decorator which uses any parameter annotations as preprocessing functions

+
+
@with_cast
+def _f(a, b:Path, c:str='', d=0): return (a,b,c,d)
+
+test_eq(_f(1, '.', 3), (1,Path('.'),'3',0))
+test_eq(_f(1, '.'), (1,Path('.'),'',0))
+
+@with_cast
+def _g(a:int=0)->str: return a
+
+test_eq(_g(4.0), '4')
+test_eq(_g(4.4), '4')
+test_eq(_g(2), '2')
+
+
+

source

+
+
+

store_attr

+
+
 store_attr (names=None, but='', cast=False, store_args=None, **attrs)
+
+

Store params named in comma-separated names from calling context into attrs in self

+

In it’s most basic form, you can use store_attr to shorten code like this:

+
+
class T:
+    def __init__(self, a,b,c): self.a,self.b,self.c = a,b,c
+
+

…to this:

+
+
class T:
+    def __init__(self, a,b,c): store_attr('a,b,c', self)
+
+

This class behaves as if we’d used the first form:

+
+
t = T(1,c=2,b=3)
+assert t.a==1 and t.b==3 and t.c==2
+
+
+
class T1:
+    def __init__(self, a,b,c): store_attr()
+
+

In addition, it stores the attrs as a dict in __stored_args__, which you can use for display, logging, and so forth.

+
+
test_eq(t.__stored_args__, {'a':1, 'b':3, 'c':2})
+
+

Since you normally want to use the first argument (often called self) for storing attributes, it’s optional:

+
+
class T:
+    def __init__(self, a,b,c:str): store_attr('a,b,c')
+
+t = T(1,c=2,b=3)
+assert t.a==1 and t.b==3 and t.c==2
+
+

With cast=True any parameter annotations will be used as preprocessing functions for the corresponding arguments:

+
+
class T:
+    def __init__(self, a:listify, b, c:str): store_attr('a,b,c', cast=True)
+
+t = T(1,c=2,b=3)
+assert t.a==[1] and t.b==3 and t.c=='2'
+
+

You can inherit from a class using store_attr, and just call it again to add in any new attributes added in the derived class:

+
+
class T2(T):
+    def __init__(self, d, **kwargs):
+        super().__init__(**kwargs)
+        store_attr('d')
+
+t = T2(d=1,a=2,b=3,c=4)
+assert t.a==2 and t.b==3 and t.c==4 and t.d==1
+
+

You can skip passing a list of attrs to store. In this case, all arguments passed to the method are stored:

+
+
class T:
+    def __init__(self, a,b,c): store_attr()
+
+t = T(1,c=2,b=3)
+assert t.a==1 and t.b==3 and t.c==2
+
+
+
class T4(T):
+    def __init__(self, d, **kwargs):
+        super().__init__(**kwargs)
+        store_attr()
+
+t = T4(4, a=1,c=2,b=3)
+assert t.a==1 and t.b==3 and t.c==2 and t.d==4
+
+
+
class T4:
+    def __init__(self, *, a: int, b: float = 1):
+        store_attr()
+        
+t = T4(a=3)
+assert t.a==3 and t.b==1
+t = T4(a=3, b=2)
+assert t.a==3 and t.b==2
+
+

You can skip some attrs by passing but:

+
+
class T:
+    def __init__(self, a,b,c): store_attr(but='a')
+
+t = T(1,c=2,b=3)
+assert t.b==3 and t.c==2
+assert not hasattr(t,'a')
+
+

You can also pass keywords to store_attr, which is identical to setting the attrs directly, but also stores them in __stored_args__.

+
+
class T:
+    def __init__(self): store_attr(a=1)
+
+t = T()
+assert t.a==1
+
+

You can also use store_attr inside functions.

+
+
def create_T(a, b):
+    t = SimpleNamespace()
+    store_attr(self=t)
+    return t
+
+t = create_T(a=1, b=2)
+assert t.a==1 and t.b==2
+
+
+

source

+
+
+

attrdict

+
+
 attrdict (o, *ks, default=None)
+
+

Dict from each k in ks to getattr(o,k)

+
+
class T:
+    def __init__(self, a,b,c): store_attr()
+
+t = T(1,c=2,b=3)
+test_eq(attrdict(t,'b','c'), {'b':3, 'c':2})
+
+
+

source

+
+
+

properties

+
+
 properties (cls, *ps)
+
+

Change attrs in cls with names in ps to properties

+
+
class T:
+    def a(self): return 1
+    def b(self): return 2
+properties(T,'a')
+
+test_eq(T().a,1)
+test_eq(T().b(),2)
+
+
+

source

+
+
+

camel2words

+
+
 camel2words (s, space=' ')
+
+

Convert CamelCase to ‘spaced words’

+
+
test_eq(camel2words('ClassAreCamel'), 'Class Are Camel')
+
+
+

source

+
+
+

camel2snake

+
+
 camel2snake (name)
+
+

Convert CamelCase to snake_case

+
+
test_eq(camel2snake('ClassAreCamel'), 'class_are_camel')
+test_eq(camel2snake('Already_Snake'), 'already__snake')
+
+
+

source

+
+
+

snake2camel

+
+
 snake2camel (s)
+
+

Convert snake_case to CamelCase

+
+
test_eq(snake2camel('a_b_cc'), 'ABCc')
+
+
+

source

+
+
+

class2attr

+
+
 class2attr (cls_name)
+
+

Return the snake-cased name of the class; strip ending cls_name if it exists.

+
+
class Parent:
+    @property
+    def name(self): return class2attr(self, 'Parent')
+
+class ChildOfParent(Parent): pass
+class ParentChildOf(Parent): pass
+
+p = Parent()
+cp = ChildOfParent()
+cp2 = ParentChildOf()
+
+test_eq(p.name, 'parent')
+test_eq(cp.name, 'child_of')
+test_eq(cp2.name, 'parent_child_of')
+
+
+

source

+
+
+

getcallable

+
+
 getcallable (o, attr)
+
+

Calls getattr with a default of noop

+
+
class Math:
+    def addition(self,a,b): return a+b
+
+m = Math()
+
+test_eq(getcallable(m, "addition")(a=1,b=2), 3)
+test_eq(getcallable(m, "subtraction")(a=1,b=2), None)
+
+
+

source

+
+
+

getattrs

+
+
 getattrs (o, *attrs, default=None)
+
+

List of all attrs in o

+
+
from fractions import Fraction
+
+
+
getattrs(Fraction(1,2), 'numerator', 'denominator')
+
+
[1, 2]
+
+
+
+

source

+
+
+

hasattrs

+
+
 hasattrs (o, attrs)
+
+

Test whether o contains all attrs

+
+
assert hasattrs(1,('imag','real'))
+assert not hasattrs(1,('imag','foo'))
+
+
+

source

+
+
+

setattrs

+
+
 setattrs (dest, flds, src)
+
+
+
d = dict(a=1,bb="2",ignore=3)
+o = SimpleNamespace()
+setattrs(o, "a,bb", d)
+test_eq(o.a, 1)
+test_eq(o.bb, "2")
+
+
+
d = SimpleNamespace(a=1,bb="2",ignore=3)
+o = SimpleNamespace()
+setattrs(o, "a,bb", d)
+test_eq(o.a, 1)
+test_eq(o.bb, "2")
+
+
+

source

+
+
+

try_attrs

+
+
 try_attrs (obj, *attrs)
+
+

Return first attr that exists in obj

+
+
test_eq(try_attrs(1, 'real'), 1)
+test_eq(try_attrs(1, 'foobar', 'real'), 1)
+
+
+
+
+

Attribute Delegation

+
+

source

+
+

GetAttrBase

+
+
 GetAttrBase ()
+
+

Basic delegation of __getattr__ and __dir__

+
+

source

+
+

GetAttr

+
+
 GetAttr ()
+
+

Inherit from this to have all attr accesses in self._xtra passed down to self.default

+

Inherit from GetAttr to have attr access passed down to an instance attribute. This makes it easy to create composites that don’t require callers to know about their components. For a more detailed discussion of how this works as well as relevant context, we suggest reading the delegated composition section of this blog article.

+

You can customise the behaviour of GetAttr in subclasses via; - _default - By default, this is set to 'default', so attr access is passed down to self.default - _default can be set to the name of any instance attribute that does not start with dunder __ - _xtra - By default, this is None, so all attr access is passed down - You can limit which attrs get passed down by setting _xtra to a list of attribute names

+

To illuminate the utility of GetAttr, suppose we have the following two classes, _WebPage which is a superclass of _ProductPage, which we wish to compose like so:

+
+
class _WebPage:
+    def __init__(self, title, author="Jeremy"):
+        self.title,self.author = title,author
+
+class _ProductPage:
+    def __init__(self, page, price): self.page,self.price = page,price
+        
+page = _WebPage('Soap', author="Sylvain")
+p = _ProductPage(page, 15.0)
+
+

How do we make it so we can just write p.author, instead of p.page.author to access the author attribute? We can use GetAttr, of course! First, we subclass GetAttr when defining _ProductPage. Next, we set self.default to the object whose attributes we want to be able to access directly, which in this case is the page argument passed on initialization:

+
+
class _ProductPage(GetAttr):
+    def __init__(self, page, price): self.default,self.price = page,price #self.default allows you to access page directly.
+
+p = _ProductPage(page, 15.0)
+
+

Now, we can access the author attribute directly from the instance:

+
+
test_eq(p.author, 'Sylvain')
+
+

If you wish to store the object you are composing in an attribute other than self.default, you can set the class attribute _data as shown below. This is useful in the case where you might have a name collision with self.default:

+
+
class _C(GetAttr):
+    _default = '_data' # use different component name; `self._data` rather than `self.default`
+    def __init__(self,a): self._data = a
+    def foo(self): noop
+
+t = _C('Hi')
+test_eq(t._data, 'Hi') 
+test_fail(lambda: t.default) # we no longer have self.default
+test_eq(t.lower(), 'hi')
+test_eq(t.upper(), 'HI')
+assert 'lower' in dir(t)
+assert 'upper' in dir(t)
+
+

By default, all attributes and methods of the object you are composing are retained. In the below example, we compose a str object with the class _C. This allows us to directly call string methods on instances of class _C, such as str.lower() or str.upper():

+
+
class _C(GetAttr):
+    # allow all attributes and methods to get passed to `self.default` (by leaving _xtra=None)
+    def __init__(self,a): self.default = a
+    def foo(self): noop
+
+t = _C('Hi')
+test_eq(t.lower(), 'hi')
+test_eq(t.upper(), 'HI')
+assert 'lower' in dir(t)
+assert 'upper' in dir(t)
+
+

However, you can choose which attributes or methods to retain by defining a class attribute _xtra, which is a list of allowed attribute and method names to delegate. In the below example, we only delegate the lower method from the composed str object when defining class _C:

+
+
class _C(GetAttr):
+    _xtra = ['lower'] # specify which attributes get passed to `self.default`
+    def __init__(self,a): self.default = a
+    def foo(self): noop
+
+t = _C('Hi')
+test_eq(t.default, 'Hi')
+test_eq(t.lower(), 'hi')
+test_fail(lambda: t.upper()) # upper wasn't in _xtra, so it isn't available to be called
+assert 'lower' in dir(t)
+assert 'upper' not in dir(t)
+
+

You must be careful to properly set an instance attribute in __init__ that corresponds to the class attribute _default. The below example sets the class attribute _default to data, but erroneously fails to define self.data (and instead defines self.default).

+

Failing to properly set instance attributes leads to errors when you try to access methods directly:

+
+
class _C(GetAttr):
+    _default = 'data' # use a bad component name; i.e. self.data does not exist
+    def __init__(self,a): self.default = a
+    def foo(self): noop
+        
+# TODO: should we raise an error when we create a new instance ...
+t = _C('Hi')
+test_eq(t.default, 'Hi')
+# ... or is it enough for all GetAttr features to raise errors
+test_fail(lambda: t.data)
+test_fail(lambda: t.lower())
+test_fail(lambda: t.upper())
+test_fail(lambda: dir(t))
+
+
+

source

+
+
+
+

delegate_attr

+
+
 delegate_attr (k, to)
+
+

Use in __getattr__ to delegate to attr to without inheriting from GetAttr

+

delegate_attr is a functional way to delegate attributes, and is an alternative to GetAttr. We recommend reading the documentation of GetAttr for more details around delegation.

+

You can use achieve delegation when you define __getattr__ by using delegate_attr:

+
+
class _C:
+    def __init__(self, o): self.o = o # self.o corresponds to the `to` argument in delegate_attr.
+    def __getattr__(self, k): return delegate_attr(self, k, to='o')
+    
+
+t = _C('HELLO') # delegates to a string
+test_eq(t.lower(), 'hello')
+
+t = _C(np.array([5,4,3])) # delegates to a numpy array
+test_eq(t.sum(), 12)
+
+t = _C(pd.DataFrame({'a': [1,2], 'b': [3,4]})) # delegates to a pandas.DataFrame
+test_eq(t.b.max(), 4)
+
+
+
+
+

Extensible Types

+

ShowPrint is a base class that defines a show method, which is used primarily for callbacks in fastai that expect this method to be defined.

+

Int, Float, and Str extend int, float and str respectively by adding an additional show method by inheriting from ShowPrint.

+

The code for Int is shown below:

+

Examples:

+
+
Int(0).show()
+Float(2.0).show()
+Str('Hello').show()
+
+
0
+2.0
+Hello
+
+
+
+
+

Collection functions

+

Functions that manipulate popular python collections.

+
+

source

+
+

partition

+
+
 partition (coll, f)
+
+

Partition a collection by a predicate

+
+
ts,fs = partition(range(10), mod(2))
+test_eq(fs, [0,2,4,6,8])
+test_eq(ts, [1,3,5,7,9])
+
+
+

source

+
+
+

flatten

+
+
 flatten (o)
+
+

Concatenate all collections and items as a generator

+
+

source

+
+
+

concat

+
+
 concat (colls)
+
+

Concatenate all collections and items as a list

+
+
concat([(o for o in range(2)),[2,3,4], 5])
+
+
[0, 1, 2, 3, 4, 5]
+
+
+
+
concat([["abc", "xyz"], ["foo", "bar"]])
+
+
['abc', 'xyz', 'foo', 'bar']
+
+
+
+

source

+
+
+

strcat

+
+
 strcat (its, sep:str='')
+
+

Concatenate stringified items its

+
+
test_eq(strcat(['a',2]), 'a2')
+test_eq(strcat(['a',2], ';'), 'a;2')
+
+
+

source

+
+
+

detuplify

+
+
 detuplify (x)
+
+

If x is a tuple with one thing, extract it

+
+
test_eq(detuplify(()),None)
+test_eq(detuplify([1]),1)
+test_eq(detuplify([1,2]), [1,2])
+test_eq(detuplify(np.array([[1,2]])), np.array([[1,2]]))
+
+
+

source

+
+
+

replicate

+
+
 replicate (item, match)
+
+

Create tuple of item copied len(match) times

+
+
t = [1,1]
+test_eq(replicate([1,2], t),([1,2],[1,2]))
+test_eq(replicate(1, t),(1,1))
+
+
+

source

+
+
+

setify

+
+
 setify (o)
+
+

Turn any list like-object into a set.

+
+
# test
+test_eq(setify(None),set())
+test_eq(setify('abc'),{'abc'})
+test_eq(setify([1,2,2]),{1,2})
+test_eq(setify(range(0,3)),{0,1,2})
+test_eq(setify({1,2}),{1,2})
+
+
+

source

+
+
+

merge

+
+
 merge (*ds)
+
+

Merge all dictionaries in ds

+
+
test_eq(merge(), {})
+test_eq(merge(dict(a=1,b=2)), dict(a=1,b=2))
+test_eq(merge(dict(a=1,b=2), dict(b=3,c=4), None), dict(a=1, b=3, c=4))
+
+
+

source

+
+
+

range_of

+
+
 range_of (x)
+
+

All indices of collection x (i.e. list(range(len(x))))

+
+
test_eq(range_of([1,1,1,1]), [0,1,2,3])
+
+
+

source

+
+
+

groupby

+
+
 groupby (x, key, val=<function noop>)
+
+

Like itertools.groupby but doesn’t need to be sorted, and isn’t lazy, plus some extensions

+
+
test_eq(groupby('aa ab bb'.split(), itemgetter(0)), {'a':['aa','ab'], 'b':['bb']})
+
+

Here’s an example of how to invert a grouping, using an int as key (which uses itemgetter; passing a str will use attrgetter), and using a val function:

+
+
d = {0: [1, 3, 7], 2: [3], 3: [5], 4: [8], 5: [4], 7: [5]}
+groupby(((o,k) for k,v in d.items() for o in v), 0, 1)
+
+
{1: [0], 3: [0, 2], 7: [0], 5: [3, 7], 8: [4], 4: [5]}
+
+
+
+

source

+
+
+

last_index

+
+
 last_index (x, o)
+
+

Finds the last index of occurence of x in o (returns -1 if no occurence)

+
+
test_eq(last_index(9, [1, 2, 9, 3, 4, 9, 10]), 5)
+test_eq(last_index(6, [1, 2, 9, 3, 4, 9, 10]), -1)
+
+
+

source

+
+
+

filter_dict

+
+
 filter_dict (d, func)
+
+

Filter a dict using func, applied to keys and values

+
+
letters = {o:chr(o) for o in range(65,73)}
+letters
+
+
{65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H'}
+
+
+
+
filter_dict(letters, lambda k,v: k<67 or v in 'FG')
+
+
{65: 'A', 66: 'B', 70: 'F', 71: 'G'}
+
+
+
+

source

+
+
+

filter_keys

+
+
 filter_keys (d, func)
+
+

Filter a dict using func, applied to keys

+
+
filter_keys(letters, lt(67))
+
+
{65: 'A', 66: 'B'}
+
+
+
+

source

+
+
+

filter_values

+
+
 filter_values (d, func)
+
+

Filter a dict using func, applied to values

+
+
filter_values(letters, in_('FG'))
+
+
{70: 'F', 71: 'G'}
+
+
+
+

source

+
+
+

cycle

+
+
 cycle (o)
+
+

Like itertools.cycle except creates list of Nones if o is empty

+
+
test_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2])
+test_eq(itertools.islice(cycle([]),3), [None]*3)
+test_eq(itertools.islice(cycle(None),3), [None]*3)
+test_eq(itertools.islice(cycle(1),3), [1,1,1])
+
+
+

source

+
+
+

zip_cycle

+
+
 zip_cycle (x, *args)
+
+

Like itertools.zip_longest but cycles through elements of all but first argument

+
+
test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])
+
+
+

source

+
+
+

sorted_ex

+
+
 sorted_ex (iterable, key=None, reverse=False)
+
+

Like sorted, but if key is str use attrgetter; if int use itemgetter

+
+

source

+
+
+

not_

+
+
 not_ (f)
+
+

Create new function that negates result of f

+
+
def f(a): return a>0
+test_eq(f(1),True)
+test_eq(not_(f)(1),False)
+test_eq(not_(f)(a=-1),True)
+
+
+

source

+
+
+

argwhere

+
+
 argwhere (iterable, f, negate=False, **kwargs)
+
+

Like filter_ex, but return indices for matching items

+
+

source

+
+
+

filter_ex

+
+
 filter_ex (iterable, f=<function noop>, negate=False, gen=False,
+            **kwargs)
+
+

Like filter, but passing kwargs to f, defaulting f to noop, and adding negate and gen

+
+

source

+
+
+

range_of

+
+
 range_of (a, b=None, step=None)
+
+

All indices of collection a, if a is a collection, otherwise range

+
+
test_eq(range_of([1,1,1,1]), [0,1,2,3])
+test_eq(range_of(4), [0,1,2,3])
+
+
+

source

+
+
+

renumerate

+
+
 renumerate (iterable, start=0)
+
+

Same as enumerate, but returns index as 2nd element instead of 1st

+
+
test_eq(renumerate('abc'), (('a',0),('b',1),('c',2)))
+
+
+

source

+
+
+

first

+
+
 first (x, f=None, negate=False, **kwargs)
+
+

First element of x, optionally filtered by f, or None if missing

+
+
test_eq(first(['a', 'b', 'c', 'd', 'e']), 'a')
+test_eq(first([False]), False)
+test_eq(first([False], noop), None)
+
+
+

source

+
+
+

only

+
+
 only (o)
+
+

Return the only item of o, raise if o doesn’t have exactly one item

+
+

source

+
+
+

nested_attr

+
+
 nested_attr (o, attr, default=None)
+
+

Same as getattr, but if attr includes a ., then looks inside nested objects

+
+
class CustomIndexable:
+    def __init__(self): self.data = {'a':1,'b':'v','c':{'d':5}}
+    def __getitem__(self, key): return self.data[key]
+
+custom_indexable = CustomIndexable()
+test_eq(nested_attr(custom_indexable,'a'),1)
+test_eq(nested_attr(custom_indexable,'c.d'),5)
+test_eq(nested_attr(custom_indexable,'e'),None)
+
+

class TestObj: def init(self): self.nested = {‘key’: [1, 2, {‘inner’: ‘value’}]} test_obj = TestObj()

+

test_eq(nested_attr(test_obj, ‘nested.key.2.inner’),‘value’) test_eq(nested_attr([1, 2, 3], ‘1’),2)

+
+
b = {'a':1,'b':'v','c':{'d':5}}
+test_eq(nested_attr(b,'b'),'v')
+test_eq(nested_attr(b,'c.d'),5)
+
+
+
a = SimpleNamespace(b=(SimpleNamespace(c=1)))
+test_eq(nested_attr(a, 'b.c'), getattr(getattr(a, 'b'), 'c'))
+test_eq(nested_attr(a, 'b.d'), None)
+test_eq(nested_attr(b, 'a'), 1)
+
+
+

source

+
+
+

nested_setdefault

+
+
 nested_setdefault (o, attr, default)
+
+

Same as setdefault, but if attr includes a ., then looks inside nested objects

+
+

source

+
+
+

nested_callable

+
+
 nested_callable (o, attr)
+
+

Same as nested_attr but if not found will return noop

+
+
a = SimpleNamespace(b=(SimpleNamespace(c=1)))
+test_eq(nested_callable(a, 'b.c'), getattr(getattr(a, 'b'), 'c'))
+test_eq(nested_callable(a, 'b.d'), noop)
+
+
+

source

+
+
+

nested_idx

+
+
 nested_idx (coll, *idxs)
+
+

Index into nested collections, dicts, etc, with idxs

+
+
a = {'b':[1,{'c':2}]}
+test_eq(nested_idx(a, 'nope'), None)
+test_eq(nested_idx(a, 'nope', 'nup'), None)
+test_eq(nested_idx(a, 'b', 3), None)
+test_eq(nested_idx(a), a)
+test_eq(nested_idx(a, 'b'), [1,{'c':2}])
+test_eq(nested_idx(a, 'b', 1), {'c':2})
+test_eq(nested_idx(a, 'b', 1, 'c'), 2)
+
+
+
a = SimpleNamespace(b=[1,{'c':2}])
+test_eq(nested_idx(a, 'nope'), None)
+test_eq(nested_idx(a, 'nope', 'nup'), None)
+test_eq(nested_idx(a, 'b', 3), None)
+test_eq(nested_idx(a), a)
+test_eq(nested_idx(a, 'b'), [1,{'c':2}])
+test_eq(nested_idx(a, 'b', 1), {'c':2})
+test_eq(nested_idx(a, 'b', 1, 'c'), 2)
+
+
+

source

+
+
+

set_nested_idx

+
+
 set_nested_idx (coll, value, *idxs)
+
+

Set value indexed like `nested_idx

+
+
set_nested_idx(a, 3, 'b', 0)
+test_eq(nested_idx(a, 'b', 0), 3)
+
+
+

source

+
+
+

val2idx

+
+
 val2idx (x)
+
+

Dict from value to index

+
+
test_eq(val2idx([1,2,3]), {3:2,1:0,2:1})
+
+
+

source

+
+
+

uniqueify

+
+
 uniqueify (x, sort=False, bidir=False, start=None)
+
+

Unique elements in x, optional sort, optional return reverse correspondence, optional prepend with elements.

+
+
t = [1,1,0,5,0,3]
+test_eq(uniqueify(t),[1,0,5,3])
+test_eq(uniqueify(t, sort=True),[0,1,3,5])
+test_eq(uniqueify(t, start=[7,8,6]), [7,8,6,1,0,5,3])
+v,o = uniqueify(t, bidir=True)
+test_eq(v,[1,0,5,3])
+test_eq(o,{1:0, 0: 1, 5: 2, 3: 3})
+v,o = uniqueify(t, sort=True, bidir=True)
+test_eq(v,[0,1,3,5])
+test_eq(o,{0:0, 1: 1, 3: 2, 5: 3})
+
+
+

source

+
+
+

loop_first_last

+
+
 loop_first_last (values)
+
+

Iterate and generate a tuple with a flag for first and last value.

+
+
test_eq(loop_first_last(range(3)), [(True,False,0), (False,False,1), (False,True,2)])
+
+
+

source

+
+
+

loop_first

+
+
 loop_first (values)
+
+

Iterate and generate a tuple with a flag for first value.

+
+
test_eq(loop_first(range(3)), [(True,0), (False,1), (False,2)])
+
+
+

source

+
+
+

loop_last

+
+
 loop_last (values)
+
+

Iterate and generate a tuple with a flag for last value.

+
+
test_eq(loop_last(range(3)), [(False,0), (False,1), (True,2)])
+
+
+

source

+
+
+

first_match

+
+
 first_match (lst, f, default=None)
+
+

First element of lst matching predicate f, or default if none

+
+
a = [0,2,4,5,6,7,10]
+test_eq(first_match(a, lambda o:o%2), 3)
+
+
+

source

+
+
+

last_match

+
+
 last_match (lst, f, default=None)
+
+

Last element of lst matching predicate f, or default if none

+
+
test_eq(last_match(a, lambda o:o%2), 5)
+
+
+
+
+

fastuple

+

A tuple with extended functionality.

+
+

source

+
+

fastuple

+
+
 fastuple (x=None, *rest)
+
+

A tuple with elementwise ops and more friendly init behavior

+
+
+

Friendly init behavior

+

Common failure modes when trying to initialize a tuple in python:

+
tuple(3)
+> TypeError: 'int' object is not iterable
+

or

+
tuple(3, 4)
+> TypeError: tuple expected at most 1 arguments, got 2
+

However, fastuple allows you to define tuples like this and in the usual way:

+
+
test_eq(fastuple(3), (3,))
+test_eq(fastuple(3,4), (3, 4))
+test_eq(fastuple((3,4)), (3, 4))
+
+
+
+

Elementwise operations

+
+

source

+
+
fastuple.add
+
+
 fastuple.add (*args)
+
+

+ is already defined in tuple for concat, so use add instead

+
+
test_eq(fastuple.add((1,1),(2,2)), (3,3))
+test_eq_type(fastuple(1,1).add(2), fastuple(3,3))
+test_eq(fastuple('1','2').add('2'), fastuple('12','22'))
+
+
+

source

+
+
+
fastuple.mul
+
+
 fastuple.mul (*args)
+
+

* is already defined in tuple for replicating, so use mul instead

+
+
test_eq_type(fastuple(1,1).mul(2), fastuple(2,2))
+
+
+
+
+

Other Elementwise Operations

+

Additionally, the following elementwise operations are available: - le: less than or equal - eq: equal - gt: greater than - min: minimum of

+
+
test_eq(fastuple(3,1).le(1), (False, True))
+test_eq(fastuple(3,1).eq(1), (False, True))
+test_eq(fastuple(3,1).gt(1), (True, False))
+test_eq(fastuple(3,1).min(2), (2,1))
+
+

You can also do other elementwise operations like negate a fastuple, or subtract two fastuples:

+
+
test_eq(-fastuple(1,2), (-1,-2))
+test_eq(~fastuple(1,0,1), (False,True,False))
+
+test_eq(fastuple(1,1)-fastuple(2,2), (-1,-1))
+
+
+
test_eq(type(fastuple(1)), fastuple)
+test_eq_type(fastuple(1,2), fastuple(1,2))
+test_ne(fastuple(1,2), fastuple(1,3))
+test_eq(fastuple(), ())
+
+
+
+
+

Functions on Functions

+

Utilities for functional programming or for defining, modifying, or debugging functions.

+
+

source

+
+

bind

+
+
 bind (func, *pargs, **pkwargs)
+
+

Same as partial, except you can use arg0 arg1 etc param placeholders

+

bind is the same as partial, but also allows you to reorder positional arguments using variable name(s) arg{i} where i refers to the zero-indexed positional argument. bind as implemented currently only supports reordering of up to the first 5 positional arguments.

+

Consider the function myfunc below, which has 3 positional arguments. These arguments can be referenced as arg0, arg1, and arg1, respectively.

+
+
def myfn(a,b,c,d=1,e=2): return(a,b,c,d,e)
+
+

In the below example we bind the positional arguments of myfn as follows:

+
    +
  • The second input 14, referenced by arg1, is substituted for the first positional argument.
  • +
  • We supply a default value of 17 for the second positional argument.
  • +
  • The first input 19, referenced by arg0, is subsituted for the third positional argument.
  • +
+
+
test_eq(bind(myfn, arg1, 17, arg0, e=3)(19,14), (14,17,19,1,3))
+
+

In this next example:

+
    +
  • We set the default value to 17 for the first positional argument.
  • +
  • The first input 19 refrenced by arg0, becomes the second positional argument.
  • +
  • The second input 14 becomes the third positional argument.
  • +
  • We override the default the value for named argument e to 3.
  • +
+
+
test_eq(bind(myfn, 17, arg0, e=3)(19,14), (17,19,14,1,3))
+
+

This is an example of using bind like partial and do not reorder any arguments:

+
+
test_eq(bind(myfn)(17,19,14), (17,19,14,1,2))
+
+

bind can also be used to change default values. In the below example, we use the first input 3 to override the default value of the named argument e, and supply default values for the first three positional arguments:

+
+
test_eq(bind(myfn, 17,19,14,e=arg0)(3), (17,19,14,1,3))
+
+
+

source

+
+
+

mapt

+
+
 mapt (func, *iterables)
+
+

Tuplified map

+
+
t = [0,1,2,3]
+test_eq(mapt(operator.neg, t), (0,-1,-2,-3))
+
+
+

source

+
+
+

map_ex

+
+
 map_ex (iterable, f, *args, gen=False, **kwargs)
+
+

Like map, but use bind, and supports str and indexing

+
+
test_eq(map_ex(t,operator.neg), [0,-1,-2,-3])
+
+

If f is a string then it is treated as a format string to create the mapping:

+
+
test_eq(map_ex(t, '#{}#'), ['#0#','#1#','#2#','#3#'])
+
+

If f is a dictionary (or anything supporting __getitem__) then it is indexed to create the mapping:

+
+
test_eq(map_ex(t, list('abcd')), list('abcd'))
+
+

You can also pass the same arg params that bind accepts:

+
+
def f(a=None,b=None): return b
+test_eq(map_ex(t, f, b=arg0), range(4))
+
+
+

source

+
+
+

compose

+
+
 compose (*funcs, order=None)
+
+

Create a function that composes all functions in funcs, passing along remaining *args and **kwargs to all

+
+
f1 = lambda o,p=0: (o*2)+p
+f2 = lambda o,p=1: (o+1)/p
+test_eq(f2(f1(3)), compose(f1,f2)(3))
+test_eq(f2(f1(3,p=3),p=3), compose(f1,f2)(3,p=3))
+test_eq(f2(f1(3,  3),  3), compose(f1,f2)(3,  3))
+
+f1.order = 1
+test_eq(f1(f2(3)), compose(f1,f2, order="order")(3))
+
+
+

source

+
+
+

maps

+
+
 maps (*args, retain=<function noop>)
+
+

Like map, except funcs are composed first

+
+
test_eq(maps([1]), [1])
+test_eq(maps(operator.neg, [1,2]), [-1,-2])
+test_eq(maps(operator.neg, operator.neg, [1,2]), [1,2])
+
+
+

source

+
+
+

partialler

+
+
 partialler (f, *args, order=None, **kwargs)
+
+

Like functools.partial but also copies over docstring

+
+
def _f(x,a=1):
+    "test func"
+    return x-a
+_f.order=1
+
+f = partialler(_f, 2)
+test_eq(f.order, 1)
+test_eq(f(3), -1)
+f = partialler(_f, a=2, order=3)
+test_eq(f.__doc__, "test func")
+test_eq(f.order, 3)
+test_eq(f(3), _f(3,2))
+
+
+
class partial0:
+    "Like `partialler`, but args passed to callable are inserted at started, instead of at end"
+    def __init__(self, f, *args, order=None, **kwargs):
+        self.f,self.args,self.kwargs = f,args,kwargs
+        self.order = ifnone(order, getattr(f,'order',None))
+        self.__doc__ = f.__doc__
+
+    def __call__(self, *args, **kwargs): return self.f(*args, *self.args, **kwargs, **self.kwargs)
+
+
+
f = partial0(_f, 2)
+test_eq(f.order, 1)
+test_eq(f(3), 1) # NB: different to `partialler` example
+
+
+

source

+
+
+

instantiate

+
+
 instantiate (t)
+
+

Instantiate t if it’s a type, otherwise do nothing

+
+
test_eq_type(instantiate(int), 0)
+test_eq_type(instantiate(1), 1)
+
+
+

source

+
+
+

using_attr

+
+
 using_attr (f, attr)
+
+

Construct a function which applies f to the argument’s attribute attr

+
+
t = Path('/a/b.txt')
+f = using_attr(str.upper, 'name')
+test_eq(f(t), 'B.TXT')
+
+
+
+

Self (with an uppercase S)

+

A Concise Way To Create Lambdas

+

This is a concise way to create lambdas that are calling methods on an object (note the capitalization!)

+

Self.sum(), for instance, is a shortcut for lambda o: o.sum().

+
+
f = Self.sum()
+x = np.array([3.,1])
+test_eq(f(x), 4.)
+
+# This is equivalent to above
+f = lambda o: o.sum()
+x = np.array([3.,1])
+test_eq(f(x), 4.)
+
+f = Self.argmin()
+arr = np.array([1,2,3,4,5])
+test_eq(f(arr), arr.argmin())
+
+f = Self.sum().is_integer()
+x = np.array([3.,1])
+test_eq(f(x), True)
+
+f = Self.sum().real.is_integer()
+x = np.array([3.,1])
+test_eq(f(x), True)
+
+f = Self.imag()
+test_eq(f(3), 0)
+
+f = Self[1]
+test_eq(f(x), 1)
+
+

Self is also callable, which creates a function which calls any function passed to it, using the arguments passed to Self:

+
+
def f(a, b=3): return a+b+2
+def g(a, b=3): return a*b
+fg = Self(1,b=2)
+list(map(fg, [f,g]))
+
+
[5, 2]
+
+
+
+
+
+

Patching

+
+

source

+
+

copy_func

+
+
 copy_func (f)
+
+

Copy a non-builtin function (NB copy.copy does not work for this)

+

Sometimes it may be desirable to make a copy of a function that doesn’t point to the original object. When you use Python’s built in copy.copy or copy.deepcopy to copy a function, you get a reference to the original object:

+
+
import copy as cp
+
+
+
def foo(): pass
+a = cp.copy(foo)
+b = cp.deepcopy(foo)
+
+a.someattr = 'hello' # since a and b point at the same object, updating a will update b
+test_eq(b.someattr, 'hello')
+
+assert a is foo and b is foo
+
+

However, with copy_func, you can retrieve a copy of a function without a reference to the original object:

+
+
c = copy_func(foo) # c is an indpendent object
+assert c is not foo
+
+
+
def g(x, *, y=3): return x+y
+test_eq(copy_func(g)(4), 7)
+
+
+

source

+
+
+

patch_to

+
+
 patch_to (cls, as_prop=False, cls_method=False)
+
+

Decorator: add f to cls

+

The @patch_to decorator allows you to monkey patch a function into a class as a method:

+
+
class _T3(int): pass  
+
+@patch_to(_T3)
+def func1(self, a): return self+a
+
+t = _T3(1) # we initialized `t` to a type int = 1
+test_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3
+
+

You can access instance properties in the usual way via self:

+
+
class _T4():
+    def __init__(self, g): self.g = g
+        
+@patch_to(_T4)
+def greet(self, x): return self.g + x
+        
+t = _T4('hello ') # this sets self.g = 'hello '
+test_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello '
+
+

You can instead specify that the method should be a class method by setting cls_method=True:

+
+
class _T5(int): attr = 3 # attr is a class attribute we will access in a later method
+    
+@patch_to(_T5, cls_method=True)
+def func(cls, x): return cls.attr + x # you can access class attributes in the normal way
+
+test_eq(_T5.func(4), 7)
+
+

Additionally you can specify that the function you want to patch should be a class attribute with as_prop=True:

+
+
@patch_to(_T5, as_prop=True)
+def add_ten(self): return self + 10
+
+t = _T5(4)
+test_eq(t.add_ten, 14)
+
+

Instead of passing one class to the @patch_to decorator, you can pass multiple classes in a tuple to simulteanously patch more than one class with the same method:

+
+
class _T6(int): pass
+class _T7(int): pass
+
+@patch_to((_T6,_T7))
+def func_mult(self, a): return self*a
+
+t = _T6(2)
+test_eq(t.func_mult(4), 8)
+t = _T7(2)
+test_eq(t.func_mult(4), 8)
+
+
+

source

+
+
+

patch

+
+
 patch (f=None, as_prop=False, cls_method=False)
+
+

Decorator: add f to the first parameter’s class (based on f’s type annotations)

+

@patch is an alternative to @patch_to that allows you similarly monkey patch class(es) by using type annotations:

+
+
class _T8(int): pass  
+
+@patch
+def func(self:_T8, a): return self+a
+
+t = _T8(1)  # we initilized `t` to a type int = 1
+test_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4
+test_eq(t.func.__qualname__, '_T8.func')
+
+

Similarly to patch_to, you can supply a union of classes instead of a single class in your type annotations to patch multiple classes:

+
+
class _T9(int): pass 
+
+@patch
+def func2(x:_T8|_T9, a): return x*a # will patch both _T8 and _T9
+
+t = _T8(2)
+test_eq(t.func2(4), 8)
+test_eq(t.func2.__qualname__, '_T8.func2')
+
+t = _T9(2)
+test_eq(t.func2(4), 8)
+test_eq(t.func2.__qualname__, '_T9.func2')
+
+

Just like patch_to decorator you can use as_prop and cls_method parameters with patch decorator:

+
+
@patch(as_prop=True)
+def add_ten(self:_T5): return self + 10
+
+t = _T5(4)
+test_eq(t.add_ten, 14)
+
+
+
class _T5(int): attr = 3 # attr is a class attribute we will access in a later method
+    
+@patch(cls_method=True)
+def func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way
+
+test_eq(_T5.func(4), 7)
+
+
+

source

+
+
+

patch_property

+
+
 patch_property (f)
+
+

Deprecated; use patch(as_prop=True) instead

+

Patching classmethod shouldn’t affect how python’s inheritance works

+
+
class FastParent: pass
+
+@patch(cls_method=True)
+def type_cls(cls: FastParent): return cls
+
+class FastChild(FastParent): pass
+
+parent = FastParent()
+test_eq(parent.type_cls(), FastParent)
+
+child = FastChild()
+test_eq(child.type_cls(), FastChild)
+
+
+
+
+

Other Helpers

+
+

source

+
+

compile_re

+
+
 compile_re (pat)
+
+

Compile pat if it’s not None

+
+
assert compile_re(None) is None
+assert compile_re('a').match('ab')
+
+
+

source

+
+

ImportEnum

+
+
 ImportEnum (value, names=None, module=None, qualname=None, type=None,
+             start=1)
+
+

An Enum that can have its values imported

+
+
_T = ImportEnum('_T', {'foobar':1, 'goobar':2})
+_T.imports()
+test_eq(foobar, _T.foobar)
+test_eq(goobar, _T.goobar)
+
+
+

source

+
+
+

StrEnum

+
+
 StrEnum (value, names=None, module=None, qualname=None, type=None,
+          start=1)
+
+

An ImportEnum that behaves like a str

+
+

source

+
+
+
+

str_enum

+
+
 str_enum (name, *vals)
+
+

Simplified creation of StrEnum types

+
+

source

+
+

ValEnum

+
+
 ValEnum (value, names=None, module=None, qualname=None, type=None,
+          start=1)
+
+

An ImportEnum that stringifies using values

+
+
_T = str_enum('_T', 'a', 'b')
+test_eq(f'{_T.a}', 'a')
+test_eq(_T.a, 'a')
+test_eq(list(_T.__members__), ['a','b'])
+print(_T.a, _T.a.upper())
+
+
a A
+
+
+
+

source

+
+
+

Stateful

+
+
 Stateful (*args, **kwargs)
+
+

A base class/mixin for objects that should not serialize all their state

+
+
class _T(Stateful):
+    def __init__(self):
+        super().__init__()
+        self.a=1
+        self._state['test']=2
+
+t = _T()
+t2 = pickle.loads(pickle.dumps(t))
+test_eq(t.a,1)
+test_eq(t._state['test'],2)
+test_eq(t2.a,1)
+test_eq(t2._state,{})
+
+

Override _init_state to do any necessary setup steps that are required during __init__ or during deserialization (e.g. pickle.load). Here’s an example of how Stateful simplifies the official Python example for Handling Stateful Objects.

+
+
class TextReader(Stateful):
+    """Print and number lines in a text file."""
+    _stateattrs=('file',)
+    def __init__(self, filename):
+        self.filename,self.lineno = filename,0
+        super().__init__()
+
+    def readline(self):
+        self.lineno += 1
+        line = self.file.readline()
+        if line: return f"{self.lineno}: {line.strip()}"
+
+    def _init_state(self):
+        self.file = open(self.filename)
+        for _ in range(self.lineno): self.file.readline()
+
+
+
reader = TextReader("00_test.ipynb")
+print(reader.readline())
+print(reader.readline())
+
+new_reader = pickle.loads(pickle.dumps(reader))
+print(reader.readline())
+
+
1: {
+2: "cells": [
+3: {
+
+
+
+

source

+
+
+
+

NotStr

+
+
 NotStr (s)
+
+

Behaves like a str, but isn’t an instance of one

+
+
s = NotStr("hello")
+assert not isinstance(s, str)
+test_eq(s, 'hello')
+test_eq(s*2, 'hellohello')
+test_eq(len(s), 5)
+
+
+

source

+
+

PrettyString

+

Little hack to get strings to show properly in Jupyter.

+

Allow strings with special characters to render properly in Jupyter. Without calling print() strings with special characters are displayed like so:

+
+
with_special_chars='a string\nwith\nnew\nlines and\ttabs'
+with_special_chars
+
+
'a string\nwith\nnew\nlines and\ttabs'
+
+
+

We can correct this with PrettyString:

+
+
PrettyString(with_special_chars)
+
+
a string
+with
+new
+lines and   tabs
+
+
+
+

source

+
+
+
+

even_mults

+
+
 even_mults (start, stop, n)
+
+

Build log-stepped array from start to stop in n steps.

+
+
test_eq(even_mults(2,8,3), [2,4,8])
+test_eq(even_mults(2,32,5), [2,4,8,16,32])
+test_eq(even_mults(2,8,1), 8)
+
+
+

source

+
+
+

num_cpus

+
+
 num_cpus ()
+
+

Get number of cpus

+
+
num_cpus()
+
+
10
+
+
+
+

source

+
+
+

add_props

+
+
 add_props (f, g=None, n=2)
+
+

Create properties passing each of range(n) to f

+
+
class _T(): a,b = add_props(lambda i,x:i*2)
+
+t = _T()
+test_eq(t.a,0)
+test_eq(t.b,2)
+
+
+
class _T(): 
+    def __init__(self, v): self.v=v
+    def _set(i, self, v): self.v[i] = v
+    a,b = add_props(lambda i,x: x.v[i], _set)
+
+t = _T([0,2])
+test_eq(t.a,0)
+test_eq(t.b,2)
+t.a = t.a+1
+t.b = 3
+test_eq(t.a,1)
+test_eq(t.b,3)
+
+
+

source

+
+
+

str2bool

+
+
 str2bool (s)
+
+

Case-insensitive convert string s too a bool (y,yes,t,true,on,1->True)

+

True values are ‘y’, ‘yes’, ‘t’, ‘true’, ‘on’, and ‘1’; false values are ‘n’, ‘no’, ‘f’, ‘false’, ‘off’, and ‘0’. Raises ValueError if ‘val’ is anything else.

+
+
for o in "y YES t True on 1".split(): assert str2bool(o)
+for o in "n no FALSE off 0".split(): assert not str2bool(o)
+for o in 0,None,'',False: assert not str2bool(o)
+for o in 1,True: assert str2bool(o)
+
+
+

source

+
+
+

str2int

+
+
 str2int (s)
+
+

Convert s to an int

+
+

source

+
+
+

str2float

+
+
 str2float (s:str)
+
+

Convert s to a float

+
+

source

+
+
+

str2list

+
+
 str2list (s:str)
+
+

Convert s to a list

+
+

source

+
+
+

str2date

+
+
 str2date (s:str)
+
+

date.fromisoformat with empty string handling

+
+

source

+
+
+

to_date

+
+
 to_date (arg)
+
+
+

source

+
+
+

to_list

+
+
 to_list (arg)
+
+
+

source

+
+
+

to_float

+
+
 to_float (arg)
+
+
+

source

+
+
+

to_int

+
+
 to_int (arg)
+
+
+

source

+
+
+

to_bool

+
+
 to_bool (arg)
+
+
+

source

+
+
+

typed

+
+
 typed (_func=None, cast=False)
+
+

Decorator to check param and return types at runtime, with optional casting

+

typed validates argument types at runtime. This is in contrast to MyPy which only offers static type checking.

+

For example, a TypeError will be raised if we try to pass an integer into the first argument of the below function:

+
+
@typed
+def discount(price:int, pct:float) -> float:
+    return (1-pct) * price
+
+with ExceptionExpected(TypeError): discount(100.0, .1)
+
+

You can have automatic casting based on heuristics by specifying typed(cast=True). If casting is not possible, a TypeError is raised.

+
+
@typed(cast=True)
+def discount(price:int, pct:float) -> float:
+    return (1-pct) * price
+
+assert 90.0 == discount(100.5, .1) # will auto cast 100.5 to the int 100
+assert 90.0 == discount(' 100 ', .1) # will auto cast the str "100" to the int 100
+with ExceptionExpected(TypeError): discount("a", .1)
+
+

We can also optionally allow multiple types by enumarating the types in a tuple as illustrated below:

+
+
@typed
+def discount(price:int|float, pct:float): 
+    return (1-pct) * price
+
+assert 90.0 == discount(100.0, .1)
+
+@typed(cast=True)
+def discount(price:int|None, pct:float):
+    return (1-pct) * price
+
+assert 90.0 == discount(100.0, .1)
+
+

We currently do not support union types when casting.

+
+
@typed(cast=True)
+def discount(price:int|float, pct:float):
+    return (1-pct) * price
+
+with ExceptionExpected(AssertionError): assert 90.0 == discount("100.0", .1)
+
+

typed works with classes, too:

+
+
class Foo:
+    @typed
+    def __init__(self, a:int, b: int, c:str): pass
+    @typed(cast=True)
+    def test(cls, d:str): return d
+
+with ExceptionExpected(TypeError): Foo(1, 2, 3) 
+assert isinstance(Foo(1,2, 'a string').test(10), str)
+
+

It also works with custom types.

+
+
@typed
+def test_foo(foo: Foo): pass
+
+with ExceptionExpected(TypeError): test_foo(1)
+test_foo(Foo(1, 2, 'a string'))
+
+
+
class Bar:
+    @typed
+    def __init__(self, a:int): self.a = a
+@typed(cast=True)
+def test_bar(bar: Bar): return bar
+
+assert isinstance(test_bar(1), Bar)
+test_eq(test_bar(1).a, 1)
+with ExceptionExpected(TypeError): test_bar("foobar")
+
+
+

source

+
+
+

exec_new

+
+
 exec_new (code)
+
+

Execute code in a new environment and return it

+
+
g = exec_new('a=1')
+test_eq(g['a'], 1)
+
+
+

source

+
+
+

exec_import

+
+
 exec_import (mod, sym)
+
+

Import sym from mod in a new environment

+
+
+
+

Notebook functions

+
+
+

ipython_shell

+
+
 ipython_shell ()
+
+

Same as get_ipython but returns False if not in IPython

+
+
+
+

in_ipython

+
+
 in_ipython ()
+
+

Check if code is running in some kind of IPython environment

+
+
+
+

in_colab

+
+
 in_colab ()
+
+

Check if the code is running in Google Colaboratory

+
+
+
+

in_jupyter

+
+
 in_jupyter ()
+
+

Check if the code is running in a jupyter notebook

+
+
+
+

in_notebook

+
+
 in_notebook ()
+
+

Check if the code is running in a jupyter notebook

+

These variables are available as booleans in fastcore.basics as IN_IPYTHON, IN_JUPYTER, IN_COLAB and IN_NOTEBOOK.

+
+
IN_IPYTHON, IN_JUPYTER, IN_COLAB, IN_NOTEBOOK
+
+
(True, True, False, True)
+
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/basics.html.md b/basics.html.md new file mode 100644 index 00000000..6ac844a3 --- /dev/null +++ b/basics.html.md @@ -0,0 +1,3622 @@ +# Basic functionality + + + + +## Basics + +------------------------------------------------------------------------ + +source + +### ifnone + +> ifnone (a, b) + +*`b` if `a` is None else `a`* + +Since `b if a is None else a` is such a common pattern, we wrap it in a +function. However, be careful, because python will evaluate *both* `a` +and `b` when calling +[`ifnone`](https://fastcore.fast.ai/basics.html#ifnone) (which it +doesn’t do if using the `if` version directly). + +``` python +test_eq(ifnone(None,1), 1) +test_eq(ifnone(2 ,1), 2) +``` + +------------------------------------------------------------------------ + +source + +### maybe_attr + +> maybe_attr (o, attr) + +*`getattr(o,attr,o)`* + +Return the attribute `attr` for object `o`. If the attribute doesn’t +exist, then return the object `o` instead. + +``` python +class myobj: myattr='foo' + +test_eq(maybe_attr(myobj, 'myattr'), 'foo') +test_eq(maybe_attr(myobj, 'another_attr'), myobj) +``` + +------------------------------------------------------------------------ + +source + +### basic_repr + +> basic_repr (flds=None) + +*Minimal `__repr__`* + +In types which provide rich display functionality in Jupyter, their +`__repr__` is also called in order to provide a fallback text +representation. Unfortunately, this includes a memory address which +changes on every invocation, making it non-deterministic. This causes +diffs to get messy and creates conflicts in git. To fix this, put +`__repr__=basic_repr()` inside your class. + +``` python +class SomeClass: __repr__=basic_repr() +repr(SomeClass()) +``` + + 'SomeClass()' + +If you pass a list of attributes (`flds`) of an object, then this will +generate a string with the name of each attribute and its corresponding +value. The format of this string is `key=value`, where `key` is the name +of the attribute, and `value` is the value of the attribute. For each +value, attempt to use the `__name__` attribute, otherwise fall back to +using the value’s `__repr__` when constructing the string. + +``` python +class SomeClass: + a=1 + b='foo' + __repr__=basic_repr('a,b') + __name__='some-class' + +repr(SomeClass()) +``` + + "SomeClass(a=1, b='foo')" + +Nested objects work too: + +``` python +class AnotherClass: + c=SomeClass() + d='bar' + __repr__=basic_repr(['c', 'd']) + +repr(AnotherClass()) +``` + + "AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')" + +Instance variables (but not class variables) are shown if +[`basic_repr`](https://fastcore.fast.ai/basics.html#basic_repr) is +called with no arguments: + +``` python +class SomeClass: + def __init__(self, a=1, b='foo'): self.a,self.b = a,b + __repr__=basic_repr() + +repr(SomeClass()) +``` + + "SomeClass(a=1, b='foo')" + +------------------------------------------------------------------------ + +source + +### BasicRepr + +> BasicRepr () + +*Base class for objects needing a basic `__repr__`* + +As a shortcut for creating a `__repr__` for instance variables, you can +inherit from +[`BasicRepr`](https://fastcore.fast.ai/basics.html#basicrepr): + +``` python +class SomeClass(BasicRepr): + def __init__(self, a=1, b='foo'): self.a,self.b = a,b + +repr(SomeClass()) +``` + + "SomeClass(a=1, b='foo')" + +------------------------------------------------------------------------ + +source + +### is_array + +> is_array (x) + +*`True` if `x` supports `__array__` or `iloc`* + +``` python +is_array(np.array(1)),is_array([1]) +``` + + (True, False) + +------------------------------------------------------------------------ + +source + +### listify + +> listify (o=None, *rest, use_list=False, match=None) + +*Convert `o` to a `list`* + +Conversion is designed to “do what you mean”, e.g: + +``` python +test_eq(listify('hi'), ['hi']) +test_eq(listify(b'hi'), [b'hi']) +test_eq(listify(array(1)), [array(1)]) +test_eq(listify(1), [1]) +test_eq(listify([1,2]), [1,2]) +test_eq(listify(range(3)), [0,1,2]) +test_eq(listify(None), []) +test_eq(listify(1,2), [1,2]) +``` + +``` python +arr = np.arange(9).reshape(3,3) +listify(arr) +``` + + [array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]])] + +``` python +listify(array([1,2])) +``` + + [array([1, 2])] + +Generators are turned into lists too: + +``` python +gen = (o for o in range(3)) +test_eq(listify(gen), [0,1,2]) +``` + +Use `match` to provide a length to match: + +``` python +test_eq(listify(1,match=3), [1,1,1]) +``` + +If `match` is a sequence, it’s length is used: + +``` python +test_eq(listify(1,match=range(3)), [1,1,1]) +``` + +If the listified item is not of length `1`, it must be the same length +as `match`: + +``` python +test_eq(listify([1,1,1],match=3), [1,1,1]) +test_fail(lambda: listify([1,1],match=3)) +``` + +------------------------------------------------------------------------ + +source + +### tuplify + +> tuplify (o, use_list=False, match=None) + +*Make `o` a tuple* + +``` python +test_eq(tuplify(None),()) +test_eq(tuplify([1,2,3]),(1,2,3)) +test_eq(tuplify(1,match=[1,2,3]),(1,1,1)) +``` + +------------------------------------------------------------------------ + +source + +### true + +> true (x) + +*Test whether `x` is truthy; collections with \>0 elements are +considered `True`* + +``` python +[(o,true(o)) for o in + (array(0),array(1),array([0]),array([0,1]),1,0,'',None)] +``` + + [(array(0), False), + (array(1), True), + (array([0]), True), + (array([0, 1]), True), + (1, True), + (0, False), + ('', False), + (None, False)] + +------------------------------------------------------------------------ + +source + +### NullType + +> NullType () + +*An object that is `False` and can be called, chained, and indexed* + +``` python +bool(null.hi().there[3]) +``` + + False + +------------------------------------------------------------------------ + +source + +### tonull + +> tonull (x) + +*Convert `None` to `null`* + +``` python +bool(tonull(None).hi().there[3]) +``` + + False + +------------------------------------------------------------------------ + +source + +### get_class + +> get_class (nm, *fld_names, sup=None, doc=None, funcs=None, anno=None, +> **flds) + +*Dynamically create a class, optionally inheriting from `sup`, +containing `fld_names`* + +``` python +_t = get_class('_t', 'a', b=2, anno={'b':int}) +t = _t() +test_eq(t.a, None) +test_eq(t.b, 2) +t = _t(1, b=3) +test_eq(t.a, 1) +test_eq(t.b, 3) +t = _t(1, 3) +test_eq(t.a, 1) +test_eq(t.b, 3) +test_eq(t, pickle.loads(pickle.dumps(t))) +test_eq(_t.__annotations__, {'b':int, 'a':typing.Any}) +repr(t) +``` + + '__main__._t(a=1, b=3)' + +Most often you’ll want to call +[`mk_class`](https://fastcore.fast.ai/basics.html#mk_class), since it +adds the class to your module. See +[`mk_class`](https://fastcore.fast.ai/basics.html#mk_class) for more +details and examples of use (which also apply to +[`get_class`](https://fastcore.fast.ai/basics.html#get_class)). + +------------------------------------------------------------------------ + +source + +### mk_class + +> mk_class (nm, *fld_names, sup=None, doc=None, funcs=None, mod=None, +> anno=None, **flds) + +*Create a class using +[`get_class`](https://fastcore.fast.ai/basics.html#get_class) and add to +the caller’s module* + +Any `kwargs` will be added as class attributes, and `sup` is an optional +(tuple of) base classes. + +``` python +mk_class('_t', a=1, sup=dict) +t = _t() +test_eq(t.a, 1) +assert(isinstance(t,dict)) +``` + +A `__init__` is provided that sets attrs for any `kwargs`, and for any +`args` (matching by position to fields), along with a `__repr__` which +prints all attrs. The docstring is set to `doc`. You can pass `funcs` +which will be added as attrs with the function names. + +``` python +def foo(self): return 1 +mk_class('_t', 'a', sup=dict, doc='test doc', funcs=foo) + +t = _t(3, b=2) +test_eq(t.a, 3) +test_eq(t.b, 2) +test_eq(t.foo(), 1) +test_eq(t.__doc__, 'test doc') +t +``` + + {} + +------------------------------------------------------------------------ + +source + +### wrap_class + +> wrap_class (nm, *fld_names, sup=None, doc=None, funcs=None, **flds) + +*Decorator: makes function a method of a new class `nm` passing +parameters to +[`mk_class`](https://fastcore.fast.ai/basics.html#mk_class)* + +``` python +@wrap_class('_t', a=2) +def bar(self,x): return x+1 + +t = _t() +test_eq(t.a, 2) +test_eq(t.bar(3), 4) +``` + +------------------------------------------------------------------------ + +source + +#### ignore_exceptions + +> ignore_exceptions () + +*Context manager to ignore exceptions* + +``` python +with ignore_exceptions(): + # Exception will be ignored + raise Exception +``` + +------------------------------------------------------------------------ + +source + +### exec_local + +> exec_local (code, var_name) + +*Call `exec` on `code` and return the var `var_name`* + +``` python +test_eq(exec_local("a=1", "a"), 1) +``` + +------------------------------------------------------------------------ + +source + +### risinstance + +> risinstance (types, obj=None) + +*Curried `isinstance` but with args reversed* + +``` python +assert risinstance(int, 1) +assert not risinstance(str, 0) +assert risinstance(int)(1) +assert not risinstance(int)(None) +``` + +`types` can also be strings: + +``` python +assert risinstance(('str','int'), 'a') +assert risinstance('str', 'a') +assert not risinstance('int', 'a') +``` + +------------------------------------------------------------------------ + +source + +### ver2tuple + +> ver2tuple (v:str) + +``` python +test_eq(ver2tuple('3.8.1'), (3,8,1)) +test_eq(ver2tuple('3.1'), (3,1,0)) +test_eq(ver2tuple('3.'), (3,0,0)) +test_eq(ver2tuple('3'), (3,0,0)) +``` + +## NoOp + +These are used when you need a pass-through function. + +------------------------------------------------------------------------ + +### noop + +> noop (x=None, *args, **kwargs) + +*Do nothing* + +``` python +noop() +test_eq(noop(1),1) +``` + +------------------------------------------------------------------------ + +### noops + +> noops (x=None, *args, **kwargs) + +*Do nothing (method)* + +``` python +class _t: foo=noops +test_eq(_t().foo(1),1) +``` + +## Infinite Lists + +These lists are useful for things like padding an array or adding index +column(s) to arrays. + +[`Inf`](https://fastcore.fast.ai/basics.html#inf) defines the following +properties: + +- `count: itertools.count()` +- `zeros: itertools.cycle([0])` +- `ones : itertools.cycle([1])` +- `nones: itertools.cycle([None])` + +``` python +test_eq([o for i,o in zip(range(5), Inf.count)], + [0, 1, 2, 3, 4]) + +test_eq([o for i,o in zip(range(5), Inf.zeros)], + [0]*5) + +test_eq([o for i,o in zip(range(5), Inf.ones)], + [1]*5) + +test_eq([o for i,o in zip(range(5), Inf.nones)], + [None]*5) +``` + +## Operator Functions + +------------------------------------------------------------------------ + +source + +### in\_ + +> in_ (x, a) + +*`True` if `x in a`* + +``` python +# test if element is in another +assert in_('c', ('b', 'c', 'a')) +assert in_(4, [2,3,4,5]) +assert in_('t', 'fastai') +test_fail(in_('h', 'fastai')) + +# use in_ as a partial +assert in_('fastai')('t') +assert in_([2,3,4,5])(4) +test_fail(in_('fastai')('h')) +``` + +In addition to [`in_`](https://fastcore.fast.ai/basics.html#in_), the +following functions are provided matching the behavior of the equivalent +versions in `operator`: *lt gt le ge eq ne add sub mul truediv is\_ +is_not mod*. + +``` python +lt(3,5),gt(3,5),is_(None,None),in_(0,[1,2]),mod(3,2) +``` + + (True, False, True, False, 1) + +Similarly to `_in`, they also have additional functionality: if you only +pass one param, they return a partial function that passes that param as +the second positional parameter. + +``` python +lt(5)(3),gt(5)(3),is_(None)(None),in_([1,2])(0),mod(2)(3) +``` + + (True, False, True, False, 1) + +------------------------------------------------------------------------ + +source + +### ret_true + +> ret_true (*args, **kwargs) + +*Predicate: always `True`* + +``` python +assert ret_true(1,2,3) +assert ret_true(False) +``` + +------------------------------------------------------------------------ + +source + +### ret_false + +> ret_false (*args, **kwargs) + +*Predicate: always `False`* + +------------------------------------------------------------------------ + +source + +### stop + +> stop (e=) + +*Raises exception `e` (by default `StopIteration`)* + +------------------------------------------------------------------------ + +source + +### gen + +> gen (func, seq, cond=) + +*Like `(func(o) for o in seq if cond(func(o)))` but handles +`StopIteration`* + +``` python +test_eq(gen(noop, Inf.count, lt(5)), + range(5)) +test_eq(gen(operator.neg, Inf.count, gt(-5)), + [0,-1,-2,-3,-4]) +test_eq(gen(lambda o:o if o<5 else stop(), Inf.count), + range(5)) +``` + +------------------------------------------------------------------------ + +source + +### chunked + +> chunked (it, chunk_sz=None, drop_last=False, n_chunks=None) + +*Return batches from iterator `it` of size `chunk_sz` (or return +`n_chunks` total)* + +Note that you must pass either `chunk_sz`, or `n_chunks`, but not both. + +``` python +t = list(range(10)) +test_eq(chunked(t,3), [[0,1,2], [3,4,5], [6,7,8], [9]]) +test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ]) + +t = map(lambda o:stop() if o==6 else o, Inf.count) +test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5]]) +t = map(lambda o:stop() if o==7 else o, Inf.count) +test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5], [6]]) + +t = np.arange(10) +test_eq(chunked(t,3), [[0,1,2], [3,4,5], [6,7,8], [9]]) +test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ]) + +test_eq(chunked([], 3), []) +test_eq(chunked([], n_chunks=3), []) +``` + +------------------------------------------------------------------------ + +source + +### otherwise + +> otherwise (x, tst, y) + +*`y if tst(x) else x`* + +``` python +test_eq(otherwise(2+1, gt(3), 4), 3) +test_eq(otherwise(2+1, gt(2), 4), 4) +``` + +## Attribute Helpers + +These functions reduce boilerplate when setting or manipulating +attributes or properties of objects. + +------------------------------------------------------------------------ + +source + +### custom_dir + +> custom_dir (c, add) + +*Implement custom `__dir__`, adding `add` to `cls`* + +[`custom_dir`](https://fastcore.fast.ai/basics.html#custom_dir) allows +you extract the [`__dict__` property of a +class](https://stackoverflow.com/questions/19907442/explain-dict-attribute) +and appends the list `add` to it. + +``` python +class _T: + def f(): pass + +s = custom_dir(_T(), add=['foo', 'bar']) +assert {'foo', 'bar', 'f'}.issubset(s) +``` + +------------------------------------------------------------------------ + +source + +### AttrDict + +*`dict` subclass that also provides access to keys as attrs* + +``` python +d = AttrDict(a=1,b="two") +test_eq(d.a, 1) +test_eq(d['b'], 'two') +test_eq(d.get('c','nope'), 'nope') +d.b = 2 +test_eq(d.b, 2) +test_eq(d['b'], 2) +d['b'] = 3 +test_eq(d['b'], 3) +test_eq(d.b, 3) +assert 'a' in dir(d) +``` + +[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict) will pretty +print in Jupyter Notebooks: + +``` python +_test_dict = {'a':1, 'b': {'c':1, 'd':2}, 'c': {'c':1, 'd':2}, 'd': {'c':1, 'd':2}, + 'e': {'c':1, 'd':2}, 'f': {'c':1, 'd':2, 'e': 4, 'f':[1,2,3,4,5]}} +AttrDict(_test_dict) +``` + +``` json +{ 'a': 1, + 'b': {'c': 1, 'd': 2}, + 'c': {'c': 1, 'd': 2}, + 'd': {'c': 1, 'd': 2}, + 'e': {'c': 1, 'd': 2}, + 'f': {'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}} +``` + +------------------------------------------------------------------------ + +source + +### AttrDictDefault + +> AttrDictDefault (*args, default_=None, **kwargs) + +*[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict) subclass +that returns `None` for missing attrs* + +``` python +d = AttrDictDefault(a=1,b="two", default_='nope') +test_eq(d.a, 1) +test_eq(d['b'], 'two') +test_eq(d.c, 'nope') +``` + +------------------------------------------------------------------------ + +source + +### NS + +*`SimpleNamespace` subclass that also adds `iter` and `dict` support* + +This is very similar to +[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict), but since +it starts with `SimpleNamespace`, it has some differences in behavior. +You can use it just like `SimpleNamespace`: + +``` python +d = NS(**_test_dict) +d +``` + + namespace(a=1, + b={'c': 1, 'd': 2}, + c={'c': 1, 'd': 2}, + d={'c': 1, 'd': 2}, + e={'c': 1, 'd': 2}, + f={'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}) + +…but you can also index it to get/set: + +``` python +d['a'] +``` + + 1 + +…and iterate t: + +``` python +list(d) +``` + + ['a', 'b', 'c', 'd', 'e', 'f'] + +------------------------------------------------------------------------ + +source + +### get_annotations_ex + +> get_annotations_ex (obj, globals=None, locals=None) + +*Backport of py3.10 `get_annotations` that returns globals/locals* + +In Python 3.10 `inspect.get_annotations` was added. However previous +versions of Python are unable to evaluate type annotations correctly if +`from future import __annotations__` is used. Furthermore, *all* +annotations are evaluated, even if only some subset are needed. +[`get_annotations_ex`](https://fastcore.fast.ai/basics.html#get_annotations_ex) +provides the same functionality as `inspect.get_annotations`, but works +on earlier versions of Python, and returns the `globals` and `locals` +needed to evaluate types. + +------------------------------------------------------------------------ + +source + +### eval_type + +> eval_type (t, glb, loc) + +*`eval` a type or collection of types, if needed, for annotations in +py3.10+* + +In py3.10, or if `from future import __annotations__` is used, `a` is a +`str`: + +``` python +class _T2a: pass +def func(a: _T2a): pass +ann,glb,loc = get_annotations_ex(func) + +eval_type(ann['a'], glb, loc) +``` + + __main__._T2a + +`|` is supported for defining `Union` types when using +[`eval_type`](https://fastcore.fast.ai/basics.html#eval_type) even for +python versions prior to 3.9: + +``` python +class _T2b: pass +def func(a: _T2a|_T2b): pass +ann,glb,loc = get_annotations_ex(func) + +eval_type(ann['a'], glb, loc) +``` + + typing.Union[__main__._T2a, __main__._T2b] + +------------------------------------------------------------------------ + +source + +### type_hints + +> type_hints (f) + +*Like `typing.get_type_hints` but returns `{}` if not allowed type* + +Below is a list of allowed types for type hints in python: + +``` python +list(typing._allowed_types) +``` + + [function, + builtin_function_or_method, + method, + module, + wrapper_descriptor, + method-wrapper, + method_descriptor] + +For example, type `func` is allowed so +[`type_hints`](https://fastcore.fast.ai/basics.html#type_hints) returns +the same value as `typing.get_hints`: + +``` python +def f(a:int)->bool: ... # a function with type hints (allowed) +exp = {'a':int,'return':bool} +test_eq(type_hints(f), typing.get_type_hints(f)) +test_eq(type_hints(f), exp) +``` + +However, `class` is not an allowed type, so +[`type_hints`](https://fastcore.fast.ai/basics.html#type_hints) returns +`{}`: + +``` python +class _T: + def __init__(self, a:int=0)->bool: ... +assert not type_hints(_T) +``` + +------------------------------------------------------------------------ + +source + +### annotations + +> annotations (o) + +*Annotations for `o`, or `type(o)`* + +This supports a wider range of situations than +[`type_hints`](https://fastcore.fast.ai/basics.html#type_hints), by +checking `type()` and `__init__` for annotations too: + +``` python +for o in _T,_T(),_T.__init__,f: test_eq(annotations(o), exp) +assert not annotations(int) +assert not annotations(print) +``` + +------------------------------------------------------------------------ + +source + +### anno_ret + +> anno_ret (func) + +*Get the return annotation of `func`* + +``` python +def f(x) -> float: return x +test_eq(anno_ret(f), float) + +def f(x) -> typing.Tuple[float,float]: return x +assert anno_ret(f)==typing.Tuple[float,float] +``` + +If your return annotation is `None`, +[`anno_ret`](https://fastcore.fast.ai/basics.html#anno_ret) will return +`NoneType` (and not `None`): + +``` python +def f(x) -> None: return x + +test_eq(anno_ret(f), NoneType) +assert anno_ret(f) is not None # returns NoneType instead of None +``` + +If your function does not have a return type, or if you pass in `None` +instead of a function, then +[`anno_ret`](https://fastcore.fast.ai/basics.html#anno_ret) returns +`None`: + +``` python +def f(x): return x + +test_eq(anno_ret(f), None) +test_eq(anno_ret(None), None) # instead of passing in a func, pass in None +``` + +------------------------------------------------------------------------ + +source + +### signature_ex + +> signature_ex (obj, eval_str:bool=False) + +*Backport of `inspect.signature(..., eval_str=True` to \source + +### union2tuple + +> union2tuple (t) + +``` python +test_eq(union2tuple(Union[int,str]), (int,str)) +test_eq(union2tuple(int), int) +assert union2tuple(Tuple[int,str])==Tuple[int,str] +test_eq(union2tuple((int,str)), (int,str)) +if UnionType: test_eq(union2tuple(int|str), (int,str)) +``` + +------------------------------------------------------------------------ + +source + +### argnames + +> argnames (f, frame=False) + +*Names of arguments to function or frame `f`* + +``` python +test_eq(argnames(f), ['x']) +``` + +------------------------------------------------------------------------ + +source + +### with_cast + +> with_cast (f) + +*Decorator which uses any parameter annotations as preprocessing +functions* + +``` python +@with_cast +def _f(a, b:Path, c:str='', d=0): return (a,b,c,d) + +test_eq(_f(1, '.', 3), (1,Path('.'),'3',0)) +test_eq(_f(1, '.'), (1,Path('.'),'',0)) + +@with_cast +def _g(a:int=0)->str: return a + +test_eq(_g(4.0), '4') +test_eq(_g(4.4), '4') +test_eq(_g(2), '2') +``` + +------------------------------------------------------------------------ + +source + +### store_attr + +> store_attr (names=None, but='', cast=False, store_args=None, **attrs) + +*Store params named in comma-separated `names` from calling context into +attrs in `self`* + +In it’s most basic form, you can use +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr) to +shorten code like this: + +``` python +class T: + def __init__(self, a,b,c): self.a,self.b,self.c = a,b,c +``` + +…to this: + +``` python +class T: + def __init__(self, a,b,c): store_attr('a,b,c', self) +``` + +This class behaves as if we’d used the first form: + +``` python +t = T(1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 +``` + +``` python +class T1: + def __init__(self, a,b,c): store_attr() +``` + +In addition, it stores the attrs as a `dict` in `__stored_args__`, which +you can use for display, logging, and so forth. + +``` python +test_eq(t.__stored_args__, {'a':1, 'b':3, 'c':2}) +``` + +Since you normally want to use the first argument (often called `self`) +for storing attributes, it’s optional: + +``` python +class T: + def __init__(self, a,b,c:str): store_attr('a,b,c') + +t = T(1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 +``` + +With `cast=True` any parameter annotations will be used as preprocessing +functions for the corresponding arguments: + +``` python +class T: + def __init__(self, a:listify, b, c:str): store_attr('a,b,c', cast=True) + +t = T(1,c=2,b=3) +assert t.a==[1] and t.b==3 and t.c=='2' +``` + +You can inherit from a class using +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr), and +just call it again to add in any new attributes added in the derived +class: + +``` python +class T2(T): + def __init__(self, d, **kwargs): + super().__init__(**kwargs) + store_attr('d') + +t = T2(d=1,a=2,b=3,c=4) +assert t.a==2 and t.b==3 and t.c==4 and t.d==1 +``` + +You can skip passing a list of attrs to store. In this case, all +arguments passed to the method are stored: + +``` python +class T: + def __init__(self, a,b,c): store_attr() + +t = T(1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 +``` + +``` python +class T4(T): + def __init__(self, d, **kwargs): + super().__init__(**kwargs) + store_attr() + +t = T4(4, a=1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 and t.d==4 +``` + +``` python +class T4: + def __init__(self, *, a: int, b: float = 1): + store_attr() + +t = T4(a=3) +assert t.a==3 and t.b==1 +t = T4(a=3, b=2) +assert t.a==3 and t.b==2 +``` + +You can skip some attrs by passing `but`: + +``` python +class T: + def __init__(self, a,b,c): store_attr(but='a') + +t = T(1,c=2,b=3) +assert t.b==3 and t.c==2 +assert not hasattr(t,'a') +``` + +You can also pass keywords to +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr), which +is identical to setting the attrs directly, but also stores them in +`__stored_args__`. + +``` python +class T: + def __init__(self): store_attr(a=1) + +t = T() +assert t.a==1 +``` + +You can also use store_attr inside functions. + +``` python +def create_T(a, b): + t = SimpleNamespace() + store_attr(self=t) + return t + +t = create_T(a=1, b=2) +assert t.a==1 and t.b==2 +``` + +------------------------------------------------------------------------ + +source + +### attrdict + +> attrdict (o, *ks, default=None) + +*Dict from each `k` in `ks` to `getattr(o,k)`* + +``` python +class T: + def __init__(self, a,b,c): store_attr() + +t = T(1,c=2,b=3) +test_eq(attrdict(t,'b','c'), {'b':3, 'c':2}) +``` + +------------------------------------------------------------------------ + +source + +### properties + +> properties (cls, *ps) + +*Change attrs in `cls` with names in `ps` to properties* + +``` python +class T: + def a(self): return 1 + def b(self): return 2 +properties(T,'a') + +test_eq(T().a,1) +test_eq(T().b(),2) +``` + +------------------------------------------------------------------------ + +source + +### camel2words + +> camel2words (s, space=' ') + +*Convert CamelCase to ‘spaced words’* + +``` python +test_eq(camel2words('ClassAreCamel'), 'Class Are Camel') +``` + +------------------------------------------------------------------------ + +source + +### camel2snake + +> camel2snake (name) + +*Convert CamelCase to snake_case* + +``` python +test_eq(camel2snake('ClassAreCamel'), 'class_are_camel') +test_eq(camel2snake('Already_Snake'), 'already__snake') +``` + +------------------------------------------------------------------------ + +source + +### snake2camel + +> snake2camel (s) + +*Convert snake_case to CamelCase* + +``` python +test_eq(snake2camel('a_b_cc'), 'ABCc') +``` + +------------------------------------------------------------------------ + +source + +### class2attr + +> class2attr (cls_name) + +*Return the snake-cased name of the class; strip ending `cls_name` if it +exists.* + +``` python +class Parent: + @property + def name(self): return class2attr(self, 'Parent') + +class ChildOfParent(Parent): pass +class ParentChildOf(Parent): pass + +p = Parent() +cp = ChildOfParent() +cp2 = ParentChildOf() + +test_eq(p.name, 'parent') +test_eq(cp.name, 'child_of') +test_eq(cp2.name, 'parent_child_of') +``` + +------------------------------------------------------------------------ + +source + +### getcallable + +> getcallable (o, attr) + +*Calls `getattr` with a default of `noop`* + +``` python +class Math: + def addition(self,a,b): return a+b + +m = Math() + +test_eq(getcallable(m, "addition")(a=1,b=2), 3) +test_eq(getcallable(m, "subtraction")(a=1,b=2), None) +``` + +------------------------------------------------------------------------ + +source + +### getattrs + +> getattrs (o, *attrs, default=None) + +*List of all `attrs` in `o`* + +``` python +from fractions import Fraction +``` + +``` python +getattrs(Fraction(1,2), 'numerator', 'denominator') +``` + + [1, 2] + +------------------------------------------------------------------------ + +source + +### hasattrs + +> hasattrs (o, attrs) + +*Test whether `o` contains all `attrs`* + +``` python +assert hasattrs(1,('imag','real')) +assert not hasattrs(1,('imag','foo')) +``` + +------------------------------------------------------------------------ + +source + +### setattrs + +> setattrs (dest, flds, src) + +``` python +d = dict(a=1,bb="2",ignore=3) +o = SimpleNamespace() +setattrs(o, "a,bb", d) +test_eq(o.a, 1) +test_eq(o.bb, "2") +``` + +``` python +d = SimpleNamespace(a=1,bb="2",ignore=3) +o = SimpleNamespace() +setattrs(o, "a,bb", d) +test_eq(o.a, 1) +test_eq(o.bb, "2") +``` + +------------------------------------------------------------------------ + +source + +### try_attrs + +> try_attrs (obj, *attrs) + +*Return first attr that exists in `obj`* + +``` python +test_eq(try_attrs(1, 'real'), 1) +test_eq(try_attrs(1, 'foobar', 'real'), 1) +``` + +## Attribute Delegation + +------------------------------------------------------------------------ + +source + +### GetAttrBase + +> GetAttrBase () + +*Basic delegation of +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) and +`__dir__`* + +------------------------------------------------------------------------ + +source + +#### GetAttr + +> GetAttr () + +*Inherit from this to have all attr accesses in `self._xtra` passed down +to `self.default`* + +Inherit from [`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) +to have attr access passed down to an instance attribute. This makes it +easy to create composites that don’t require callers to know about their +components. For a more detailed discussion of how this works as well as +relevant context, we suggest reading the [delegated composition section +of this blog article](https://www.fast.ai/2019/08/06/delegation/). + +You can customise the behaviour of +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) in subclasses +via; - `_default` - By default, this is set to `'default'`, so attr +access is passed down to `self.default` - `_default` can be set to the +name of any instance attribute that does not start with dunder `__` - +`_xtra` - By default, this is `None`, so all attr access is passed +down - You can limit which attrs get passed down by setting `_xtra` to a +list of attribute names + +To illuminate the utility of +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr), suppose we +have the following two classes, `_WebPage` which is a superclass of +`_ProductPage`, which we wish to compose like so: + +``` python +class _WebPage: + def __init__(self, title, author="Jeremy"): + self.title,self.author = title,author + +class _ProductPage: + def __init__(self, page, price): self.page,self.price = page,price + +page = _WebPage('Soap', author="Sylvain") +p = _ProductPage(page, 15.0) +``` + +How do we make it so we can just write `p.author`, instead of +`p.page.author` to access the `author` attribute? We can use +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr), of course! +First, we subclass +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) when defining +`_ProductPage`. Next, we set `self.default` to the object whose +attributes we want to be able to access directly, which in this case is +the `page` argument passed on initialization: + +``` python +class _ProductPage(GetAttr): + def __init__(self, page, price): self.default,self.price = page,price #self.default allows you to access page directly. + +p = _ProductPage(page, 15.0) +``` + +Now, we can access the `author` attribute directly from the instance: + +``` python +test_eq(p.author, 'Sylvain') +``` + +If you wish to store the object you are composing in an attribute other +than `self.default`, you can set the class attribute `_data` as shown +below. This is useful in the case where you might have a name collision +with `self.default`: + +``` python +class _C(GetAttr): + _default = '_data' # use different component name; `self._data` rather than `self.default` + def __init__(self,a): self._data = a + def foo(self): noop + +t = _C('Hi') +test_eq(t._data, 'Hi') +test_fail(lambda: t.default) # we no longer have self.default +test_eq(t.lower(), 'hi') +test_eq(t.upper(), 'HI') +assert 'lower' in dir(t) +assert 'upper' in dir(t) +``` + +By default, all attributes and methods of the object you are composing +are retained. In the below example, we compose a `str` object with the +class `_C`. This allows us to directly call string methods on instances +of class `_C`, such as `str.lower()` or `str.upper()`: + +``` python +class _C(GetAttr): + # allow all attributes and methods to get passed to `self.default` (by leaving _xtra=None) + def __init__(self,a): self.default = a + def foo(self): noop + +t = _C('Hi') +test_eq(t.lower(), 'hi') +test_eq(t.upper(), 'HI') +assert 'lower' in dir(t) +assert 'upper' in dir(t) +``` + +However, you can choose which attributes or methods to retain by +defining a class attribute `_xtra`, which is a list of allowed attribute +and method names to delegate. In the below example, we only delegate the +`lower` method from the composed `str` object when defining class `_C`: + +``` python +class _C(GetAttr): + _xtra = ['lower'] # specify which attributes get passed to `self.default` + def __init__(self,a): self.default = a + def foo(self): noop + +t = _C('Hi') +test_eq(t.default, 'Hi') +test_eq(t.lower(), 'hi') +test_fail(lambda: t.upper()) # upper wasn't in _xtra, so it isn't available to be called +assert 'lower' in dir(t) +assert 'upper' not in dir(t) +``` + +You must be careful to properly set an instance attribute in `__init__` +that corresponds to the class attribute `_default`. The below example +sets the class attribute `_default` to `data`, but erroneously fails to +define `self.data` (and instead defines `self.default`). + +Failing to properly set instance attributes leads to errors when you try +to access methods directly: + +``` python +class _C(GetAttr): + _default = 'data' # use a bad component name; i.e. self.data does not exist + def __init__(self,a): self.default = a + def foo(self): noop + +# TODO: should we raise an error when we create a new instance ... +t = _C('Hi') +test_eq(t.default, 'Hi') +# ... or is it enough for all GetAttr features to raise errors +test_fail(lambda: t.data) +test_fail(lambda: t.lower()) +test_fail(lambda: t.upper()) +test_fail(lambda: dir(t)) +``` + +------------------------------------------------------------------------ + +source + +### delegate_attr + +> delegate_attr (k, to) + +*Use in [`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) +to delegate to attr `to` without inheriting from +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr)* + +[`delegate_attr`](https://fastcore.fast.ai/basics.html#delegate_attr) is +a functional way to delegate attributes, and is an alternative to +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr). We recommend +reading the documentation of +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) for more +details around delegation. + +You can use achieve delegation when you define +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) by using +[`delegate_attr`](https://fastcore.fast.ai/basics.html#delegate_attr): + +``` python +class _C: + def __init__(self, o): self.o = o # self.o corresponds to the `to` argument in delegate_attr. + def __getattr__(self, k): return delegate_attr(self, k, to='o') + + +t = _C('HELLO') # delegates to a string +test_eq(t.lower(), 'hello') + +t = _C(np.array([5,4,3])) # delegates to a numpy array +test_eq(t.sum(), 12) + +t = _C(pd.DataFrame({'a': [1,2], 'b': [3,4]})) # delegates to a pandas.DataFrame +test_eq(t.b.max(), 4) +``` + +## Extensible Types + +[`ShowPrint`](https://fastcore.fast.ai/basics.html#showprint) is a base +class that defines a `show` method, which is used primarily for +callbacks in fastai that expect this method to be defined. + +[`Int`](https://fastcore.fast.ai/basics.html#int), +[`Float`](https://fastcore.fast.ai/basics.html#float), and +[`Str`](https://fastcore.fast.ai/basics.html#str) extend `int`, `float` +and `str` respectively by adding an additional `show` method by +inheriting from +[`ShowPrint`](https://fastcore.fast.ai/basics.html#showprint). + +The code for [`Int`](https://fastcore.fast.ai/basics.html#int) is shown +below: + +Examples: + +``` python +Int(0).show() +Float(2.0).show() +Str('Hello').show() +``` + + 0 + 2.0 + Hello + +## Collection functions + +Functions that manipulate popular python collections. + +------------------------------------------------------------------------ + +source + +### partition + +> partition (coll, f) + +*Partition a collection by a predicate* + +``` python +ts,fs = partition(range(10), mod(2)) +test_eq(fs, [0,2,4,6,8]) +test_eq(ts, [1,3,5,7,9]) +``` + +------------------------------------------------------------------------ + +source + +### flatten + +> flatten (o) + +*Concatenate all collections and items as a generator* + +------------------------------------------------------------------------ + +source + +### concat + +> concat (colls) + +*Concatenate all collections and items as a list* + +``` python +concat([(o for o in range(2)),[2,3,4], 5]) +``` + + [0, 1, 2, 3, 4, 5] + +``` python +concat([["abc", "xyz"], ["foo", "bar"]]) +``` + + ['abc', 'xyz', 'foo', 'bar'] + +------------------------------------------------------------------------ + +source + +### strcat + +> strcat (its, sep:str='') + +*Concatenate stringified items `its`* + +``` python +test_eq(strcat(['a',2]), 'a2') +test_eq(strcat(['a',2], ';'), 'a;2') +``` + +------------------------------------------------------------------------ + +source + +### detuplify + +> detuplify (x) + +*If `x` is a tuple with one thing, extract it* + +``` python +test_eq(detuplify(()),None) +test_eq(detuplify([1]),1) +test_eq(detuplify([1,2]), [1,2]) +test_eq(detuplify(np.array([[1,2]])), np.array([[1,2]])) +``` + +------------------------------------------------------------------------ + +source + +### replicate + +> replicate (item, match) + +*Create tuple of `item` copied `len(match)` times* + +``` python +t = [1,1] +test_eq(replicate([1,2], t),([1,2],[1,2])) +test_eq(replicate(1, t),(1,1)) +``` + +------------------------------------------------------------------------ + +source + +### setify + +> setify (o) + +*Turn any list like-object into a set.* + +``` python +# test +test_eq(setify(None),set()) +test_eq(setify('abc'),{'abc'}) +test_eq(setify([1,2,2]),{1,2}) +test_eq(setify(range(0,3)),{0,1,2}) +test_eq(setify({1,2}),{1,2}) +``` + +------------------------------------------------------------------------ + +source + +### merge + +> merge (*ds) + +*Merge all dictionaries in `ds`* + +``` python +test_eq(merge(), {}) +test_eq(merge(dict(a=1,b=2)), dict(a=1,b=2)) +test_eq(merge(dict(a=1,b=2), dict(b=3,c=4), None), dict(a=1, b=3, c=4)) +``` + +------------------------------------------------------------------------ + +source + +### range_of + +> range_of (x) + +*All indices of collection `x` (i.e. `list(range(len(x)))`)* + +``` python +test_eq(range_of([1,1,1,1]), [0,1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### groupby + +> groupby (x, key, val=) + +*Like `itertools.groupby` but doesn’t need to be sorted, and isn’t lazy, +plus some extensions* + +``` python +test_eq(groupby('aa ab bb'.split(), itemgetter(0)), {'a':['aa','ab'], 'b':['bb']}) +``` + +Here’s an example of how to *invert* a grouping, using an `int` as `key` +(which uses `itemgetter`; passing a `str` will use `attrgetter`), and +using a `val` function: + +``` python +d = {0: [1, 3, 7], 2: [3], 3: [5], 4: [8], 5: [4], 7: [5]} +groupby(((o,k) for k,v in d.items() for o in v), 0, 1) +``` + + {1: [0], 3: [0, 2], 7: [0], 5: [3, 7], 8: [4], 4: [5]} + +------------------------------------------------------------------------ + +source + +### last_index + +> last_index (x, o) + +*Finds the last index of occurence of `x` in `o` (returns -1 if no +occurence)* + +``` python +test_eq(last_index(9, [1, 2, 9, 3, 4, 9, 10]), 5) +test_eq(last_index(6, [1, 2, 9, 3, 4, 9, 10]), -1) +``` + +------------------------------------------------------------------------ + +source + +### filter_dict + +> filter_dict (d, func) + +*Filter a `dict` using `func`, applied to keys and values* + +``` python +letters = {o:chr(o) for o in range(65,73)} +letters +``` + + {65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H'} + +``` python +filter_dict(letters, lambda k,v: k<67 or v in 'FG') +``` + + {65: 'A', 66: 'B', 70: 'F', 71: 'G'} + +------------------------------------------------------------------------ + +source + +### filter_keys + +> filter_keys (d, func) + +*Filter a `dict` using `func`, applied to keys* + +``` python +filter_keys(letters, lt(67)) +``` + + {65: 'A', 66: 'B'} + +------------------------------------------------------------------------ + +source + +### filter_values + +> filter_values (d, func) + +*Filter a `dict` using `func`, applied to values* + +``` python +filter_values(letters, in_('FG')) +``` + + {70: 'F', 71: 'G'} + +------------------------------------------------------------------------ + +source + +### cycle + +> cycle (o) + +*Like `itertools.cycle` except creates list of `None`s if `o` is empty* + +``` python +test_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2]) +test_eq(itertools.islice(cycle([]),3), [None]*3) +test_eq(itertools.islice(cycle(None),3), [None]*3) +test_eq(itertools.islice(cycle(1),3), [1,1,1]) +``` + +------------------------------------------------------------------------ + +source + +### zip_cycle + +> zip_cycle (x, *args) + +*Like `itertools.zip_longest` but +[`cycle`](https://fastcore.fast.ai/foundation.html#cycle)s through +elements of all but first argument* + +``` python +test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')]) +``` + +------------------------------------------------------------------------ + +source + +### sorted_ex + +> sorted_ex (iterable, key=None, reverse=False) + +*Like `sorted`, but if key is str use `attrgetter`; if int use +`itemgetter`* + +------------------------------------------------------------------------ + +source + +### not\_ + +> not_ (f) + +*Create new function that negates result of `f`* + +``` python +def f(a): return a>0 +test_eq(f(1),True) +test_eq(not_(f)(1),False) +test_eq(not_(f)(a=-1),True) +``` + +------------------------------------------------------------------------ + +source + +### argwhere + +> argwhere (iterable, f, negate=False, **kwargs) + +*Like [`filter_ex`](https://fastcore.fast.ai/basics.html#filter_ex), but +return indices for matching items* + +------------------------------------------------------------------------ + +source + +### filter_ex + +> filter_ex (iterable, f=, negate=False, gen=False, +> **kwargs) + +*Like `filter`, but passing `kwargs` to `f`, defaulting `f` to `noop`, +and adding `negate` and +[`gen`](https://fastcore.fast.ai/basics.html#gen)* + +------------------------------------------------------------------------ + +source + +### range_of + +> range_of (a, b=None, step=None) + +*All indices of collection `a`, if `a` is a collection, otherwise +`range`* + +``` python +test_eq(range_of([1,1,1,1]), [0,1,2,3]) +test_eq(range_of(4), [0,1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### renumerate + +> renumerate (iterable, start=0) + +*Same as `enumerate`, but returns index as 2nd element instead of 1st* + +``` python +test_eq(renumerate('abc'), (('a',0),('b',1),('c',2))) +``` + +------------------------------------------------------------------------ + +source + +### first + +> first (x, f=None, negate=False, **kwargs) + +*First element of `x`, optionally filtered by `f`, or None if missing* + +``` python +test_eq(first(['a', 'b', 'c', 'd', 'e']), 'a') +test_eq(first([False]), False) +test_eq(first([False], noop), None) +``` + +------------------------------------------------------------------------ + +source + +### only + +> only (o) + +*Return the only item of `o`, raise if `o` doesn’t have exactly one +item* + +------------------------------------------------------------------------ + +source + +### nested_attr + +> nested_attr (o, attr, default=None) + +*Same as `getattr`, but if `attr` includes a `.`, then looks inside +nested objects* + +``` python +class CustomIndexable: + def __init__(self): self.data = {'a':1,'b':'v','c':{'d':5}} + def __getitem__(self, key): return self.data[key] + +custom_indexable = CustomIndexable() +test_eq(nested_attr(custom_indexable,'a'),1) +test_eq(nested_attr(custom_indexable,'c.d'),5) +test_eq(nested_attr(custom_indexable,'e'),None) +``` + +class TestObj: def **init**(self): self.nested = {‘key’: \[1, 2, +{‘inner’: ‘value’}\]} test_obj = TestObj() + +test_eq(nested_attr(test_obj, ‘nested.key.2.inner’),‘value’) +test_eq(nested_attr(\[1, 2, 3\], ‘1’),2) + +``` python +b = {'a':1,'b':'v','c':{'d':5}} +test_eq(nested_attr(b,'b'),'v') +test_eq(nested_attr(b,'c.d'),5) +``` + +``` python +a = SimpleNamespace(b=(SimpleNamespace(c=1))) +test_eq(nested_attr(a, 'b.c'), getattr(getattr(a, 'b'), 'c')) +test_eq(nested_attr(a, 'b.d'), None) +test_eq(nested_attr(b, 'a'), 1) +``` + +------------------------------------------------------------------------ + +source + +### nested_setdefault + +> nested_setdefault (o, attr, default) + +*Same as `setdefault`, but if `attr` includes a `.`, then looks inside +nested objects* + +------------------------------------------------------------------------ + +source + +### nested_callable + +> nested_callable (o, attr) + +*Same as +[`nested_attr`](https://fastcore.fast.ai/basics.html#nested_attr) but if +not found will return `noop`* + +``` python +a = SimpleNamespace(b=(SimpleNamespace(c=1))) +test_eq(nested_callable(a, 'b.c'), getattr(getattr(a, 'b'), 'c')) +test_eq(nested_callable(a, 'b.d'), noop) +``` + +------------------------------------------------------------------------ + +source + +### nested_idx + +> nested_idx (coll, *idxs) + +*Index into nested collections, dicts, etc, with `idxs`* + +``` python +a = {'b':[1,{'c':2}]} +test_eq(nested_idx(a, 'nope'), None) +test_eq(nested_idx(a, 'nope', 'nup'), None) +test_eq(nested_idx(a, 'b', 3), None) +test_eq(nested_idx(a), a) +test_eq(nested_idx(a, 'b'), [1,{'c':2}]) +test_eq(nested_idx(a, 'b', 1), {'c':2}) +test_eq(nested_idx(a, 'b', 1, 'c'), 2) +``` + +``` python +a = SimpleNamespace(b=[1,{'c':2}]) +test_eq(nested_idx(a, 'nope'), None) +test_eq(nested_idx(a, 'nope', 'nup'), None) +test_eq(nested_idx(a, 'b', 3), None) +test_eq(nested_idx(a), a) +test_eq(nested_idx(a, 'b'), [1,{'c':2}]) +test_eq(nested_idx(a, 'b', 1), {'c':2}) +test_eq(nested_idx(a, 'b', 1, 'c'), 2) +``` + +------------------------------------------------------------------------ + +source + +### set_nested_idx + +> set_nested_idx (coll, value, *idxs) + +*Set value indexed like \`nested_idx* + +``` python +set_nested_idx(a, 3, 'b', 0) +test_eq(nested_idx(a, 'b', 0), 3) +``` + +------------------------------------------------------------------------ + +source + +### val2idx + +> val2idx (x) + +*Dict from value to index* + +``` python +test_eq(val2idx([1,2,3]), {3:2,1:0,2:1}) +``` + +------------------------------------------------------------------------ + +source + +### uniqueify + +> uniqueify (x, sort=False, bidir=False, start=None) + +*Unique elements in `x`, optional `sort`, optional return reverse +correspondence, optional prepend with elements.* + +``` python +t = [1,1,0,5,0,3] +test_eq(uniqueify(t),[1,0,5,3]) +test_eq(uniqueify(t, sort=True),[0,1,3,5]) +test_eq(uniqueify(t, start=[7,8,6]), [7,8,6,1,0,5,3]) +v,o = uniqueify(t, bidir=True) +test_eq(v,[1,0,5,3]) +test_eq(o,{1:0, 0: 1, 5: 2, 3: 3}) +v,o = uniqueify(t, sort=True, bidir=True) +test_eq(v,[0,1,3,5]) +test_eq(o,{0:0, 1: 1, 3: 2, 5: 3}) +``` + +------------------------------------------------------------------------ + +source + +### loop_first_last + +> loop_first_last (values) + +*Iterate and generate a tuple with a flag for first and last value.* + +``` python +test_eq(loop_first_last(range(3)), [(True,False,0), (False,False,1), (False,True,2)]) +``` + +------------------------------------------------------------------------ + +source + +### loop_first + +> loop_first (values) + +*Iterate and generate a tuple with a flag for first value.* + +``` python +test_eq(loop_first(range(3)), [(True,0), (False,1), (False,2)]) +``` + +------------------------------------------------------------------------ + +source + +### loop_last + +> loop_last (values) + +*Iterate and generate a tuple with a flag for last value.* + +``` python +test_eq(loop_last(range(3)), [(False,0), (False,1), (True,2)]) +``` + +------------------------------------------------------------------------ + +source + +### first_match + +> first_match (lst, f, default=None) + +*First element of `lst` matching predicate `f`, or `default` if none* + +``` python +a = [0,2,4,5,6,7,10] +test_eq(first_match(a, lambda o:o%2), 3) +``` + +------------------------------------------------------------------------ + +source + +### last_match + +> last_match (lst, f, default=None) + +*Last element of `lst` matching predicate `f`, or `default` if none* + +``` python +test_eq(last_match(a, lambda o:o%2), 5) +``` + +## fastuple + +A tuple with extended functionality. + +------------------------------------------------------------------------ + +source + +#### fastuple + +> fastuple (x=None, *rest) + +*A `tuple` with elementwise ops and more friendly **init** behavior* + +#### Friendly init behavior + +Common failure modes when trying to initialize a tuple in python: + +``` py +tuple(3) +> TypeError: 'int' object is not iterable +``` + +or + +``` py +tuple(3, 4) +> TypeError: tuple expected at most 1 arguments, got 2 +``` + +However, [`fastuple`](https://fastcore.fast.ai/basics.html#fastuple) +allows you to define tuples like this and in the usual way: + +``` python +test_eq(fastuple(3), (3,)) +test_eq(fastuple(3,4), (3, 4)) +test_eq(fastuple((3,4)), (3, 4)) +``` + +#### Elementwise operations + +------------------------------------------------------------------------ + +source + +##### fastuple.add + +> fastuple.add (*args) + +*`+` is already defined in `tuple` for concat, so use `add` instead* + +``` python +test_eq(fastuple.add((1,1),(2,2)), (3,3)) +test_eq_type(fastuple(1,1).add(2), fastuple(3,3)) +test_eq(fastuple('1','2').add('2'), fastuple('12','22')) +``` + +------------------------------------------------------------------------ + +source + +##### fastuple.mul + +> fastuple.mul (*args) + +*`*` is already defined in `tuple` for replicating, so use `mul` +instead* + +``` python +test_eq_type(fastuple(1,1).mul(2), fastuple(2,2)) +``` + +#### Other Elementwise Operations + +Additionally, the following elementwise operations are available: - +`le`: less than or equal - `eq`: equal - `gt`: greater than - `min`: +minimum of + +``` python +test_eq(fastuple(3,1).le(1), (False, True)) +test_eq(fastuple(3,1).eq(1), (False, True)) +test_eq(fastuple(3,1).gt(1), (True, False)) +test_eq(fastuple(3,1).min(2), (2,1)) +``` + +You can also do other elementwise operations like negate a +[`fastuple`](https://fastcore.fast.ai/basics.html#fastuple), or subtract +two [`fastuple`](https://fastcore.fast.ai/basics.html#fastuple)s: + +``` python +test_eq(-fastuple(1,2), (-1,-2)) +test_eq(~fastuple(1,0,1), (False,True,False)) + +test_eq(fastuple(1,1)-fastuple(2,2), (-1,-1)) +``` + +``` python +test_eq(type(fastuple(1)), fastuple) +test_eq_type(fastuple(1,2), fastuple(1,2)) +test_ne(fastuple(1,2), fastuple(1,3)) +test_eq(fastuple(), ()) +``` + +## Functions on Functions + +Utilities for functional programming or for defining, modifying, or +debugging functions. + +------------------------------------------------------------------------ + +source + +### bind + +> bind (func, *pargs, **pkwargs) + +*Same as `partial`, except you can use `arg0` `arg1` etc param +placeholders* + +[`bind`](https://fastcore.fast.ai/basics.html#bind) is the same as +`partial`, but also allows you to reorder positional arguments using +variable name(s) `arg{i}` where i refers to the zero-indexed positional +argument. [`bind`](https://fastcore.fast.ai/basics.html#bind) as +implemented currently only supports reordering of up to the first 5 +positional arguments. + +Consider the function `myfunc` below, which has 3 positional arguments. +These arguments can be referenced as `arg0`, `arg1`, and `arg1`, +respectively. + +``` python +def myfn(a,b,c,d=1,e=2): return(a,b,c,d,e) +``` + +In the below example we bind the positional arguments of `myfn` as +follows: + +- The second input `14`, referenced by `arg1`, is substituted for the + first positional argument. +- We supply a default value of `17` for the second positional argument. +- The first input `19`, referenced by `arg0`, is subsituted for the + third positional argument. + +``` python +test_eq(bind(myfn, arg1, 17, arg0, e=3)(19,14), (14,17,19,1,3)) +``` + +In this next example: + +- We set the default value to `17` for the first positional argument. +- The first input `19` refrenced by `arg0`, becomes the second + positional argument. +- The second input `14` becomes the third positional argument. +- We override the default the value for named argument `e` to `3`. + +``` python +test_eq(bind(myfn, 17, arg0, e=3)(19,14), (17,19,14,1,3)) +``` + +This is an example of using +[`bind`](https://fastcore.fast.ai/basics.html#bind) like `partial` and +do not reorder any arguments: + +``` python +test_eq(bind(myfn)(17,19,14), (17,19,14,1,2)) +``` + +[`bind`](https://fastcore.fast.ai/basics.html#bind) can also be used to +change default values. In the below example, we use the first input `3` +to override the default value of the named argument `e`, and supply +default values for the first three positional arguments: + +``` python +test_eq(bind(myfn, 17,19,14,e=arg0)(3), (17,19,14,1,3)) +``` + +------------------------------------------------------------------------ + +source + +### mapt + +> mapt (func, *iterables) + +*Tuplified `map`* + +``` python +t = [0,1,2,3] +test_eq(mapt(operator.neg, t), (0,-1,-2,-3)) +``` + +------------------------------------------------------------------------ + +source + +### map_ex + +> map_ex (iterable, f, *args, gen=False, **kwargs) + +*Like `map`, but use +[`bind`](https://fastcore.fast.ai/basics.html#bind), and supports `str` +and indexing* + +``` python +test_eq(map_ex(t,operator.neg), [0,-1,-2,-3]) +``` + +If `f` is a string then it is treated as a format string to create the +mapping: + +``` python +test_eq(map_ex(t, '#{}#'), ['#0#','#1#','#2#','#3#']) +``` + +If `f` is a dictionary (or anything supporting `__getitem__`) then it is +indexed to create the mapping: + +``` python +test_eq(map_ex(t, list('abcd')), list('abcd')) +``` + +You can also pass the same `arg` params that +[`bind`](https://fastcore.fast.ai/basics.html#bind) accepts: + +``` python +def f(a=None,b=None): return b +test_eq(map_ex(t, f, b=arg0), range(4)) +``` + +------------------------------------------------------------------------ + +source + +### compose + +> compose (*funcs, order=None) + +*Create a function that composes all functions in `funcs`, passing along +remaining `*args` and `**kwargs` to all* + +``` python +f1 = lambda o,p=0: (o*2)+p +f2 = lambda o,p=1: (o+1)/p +test_eq(f2(f1(3)), compose(f1,f2)(3)) +test_eq(f2(f1(3,p=3),p=3), compose(f1,f2)(3,p=3)) +test_eq(f2(f1(3, 3), 3), compose(f1,f2)(3, 3)) + +f1.order = 1 +test_eq(f1(f2(3)), compose(f1,f2, order="order")(3)) +``` + +------------------------------------------------------------------------ + +source + +### maps + +> maps (*args, retain=) + +*Like `map`, except funcs are composed first* + +``` python +test_eq(maps([1]), [1]) +test_eq(maps(operator.neg, [1,2]), [-1,-2]) +test_eq(maps(operator.neg, operator.neg, [1,2]), [1,2]) +``` + +------------------------------------------------------------------------ + +source + +### partialler + +> partialler (f, *args, order=None, **kwargs) + +*Like `functools.partial` but also copies over docstring* + +``` python +def _f(x,a=1): + "test func" + return x-a +_f.order=1 + +f = partialler(_f, 2) +test_eq(f.order, 1) +test_eq(f(3), -1) +f = partialler(_f, a=2, order=3) +test_eq(f.__doc__, "test func") +test_eq(f.order, 3) +test_eq(f(3), _f(3,2)) +``` + +``` python +class partial0: + "Like `partialler`, but args passed to callable are inserted at started, instead of at end" + def __init__(self, f, *args, order=None, **kwargs): + self.f,self.args,self.kwargs = f,args,kwargs + self.order = ifnone(order, getattr(f,'order',None)) + self.__doc__ = f.__doc__ + + def __call__(self, *args, **kwargs): return self.f(*args, *self.args, **kwargs, **self.kwargs) +``` + +``` python +f = partial0(_f, 2) +test_eq(f.order, 1) +test_eq(f(3), 1) # NB: different to `partialler` example +``` + +------------------------------------------------------------------------ + +source + +### instantiate + +> instantiate (t) + +*Instantiate `t` if it’s a type, otherwise do nothing* + +``` python +test_eq_type(instantiate(int), 0) +test_eq_type(instantiate(1), 1) +``` + +------------------------------------------------------------------------ + +source + +### using_attr + +> using_attr (f, attr) + +*Construct a function which applies `f` to the argument’s attribute +`attr`* + +``` python +t = Path('/a/b.txt') +f = using_attr(str.upper, 'name') +test_eq(f(t), 'B.TXT') +``` + +### Self (with an *uppercase* S) + +A Concise Way To Create Lambdas + +This is a concise way to create lambdas that are calling methods on an +object (note the capitalization!) + +`Self.sum()`, for instance, is a shortcut for `lambda o: o.sum()`. + +``` python +f = Self.sum() +x = np.array([3.,1]) +test_eq(f(x), 4.) + +# This is equivalent to above +f = lambda o: o.sum() +x = np.array([3.,1]) +test_eq(f(x), 4.) + +f = Self.argmin() +arr = np.array([1,2,3,4,5]) +test_eq(f(arr), arr.argmin()) + +f = Self.sum().is_integer() +x = np.array([3.,1]) +test_eq(f(x), True) + +f = Self.sum().real.is_integer() +x = np.array([3.,1]) +test_eq(f(x), True) + +f = Self.imag() +test_eq(f(3), 0) + +f = Self[1] +test_eq(f(x), 1) +``` + +`Self` is also callable, which creates a function which calls any +function passed to it, using the arguments passed to `Self`: + +``` python +def f(a, b=3): return a+b+2 +def g(a, b=3): return a*b +fg = Self(1,b=2) +list(map(fg, [f,g])) +``` + + [5, 2] + +## Patching + +------------------------------------------------------------------------ + +source + +### copy_func + +> copy_func (f) + +*Copy a non-builtin function (NB `copy.copy` does not work for this)* + +Sometimes it may be desirable to make a copy of a function that doesn’t +point to the original object. When you use Python’s built in `copy.copy` +or `copy.deepcopy` to copy a function, you get a reference to the +original object: + +``` python +import copy as cp +``` + +``` python +def foo(): pass +a = cp.copy(foo) +b = cp.deepcopy(foo) + +a.someattr = 'hello' # since a and b point at the same object, updating a will update b +test_eq(b.someattr, 'hello') + +assert a is foo and b is foo +``` + +However, with +[`copy_func`](https://fastcore.fast.ai/basics.html#copy_func), you can +retrieve a copy of a function without a reference to the original +object: + +``` python +c = copy_func(foo) # c is an indpendent object +assert c is not foo +``` + +``` python +def g(x, *, y=3): return x+y +test_eq(copy_func(g)(4), 7) +``` + +------------------------------------------------------------------------ + +source + +### patch_to + +> patch_to (cls, as_prop=False, cls_method=False) + +*Decorator: add `f` to `cls`* + +The `@patch_to` decorator allows you to [monkey +patch](https://stackoverflow.com/questions/5626193/what-is-monkey-patching) +a function into a class as a method: + +``` python +class _T3(int): pass + +@patch_to(_T3) +def func1(self, a): return self+a + +t = _T3(1) # we initialized `t` to a type int = 1 +test_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3 +``` + +You can access instance properties in the usual way via `self`: + +``` python +class _T4(): + def __init__(self, g): self.g = g + +@patch_to(_T4) +def greet(self, x): return self.g + x + +t = _T4('hello ') # this sets self.g = 'hello ' +test_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello ' +``` + +You can instead specify that the method should be a class method by +setting `cls_method=True`: + +``` python +class _T5(int): attr = 3 # attr is a class attribute we will access in a later method + +@patch_to(_T5, cls_method=True) +def func(cls, x): return cls.attr + x # you can access class attributes in the normal way + +test_eq(_T5.func(4), 7) +``` + +Additionally you can specify that the function you want to patch should +be a class attribute with `as_prop=True`: + +``` python +@patch_to(_T5, as_prop=True) +def add_ten(self): return self + 10 + +t = _T5(4) +test_eq(t.add_ten, 14) +``` + +Instead of passing one class to the `@patch_to` decorator, you can pass +multiple classes in a tuple to simulteanously patch more than one class +with the same method: + +``` python +class _T6(int): pass +class _T7(int): pass + +@patch_to((_T6,_T7)) +def func_mult(self, a): return self*a + +t = _T6(2) +test_eq(t.func_mult(4), 8) +t = _T7(2) +test_eq(t.func_mult(4), 8) +``` + +------------------------------------------------------------------------ + +source + +### patch + +> patch (f=None, as_prop=False, cls_method=False) + +*Decorator: add `f` to the first parameter’s class (based on f’s type +annotations)* + +`@patch` is an alternative to `@patch_to` that allows you similarly +monkey patch class(es) by using [type +annotations](https://docs.python.org/3/library/typing.html): + +``` python +class _T8(int): pass + +@patch +def func(self:_T8, a): return self+a + +t = _T8(1) # we initilized `t` to a type int = 1 +test_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4 +test_eq(t.func.__qualname__, '_T8.func') +``` + +Similarly to +[`patch_to`](https://fastcore.fast.ai/basics.html#patch_to), you can +supply a union of classes instead of a single class in your type +annotations to patch multiple classes: + +``` python +class _T9(int): pass + +@patch +def func2(x:_T8|_T9, a): return x*a # will patch both _T8 and _T9 + +t = _T8(2) +test_eq(t.func2(4), 8) +test_eq(t.func2.__qualname__, '_T8.func2') + +t = _T9(2) +test_eq(t.func2(4), 8) +test_eq(t.func2.__qualname__, '_T9.func2') +``` + +Just like [`patch_to`](https://fastcore.fast.ai/basics.html#patch_to) +decorator you can use `as_prop` and `cls_method` parameters with +[`patch`](https://fastcore.fast.ai/basics.html#patch) decorator: + +``` python +@patch(as_prop=True) +def add_ten(self:_T5): return self + 10 + +t = _T5(4) +test_eq(t.add_ten, 14) +``` + +``` python +class _T5(int): attr = 3 # attr is a class attribute we will access in a later method + +@patch(cls_method=True) +def func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way + +test_eq(_T5.func(4), 7) +``` + +------------------------------------------------------------------------ + +source + +### patch_property + +> patch_property (f) + +*Deprecated; use `patch(as_prop=True)` instead* + +Patching `classmethod` shouldn’t affect how python’s inheritance works + +``` python +class FastParent: pass + +@patch(cls_method=True) +def type_cls(cls: FastParent): return cls + +class FastChild(FastParent): pass + +parent = FastParent() +test_eq(parent.type_cls(), FastParent) + +child = FastChild() +test_eq(child.type_cls(), FastChild) +``` + +## Other Helpers + +------------------------------------------------------------------------ + +source + +### compile_re + +> compile_re (pat) + +*Compile `pat` if it’s not None* + +``` python +assert compile_re(None) is None +assert compile_re('a').match('ab') +``` + +------------------------------------------------------------------------ + +source + +#### ImportEnum + +> ImportEnum (value, names=None, module=None, qualname=None, type=None, +> start=1) + +*An `Enum` that can have its values imported* + +``` python +_T = ImportEnum('_T', {'foobar':1, 'goobar':2}) +_T.imports() +test_eq(foobar, _T.foobar) +test_eq(goobar, _T.goobar) +``` + +------------------------------------------------------------------------ + +source + +#### StrEnum + +> StrEnum (value, names=None, module=None, qualname=None, type=None, +> start=1) + +*An [`ImportEnum`](https://fastcore.fast.ai/basics.html#importenum) that +behaves like a `str`* + +------------------------------------------------------------------------ + +source + +### str_enum + +> str_enum (name, *vals) + +*Simplified creation of +[`StrEnum`](https://fastcore.fast.ai/basics.html#strenum) types* + +------------------------------------------------------------------------ + +source + +#### ValEnum + +> ValEnum (value, names=None, module=None, qualname=None, type=None, +> start=1) + +*An [`ImportEnum`](https://fastcore.fast.ai/basics.html#importenum) that +stringifies using values* + +``` python +_T = str_enum('_T', 'a', 'b') +test_eq(f'{_T.a}', 'a') +test_eq(_T.a, 'a') +test_eq(list(_T.__members__), ['a','b']) +print(_T.a, _T.a.upper()) +``` + + a A + +------------------------------------------------------------------------ + +source + +#### Stateful + +> Stateful (*args, **kwargs) + +*A base class/mixin for objects that should not serialize all their +state* + +``` python +class _T(Stateful): + def __init__(self): + super().__init__() + self.a=1 + self._state['test']=2 + +t = _T() +t2 = pickle.loads(pickle.dumps(t)) +test_eq(t.a,1) +test_eq(t._state['test'],2) +test_eq(t2.a,1) +test_eq(t2._state,{}) +``` + +Override `_init_state` to do any necessary setup steps that are required +during `__init__` or during deserialization (e.g. `pickle.load`). Here’s +an example of how +[`Stateful`](https://fastcore.fast.ai/basics.html#stateful) simplifies +the official Python example for [Handling Stateful +Objects](https://docs.python.org/3/library/pickle.html#handling-stateful-objects). + +``` python +class TextReader(Stateful): + """Print and number lines in a text file.""" + _stateattrs=('file',) + def __init__(self, filename): + self.filename,self.lineno = filename,0 + super().__init__() + + def readline(self): + self.lineno += 1 + line = self.file.readline() + if line: return f"{self.lineno}: {line.strip()}" + + def _init_state(self): + self.file = open(self.filename) + for _ in range(self.lineno): self.file.readline() +``` + +``` python +reader = TextReader("00_test.ipynb") +print(reader.readline()) +print(reader.readline()) + +new_reader = pickle.loads(pickle.dumps(reader)) +print(reader.readline()) +``` + + 1: { + 2: "cells": [ + 3: { + +------------------------------------------------------------------------ + +source + +### NotStr + +> NotStr (s) + +*Behaves like a `str`, but isn’t an instance of one* + +``` python +s = NotStr("hello") +assert not isinstance(s, str) +test_eq(s, 'hello') +test_eq(s*2, 'hellohello') +test_eq(len(s), 5) +``` + +------------------------------------------------------------------------ + +source + +#### PrettyString + +*Little hack to get strings to show properly in Jupyter.* + +Allow strings with special characters to render properly in Jupyter. +Without calling `print()` strings with special characters are displayed +like so: + +``` python +with_special_chars='a string\nwith\nnew\nlines and\ttabs' +with_special_chars +``` + + 'a string\nwith\nnew\nlines and\ttabs' + +We can correct this with +[`PrettyString`](https://fastcore.fast.ai/basics.html#prettystring): + +``` python +PrettyString(with_special_chars) +``` + + a string + with + new + lines and tabs + +------------------------------------------------------------------------ + +source + +### even_mults + +> even_mults (start, stop, n) + +*Build log-stepped array from `start` to +[`stop`](https://fastcore.fast.ai/basics.html#stop) in `n` steps.* + +``` python +test_eq(even_mults(2,8,3), [2,4,8]) +test_eq(even_mults(2,32,5), [2,4,8,16,32]) +test_eq(even_mults(2,8,1), 8) +``` + +------------------------------------------------------------------------ + +source + +### num_cpus + +> num_cpus () + +*Get number of cpus* + +``` python +num_cpus() +``` + + 10 + +------------------------------------------------------------------------ + +source + +### add_props + +> add_props (f, g=None, n=2) + +*Create properties passing each of `range(n)` to f* + +``` python +class _T(): a,b = add_props(lambda i,x:i*2) + +t = _T() +test_eq(t.a,0) +test_eq(t.b,2) +``` + +``` python +class _T(): + def __init__(self, v): self.v=v + def _set(i, self, v): self.v[i] = v + a,b = add_props(lambda i,x: x.v[i], _set) + +t = _T([0,2]) +test_eq(t.a,0) +test_eq(t.b,2) +t.a = t.a+1 +t.b = 3 +test_eq(t.a,1) +test_eq(t.b,3) +``` + +------------------------------------------------------------------------ + +source + +### str2bool + +> str2bool (s) + +*Case-insensitive convert string `s` too a bool +(`y`,`yes`,`t`,[`true`](https://fastcore.fast.ai/basics.html#true),`on`,`1`-\>`True`)* + +True values are ‘y’, ‘yes’, ‘t’, ‘true’, ‘on’, and ‘1’; false values are +‘n’, ‘no’, ‘f’, ‘false’, ‘off’, and ‘0’. Raises `ValueError` if ‘val’ is +anything else. + +``` python +for o in "y YES t True on 1".split(): assert str2bool(o) +for o in "n no FALSE off 0".split(): assert not str2bool(o) +for o in 0,None,'',False: assert not str2bool(o) +for o in 1,True: assert str2bool(o) +``` + +------------------------------------------------------------------------ + +source + +### str2int + +> str2int (s) + +*Convert `s` to an `int`* + +------------------------------------------------------------------------ + +source + +### str2float + +> str2float (s:str) + +*Convert `s` to a float* + +------------------------------------------------------------------------ + +source + +### str2list + +> str2list (s:str) + +*Convert `s` to a list* + +------------------------------------------------------------------------ + +source + +### str2date + +> str2date (s:str) + +*`date.fromisoformat` with empty string handling* + +------------------------------------------------------------------------ + +source + +### to_date + +> to_date (arg) + +------------------------------------------------------------------------ + +source + +### to_list + +> to_list (arg) + +------------------------------------------------------------------------ + +source + +### to_float + +> to_float (arg) + +------------------------------------------------------------------------ + +source + +### to_int + +> to_int (arg) + +------------------------------------------------------------------------ + +source + +### to_bool + +> to_bool (arg) + +------------------------------------------------------------------------ + +source + +### typed + +> typed (_func=None, cast=False) + +*Decorator to check param and return types at runtime, with optional +casting* + +[`typed`](https://fastcore.fast.ai/basics.html#typed) validates argument +types at **runtime**. This is in contrast to +[MyPy](http://mypy-lang.org/) which only offers static type checking. + +For example, a `TypeError` will be raised if we try to pass an integer +into the first argument of the below function: + +``` python +@typed +def discount(price:int, pct:float) -> float: + return (1-pct) * price + +with ExceptionExpected(TypeError): discount(100.0, .1) +``` + +You can have automatic casting based on heuristics by specifying +`typed(cast=True)`. If casting is not possible, a `TypeError` is raised. + +``` python +@typed(cast=True) +def discount(price:int, pct:float) -> float: + return (1-pct) * price + +assert 90.0 == discount(100.5, .1) # will auto cast 100.5 to the int 100 +assert 90.0 == discount(' 100 ', .1) # will auto cast the str "100" to the int 100 +with ExceptionExpected(TypeError): discount("a", .1) +``` + +We can also optionally allow multiple types by enumarating the types in +a tuple as illustrated below: + +``` python +@typed +def discount(price:int|float, pct:float): + return (1-pct) * price + +assert 90.0 == discount(100.0, .1) + +@typed(cast=True) +def discount(price:int|None, pct:float): + return (1-pct) * price + +assert 90.0 == discount(100.0, .1) +``` + +We currently do not support union types when casting. + +``` python +@typed(cast=True) +def discount(price:int|float, pct:float): + return (1-pct) * price + +with ExceptionExpected(AssertionError): assert 90.0 == discount("100.0", .1) +``` + +[`typed`](https://fastcore.fast.ai/basics.html#typed) works with +classes, too: + +``` python +class Foo: + @typed + def __init__(self, a:int, b: int, c:str): pass + @typed(cast=True) + def test(cls, d:str): return d + +with ExceptionExpected(TypeError): Foo(1, 2, 3) +assert isinstance(Foo(1,2, 'a string').test(10), str) +``` + +It also works with custom types. + +``` python +@typed +def test_foo(foo: Foo): pass + +with ExceptionExpected(TypeError): test_foo(1) +test_foo(Foo(1, 2, 'a string')) +``` + +``` python +class Bar: + @typed + def __init__(self, a:int): self.a = a +@typed(cast=True) +def test_bar(bar: Bar): return bar + +assert isinstance(test_bar(1), Bar) +test_eq(test_bar(1).a, 1) +with ExceptionExpected(TypeError): test_bar("foobar") +``` + +------------------------------------------------------------------------ + +source + +### exec_new + +> exec_new (code) + +*Execute `code` in a new environment and return it* + +``` python +g = exec_new('a=1') +test_eq(g['a'], 1) +``` + +------------------------------------------------------------------------ + +source + +### exec_import + +> exec_import (mod, sym) + +*Import `sym` from `mod` in a new environment* + +## Notebook functions + +------------------------------------------------------------------------ + +### ipython_shell + +> ipython_shell () + +*Same as `get_ipython` but returns `False` if not in IPython* + +------------------------------------------------------------------------ + +### in_ipython + +> in_ipython () + +*Check if code is running in some kind of IPython environment* + +------------------------------------------------------------------------ + +### in_colab + +> in_colab () + +*Check if the code is running in Google Colaboratory* + +------------------------------------------------------------------------ + +### in_jupyter + +> in_jupyter () + +*Check if the code is running in a jupyter notebook* + +------------------------------------------------------------------------ + +### in_notebook + +> in_notebook () + +*Check if the code is running in a jupyter notebook* + +These variables are available as booleans in `fastcore.basics` as +`IN_IPYTHON`, `IN_JUPYTER`, `IN_COLAB` and `IN_NOTEBOOK`. + +``` python +IN_IPYTHON, IN_JUPYTER, IN_COLAB, IN_NOTEBOOK +``` + + (True, True, False, True) diff --git a/dispatch.html b/dispatch.html new file mode 100644 index 00000000..cc906abc --- /dev/null +++ b/dispatch.html @@ -0,0 +1,1283 @@ + + + + + + + + + + +Type dispatch – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Type dispatch

+
+ +
+
+ Basic single and dual parameter dispatch +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from nbdev.showdoc import *
+from fastcore.test import *
+from fastcore.nb_imports import *
+
+
+

Helpers

+
+

source

+
+

lenient_issubclass

+
+
 lenient_issubclass (cls, types)
+
+

If possible return whether cls is a subclass of types, otherwise return False.

+
+
assert not lenient_issubclass(typing.Collection, list)
+assert lenient_issubclass(list, typing.Collection)
+assert lenient_issubclass(typing.Collection, object)
+assert lenient_issubclass(typing.List, typing.Collection)
+assert not lenient_issubclass(typing.Collection, typing.List)
+assert not lenient_issubclass(object, typing.Callable)
+
+
+

source

+
+
+

sorted_topologically

+
+
 sorted_topologically (iterable, cmp=<built-in function lt>,
+                       reverse=False)
+
+

Return a new list containing all items from the iterable sorted topologically

+
+
td = [3, 1, 2, 5]
+test_eq(sorted_topologically(td), [1, 2, 3, 5])
+test_eq(sorted_topologically(td, reverse=True), [5, 3, 2, 1])
+
+
+
td = {int:1, numbers.Number:2, numbers.Integral:3}
+test_eq(sorted_topologically(td, cmp=lenient_issubclass), [int, numbers.Integral, numbers.Number])
+
+
+
td = [numbers.Integral, tuple, list, int, dict]
+td = sorted_topologically(td, cmp=lenient_issubclass)
+assert td.index(int) < td.index(numbers.Integral)
+
+
+
+
+

TypeDispatch

+

Type dispatch, or Multiple dispatch, allows you to change the way a function behaves based upon the input types it recevies. This is a prominent feature in some programming languages like Julia. For example, this is a conceptual example of how multiple dispatch works in Julia, returning different values depending on the input types of x and y:

+
collide_with(x::Asteroid, y::Asteroid) = ... 
+# deal with asteroid hitting asteroid
+
+collide_with(x::Asteroid, y::Spaceship) = ... 
+# deal with asteroid hitting spaceship
+
+collide_with(x::Spaceship, y::Asteroid) = ... 
+# deal with spaceship hitting asteroid
+
+collide_with(x::Spaceship, y::Spaceship) = ... 
+# deal with spaceship hitting spaceship
+

Type dispatch can be especially useful in data science, where you might allow different input types (i.e. numpy arrays and pandas dataframes) to function that processes data. Type dispatch allows you to have a common API for functions that do similar tasks.

+

The TypeDispatch class allows us to achieve type dispatch in Python. It contains a dictionary that maps types from type annotations to functions, which ensures that the proper function is called when passed inputs.

+
+

source

+
+

TypeDispatch

+
+
 TypeDispatch (funcs=(), bases=())
+
+

Dictionary-like object; __getitem__ matches keys of types using issubclass

+

To demonstrate how TypeDispatch works, we define a set of functions that accept a variety of input types, specified with different type annotations:

+
+
def f2(x:int, y:float): return x+y              #int and float for 2nd arg
+def f_nin(x:numbers.Integral)->int:  return x+1 #integral numeric
+def f_ni2(x:int): return x                      #integer
+def f_bll(x:bool|list): return x              #bool or list
+def f_num(x:numbers.Number): return x           #Number (root of numerics)
+
+

We can optionally initialize TypeDispatch with a list of functions we want to search. Printing an instance of TypeDispatch will display convenient mapping of types -> functions:

+
+
t = TypeDispatch([f_nin,f_ni2,f_num,f_bll,None])
+t
+
+
(bool,object) -> f_bll
+(int,object) -> f_ni2
+(Integral,object) -> f_nin
+(Number,object) -> f_num
+(list,object) -> f_bll
+(object,object) -> NoneType
+
+
+

Note that only the first two arguments are used for TypeDispatch. If your function only contains one argument, the second parameter will be shown as object. If you pass None into TypeDispatch, then this will be displayed as (object, object) -> NoneType.

+

TypeDispatch is a dictionary-like object, which means that you can retrieve a function by the associated type annotation. For example, the statement:

+
t[float]
+

Will return f_num because that is the matching function that has a type annotation that is a super-class of of float - numbers.Number:

+
+
assert issubclass(float, numbers.Number)
+test_eq(t[float], f_num)
+
+

The same is true for other types as well:

+
+
test_eq(t[np.int32], f_nin)
+test_eq(t[bool], f_bll)
+test_eq(t[list], f_bll)
+test_eq(t[np.int32], f_nin)
+
+

If you try to get a type that doesn’t match, TypeDispatch will return None:

+
+
test_eq(t[str], None)
+
+
+

source

+
+
+

TypeDispatch.add

+
+
 TypeDispatch.add (f)
+
+

Add type t and function f

+

This method allows you to add an additional function to an existing TypeDispatch instance :

+
+
def f_col(x:typing.Collection): return x
+t.add(f_col)
+test_eq(t[str], f_col)
+t
+
+
(bool,object) -> f_bll
+(int,object) -> f_ni2
+(Integral,object) -> f_nin
+(Number,object) -> f_num
+(list,object) -> f_bll
+(typing.Collection,object) -> f_col
+(object,object) -> NoneType
+
+
+

If you accidentally add the same function more than once things will still work as expected:

+
+
t.add(f_ni2) 
+test_eq(t[int], f_ni2)
+
+

However, if you add a function that has a type collision that raises an ambiguity, this will automatically resolve to the latest function added:

+
+
def f_ni3(z:int): return z # collides with f_ni2 with same type annotations
+t.add(f_ni3) 
+test_eq(t[int], f_ni3)
+
+
+

Using bases:

+

The argument bases can optionally accept a single instance of TypeDispatch or a collection (i.e. a tuple or list) of TypeDispatch objects. This can provide functionality similar to multiple inheritance.

+

These are searched for matching functions if no match in your list of functions:

+
+
def f_str(x:str): return x+'1'
+
+t = TypeDispatch([f_nin,f_ni2,f_num,f_bll,None])
+t2 = TypeDispatch(f_str, bases=t) # you can optionally supply a list of TypeDispatch objects for `bases`.
+t2
+
+
(str,object) -> f_str
+(bool,object) -> f_bll
+(int,object) -> f_ni2
+(Integral,object) -> f_nin
+(Number,object) -> f_num
+(list,object) -> f_bll
+(object,object) -> NoneType
+
+
+
+
test_eq(t2[int], f_ni2)       # searches `t` b/c not found in `t2`
+test_eq(t2[np.int32], f_nin)  # searches `t` b/c not found in `t2`
+test_eq(t2[float], f_num)     # searches `t` b/c not found in `t2`
+test_eq(t2[bool], f_bll)      # searches `t` b/c not found in `t2`
+test_eq(t2[str], f_str)       # found in `t`!
+test_eq(t2('a'), 'a1')        # found in `t`!, and uses __call__
+
+o = np.int32(1)
+test_eq(t2(o), 2)             # found in `t2` and uses __call__
+
+
+
+

Up To Two Arguments

+

TypeDispatch supports up to two arguments when searching for the appropriate function. The following functions f1 and f2 both have two parameters:

+
+
def f1(x:numbers.Integral, y): return x+1  #Integral is a numeric type
+def f2(x:int, y:float): return x+y
+t = TypeDispatch([f1,f2])
+t
+
+
(int,float) -> f2
+(Integral,object) -> f1
+
+
+

You can lookup functions from a TypeDispatch instance with two parameters like this:

+
+
test_eq(t[np.int32], f1)
+test_eq(t[int,float], f2)
+
+

Keep in mind that anything beyond the first two parameters are ignored, and any collisions will be resolved in favor of the most recent function added. In the below example, f1 is ignored in favor of f2 because the first two parameters have identical type hints:

+
+
def f1(a:str, b:int, c:list): return a
+def f2(a: str, b:int): return b
+t = TypeDispatch([f1,f2])
+test_eq(t[str, int], f2)
+t
+
+
(str,int) -> f2
+
+
+
+
+

Matching

+

Type Dispatch matches types with functions according to whether the supplied class is a subclass or the same class of the type annotation(s) of associated functions.

+

Let’s consider an example where we try to retrieve the function corresponding to types of [np.int32, float].

+

In this scenario, f2 will not be matched. This is because the first type annotation of f2, int, is not a superclass (or the same class) of np.int32:

+
+
def f1(x:numbers.Integral, y): return x+1
+def f2(x:int, y:float): return x+y
+t = TypeDispatch([f1,f2])
+
+assert not issubclass(np.int32, int)
+
+

Instead, f1 is a valid match, as its first argument is annoted with the type numbers.Integeral, which np.int32 is a subclass of:

+
+
assert issubclass(np.int32, numbers.Integral)
+test_eq(t[np.int32,float], f1)
+
+

In f1 , the 2nd parameter y is not annotated, which means TypeDispatch will match anything where the first argument matches int that is not matched with anything else:

+
+
assert issubclass(int, numbers.Integral) # int is a subclass of numbers.Integral
+test_eq(t[int], f1)
+test_eq(t[int,int], f1)
+
+

If no match is possible, None is returned:

+
+
test_eq(t[float,float], None)
+
+
+

source

+
+
+
+

TypeDispatch.__call__

+
+
 TypeDispatch.__call__ (*args, **kwargs)
+
+

Call self as a function.

+

TypeDispatch is also callable. When you call an instance of TypeDispatch, it will execute the relevant function:

+
+
def f_arr(x:np.ndarray): return x.sum()
+def f_int(x:np.int32): return x+1
+t = TypeDispatch([f_arr, f_int])
+
+arr = np.array([5,4,3,2,1])
+test_eq(t(arr), 15) # dispatches to f_arr
+
+o = np.int32(1)
+test_eq(t(o), 2) # dispatches to f_int
+assert t.first() is not None
+
+

You can also call an instance of of TypeDispatch when there are two parameters:

+
+
def f1(x:numbers.Integral, y): return x+1
+def f2(x:int, y:float): return x+y
+t = TypeDispatch([f1,f2])
+
+test_eq(t(3,2.0), 5)
+test_eq(t(3,2), 4)
+
+

When no match is found, a TypeDispatch instance becomes an identity function. This default behavior is leveraged by fasatai for data transformations to provide a sensible default when a matching function cannot be found.

+
+
test_eq(t('a'), 'a')
+
+
+

source

+
+
+

TypeDispatch.returns

+
+
 TypeDispatch.returns (x)
+
+

Get the return type of annotation of x.

+

You can optionally pass an object to TypeDispatch.returns and get the return type annotation back:

+
+
def f1(x:int) -> np.ndarray: return np.array(x)
+def f2(x:str) -> float: return List
+def f3(x:float): return List # f3 has no return type annotation
+
+t = TypeDispatch([f1, f2, f3])
+
+test_eq(t.returns(1), np.ndarray)  # dispatched to f1
+test_eq(t.returns('Hello'), float) # dispatched to f2
+test_eq(t.returns(1.0), None)      # dispatched to f3
+
+class _Test: pass
+_test = _Test()
+test_eq(t.returns(_test), None) # type `_Test` not found, so None returned
+
+
+

Using TypeDispatch With Methods

+

You can use TypeDispatch when defining methods as well:

+
+
def m_nin(self, x:str|numbers.Integral): return str(x)+'1'
+def m_bll(self, x:bool): self.foo='a'
+def m_num(self, x:numbers.Number): return x*2
+
+t = TypeDispatch([m_nin,m_num,m_bll])
+class A: f = t # set class attribute `f` equal to a TypeDispatch instance
+    
+a = A()
+test_eq(a.f(1), '11')  #dispatch to m_nin
+test_eq(a.f(1.), 2.)   #dispatch to m_num
+test_is(a.f.inst, a)
+
+a.f(False) # this triggers t.m_bll to run, which sets self.foo to 'a'
+test_eq(a.foo, 'a')
+
+

As discussed in TypeDispatch.__call__, when there is not a match, TypeDispatch.__call__ becomes an identity function. In the below example, a tuple does not match any type annotations so a tuple is returned:

+
+
test_eq(a.f(()), ())
+
+

We extend the previous example by using bases to add an additional method that supports tuples:

+
+
def m_tup(self, x:tuple): return x+(1,)
+t2 = TypeDispatch(m_tup, bases=t)
+
+class A2: f = t2
+a2 = A2()
+test_eq(a2.f(1), '11')
+test_eq(a2.f(1.), 2.)
+test_is(a2.f.inst, a2)
+a2.f(False)
+test_eq(a2.foo, 'a')
+test_eq(a2.f(()), (1,))
+
+
+
+

Using TypeDispatch With Class Methods

+

You can use TypeDispatch when defining class methods too:

+
+
def m_nin(cls, x:str|numbers.Integral): return str(x)+'1'
+def m_bll(cls, x:bool): cls.foo='a'
+def m_num(cls, x:numbers.Number): return x*2
+
+t = TypeDispatch([m_nin,m_num,m_bll])
+class A: f = t # set class attribute `f` equal to a TypeDispatch
+
+test_eq(A.f(1), '11')  #dispatch to m_nin
+test_eq(A.f(1.), 2.)   #dispatch to m_num
+test_is(A.f.owner, A)
+
+A.f(False) # this triggers t.m_bll to run, which sets A.foo to 'a'
+test_eq(A.foo, 'a')
+
+
+
+
+
+

typedispatch Decorator

+
+

source

+
+

DispatchReg

+
+
 DispatchReg ()
+
+

A global registry for TypeDispatch objects keyed by function name

+
+
@typedispatch
+def f_td_test(x, y): return f'{x}{y}'
+@typedispatch
+def f_td_test(x:numbers.Integral|int, y): return x+1
+@typedispatch
+def f_td_test(x:int, y:float): return x+y
+@typedispatch
+def f_td_test(x:int, y:int): return x*y
+
+test_eq(f_td_test(3,2.0), 5)
+assert issubclass(int, numbers.Integral)
+test_eq(f_td_test(3,2), 6)
+
+test_eq(f_td_test('a','b'), 'ab')
+
+
+

Using typedispatch With other decorators

+

You can use typedispatch with classmethod and staticmethod decorator

+
+
class A:
+    @typedispatch
+    def f_td_test(self, x:numbers.Integral, y): return x+1
+    @typedispatch
+    @classmethod
+    def f_td_test(cls, x:int, y:float): return x+y
+    @typedispatch
+    @staticmethod
+    def f_td_test(x:int, y:int): return x*y
+    
+test_eq(A.f_td_test(3,2), 6)
+test_eq(A.f_td_test(3,2.0), 5)
+test_eq(A().f_td_test(3,'2.0'), 4)
+
+
+
+
+
+

Casting

+

Now that we can dispatch on types, let’s make it easier to cast objects to a different type.

+
+

source

+
+

retain_meta

+
+
 retain_meta (x, res, as_copy=False)
+
+

Call res.set_meta(x), if it exists

+
+

source

+
+
+

default_set_meta

+
+
 default_set_meta (x, as_copy=False)
+
+

Copy over _meta from x to res, if it’s missing

+
+
+
+

(object,object) -> cast

+

Dictionary-like object; __getitem__ matches keys of types using issubclass

+

This works both for plain python classes:…

+
+
mk_class('_T1', 'a')   # mk_class is a fastai utility that constructs a class.
+class _T2(_T1): pass
+
+t = _T1(a=1)
+t2 = cast(t, _T2)        
+assert t2 is t            # t2 refers to the same object as t
+assert isinstance(t, _T2) # t also changed in-place
+assert isinstance(t2, _T2)
+
+test_eq_type(_T2(a=1), t2)
+
+

…as well as for arrays and tensors.

+
+
class _T1(ndarray): pass
+
+t = array([1])
+t2 = cast(t, _T1)
+test_eq(array([1]), t2)
+test_eq(_T1, type(t2))
+
+

To customize casting for other types, define a separate cast function with typedispatch for your type.

+
+

source

+
+
+

retain_type

+
+
 retain_type (new, old=None, typ=None, as_copy=False)
+
+

Cast new to type of old or typ if it’s a superclass

+
+
class _T(tuple): pass
+a = _T((1,2))
+b = tuple((1,2))
+c = retain_type(b, typ=_T)
+test_eq_type(c, a)
+
+

If old has a _meta attribute, its content is passed when casting new to the type of old. In the below example, only the attribute a, but not other_attr is kept, because other_attr is not in _meta:

+
+
class _A():
+    set_meta = default_set_meta
+    def __init__(self, t): self.t=t
+
+class _B1(_A):
+    def __init__(self, t, a=1):
+        super().__init__(t)
+        self._meta = {'a':a}
+        self.other_attr = 'Hello' # will not be kept after casting.
+        
+x = _B1(1, a=2)
+b = _A(1)
+c = retain_type(b, old=x)
+test_eq(c._meta, {'a': 2})
+assert not getattr(c, 'other_attr', None)
+
+
+

source

+
+
+

retain_types

+
+
 retain_types (new, old=None, typs=None)
+
+

Cast each item of new to type of matching item in old if it’s a superclass

+
+
class T(tuple): pass
+
+t1,t2 = retain_types((1,(1,(1,1))), (2,T((2,T((3,4))))))
+test_eq_type(t1, 1)
+test_eq_type(t2, T((1,T((1,1)))))
+
+t1,t2 = retain_types((1,(1,(1,1))), typs = {tuple: [int, {T: [int, {T: [int,int]}]}]})
+test_eq_type(t1, 1)
+test_eq_type(t2, T((1,T((1,1)))))
+
+
+

source

+
+
+

explode_types

+
+
 explode_types (o)
+
+

Return the type of o, potentially in nested dictionaries for thing that are listy

+
+
test_eq(explode_types((2,T((2,T((3,4)))))), {tuple: [int, {T: [int, {T: [int,int]}]}]})
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/dispatch.html.md b/dispatch.html.md new file mode 100644 index 00000000..7a9f8195 --- /dev/null +++ b/dispatch.html.md @@ -0,0 +1,722 @@ +# Type dispatch + + + + +``` python +from nbdev.showdoc import * +from fastcore.test import * +from fastcore.nb_imports import * +``` + +## Helpers + +------------------------------------------------------------------------ + +source + +### lenient_issubclass + +> lenient_issubclass (cls, types) + +*If possible return whether `cls` is a subclass of `types`, otherwise +return False.* + +``` python +assert not lenient_issubclass(typing.Collection, list) +assert lenient_issubclass(list, typing.Collection) +assert lenient_issubclass(typing.Collection, object) +assert lenient_issubclass(typing.List, typing.Collection) +assert not lenient_issubclass(typing.Collection, typing.List) +assert not lenient_issubclass(object, typing.Callable) +``` + +------------------------------------------------------------------------ + +source + +### sorted_topologically + +> sorted_topologically (iterable, cmp=, +> reverse=False) + +*Return a new list containing all items from the iterable sorted +topologically* + +``` python +td = [3, 1, 2, 5] +test_eq(sorted_topologically(td), [1, 2, 3, 5]) +test_eq(sorted_topologically(td, reverse=True), [5, 3, 2, 1]) +``` + +``` python +td = {int:1, numbers.Number:2, numbers.Integral:3} +test_eq(sorted_topologically(td, cmp=lenient_issubclass), [int, numbers.Integral, numbers.Number]) +``` + +``` python +td = [numbers.Integral, tuple, list, int, dict] +td = sorted_topologically(td, cmp=lenient_issubclass) +assert td.index(int) < td.index(numbers.Integral) +``` + +## TypeDispatch + +Type dispatch, or [Multiple +dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch#Julia), allows +you to change the way a function behaves based upon the input types it +recevies. This is a prominent feature in some programming languages like +Julia. For example, this is a [conceptual +example](https://en.wikipedia.org/wiki/Multiple_dispatch#Julia) of how +multiple dispatch works in Julia, returning different values depending +on the input types of x and y: + +``` julia +collide_with(x::Asteroid, y::Asteroid) = ... +# deal with asteroid hitting asteroid + +collide_with(x::Asteroid, y::Spaceship) = ... +# deal with asteroid hitting spaceship + +collide_with(x::Spaceship, y::Asteroid) = ... +# deal with spaceship hitting asteroid + +collide_with(x::Spaceship, y::Spaceship) = ... +# deal with spaceship hitting spaceship +``` + +Type dispatch can be especially useful in data science, where you might +allow different input types (i.e. numpy arrays and pandas dataframes) to +function that processes data. Type dispatch allows you to have a common +API for functions that do similar tasks. + +The +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +class allows us to achieve type dispatch in Python. It contains a +dictionary that maps types from type annotations to functions, which +ensures that the proper function is called when passed inputs. + +------------------------------------------------------------------------ + +source + +### TypeDispatch + +> TypeDispatch (funcs=(), bases=()) + +*Dictionary-like object; `__getitem__` matches keys of types using +`issubclass`* + +To demonstrate how +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +works, we define a set of functions that accept a variety of input +types, specified with different type annotations: + +``` python +def f2(x:int, y:float): return x+y #int and float for 2nd arg +def f_nin(x:numbers.Integral)->int: return x+1 #integral numeric +def f_ni2(x:int): return x #integer +def f_bll(x:bool|list): return x #bool or list +def f_num(x:numbers.Number): return x #Number (root of numerics) +``` + +We can optionally initialize +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +with a list of functions we want to search. Printing an instance of +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +will display convenient mapping of types -\> functions: + +``` python +t = TypeDispatch([f_nin,f_ni2,f_num,f_bll,None]) +t +``` + + (bool,object) -> f_bll + (int,object) -> f_ni2 + (Integral,object) -> f_nin + (Number,object) -> f_num + (list,object) -> f_bll + (object,object) -> NoneType + +Note that only the first two arguments are used for +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch). +If your function only contains one argument, the second parameter will +be shown as `object`. If you pass `None` into +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch), +then this will be displayed as `(object, object) -> NoneType`. + +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) is +a dictionary-like object, which means that you can retrieve a function +by the associated type annotation. For example, the statement: + +``` py +t[float] +``` + +Will return `f_num` because that is the matching function that has a +type annotation that is a super-class of of `float` - `numbers.Number`: + +``` python +assert issubclass(float, numbers.Number) +test_eq(t[float], f_num) +``` + +The same is true for other types as well: + +``` python +test_eq(t[np.int32], f_nin) +test_eq(t[bool], f_bll) +test_eq(t[list], f_bll) +test_eq(t[np.int32], f_nin) +``` + +If you try to get a type that doesn’t match, +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +will return `None`: + +``` python +test_eq(t[str], None) +``` + +------------------------------------------------------------------------ + +source + +### TypeDispatch.add + +> TypeDispatch.add (f) + +*Add type `t` and function `f`* + +This method allows you to add an additional function to an existing +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +instance : + +``` python +def f_col(x:typing.Collection): return x +t.add(f_col) +test_eq(t[str], f_col) +t +``` + + (bool,object) -> f_bll + (int,object) -> f_ni2 + (Integral,object) -> f_nin + (Number,object) -> f_num + (list,object) -> f_bll + (typing.Collection,object) -> f_col + (object,object) -> NoneType + +If you accidentally add the same function more than once things will +still work as expected: + +``` python +t.add(f_ni2) +test_eq(t[int], f_ni2) +``` + +However, if you add a function that has a type collision that raises an +ambiguity, this will automatically resolve to the latest function added: + +``` python +def f_ni3(z:int): return z # collides with f_ni2 with same type annotations +t.add(f_ni3) +test_eq(t[int], f_ni3) +``` + +#### Using `bases`: + +The argument `bases` can optionally accept a single instance of +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) or +a collection (i.e. a tuple or list) of +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +objects. This can provide functionality similar to multiple inheritance. + +These are searched for matching functions if no match in your list of +functions: + +``` python +def f_str(x:str): return x+'1' + +t = TypeDispatch([f_nin,f_ni2,f_num,f_bll,None]) +t2 = TypeDispatch(f_str, bases=t) # you can optionally supply a list of TypeDispatch objects for `bases`. +t2 +``` + + (str,object) -> f_str + (bool,object) -> f_bll + (int,object) -> f_ni2 + (Integral,object) -> f_nin + (Number,object) -> f_num + (list,object) -> f_bll + (object,object) -> NoneType + +``` python +test_eq(t2[int], f_ni2) # searches `t` b/c not found in `t2` +test_eq(t2[np.int32], f_nin) # searches `t` b/c not found in `t2` +test_eq(t2[float], f_num) # searches `t` b/c not found in `t2` +test_eq(t2[bool], f_bll) # searches `t` b/c not found in `t2` +test_eq(t2[str], f_str) # found in `t`! +test_eq(t2('a'), 'a1') # found in `t`!, and uses __call__ + +o = np.int32(1) +test_eq(t2(o), 2) # found in `t2` and uses __call__ +``` + +#### Up To Two Arguments + +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +supports up to two arguments when searching for the appropriate +function. The following functions `f1` and `f2` both have two +parameters: + +``` python +def f1(x:numbers.Integral, y): return x+1 #Integral is a numeric type +def f2(x:int, y:float): return x+y +t = TypeDispatch([f1,f2]) +t +``` + + (int,float) -> f2 + (Integral,object) -> f1 + +You can lookup functions from a +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +instance with two parameters like this: + +``` python +test_eq(t[np.int32], f1) +test_eq(t[int,float], f2) +``` + +Keep in mind that anything beyond the first two parameters are ignored, +and any collisions will be resolved in favor of the most recent function +added. In the below example, `f1` is ignored in favor of `f2` because +the first two parameters have identical type hints: + +``` python +def f1(a:str, b:int, c:list): return a +def f2(a: str, b:int): return b +t = TypeDispatch([f1,f2]) +test_eq(t[str, int], f2) +t +``` + + (str,int) -> f2 + +#### Matching + +`Type Dispatch` matches types with functions according to whether the +supplied class is a subclass or the same class of the type annotation(s) +of associated functions. + +Let’s consider an example where we try to retrieve the function +corresponding to types of `[np.int32, float]`. + +In this scenario, `f2` will not be matched. This is because the first +type annotation of `f2`, `int`, is not a superclass (or the same class) +of `np.int32`: + +``` python +def f1(x:numbers.Integral, y): return x+1 +def f2(x:int, y:float): return x+y +t = TypeDispatch([f1,f2]) + +assert not issubclass(np.int32, int) +``` + +Instead, `f1` is a valid match, as its first argument is annoted with +the type `numbers.Integeral`, which `np.int32` is a subclass of: + +``` python +assert issubclass(np.int32, numbers.Integral) +test_eq(t[np.int32,float], f1) +``` + +In `f1` , the 2nd parameter `y` is not annotated, which means +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +will match anything where the first argument matches `int` that is not +matched with anything else: + +``` python +assert issubclass(int, numbers.Integral) # int is a subclass of numbers.Integral +test_eq(t[int], f1) +test_eq(t[int,int], f1) +``` + +If no match is possible, `None` is returned: + +``` python +test_eq(t[float,float], None) +``` + +------------------------------------------------------------------------ + +source + +### TypeDispatch.\_\_call\_\_ + +> TypeDispatch.__call__ (*args, **kwargs) + +*Call self as a function.* + +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) is +also callable. When you call an instance of +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch), +it will execute the relevant function: + +``` python +def f_arr(x:np.ndarray): return x.sum() +def f_int(x:np.int32): return x+1 +t = TypeDispatch([f_arr, f_int]) + +arr = np.array([5,4,3,2,1]) +test_eq(t(arr), 15) # dispatches to f_arr + +o = np.int32(1) +test_eq(t(o), 2) # dispatches to f_int +assert t.first() is not None +``` + +You can also call an instance of of +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +when there are two parameters: + +``` python +def f1(x:numbers.Integral, y): return x+1 +def f2(x:int, y:float): return x+y +t = TypeDispatch([f1,f2]) + +test_eq(t(3,2.0), 5) +test_eq(t(3,2), 4) +``` + +When no match is found, a +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +instance becomes an identity function. This default behavior is +leveraged by fasatai for data transformations to provide a sensible +default when a matching function cannot be found. + +``` python +test_eq(t('a'), 'a') +``` + +------------------------------------------------------------------------ + +source + +### TypeDispatch.returns + +> TypeDispatch.returns (x) + +*Get the return type of annotation of `x`.* + +You can optionally pass an object to +[`TypeDispatch.returns`](https://fastcore.fast.ai/dispatch.html#typedispatch.returns) +and get the return type annotation back: + +``` python +def f1(x:int) -> np.ndarray: return np.array(x) +def f2(x:str) -> float: return List +def f3(x:float): return List # f3 has no return type annotation + +t = TypeDispatch([f1, f2, f3]) + +test_eq(t.returns(1), np.ndarray) # dispatched to f1 +test_eq(t.returns('Hello'), float) # dispatched to f2 +test_eq(t.returns(1.0), None) # dispatched to f3 + +class _Test: pass +_test = _Test() +test_eq(t.returns(_test), None) # type `_Test` not found, so None returned +``` + +#### Using TypeDispatch With Methods + +You can use +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +when defining methods as well: + +``` python +def m_nin(self, x:str|numbers.Integral): return str(x)+'1' +def m_bll(self, x:bool): self.foo='a' +def m_num(self, x:numbers.Number): return x*2 + +t = TypeDispatch([m_nin,m_num,m_bll]) +class A: f = t # set class attribute `f` equal to a TypeDispatch instance + +a = A() +test_eq(a.f(1), '11') #dispatch to m_nin +test_eq(a.f(1.), 2.) #dispatch to m_num +test_is(a.f.inst, a) + +a.f(False) # this triggers t.m_bll to run, which sets self.foo to 'a' +test_eq(a.foo, 'a') +``` + +As discussed in +[`TypeDispatch.__call__`](https://fastcore.fast.ai/dispatch.html#typedispatch.__call__), +when there is not a match, +[`TypeDispatch.__call__`](https://fastcore.fast.ai/dispatch.html#typedispatch.__call__) +becomes an identity function. In the below example, a tuple does not +match any type annotations so a tuple is returned: + +``` python +test_eq(a.f(()), ()) +``` + +We extend the previous example by using `bases` to add an additional +method that supports tuples: + +``` python +def m_tup(self, x:tuple): return x+(1,) +t2 = TypeDispatch(m_tup, bases=t) + +class A2: f = t2 +a2 = A2() +test_eq(a2.f(1), '11') +test_eq(a2.f(1.), 2.) +test_is(a2.f.inst, a2) +a2.f(False) +test_eq(a2.foo, 'a') +test_eq(a2.f(()), (1,)) +``` + +#### Using TypeDispatch With Class Methods + +You can use +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +when defining class methods too: + +``` python +def m_nin(cls, x:str|numbers.Integral): return str(x)+'1' +def m_bll(cls, x:bool): cls.foo='a' +def m_num(cls, x:numbers.Number): return x*2 + +t = TypeDispatch([m_nin,m_num,m_bll]) +class A: f = t # set class attribute `f` equal to a TypeDispatch + +test_eq(A.f(1), '11') #dispatch to m_nin +test_eq(A.f(1.), 2.) #dispatch to m_num +test_is(A.f.owner, A) + +A.f(False) # this triggers t.m_bll to run, which sets A.foo to 'a' +test_eq(A.foo, 'a') +``` + +## typedispatch Decorator + +------------------------------------------------------------------------ + +source + +### DispatchReg + +> DispatchReg () + +*A global registry for +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +objects keyed by function name* + +``` python +@typedispatch +def f_td_test(x, y): return f'{x}{y}' +@typedispatch +def f_td_test(x:numbers.Integral|int, y): return x+1 +@typedispatch +def f_td_test(x:int, y:float): return x+y +@typedispatch +def f_td_test(x:int, y:int): return x*y + +test_eq(f_td_test(3,2.0), 5) +assert issubclass(int, numbers.Integral) +test_eq(f_td_test(3,2), 6) + +test_eq(f_td_test('a','b'), 'ab') +``` + +#### Using typedispatch With other decorators + +You can use `typedispatch` with `classmethod` and `staticmethod` +decorator + +``` python +class A: + @typedispatch + def f_td_test(self, x:numbers.Integral, y): return x+1 + @typedispatch + @classmethod + def f_td_test(cls, x:int, y:float): return x+y + @typedispatch + @staticmethod + def f_td_test(x:int, y:int): return x*y + +test_eq(A.f_td_test(3,2), 6) +test_eq(A.f_td_test(3,2.0), 5) +test_eq(A().f_td_test(3,'2.0'), 4) +``` + +## Casting + +Now that we can dispatch on types, let’s make it easier to cast objects +to a different type. + +------------------------------------------------------------------------ + +source + +### retain_meta + +> retain_meta (x, res, as_copy=False) + +*Call `res.set_meta(x)`, if it exists* + +------------------------------------------------------------------------ + +source + +### default_set_meta + +> default_set_meta (x, as_copy=False) + +*Copy over `_meta` from `x` to `res`, if it’s missing* + +------------------------------------------------------------------------ + +### (object,object) -\> cast + +*Dictionary-like object; `__getitem__` matches keys of types using +`issubclass`* + +This works both for plain python classes:… + +``` python +mk_class('_T1', 'a') # mk_class is a fastai utility that constructs a class. +class _T2(_T1): pass + +t = _T1(a=1) +t2 = cast(t, _T2) +assert t2 is t # t2 refers to the same object as t +assert isinstance(t, _T2) # t also changed in-place +assert isinstance(t2, _T2) + +test_eq_type(_T2(a=1), t2) +``` + +…as well as for arrays and tensors. + +``` python +class _T1(ndarray): pass + +t = array([1]) +t2 = cast(t, _T1) +test_eq(array([1]), t2) +test_eq(_T1, type(t2)) +``` + +To customize casting for other types, define a separate +[`cast`](https://fastcore.fast.ai/dispatch.html#cast) function with +`typedispatch` for your type. + +------------------------------------------------------------------------ + +source + +### retain_type + +> retain_type (new, old=None, typ=None, as_copy=False) + +*Cast `new` to type of `old` or `typ` if it’s a superclass* + +``` python +class _T(tuple): pass +a = _T((1,2)) +b = tuple((1,2)) +c = retain_type(b, typ=_T) +test_eq_type(c, a) +``` + +If `old` has a `_meta` attribute, its content is passed when casting +`new` to the type of `old`. In the below example, only the attribute +`a`, but not `other_attr` is kept, because `other_attr` is not in +`_meta`: + +``` python +class _A(): + set_meta = default_set_meta + def __init__(self, t): self.t=t + +class _B1(_A): + def __init__(self, t, a=1): + super().__init__(t) + self._meta = {'a':a} + self.other_attr = 'Hello' # will not be kept after casting. + +x = _B1(1, a=2) +b = _A(1) +c = retain_type(b, old=x) +test_eq(c._meta, {'a': 2}) +assert not getattr(c, 'other_attr', None) +``` + +------------------------------------------------------------------------ + +source + +### retain_types + +> retain_types (new, old=None, typs=None) + +*Cast each item of `new` to type of matching item in `old` if it’s a +superclass* + +``` python +class T(tuple): pass + +t1,t2 = retain_types((1,(1,(1,1))), (2,T((2,T((3,4)))))) +test_eq_type(t1, 1) +test_eq_type(t2, T((1,T((1,1))))) + +t1,t2 = retain_types((1,(1,(1,1))), typs = {tuple: [int, {T: [int, {T: [int,int]}]}]}) +test_eq_type(t1, 1) +test_eq_type(t2, T((1,T((1,1))))) +``` + +------------------------------------------------------------------------ + +source + +### explode_types + +> explode_types (o) + +*Return the type of `o`, potentially in nested dictionaries for thing +that are listy* + +``` python +test_eq(explode_types((2,T((2,T((3,4)))))), {tuple: [int, {T: [int, {T: [int,int]}]}]}) +``` diff --git a/docments.html b/docments.html new file mode 100644 index 00000000..1e416a38 --- /dev/null +++ b/docments.html @@ -0,0 +1,1104 @@ + + + + + + + + + + +Docments – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Docments

+
+ +
+
+ Document parameters using comments. +
+
+ + +
+ + + + +
+ + + +
+ + + +

docments provides programmatic access to comments in function parameters and return types. It can be used to create more developer-friendly documentation, CLI, etc tools.

+
+

Why?

+

Without docments, if you want to document your parameters, you have to repeat param names in docstrings, since they’re already in the function signature. The parameters have to be kept synchronized in the two places as you change your code. Readers of your code have to look back and forth between two places to understand what’s happening. So it’s more work for you, and for your users.

+

Furthermore, to have parameter documentation formatted nicely without docments, you have to use special magic docstring formatting, often with odd quirks, which is a pain to create and maintain, and awkward to read in code. For instance, using numpy-style documentation:

+
+
def add_np(a:int, b:int=0)->int:
+    """The sum of two numbers.
+    
+    Used to demonstrate numpy-style docstrings.
+
+Parameters
+----------
+a : int
+    the 1st number to add
+b : int
+    the 2nd number to add (default: 0)
+
+Returns
+-------
+int
+    the result of adding `a` to `b`"""
+    return a+b
+
+

By comparison, here’s the same thing using docments:

+
+
def add(
+    a:int, # the 1st number to add
+    b=0,   # the 2nd number to add
+)->int:    # the result of adding `a` to `b`
+    "The sum of two numbers."
+    return a+b
+
+
+
+

Numpy docstring helper functions

+

docments also supports numpy-style docstrings, or a mix or numpy-style and docments parameter documentation. The functions in this section help get and parse this information.

+
+

source

+
+

docstring

+
+
 docstring (sym)
+
+

Get docstring for sym for functions ad classes

+
+
test_eq(docstring(add), "The sum of two numbers.")
+
+
+

source

+
+
+

parse_docstring

+
+
 parse_docstring (sym)
+
+

Parse a numpy-style docstring in sym

+
+
# parse_docstring(add_np)
+
+
+

source

+
+
+

isdataclass

+
+
 isdataclass (s)
+
+

Check if s is a dataclass but not a dataclass’ instance

+
+

source

+
+
+

get_dataclass_source

+
+
 get_dataclass_source (s)
+
+

Get source code for dataclass s

+
+

source

+
+
+

get_source

+
+
 get_source (s)
+
+

Get source code for string, function object or dataclass s

+
+

source

+
+
+

get_name

+
+
 get_name (obj)
+
+

Get the name of obj

+
+
test_eq(get_name(in_ipython), 'in_ipython')
+test_eq(get_name(L.map), 'map')
+
+
+

source

+
+
+

qual_name

+
+
 qual_name (obj)
+
+

Get the qualified name of obj

+
+
assert qual_name(docscrape) == 'fastcore.docscrape'
+
+
+
+
+

Docments

+
+

source

+
+

docments

+
+
 docments (elt, full=False, returns=True, eval_str=False)
+
+

Generates a docment

+

The returned dict has parameter names as keys, docments as values. The return value comment appears in the return, unless returns=False. Using the add definition above, we get:

+
+
def add(
+    a:int, # the 1st number to add
+    b=0,   # the 2nd number to add
+)->int:    # the result of adding `a` to `b`
+    "The sum of two numbers."
+    return a+b
+
+docments(add)
+
+
{ 'a': 'the 1st number to add',
+  'b': 'the 2nd number to add',
+  'return': 'the result of adding `a` to `b`'}
+
+
+

If you pass full=True, the values are dict of defaults, types, and docments as values. Note that the type annotation is inferred from the default value, if the annotation is empty and a default is supplied.

+
+
docments(add, full=True)
+
+
{ 'a': { 'anno': <class 'int'>,
+         'default': <class 'inspect._empty'>,
+         'docment': 'the 1st number to add'},
+  'b': { 'anno': <class 'int'>,
+         'default': 0,
+         'docment': 'the 2nd number to add'},
+  'return': { 'anno': <class 'int'>,
+              'default': <class 'inspect._empty'>,
+              'docment': 'the result of adding `a` to `b`'}}
+
+
+

To evaluate stringified annotations (from python 3.10), use eval_str:

+
+
docments(add, full=True, eval_str=True)['a']
+
+
{ 'anno': <class 'int'>,
+  'default': <class 'inspect._empty'>,
+  'docment': 'the 1st number to add'}
+
+
+

If you need more space to document a parameter, place one or more lines of comments above the parameter, or above the return type. You can mix-and-match these docment styles:

+
+
def add(
+    # The first operand
+    a:int,
+    # This is the second of the operands to the *addition* operator.
+    # Note that passing a negative value here is the equivalent of the *subtraction* operator.
+    b:int,
+)->int: # The result is calculated using Python's builtin `+` operator.
+    "Add `a` to `b`"
+    return a+b
+
+
+
docments(add)
+
+
{ 'a': 'The first operand',
+  'b': 'This is the second of the operands to the *addition* operator.\n'
+       'Note that passing a negative value here is the equivalent of the '
+       '*subtraction* operator.',
+  'return': "The result is calculated using Python's builtin `+` operator."}
+
+
+

Docments works with async functions, too:

+
+
async def add_async(
+    # The first operand
+    a:int,
+    # This is the second of the operands to the *addition* operator.
+    # Note that passing a negative value here is the equivalent of the *subtraction* operator.
+    b:int,
+)->int: # The result is calculated using Python's builtin `+` operator.
+    "Add `a` to `b`"
+    return a+b
+
+
+
test_eq(docments(add_async), docments(add))
+
+

You can also use docments with classes and methods:

+
+
class Adder:
+    "An addition calculator"
+    def __init__(self,
+        a:int, # First operand
+        b:int, # 2nd operand
+    ): self.a,self.b = a,b
+    
+    def calculate(self
+                 )->int: # Integral result of addition operator
+        "Add `a` to `b`"
+        return a+b
+
+
+
docments(Adder)
+
+
{'a': 'First operand', 'b': '2nd operand', 'return': None}
+
+
+
+
docments(Adder.calculate)
+
+
{'return': 'Integral result of addition operator', 'self': None}
+
+
+

docments can also be extracted from numpy-style docstrings:

+
+
print(add_np.__doc__)
+
+
The sum of two numbers.
+    
+    Used to demonstrate numpy-style docstrings.
+
+Parameters
+----------
+a : int
+    the 1st number to add
+b : int
+    the 2nd number to add (default: 0)
+
+Returns
+-------
+int
+    the result of adding `a` to `b`
+
+
+
+
docments(add_np)
+
+
{ 'a': 'the 1st number to add',
+  'b': 'the 2nd number to add (default: 0)',
+  'return': 'the result of adding `a` to `b`'}
+
+
+

You can even mix and match docments and numpy parameters:

+
+
def add_mixed(a:int, # the first number to add
+              b
+             )->int: # the result
+    """The sum of two numbers.
+
+Parameters
+----------
+b : int
+    the 2nd number to add (default: 0)"""
+    return a+b
+
+
+
docments(add_mixed, full=True)
+
+
{ 'a': { 'anno': <class 'int'>,
+         'default': <class 'inspect._empty'>,
+         'docment': 'the first number to add'},
+  'b': { 'anno': 'int',
+         'default': <class 'inspect._empty'>,
+         'docment': 'the 2nd number to add (default: 0)'},
+  'return': { 'anno': <class 'int'>,
+              'default': <class 'inspect._empty'>,
+              'docment': 'the result'}}
+
+
+

You can use docments with dataclasses, however if the class was defined in online notebook, docments will not contain parameters’ comments. This is because the source code is not available in the notebook. After converting the notebook to a module, the docments will be available. Thus, documentation will have correct parameters’ comments.

+

Docments even works with delegates:

+
+
from fastcore.meta import delegates
+
+
+
def _a(a:int=2): return a # First
+
+@delegates(_a)
+def _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second
+
+docments(_b)
+
+
{'a': 'First', 'b': 'Second', 'return': None}
+
+
+
+
+
+

Extract docstrings

+
+

source

+
+

extract_docstrings

+
+
 extract_docstrings (code)
+
+

Create a dict from function/class/method names to tuples of docstrings and param lists

+
+
sample_code = """
+"This is a module."
+
+def top_func(a, b, *args, **kw):
+    "This is top-level."
+    pass
+
+class SampleClass:
+    "This is a class."
+
+    def __init__(self, x, y):
+        "Constructor for SampleClass."
+        pass
+
+    def method1(self, param1):
+        "This is method1."
+        pass
+
+    def _private_method(self):
+        "This should not be included."
+        pass
+
+class AnotherClass:
+    def __init__(self, a, b):
+        "This class has no separate docstring."
+        pass"""
+
+exp = {'_module': ('This is a module.', ''),
+       'top_func': ('This is top-level.', 'a, b, *args, **kw'),
+       'SampleClass': ('This is a class.', 'self, x, y'),
+       'SampleClass.method1': ('This is method1.', 'self, param1'),
+       'AnotherClass': ('This class has no separate docstring.', 'self, a, b')}
+test_eq(extract_docstrings(sample_code), exp)
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/docments.html.md b/docments.html.md new file mode 100644 index 00000000..b8ca5147 --- /dev/null +++ b/docments.html.md @@ -0,0 +1,450 @@ +# Docments + + + + +[`docments`](https://fastcore.fast.ai/docments.html#docments) provides +programmatic access to comments in function parameters and return types. +It can be used to create more developer-friendly documentation, CLI, etc +tools. + +## Why? + +Without docments, if you want to document your parameters, you have to +repeat param names in docstrings, since they’re already in the function +signature. The parameters have to be kept synchronized in the two places +as you change your code. Readers of your code have to look back and +forth between two places to understand what’s happening. So it’s more +work for you, and for your users. + +Furthermore, to have parameter documentation formatted nicely without +docments, you have to use special magic docstring formatting, often with +[odd +quirks](https://stackoverflow.com/questions/62167540/why-do-definitions-have-a-space-before-the-colon-in-numpy-docstring-sections), +which is a pain to create and maintain, and awkward to read in code. For +instance, using [numpy-style +documentation](https://numpydoc.readthedocs.io/en/latest/format.html): + +``` python +def add_np(a:int, b:int=0)->int: + """The sum of two numbers. + + Used to demonstrate numpy-style docstrings. + +Parameters +---------- +a : int + the 1st number to add +b : int + the 2nd number to add (default: 0) + +Returns +------- +int + the result of adding `a` to `b`""" + return a+b +``` + +By comparison, here’s the same thing using docments: + +``` python +def add( + a:int, # the 1st number to add + b=0, # the 2nd number to add +)->int: # the result of adding `a` to `b` + "The sum of two numbers." + return a+b +``` + +## Numpy docstring helper functions + +[`docments`](https://fastcore.fast.ai/docments.html#docments) also +supports numpy-style docstrings, or a mix or numpy-style and docments +parameter documentation. The functions in this section help get and +parse this information. + +------------------------------------------------------------------------ + +source + +### docstring + +> docstring (sym) + +*Get docstring for `sym` for functions ad classes* + +``` python +test_eq(docstring(add), "The sum of two numbers.") +``` + +------------------------------------------------------------------------ + +source + +### parse_docstring + +> parse_docstring (sym) + +*Parse a numpy-style docstring in `sym`* + +``` python +# parse_docstring(add_np) +``` + +------------------------------------------------------------------------ + +source + +### isdataclass + +> isdataclass (s) + +*Check if `s` is a dataclass but not a dataclass’ instance* + +------------------------------------------------------------------------ + +source + +### get_dataclass_source + +> get_dataclass_source (s) + +*Get source code for dataclass `s`* + +------------------------------------------------------------------------ + +source + +### get_source + +> get_source (s) + +*Get source code for string, function object or dataclass `s`* + +------------------------------------------------------------------------ + +source + +### get_name + +> get_name (obj) + +*Get the name of `obj`* + +``` python +test_eq(get_name(in_ipython), 'in_ipython') +test_eq(get_name(L.map), 'map') +``` + +------------------------------------------------------------------------ + +source + +### qual_name + +> qual_name (obj) + +*Get the qualified name of `obj`* + +``` python +assert qual_name(docscrape) == 'fastcore.docscrape' +``` + +## Docments + +------------------------------------------------------------------------ + +source + +### docments + +> docments (elt, full=False, returns=True, eval_str=False) + +*Generates a `docment`* + +The returned `dict` has parameter names as keys, docments as values. The +return value comment appears in the `return`, unless `returns=False`. +Using the `add` definition above, we get: + +``` python +def add( + a:int, # the 1st number to add + b=0, # the 2nd number to add +)->int: # the result of adding `a` to `b` + "The sum of two numbers." + return a+b + +docments(add) +``` + +``` json +{ 'a': 'the 1st number to add', + 'b': 'the 2nd number to add', + 'return': 'the result of adding `a` to `b`'} +``` + +If you pass `full=True`, the values are `dict` of defaults, types, and +docments as values. Note that the type annotation is inferred from the +default value, if the annotation is empty and a default is supplied. + +``` python +docments(add, full=True) +``` + +``` json +{ 'a': { 'anno': , + 'default': , + 'docment': 'the 1st number to add'}, + 'b': { 'anno': , + 'default': 0, + 'docment': 'the 2nd number to add'}, + 'return': { 'anno': , + 'default': , + 'docment': 'the result of adding `a` to `b`'}} +``` + +To evaluate stringified annotations (from python 3.10), use `eval_str`: + +``` python +docments(add, full=True, eval_str=True)['a'] +``` + +``` json +{ 'anno': , + 'default': , + 'docment': 'the 1st number to add'} +``` + +If you need more space to document a parameter, place one or more lines +of comments above the parameter, or above the return type. You can +mix-and-match these docment styles: + +``` python +def add( + # The first operand + a:int, + # This is the second of the operands to the *addition* operator. + # Note that passing a negative value here is the equivalent of the *subtraction* operator. + b:int, +)->int: # The result is calculated using Python's builtin `+` operator. + "Add `a` to `b`" + return a+b +``` + +``` python +docments(add) +``` + +``` json +{ 'a': 'The first operand', + 'b': 'This is the second of the operands to the *addition* operator.\n' + 'Note that passing a negative value here is the equivalent of the ' + '*subtraction* operator.', + 'return': "The result is calculated using Python's builtin `+` operator."} +``` + +Docments works with async functions, too: + +``` python +async def add_async( + # The first operand + a:int, + # This is the second of the operands to the *addition* operator. + # Note that passing a negative value here is the equivalent of the *subtraction* operator. + b:int, +)->int: # The result is calculated using Python's builtin `+` operator. + "Add `a` to `b`" + return a+b +``` + +``` python +test_eq(docments(add_async), docments(add)) +``` + +You can also use docments with classes and methods: + +``` python +class Adder: + "An addition calculator" + def __init__(self, + a:int, # First operand + b:int, # 2nd operand + ): self.a,self.b = a,b + + def calculate(self + )->int: # Integral result of addition operator + "Add `a` to `b`" + return a+b +``` + +``` python +docments(Adder) +``` + +``` json +{'a': 'First operand', 'b': '2nd operand', 'return': None} +``` + +``` python +docments(Adder.calculate) +``` + +``` json +{'return': 'Integral result of addition operator', 'self': None} +``` + +docments can also be extracted from numpy-style docstrings: + +``` python +print(add_np.__doc__) +``` + + The sum of two numbers. + + Used to demonstrate numpy-style docstrings. + + Parameters + ---------- + a : int + the 1st number to add + b : int + the 2nd number to add (default: 0) + + Returns + ------- + int + the result of adding `a` to `b` + +``` python +docments(add_np) +``` + +``` json +{ 'a': 'the 1st number to add', + 'b': 'the 2nd number to add (default: 0)', + 'return': 'the result of adding `a` to `b`'} +``` + +You can even mix and match docments and numpy parameters: + +``` python +def add_mixed(a:int, # the first number to add + b + )->int: # the result + """The sum of two numbers. + +Parameters +---------- +b : int + the 2nd number to add (default: 0)""" + return a+b +``` + +``` python +docments(add_mixed, full=True) +``` + +``` json +{ 'a': { 'anno': , + 'default': , + 'docment': 'the first number to add'}, + 'b': { 'anno': 'int', + 'default': , + 'docment': 'the 2nd number to add (default: 0)'}, + 'return': { 'anno': , + 'default': , + 'docment': 'the result'}} +``` + +You can use docments with dataclasses, however if the class was defined +in online notebook, docments will not contain parameters’ comments. This +is because the source code is not available in the notebook. After +converting the notebook to a module, the docments will be available. +Thus, documentation will have correct parameters’ comments. + +Docments even works with +[`delegates`](https://fastcore.fast.ai/meta.html#delegates): + +``` python +from fastcore.meta import delegates +``` + +``` python +def _a(a:int=2): return a # First + +@delegates(_a) +def _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second + +docments(_b) +``` + +``` json +{'a': 'First', 'b': 'Second', 'return': None} +``` + +## Extract docstrings + +------------------------------------------------------------------------ + +source + +### extract_docstrings + +> extract_docstrings (code) + +*Create a dict from function/class/method names to tuples of docstrings +and param lists* + +``` python +sample_code = """ +"This is a module." + +def top_func(a, b, *args, **kw): + "This is top-level." + pass + +class SampleClass: + "This is a class." + + def __init__(self, x, y): + "Constructor for SampleClass." + pass + + def method1(self, param1): + "This is method1." + pass + + def _private_method(self): + "This should not be included." + pass + +class AnotherClass: + def __init__(self, a, b): + "This class has no separate docstring." + pass""" + +exp = {'_module': ('This is a module.', ''), + 'top_func': ('This is top-level.', 'a, b, *args, **kw'), + 'SampleClass': ('This is a class.', 'self, x, y'), + 'SampleClass.method1': ('This is method1.', 'self, param1'), + 'AnotherClass': ('This class has no separate docstring.', 'self, a, b')} +test_eq(extract_docstrings(sample_code), exp) +``` diff --git a/foundation.html b/foundation.html new file mode 100644 index 00000000..2bd611d8 --- /dev/null +++ b/foundation.html @@ -0,0 +1,1565 @@ + + + + + + + + + + +Foundation – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Foundation

+
+ +
+
+ The L class and helpers for it +
+
+ + +
+ + + + +
+ + + +
+ + + +
+

Foundational Functions

+
+

source

+
+

working_directory

+
+
 working_directory (path)
+
+

Change working directory to path and return to previous on exit.

+
+

source

+
+
+

add_docs

+
+
 add_docs (cls, cls_doc=None, **docs)
+
+

Copy values from docs to cls docstrings, and confirm all public methods are documented

+

add_docs allows you to add docstrings to a class and its associated methods. This function allows you to group docstrings together seperate from your code, which enables you to define one-line functions as well as organize your code more succintly. We believe this confers a number of benefits which we discuss in our style guide.

+

Suppose you have the following undocumented class:

+
+
class T:
+    def foo(self): pass
+    def bar(self): pass
+
+

You can add documentation to this class like so:

+
+
add_docs(T, cls_doc="A docstring for the class.",
+            foo="The foo method.",
+            bar="The bar method.")
+
+

Now, docstrings will appear as expected:

+
+
test_eq(T.__doc__, "A docstring for the class.")
+test_eq(T.foo.__doc__, "The foo method.")
+test_eq(T.bar.__doc__, "The bar method.")
+
+

add_docs also validates that all of your public methods contain a docstring. If one of your methods is not documented, it will raise an error:

+
+
class T:
+    def foo(self): pass
+    def bar(self): pass
+
+f=lambda: add_docs(T, "A docstring for the class.", foo="The foo method.")
+test_fail(f, contains="Missing docs")
+
+
+

source

+
+
+

docs

+
+
 docs (cls)
+
+

Decorator version of add_docs, using _docs dict

+

Instead of using add_docs, you can use the decorator docs as shown below. Note that the docstring for the class can be set with the argument cls_doc:

+
+
@docs
+class _T:
+    def f(self): pass
+    def g(cls): pass
+    
+    _docs = dict(cls_doc="The class docstring", 
+                 f="The docstring for method f.",
+                 g="A different docstring for method g.")
+
+    
+test_eq(_T.__doc__, "The class docstring")
+test_eq(_T.f.__doc__, "The docstring for method f.")
+test_eq(_T.g.__doc__, "A different docstring for method g.")
+
+

For either the docs decorator or the add_docs function, you can still define your docstrings in the normal way. Below we set the docstring for the class as usual, but define the method docstrings through the _docs attribute:

+
+
@docs
+class _T:
+    "The class docstring"
+    def f(self): pass
+    _docs = dict(f="The docstring for method f.")
+
+    
+test_eq(_T.__doc__, "The class docstring")
+test_eq(_T.f.__doc__, "The docstring for method f.")
+
+
+
+
+

is_iter

+
+
 is_iter (o)
+
+

Test whether o can be used in a for loop

+
+
assert is_iter([1])
+assert not is_iter(array(1))
+assert is_iter(array([1,2]))
+assert (o for o in range(3))
+
+
+

source

+
+
+

coll_repr

+
+
 coll_repr (c, max_n=10)
+
+

String repr of up to max_n items of (possibly lazy) collection c

+

coll_repr is used to provide a more informative __repr__ about list-like objects. coll_repr and is used by L to build a __repr__ that displays the length of a list in addition to a preview of a list.

+

Below is an example of the __repr__ string created for a list of 1000 elements:

+
+
test_eq(coll_repr(range(1000)),    '(#1000) [0,1,2,3,4,5,6,7,8,9...]')
+test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]')
+test_eq(coll_repr(range(10),   5),   '(#10) [0,1,2,3,4...]')
+test_eq(coll_repr(range(5),    5),    '(#5) [0,1,2,3,4]')
+
+

We can set the option max_n to optionally preview a specified number of items instead of the default:

+
+
test_eq(coll_repr(range(1000), max_n=5), '(#1000) [0,1,2,3,4...]')
+
+
+

source

+
+
+

is_bool

+
+
 is_bool (x)
+
+

Check whether x is a bool or None

+
+

source

+
+
+

mask2idxs

+
+
 mask2idxs (mask)
+
+

Convert bool mask or index list to index L

+
+
test_eq(mask2idxs([False,True,False,True]), [1,3])
+test_eq(mask2idxs(array([False,True,False,True])), [1,3])
+test_eq(mask2idxs(array([1,2,3])), [1,2,3])
+
+
+

source

+
+
+

cycle

+
+
 cycle (o)
+
+

Like itertools.cycle except creates list of Nones if o is empty

+
+
test_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2])
+test_eq(itertools.islice(cycle([]),3), [None]*3)
+test_eq(itertools.islice(cycle(None),3), [None]*3)
+test_eq(itertools.islice(cycle(1),3), [1,1,1])
+
+
+

source

+
+
+

zip_cycle

+
+
 zip_cycle (x, *args)
+
+

Like itertools.zip_longest but cycles through elements of all but first argument

+
+
test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])
+
+
+

source

+
+
+

is_indexer

+
+
 is_indexer (idx)
+
+

Test whether idx will index a single item in a list

+

You can, for example index a single item in a list with an integer or a 0-dimensional numpy array:

+
+
assert is_indexer(1)
+assert is_indexer(np.array(1))
+
+

However, you cannot index into single item in a list with another list or a numpy array with ndim > 0.

+
+
assert not is_indexer([1, 2])
+assert not is_indexer(np.array([[1, 2], [3, 4]]))
+
+
+
+
+

L helpers

+
+

source

+
+

CollBase

+
+
 CollBase (items)
+
+

Base class for composing a list of items

+

ColBase is a base class that emulates the functionality of a python list:

+
+
class _T(CollBase): pass
+l = _T([1,2,3,4,5])
+
+test_eq(len(l), 5) # __len__
+test_eq(l[-1], 5); test_eq(l[0], 1) #__getitem__
+l[2] = 100; test_eq(l[2], 100)      # __set_item__
+del l[0]; test_eq(len(l), 4)        # __delitem__
+test_eq(str(l), '[2, 100, 4, 5]')   # __repr__
+
+
+

source

+
+
+

L

+
+
 L (x=None, *args, **kwargs)
+
+

Behaves like a list of items but can also index with list of indices or masks

+

L is a drop in replacement for a python list. Inspired by NumPy, L, supports advanced indexing and has additional methods (outlined below) that provide additional functionality and encourage simple expressive code. For example, the code below takes a list of pairs, selects the second item of each pair, takes its absolute value, filters items greater than 4, and adds them up:

+
+
from fastcore.utils import gt
+
+
+
d = dict(a=1,b=-5,d=6,e=9).items()
+test_eq(L(d).itemgot(1).map(abs).filter(gt(4)).sum(), 20) # abs(-5) + abs(6) + abs(9) = 20; 1 was filtered out.
+
+

Read this overview section for a quick tutorial of L, as well as background on the name.

+

You can create an L from an existing iterable (e.g. a list, range, etc) and access or modify it with an int list/tuple index, mask, int, or slice. All list methods can also be used with L.

+
+
t = L(range(12))
+test_eq(t, list(range(12)))
+test_ne(t, list(range(11)))
+t.reverse()
+test_eq(t[0], 11)
+t[3] = "h"
+test_eq(t[3], "h")
+t[3,5] = ("j","k")
+test_eq(t[3,5], ["j","k"])
+test_eq(t, L(t))
+test_eq(L(L(1,2),[3,4]), ([1,2],[3,4]))
+t
+
+
(#12) [11,10,9,'j',7,'k',5,4,3,2...]
+
+
+

Any L is a Sequence so you can use it with methods like random.sample:

+
+
assert isinstance(t, Sequence)
+
+
+
import random
+
+
+
random.seed(0)
+random.sample(t, 3)
+
+
[5, 0, 11]
+
+
+

There are optimized indexers for arrays, tensors, and DataFrames.

+
+
import pandas as pd
+
+
+
arr = np.arange(9).reshape(3,3)
+t = L(arr, use_list=None)
+test_eq(t[1,2], arr[[1,2]])
+
+df = pd.DataFrame({'a':[1,2,3]})
+t = L(df, use_list=None)
+test_eq(t[1,2], L(pd.DataFrame({'a':[2,3]}, index=[1,2]), use_list=None))
+
+

You can also modify an L with append, +, and *.

+
+
t = L()
+test_eq(t, [])
+t.append(1)
+test_eq(t, [1])
+t += [3,2]
+test_eq(t, [1,3,2])
+t = t + [4]
+test_eq(t, [1,3,2,4])
+t = 5 + t
+test_eq(t, [5,1,3,2,4])
+test_eq(L(1,2,3), [1,2,3])
+test_eq(L(1,2,3), L(1,2,3))
+t = L(1)*5
+t = t.map(operator.neg)
+test_eq(t,[-1]*5)
+test_eq(~L([True,False,False]), L([False,True,True]))
+t = L(range(4))
+test_eq(zip(t, L(1).cycle()), zip(range(4),(1,1,1,1)))
+t = L.range(100)
+test_shuffled(t,t.shuffle())
+
+
+
test_eq(L([]).sum(), 0)
+test_eq(L([]).product(), 1)
+
+
+
def _f(x,a=0): return x+a
+t = L(1)*5
+test_eq(t.map(_f), t)
+test_eq(t.map(_f,1), [2]*5)
+test_eq(t.map(_f,a=2), [3]*5)
+
+

An L can be constructed from anything iterable, although tensors and arrays will not be iterated over on construction, unless you pass use_list to the constructor.

+
+
test_eq(L([1,2,3]),[1,2,3])
+test_eq(L(L([1,2,3])),[1,2,3])
+test_ne(L([1,2,3]),[1,2,])
+test_eq(L('abc'),['abc'])
+test_eq(L(range(0,3)),[0,1,2])
+test_eq(L(o for o in range(0,3)),[0,1,2])
+test_eq(L(array(0)),[array(0)])
+test_eq(L([array(0),array(1)]),[array(0),array(1)])
+test_eq(L(array([0.,1.1]))[0],array([0.,1.1]))
+test_eq(L(array([0.,1.1]), use_list=True), [array(0.),array(1.1)])  # `use_list=True` to unwrap arrays/arrays
+
+

If match is not None then the created list is same len as match, either by:

+
    +
  • If len(items)==1 then items is replicated,
  • +
  • Otherwise an error is raised if match and items are not already the same size.
  • +
+
+
test_eq(L(1,match=[1,2,3]),[1,1,1])
+test_eq(L([1,2],match=[2,3]),[1,2])
+test_fail(lambda: L([1,2],match=[1,2,3]))
+
+

If you create an L from an existing L then you’ll get back the original object (since L uses the NewChkMeta metaclass).

+
+
test_is(L(t), t)
+
+

An L is considred equal to a list if they have the same elements. It’s never considered equal to a str a set or a dict even if they have the same elements/keys.

+
+
test_eq(L(['a', 'b']), ['a', 'b'])
+test_ne(L(['a', 'b']), 'ab')
+test_ne(L(['a', 'b']), {'a':1, 'b':2})
+
+
+
+

L Methods

+
+

source

+
+
+

L.__getitem__

+
+
 L.__getitem__ (idx)
+
+

Retrieve idx (can be list of indices, or mask, or int) items

+
+
t = L(range(12))
+test_eq(t[1,2], [1,2])                # implicit tuple
+test_eq(t[[1,2]], [1,2])              # list
+test_eq(t[:3], [0,1,2])               # slice
+test_eq(t[[False]*11 + [True]], [11]) # mask
+test_eq(t[array(3)], 3)
+
+
+

source

+
+
+

L.__setitem__

+
+
 L.__setitem__ (idx, o)
+
+

Set idx (can be list of indices, or mask, or int) items to o (which is broadcast if not iterable)

+
+
t[4,6] = 0
+test_eq(t[4,6], [0,0])
+t[4,6] = [1,2]
+test_eq(t[4,6], [1,2])
+
+
+

source

+
+
+

L.unique

+
+
 L.unique (sort=False, bidir=False, start=None)
+
+

Unique items, in stable order

+
+
test_eq(L(4,1,2,3,4,4).unique(), [4,1,2,3])
+
+
+

source

+
+
+

L.val2idx

+
+
 L.val2idx ()
+
+

Dict from value to index

+
+
test_eq(L(1,2,3).val2idx(), {3:2,1:0,2:1})
+
+
+

source

+
+
+

L.filter

+
+
 L.filter (f=<function noop>, negate=False, **kwargs)
+
+

Create new L filtered by predicate f, passing args and kwargs to f

+
+
list(t)
+
+
[0, 1, 2, 3, 1, 5, 2, 7, 8, 9, 10, 11]
+
+
+
+
test_eq(t.filter(lambda o:o<5), [0,1,2,3,1,2])
+test_eq(t.filter(lambda o:o<5, negate=True), [5,7,8,9,10,11])
+
+
+

source

+
+
+

L.argwhere

+
+
 L.argwhere (f, negate=False, **kwargs)
+
+

Like filter, but return indices for matching items

+
+
test_eq(t.argwhere(lambda o:o<5), [0,1,2,3,4,6])
+
+
+

source

+
+
+

L.argfirst

+
+
 L.argfirst (f, negate=False)
+
+

Return index of first matching item

+
+
test_eq(t.argfirst(lambda o:o>4), 5)
+test_eq(t.argfirst(lambda o:o>4,negate=True),0)
+
+
+

source

+
+
+

L.map

+
+
 L.map (f, *args, **kwargs)
+
+

Create new L with f applied to all items, passing args and kwargs to f

+
+
test_eq(L.range(4).map(operator.neg), [0,-1,-2,-3])
+
+

If f is a string then it is treated as a format string to create the mapping:

+
+
test_eq(L.range(4).map('#{}#'), ['#0#','#1#','#2#','#3#'])
+
+

If f is a dictionary (or anything supporting __getitem__) then it is indexed to create the mapping:

+
+
test_eq(L.range(4).map(list('abcd')), list('abcd'))
+
+

You can also pass the same arg params that bind accepts:

+
+
def f(a=None,b=None): return b
+test_eq(L.range(4).map(f, b=arg0), range(4))
+
+
+

source

+
+
+

L.map_dict

+
+
 L.map_dict (f=<function noop>, *args, **kwargs)
+
+

Like map, but creates a dict from items to function results

+
+
test_eq(L(range(1,5)).map_dict(), {1:1, 2:2, 3:3, 4:4})
+test_eq(L(range(1,5)).map_dict(operator.neg), {1:-1, 2:-2, 3:-3, 4:-4})
+
+
+

source

+
+
+

L.zip

+
+
 L.zip (cycled=False)
+
+

Create new L with zip(*items)

+
+
t = L([[1,2,3],'abc'])
+test_eq(t.zip(), [(1, 'a'),(2, 'b'),(3, 'c')])
+
+
+
t = L([[1,2,3,4],['a','b','c']])
+test_eq(t.zip(cycled=True ), [(1, 'a'),(2, 'b'),(3, 'c'),(4, 'a')])
+test_eq(t.zip(cycled=False), [(1, 'a'),(2, 'b'),(3, 'c')])
+
+
+

source

+
+
+

L.map_zip

+
+
 L.map_zip (f, *args, cycled=False, **kwargs)
+
+

Combine zip and starmap

+
+
t = L([1,2,3],[2,3,4])
+test_eq(t.map_zip(operator.mul), [2,6,12])
+
+
+

source

+
+
+

L.zipwith

+
+
 L.zipwith (*rest, cycled=False)
+
+

Create new L with self zip with each of *rest

+
+
b = [[0],[1],[2,2]]
+t = L([1,2,3]).zipwith(b)
+test_eq(t, [(1,[0]), (2,[1]), (3,[2,2])])
+
+
+

source

+
+
+

L.map_zipwith

+
+
 L.map_zipwith (f, *rest, cycled=False, **kwargs)
+
+

Combine zipwith and starmap

+
+
test_eq(L(1,2,3).map_zipwith(operator.mul, [2,3,4]), [2,6,12])
+
+
+

source

+
+
+

L.itemgot

+
+
 L.itemgot (*idxs)
+
+

Create new L with item idx of all items

+
+
test_eq(t.itemgot(1), b)
+
+
+

source

+
+
+

L.attrgot

+
+
 L.attrgot (k, default=None)
+
+

Create new L with attr k (or value k for dicts) of all items.

+
+
# Example when items are not a dict
+a = [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)]
+test_eq(L(a).attrgot('b'), [4,2])
+
+#Example of when items are a dict
+b =[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}]
+test_eq(L(b).attrgot('id'), [15, 17])
+
+
+

source

+
+
+

L.sorted

+
+
 L.sorted (key=None, reverse=False)
+
+

New L sorted by key. If key is str use attrgetter; if int use itemgetter

+
+
test_eq(L(a).sorted('a').attrgot('b'), [2,4])
+
+
+

source

+
+
+

L.split

+
+
 L.split (s, sep=None, maxsplit=-1)
+
+

Class Method: Same as str.split, but returns an L

+
+
test_eq(L.split('a b c'), list('abc'))
+
+
+

source

+
+
+

L.range

+
+
 L.range (a, b=None, step=None)
+
+

Class Method: Same as range, but returns L. Can pass collection for a, to use len(a)

+
+
test_eq_type(L.range([1,1,1]), L(range(3)))
+test_eq_type(L.range(5,2,2), L(range(5,2,2)))
+
+
+

source

+
+
+

L.concat

+
+
 L.concat ()
+
+

Concatenate all elements of list

+
+
test_eq(L([0,1,2,3],4,L(5,6)).concat(), range(7))
+
+
+

source

+
+
+

L.copy

+
+
 L.copy ()
+
+

Same as list.copy, but returns an L

+
+
t = L([0,1,2,3],4,L(5,6)).copy()
+test_eq(t.concat(), range(7))
+
+
+

source

+
+
+

L.map_first

+
+
 L.map_first (f=<function noop>, g=<function noop>, *args, **kwargs)
+
+

First element of map_filter

+
+
t = L(0,1,2,3)
+test_eq(t.map_first(lambda o:o*2 if o>2 else None), 6)
+
+
+

source

+
+
+

L.setattrs

+
+
 L.setattrs (attr, val)
+
+

Call setattr on all items

+
+
t = L(SimpleNamespace(),SimpleNamespace())
+t.setattrs('foo', 'bar')
+test_eq(t.attrgot('foo'), ['bar','bar'])
+
+
+
+
+

Config

+
+

source

+
+

save_config_file

+
+
 save_config_file (file, d, **kwargs)
+
+

Write settings dict to a new config file, or overwrite the existing one.

+
+

source

+
+
+

read_config_file

+
+
 read_config_file (file, **kwargs)
+
+

Config files are saved and read using Python’s configparser.ConfigParser, inside the DEFAULT section.

+
+
_d = dict(user='fastai', lib_name='fastcore', some_path='test', some_bool=True, some_num=3)
+try:
+    save_config_file('tmp.ini', _d)
+    res = read_config_file('tmp.ini')
+finally: os.unlink('tmp.ini')
+dict(res)
+
+
{'user': 'fastai',
+ 'lib_name': 'fastcore',
+ 'some_path': 'test',
+ 'some_bool': 'True',
+ 'some_num': '3'}
+
+
+
+

source

+
+
+

Config

+
+
 Config (cfg_path, cfg_name, create=None, save=True, extra_files=None,
+         types=None)
+
+

Reading and writing ConfigParser ini files

+

Config is a convenient wrapper around ConfigParser ini files with a single section (DEFAULT).

+

Instantiate a Config from an ini file at cfg_path/cfg_name:

+
+
save_config_file('../tmp.ini', _d)
+try: cfg = Config('..', 'tmp.ini')
+finally: os.unlink('../tmp.ini')
+cfg
+
+
{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'}
+
+
+

You can create a new file if one doesn’t exist by providing a create dict:

+
+
try: cfg = Config('..', 'tmp.ini', create=_d)
+finally: os.unlink('../tmp.ini')
+cfg
+
+
{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'}
+
+
+

If you additionally pass save=False, the Config will contain the items from create without writing a new file:

+
+
cfg = Config('..', 'tmp.ini', create=_d, save=False)
+test_eq(cfg.user,'fastai')
+assert not Path('../tmp.ini').exists()
+
+
+

source

+
+
+

Config.get

+
+
 Config.get (k, default=None)
+
+

Keys can be accessed as attributes, items, or with get and an optional default:

+
+
test_eq(cfg.user,'fastai')
+test_eq(cfg['some_path'], 'test')
+test_eq(cfg.get('foo','bar'),'bar')
+
+

Extra files can be read before cfg_path/cfg_name using extra_files, in the order they appear:

+
+
with tempfile.TemporaryDirectory() as d:
+    a = Config(d, 'a.ini', {'a':0,'b':0})
+    b = Config(d, 'b.ini', {'a':1,'c':0})
+    c = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file])
+    test_eq(c.d, {'a':'2','b':'0','c':'0','d':'0'})
+
+

If you pass a dict types, then the values of that dict will be used as types to instantiate all values returned. Path is a special case – in that case, the path returned will be relative to the path containing the config file (assuming the value is relative). bool types use str2bool to convert to boolean.

+
+
_types = dict(some_path=Path, some_bool=bool, some_num=int)
+cfg = Config('..', 'tmp.ini', create=_d, save=False, types=_types)
+
+test_eq(cfg.user,'fastai')
+test_eq(cfg['some_path'].resolve(), (Path('..')/'test').resolve())
+test_eq(cfg.get('some_num'), 3)
+
+
+

source

+
+
+

Config.find

+
+
 Config.find (cfg_name, cfg_path=None, **kwargs)
+
+

Search cfg_path and its parents to find cfg_name

+

You can use Config.find to search subdirectories for a config file, starting in the current path if no path is specified:

+
+
Config.find('settings.ini').repo
+
+
'fastcore'
+
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/foundation.html.md b/foundation.html.md new file mode 100644 index 00000000..db9b1606 --- /dev/null +++ b/foundation.html.md @@ -0,0 +1,1081 @@ +# Foundation + + + + +## Foundational Functions + +------------------------------------------------------------------------ + +source + +### working_directory + +> working_directory (path) + +*Change working directory to `path` and return to previous on exit.* + +------------------------------------------------------------------------ + +source + +### add_docs + +> add_docs (cls, cls_doc=None, **docs) + +*Copy values from +[`docs`](https://fastcore.fast.ai/foundation.html#docs) to `cls` +docstrings, and confirm all public methods are documented* + +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs) allows +you to add docstrings to a class and its associated methods. This +function allows you to group docstrings together seperate from your +code, which enables you to define one-line functions as well as organize +your code more succintly. We believe this confers a number of benefits +which we discuss in [our style +guide](https://docs.fast.ai/dev/style.html). + +Suppose you have the following undocumented class: + +``` python +class T: + def foo(self): pass + def bar(self): pass +``` + +You can add documentation to this class like so: + +``` python +add_docs(T, cls_doc="A docstring for the class.", + foo="The foo method.", + bar="The bar method.") +``` + +Now, docstrings will appear as expected: + +``` python +test_eq(T.__doc__, "A docstring for the class.") +test_eq(T.foo.__doc__, "The foo method.") +test_eq(T.bar.__doc__, "The bar method.") +``` + +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs) also +validates that all of your public methods contain a docstring. If one of +your methods is not documented, it will raise an error: + +``` python +class T: + def foo(self): pass + def bar(self): pass + +f=lambda: add_docs(T, "A docstring for the class.", foo="The foo method.") +test_fail(f, contains="Missing docs") +``` + +------------------------------------------------------------------------ + +source + +### docs + +> docs (cls) + +*Decorator version of +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs), using +`_docs` dict* + +Instead of using +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs), you can +use the decorator +[`docs`](https://fastcore.fast.ai/foundation.html#docs) as shown below. +Note that the docstring for the class can be set with the argument +`cls_doc`: + +``` python +@docs +class _T: + def f(self): pass + def g(cls): pass + + _docs = dict(cls_doc="The class docstring", + f="The docstring for method f.", + g="A different docstring for method g.") + + +test_eq(_T.__doc__, "The class docstring") +test_eq(_T.f.__doc__, "The docstring for method f.") +test_eq(_T.g.__doc__, "A different docstring for method g.") +``` + +For either the [`docs`](https://fastcore.fast.ai/foundation.html#docs) +decorator or the +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs) +function, you can still define your docstrings in the normal way. Below +we set the docstring for the class as usual, but define the method +docstrings through the `_docs` attribute: + +``` python +@docs +class _T: + "The class docstring" + def f(self): pass + _docs = dict(f="The docstring for method f.") + + +test_eq(_T.__doc__, "The class docstring") +test_eq(_T.f.__doc__, "The docstring for method f.") +``` + +------------------------------------------------------------------------ + +### is_iter + +> is_iter (o) + +*Test whether `o` can be used in a `for` loop* + +``` python +assert is_iter([1]) +assert not is_iter(array(1)) +assert is_iter(array([1,2])) +assert (o for o in range(3)) +``` + +------------------------------------------------------------------------ + +source + +### coll_repr + +> coll_repr (c, max_n=10) + +*String repr of up to `max_n` items of (possibly lazy) collection `c`* + +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) is +used to provide a more informative +[`__repr__`](https://stackoverflow.com/questions/1984162/purpose-of-pythons-repr) +about list-like objects. +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) and is +used by [`L`](https://fastcore.fast.ai/foundation.html#l) to build a +`__repr__` that displays the length of a list in addition to a preview +of a list. + +Below is an example of the `__repr__` string created for a list of 1000 +elements: + +``` python +test_eq(coll_repr(range(1000)), '(#1000) [0,1,2,3,4,5,6,7,8,9...]') +test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]') +test_eq(coll_repr(range(10), 5), '(#10) [0,1,2,3,4...]') +test_eq(coll_repr(range(5), 5), '(#5) [0,1,2,3,4]') +``` + +We can set the option `max_n` to optionally preview a specified number +of items instead of the default: + +``` python +test_eq(coll_repr(range(1000), max_n=5), '(#1000) [0,1,2,3,4...]') +``` + +------------------------------------------------------------------------ + +source + +### is_bool + +> is_bool (x) + +*Check whether `x` is a bool or None* + +------------------------------------------------------------------------ + +source + +### mask2idxs + +> mask2idxs (mask) + +*Convert bool mask or index list to index +[`L`](https://fastcore.fast.ai/foundation.html#l)* + +``` python +test_eq(mask2idxs([False,True,False,True]), [1,3]) +test_eq(mask2idxs(array([False,True,False,True])), [1,3]) +test_eq(mask2idxs(array([1,2,3])), [1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### cycle + +> cycle (o) + +*Like `itertools.cycle` except creates list of `None`s if `o` is empty* + +``` python +test_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2]) +test_eq(itertools.islice(cycle([]),3), [None]*3) +test_eq(itertools.islice(cycle(None),3), [None]*3) +test_eq(itertools.islice(cycle(1),3), [1,1,1]) +``` + +------------------------------------------------------------------------ + +source + +### zip_cycle + +> zip_cycle (x, *args) + +*Like `itertools.zip_longest` but +[`cycle`](https://fastcore.fast.ai/foundation.html#cycle)s through +elements of all but first argument* + +``` python +test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')]) +``` + +------------------------------------------------------------------------ + +source + +### is_indexer + +> is_indexer (idx) + +*Test whether `idx` will index a single item in a list* + +You can, for example index a single item in a list with an integer or a +0-dimensional numpy array: + +``` python +assert is_indexer(1) +assert is_indexer(np.array(1)) +``` + +However, you cannot index into single item in a list with another list +or a numpy array with ndim \> 0. + +``` python +assert not is_indexer([1, 2]) +assert not is_indexer(np.array([[1, 2], [3, 4]])) +``` + +## [`L`](https://fastcore.fast.ai/foundation.html#l) helpers + +------------------------------------------------------------------------ + +source + +### CollBase + +> CollBase (items) + +*Base class for composing a list of `items`* + +`ColBase` is a base class that emulates the functionality of a python +`list`: + +``` python +class _T(CollBase): pass +l = _T([1,2,3,4,5]) + +test_eq(len(l), 5) # __len__ +test_eq(l[-1], 5); test_eq(l[0], 1) #__getitem__ +l[2] = 100; test_eq(l[2], 100) # __set_item__ +del l[0]; test_eq(len(l), 4) # __delitem__ +test_eq(str(l), '[2, 100, 4, 5]') # __repr__ +``` + +------------------------------------------------------------------------ + +source + +### L + +> L (x=None, *args, **kwargs) + +*Behaves like a list of `items` but can also index with list of indices +or masks* + +[`L`](https://fastcore.fast.ai/foundation.html#l) is a drop in +replacement for a python `list`. Inspired by +[NumPy](http://www.numpy.org/), +[`L`](https://fastcore.fast.ai/foundation.html#l), supports advanced +indexing and has additional methods (outlined below) that provide +additional functionality and encourage simple expressive code. For +example, the code below takes a list of pairs, selects the second item +of each pair, takes its absolute value, filters items greater than 4, +and adds them up: + +``` python +from fastcore.utils import gt +``` + +``` python +d = dict(a=1,b=-5,d=6,e=9).items() +test_eq(L(d).itemgot(1).map(abs).filter(gt(4)).sum(), 20) # abs(-5) + abs(6) + abs(9) = 20; 1 was filtered out. +``` + +Read [this overview section](https://fastcore.fast.ai/tour.html#L) for a +quick tutorial of [`L`](https://fastcore.fast.ai/foundation.html#l), as +well as background on the name. + +You can create an [`L`](https://fastcore.fast.ai/foundation.html#l) from +an existing iterable (e.g. a list, range, etc) and access or modify it +with an int list/tuple index, mask, int, or slice. All `list` methods +can also be used with [`L`](https://fastcore.fast.ai/foundation.html#l). + +``` python +t = L(range(12)) +test_eq(t, list(range(12))) +test_ne(t, list(range(11))) +t.reverse() +test_eq(t[0], 11) +t[3] = "h" +test_eq(t[3], "h") +t[3,5] = ("j","k") +test_eq(t[3,5], ["j","k"]) +test_eq(t, L(t)) +test_eq(L(L(1,2),[3,4]), ([1,2],[3,4])) +t +``` + + (#12) [11,10,9,'j',7,'k',5,4,3,2...] + +Any [`L`](https://fastcore.fast.ai/foundation.html#l) is a `Sequence` so +you can use it with methods like `random.sample`: + +``` python +assert isinstance(t, Sequence) +``` + +``` python +import random +``` + +``` python +random.seed(0) +random.sample(t, 3) +``` + + [5, 0, 11] + +There are optimized indexers for arrays, tensors, and DataFrames. + +``` python +import pandas as pd +``` + +``` python +arr = np.arange(9).reshape(3,3) +t = L(arr, use_list=None) +test_eq(t[1,2], arr[[1,2]]) + +df = pd.DataFrame({'a':[1,2,3]}) +t = L(df, use_list=None) +test_eq(t[1,2], L(pd.DataFrame({'a':[2,3]}, index=[1,2]), use_list=None)) +``` + +You can also modify an [`L`](https://fastcore.fast.ai/foundation.html#l) +with `append`, `+`, and `*`. + +``` python +t = L() +test_eq(t, []) +t.append(1) +test_eq(t, [1]) +t += [3,2] +test_eq(t, [1,3,2]) +t = t + [4] +test_eq(t, [1,3,2,4]) +t = 5 + t +test_eq(t, [5,1,3,2,4]) +test_eq(L(1,2,3), [1,2,3]) +test_eq(L(1,2,3), L(1,2,3)) +t = L(1)*5 +t = t.map(operator.neg) +test_eq(t,[-1]*5) +test_eq(~L([True,False,False]), L([False,True,True])) +t = L(range(4)) +test_eq(zip(t, L(1).cycle()), zip(range(4),(1,1,1,1))) +t = L.range(100) +test_shuffled(t,t.shuffle()) +``` + +``` python +test_eq(L([]).sum(), 0) +test_eq(L([]).product(), 1) +``` + +``` python +def _f(x,a=0): return x+a +t = L(1)*5 +test_eq(t.map(_f), t) +test_eq(t.map(_f,1), [2]*5) +test_eq(t.map(_f,a=2), [3]*5) +``` + +An [`L`](https://fastcore.fast.ai/foundation.html#l) can be constructed +from anything iterable, although tensors and arrays will not be iterated +over on construction, unless you pass `use_list` to the constructor. + +``` python +test_eq(L([1,2,3]),[1,2,3]) +test_eq(L(L([1,2,3])),[1,2,3]) +test_ne(L([1,2,3]),[1,2,]) +test_eq(L('abc'),['abc']) +test_eq(L(range(0,3)),[0,1,2]) +test_eq(L(o for o in range(0,3)),[0,1,2]) +test_eq(L(array(0)),[array(0)]) +test_eq(L([array(0),array(1)]),[array(0),array(1)]) +test_eq(L(array([0.,1.1]))[0],array([0.,1.1])) +test_eq(L(array([0.,1.1]), use_list=True), [array(0.),array(1.1)]) # `use_list=True` to unwrap arrays/arrays +``` + +If `match` is not `None` then the created list is same len as `match`, +either by: + +- If `len(items)==1` then `items` is replicated, +- Otherwise an error is raised if `match` and `items` are not already + the same size. + +``` python +test_eq(L(1,match=[1,2,3]),[1,1,1]) +test_eq(L([1,2],match=[2,3]),[1,2]) +test_fail(lambda: L([1,2],match=[1,2,3])) +``` + +If you create an [`L`](https://fastcore.fast.ai/foundation.html#l) from +an existing [`L`](https://fastcore.fast.ai/foundation.html#l) then +you’ll get back the original object (since +[`L`](https://fastcore.fast.ai/foundation.html#l) uses the +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) +metaclass). + +``` python +test_is(L(t), t) +``` + +An [`L`](https://fastcore.fast.ai/foundation.html#l) is considred equal +to a list if they have the same elements. It’s never considered equal to +a `str` a `set` or a `dict` even if they have the same elements/keys. + +``` python +test_eq(L(['a', 'b']), ['a', 'b']) +test_ne(L(['a', 'b']), 'ab') +test_ne(L(['a', 'b']), {'a':1, 'b':2}) +``` + +### [`L`](https://fastcore.fast.ai/foundation.html#l) Methods + +------------------------------------------------------------------------ + +source + +### L.\_\_getitem\_\_ + +> L.__getitem__ (idx) + +*Retrieve `idx` (can be list of indices, or mask, or int) items* + +``` python +t = L(range(12)) +test_eq(t[1,2], [1,2]) # implicit tuple +test_eq(t[[1,2]], [1,2]) # list +test_eq(t[:3], [0,1,2]) # slice +test_eq(t[[False]*11 + [True]], [11]) # mask +test_eq(t[array(3)], 3) +``` + +------------------------------------------------------------------------ + +source + +### L.\_\_setitem\_\_ + +> L.__setitem__ (idx, o) + +*Set `idx` (can be list of indices, or mask, or int) items to `o` (which +is broadcast if not iterable)* + +``` python +t[4,6] = 0 +test_eq(t[4,6], [0,0]) +t[4,6] = [1,2] +test_eq(t[4,6], [1,2]) +``` + +------------------------------------------------------------------------ + +source + +### L.unique + +> L.unique (sort=False, bidir=False, start=None) + +*Unique items, in stable order* + +``` python +test_eq(L(4,1,2,3,4,4).unique(), [4,1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### L.val2idx + +> L.val2idx () + +*Dict from value to index* + +``` python +test_eq(L(1,2,3).val2idx(), {3:2,1:0,2:1}) +``` + +------------------------------------------------------------------------ + +source + +### L.filter + +> L.filter (f=, negate=False, **kwargs) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) filtered +by predicate `f`, passing `args` and `kwargs` to `f`* + +``` python +list(t) +``` + + [0, 1, 2, 3, 1, 5, 2, 7, 8, 9, 10, 11] + +``` python +test_eq(t.filter(lambda o:o<5), [0,1,2,3,1,2]) +test_eq(t.filter(lambda o:o<5, negate=True), [5,7,8,9,10,11]) +``` + +------------------------------------------------------------------------ + +source + +### L.argwhere + +> L.argwhere (f, negate=False, **kwargs) + +*Like `filter`, but return indices for matching items* + +``` python +test_eq(t.argwhere(lambda o:o<5), [0,1,2,3,4,6]) +``` + +------------------------------------------------------------------------ + +source + +### L.argfirst + +> L.argfirst (f, negate=False) + +*Return index of first matching item* + +``` python +test_eq(t.argfirst(lambda o:o>4), 5) +test_eq(t.argfirst(lambda o:o>4,negate=True),0) +``` + +------------------------------------------------------------------------ + +source + +### L.map + +> L.map (f, *args, **kwargs) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with `f` +applied to all `items`, passing `args` and `kwargs` to `f`* + +``` python +test_eq(L.range(4).map(operator.neg), [0,-1,-2,-3]) +``` + +If `f` is a string then it is treated as a format string to create the +mapping: + +``` python +test_eq(L.range(4).map('#{}#'), ['#0#','#1#','#2#','#3#']) +``` + +If `f` is a dictionary (or anything supporting `__getitem__`) then it is +indexed to create the mapping: + +``` python +test_eq(L.range(4).map(list('abcd')), list('abcd')) +``` + +You can also pass the same `arg` params that +[`bind`](https://fastcore.fast.ai/basics.html#bind) accepts: + +``` python +def f(a=None,b=None): return b +test_eq(L.range(4).map(f, b=arg0), range(4)) +``` + +------------------------------------------------------------------------ + +source + +### L.map_dict + +> L.map_dict (f=, *args, **kwargs) + +*Like `map`, but creates a dict from `items` to function results* + +``` python +test_eq(L(range(1,5)).map_dict(), {1:1, 2:2, 3:3, 4:4}) +test_eq(L(range(1,5)).map_dict(operator.neg), {1:-1, 2:-2, 3:-3, 4:-4}) +``` + +------------------------------------------------------------------------ + +source + +### L.zip + +> L.zip (cycled=False) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with +`zip(*items)`* + +``` python +t = L([[1,2,3],'abc']) +test_eq(t.zip(), [(1, 'a'),(2, 'b'),(3, 'c')]) +``` + +``` python +t = L([[1,2,3,4],['a','b','c']]) +test_eq(t.zip(cycled=True ), [(1, 'a'),(2, 'b'),(3, 'c'),(4, 'a')]) +test_eq(t.zip(cycled=False), [(1, 'a'),(2, 'b'),(3, 'c')]) +``` + +------------------------------------------------------------------------ + +source + +### L.map_zip + +> L.map_zip (f, *args, cycled=False, **kwargs) + +*Combine `zip` and `starmap`* + +``` python +t = L([1,2,3],[2,3,4]) +test_eq(t.map_zip(operator.mul), [2,6,12]) +``` + +------------------------------------------------------------------------ + +source + +### L.zipwith + +> L.zipwith (*rest, cycled=False) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with +`self` zip with each of `*rest`* + +``` python +b = [[0],[1],[2,2]] +t = L([1,2,3]).zipwith(b) +test_eq(t, [(1,[0]), (2,[1]), (3,[2,2])]) +``` + +------------------------------------------------------------------------ + +source + +### L.map_zipwith + +> L.map_zipwith (f, *rest, cycled=False, **kwargs) + +*Combine `zipwith` and `starmap`* + +``` python +test_eq(L(1,2,3).map_zipwith(operator.mul, [2,3,4]), [2,6,12]) +``` + +------------------------------------------------------------------------ + +source + +### L.itemgot + +> L.itemgot (*idxs) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with item +`idx` of all `items`* + +``` python +test_eq(t.itemgot(1), b) +``` + +------------------------------------------------------------------------ + +source + +### L.attrgot + +> L.attrgot (k, default=None) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with attr +`k` (or value `k` for dicts) of all `items`.* + +``` python +# Example when items are not a dict +a = [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)] +test_eq(L(a).attrgot('b'), [4,2]) + +#Example of when items are a dict +b =[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}] +test_eq(L(b).attrgot('id'), [15, 17]) +``` + +------------------------------------------------------------------------ + +source + +### L.sorted + +> L.sorted (key=None, reverse=False) + +*New [`L`](https://fastcore.fast.ai/foundation.html#l) sorted by `key`. +If key is str use `attrgetter`; if int use `itemgetter`* + +``` python +test_eq(L(a).sorted('a').attrgot('b'), [2,4]) +``` + +------------------------------------------------------------------------ + +source + +### L.split + +> L.split (s, sep=None, maxsplit=-1) + +*Class Method: Same as `str.split`, but returns an +[`L`](https://fastcore.fast.ai/foundation.html#l)* + +``` python +test_eq(L.split('a b c'), list('abc')) +``` + +------------------------------------------------------------------------ + +source + +### L.range + +> L.range (a, b=None, step=None) + +*Class Method: Same as `range`, but returns +[`L`](https://fastcore.fast.ai/foundation.html#l). Can pass collection +for `a`, to use `len(a)`* + +``` python +test_eq_type(L.range([1,1,1]), L(range(3))) +test_eq_type(L.range(5,2,2), L(range(5,2,2))) +``` + +------------------------------------------------------------------------ + +source + +### L.concat + +> L.concat () + +*Concatenate all elements of list* + +``` python +test_eq(L([0,1,2,3],4,L(5,6)).concat(), range(7)) +``` + +------------------------------------------------------------------------ + +source + +### L.copy + +> L.copy () + +*Same as `list.copy`, but returns an +[`L`](https://fastcore.fast.ai/foundation.html#l)* + +``` python +t = L([0,1,2,3],4,L(5,6)).copy() +test_eq(t.concat(), range(7)) +``` + +------------------------------------------------------------------------ + +source + +### L.map_first + +> L.map_first (f=, g=, *args, **kwargs) + +*First element of `map_filter`* + +``` python +t = L(0,1,2,3) +test_eq(t.map_first(lambda o:o*2 if o>2 else None), 6) +``` + +------------------------------------------------------------------------ + +source + +### L.setattrs + +> L.setattrs (attr, val) + +*Call `setattr` on all items* + +``` python +t = L(SimpleNamespace(),SimpleNamespace()) +t.setattrs('foo', 'bar') +test_eq(t.attrgot('foo'), ['bar','bar']) +``` + +## Config + +------------------------------------------------------------------------ + +source + +### save_config_file + +> save_config_file (file, d, **kwargs) + +*Write settings dict to a new config file, or overwrite the existing +one.* + +------------------------------------------------------------------------ + +source + +### read_config_file + +> read_config_file (file, **kwargs) + +Config files are saved and read using Python’s +`configparser.ConfigParser`, inside the `DEFAULT` section. + +``` python +_d = dict(user='fastai', lib_name='fastcore', some_path='test', some_bool=True, some_num=3) +try: + save_config_file('tmp.ini', _d) + res = read_config_file('tmp.ini') +finally: os.unlink('tmp.ini') +dict(res) +``` + + {'user': 'fastai', + 'lib_name': 'fastcore', + 'some_path': 'test', + 'some_bool': 'True', + 'some_num': '3'} + +------------------------------------------------------------------------ + +source + +### Config + +> Config (cfg_path, cfg_name, create=None, save=True, extra_files=None, +> types=None) + +*Reading and writing `ConfigParser` ini files* + +[`Config`](https://fastcore.fast.ai/foundation.html#config) is a +convenient wrapper around `ConfigParser` ini files with a single section +(`DEFAULT`). + +Instantiate a +[`Config`](https://fastcore.fast.ai/foundation.html#config) from an ini +file at `cfg_path/cfg_name`: + +``` python +save_config_file('../tmp.ini', _d) +try: cfg = Config('..', 'tmp.ini') +finally: os.unlink('../tmp.ini') +cfg +``` + + {'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'} + +You can create a new file if one doesn’t exist by providing a `create` +dict: + +``` python +try: cfg = Config('..', 'tmp.ini', create=_d) +finally: os.unlink('../tmp.ini') +cfg +``` + + {'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'} + +If you additionally pass `save=False`, the +[`Config`](https://fastcore.fast.ai/foundation.html#config) will contain +the items from `create` without writing a new file: + +``` python +cfg = Config('..', 'tmp.ini', create=_d, save=False) +test_eq(cfg.user,'fastai') +assert not Path('../tmp.ini').exists() +``` + +------------------------------------------------------------------------ + +source + +### Config.get + +> Config.get (k, default=None) + +Keys can be accessed as attributes, items, or with `get` and an optional +default: + +``` python +test_eq(cfg.user,'fastai') +test_eq(cfg['some_path'], 'test') +test_eq(cfg.get('foo','bar'),'bar') +``` + +Extra files can be read *before* `cfg_path/cfg_name` using +`extra_files`, in the order they appear: + +``` python +with tempfile.TemporaryDirectory() as d: + a = Config(d, 'a.ini', {'a':0,'b':0}) + b = Config(d, 'b.ini', {'a':1,'c':0}) + c = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file]) + test_eq(c.d, {'a':'2','b':'0','c':'0','d':'0'}) +``` + +If you pass a dict `types`, then the values of that dict will be used as +types to instantiate all values returned. `Path` is a special case – in +that case, the path returned will be relative to the path containing the +config file (assuming the value is relative). `bool` types use +[`str2bool`](https://fastcore.fast.ai/basics.html#str2bool) to convert +to boolean. + +``` python +_types = dict(some_path=Path, some_bool=bool, some_num=int) +cfg = Config('..', 'tmp.ini', create=_d, save=False, types=_types) + +test_eq(cfg.user,'fastai') +test_eq(cfg['some_path'].resolve(), (Path('..')/'test').resolve()) +test_eq(cfg.get('some_num'), 3) +``` + +------------------------------------------------------------------------ + +source + +### Config.find + +> Config.find (cfg_name, cfg_path=None, **kwargs) + +*Search `cfg_path` and its parents to find `cfg_name`* + +You can use +[`Config.find`](https://fastcore.fast.ai/foundation.html#config.find) to +search subdirectories for a config file, starting in the current path if +no path is specified: + +``` python +Config.find('settings.ini').repo +``` + + 'fastcore' diff --git a/images/att_00000.png b/images/att_00000.png new file mode 100644 index 0000000000000000000000000000000000000000..7d44d3f43f23d4c14d83efaaed0dc2fbf100b55a GIT binary patch literal 35850 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 literal 0 HcmV?d00001 diff --git a/images/att_00005.png b/images/att_00005.png new file mode 100644 index 0000000000000000000000000000000000000000..de857c985b555f4c933babe4ca98eec0f65af6e7 GIT binary patch literal 8156 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 00000000..e64ab8a7 --- /dev/null +++ b/index.html @@ -0,0 +1,729 @@ + + + + + + + + + + +Welcome to fastcore – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Welcome to fastcore

+
+ +
+
+ Python goodies to make your coding faster, easier, and more maintainable +
+
+ + +
+ + + + +
+ + + +
+ + + +

Python is a powerful, dynamic language. Rather than bake everything into the language, it lets the programmer customize it to make it work for them. fastcore uses this flexibility to add to Python features inspired by other languages we’ve loved, like multiple dispatch from Julia, mixins from Ruby, and currying, binding, and more from Haskell. It also adds some “missing features” and clean up some rough edges in the Python standard library, such as simplifying parallel processing, and bringing ideas from NumPy over to Python’s list type.

+
+

Getting started

+

To install fastcore run: conda install fastcore -c fastai (if you use Anaconda, which we recommend) or pip install fastcore. For an editable install, clone this repo and run: pip install -e ".[dev]". fastcore is tested to work on Ubuntu, macOS and Windows (versions tested are those shown with the -latest suffix here).

+

fastcore contains many features, including:

+
    +
  • fastcore.test: Simple testing functions
  • +
  • fastcore.foundation: Mixins, delegation, composition, and more
  • +
  • fastcore.xtras: Utility functions to help with functional-style programming, parallel processing, and more
  • +
  • fastcore.dispatch: Multiple dispatch methods
  • +
  • fastcore.transform: Pipelines of composed partially reversible transformations
  • +
+

To get started, we recommend you read through the fastcore tour.

+
+
+

Contributing

+

After you clone this repository, please run nbdev_install_hooks in your terminal. This sets up git hooks, which clean up the notebooks to remove the extraneous stuff stored in the notebooks (e.g. which cells you ran) which causes unnecessary merge conflicts.

+

To run the tests in parallel, launch nbdev_test.

+

Before submitting a PR, check that the local library and notebooks match.

+
    +
  • If you made a change to the notebooks in one of the exported cells, you can export it to the library with nbdev_prepare.
  • +
  • If you made a change to the library, you can export it back to the notebooks with nbdev_update.
  • +
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/index.html.md b/index.html.md new file mode 100644 index 00000000..b6472b0d --- /dev/null +++ b/index.html.md @@ -0,0 +1,54 @@ +# Welcome to fastcore + + + + +Python is a powerful, dynamic language. Rather than bake everything into +the language, it lets the programmer customize it to make it work for +them. `fastcore` uses this flexibility to add to Python features +inspired by other languages we’ve loved, like multiple dispatch from +Julia, mixins from Ruby, and currying, binding, and more from Haskell. +It also adds some “missing features” and clean up some rough edges in +the Python standard library, such as simplifying parallel processing, +and bringing ideas from NumPy over to Python’s `list` type. + +## Getting started + +To install fastcore run: `conda install fastcore -c fastai` (if you use +Anaconda, which we recommend) or `pip install fastcore`. For an +[editable +install](https://stackoverflow.com/questions/35064426/when-would-the-e-editable-option-be-useful-with-pip-install), +clone this repo and run: `pip install -e ".[dev]"`. fastcore is tested +to work on Ubuntu, macOS and Windows (versions tested are those shown +with the `-latest` suffix +[here](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-runners-and-hardware-resources)). + +`fastcore` contains many features, including: + +- `fastcore.test`: Simple testing functions +- `fastcore.foundation`: Mixins, delegation, composition, and more +- `fastcore.xtras`: Utility functions to help with functional-style + programming, parallel processing, and more +- `fastcore.dispatch`: Multiple dispatch methods +- `fastcore.transform`: Pipelines of composed partially reversible + transformations + +To get started, we recommend you read through [the fastcore +tour](https://fastcore.fast.ai/tour.html). + +## Contributing + +After you clone this repository, please run `nbdev_install_hooks` in +your terminal. This sets up git hooks, which clean up the notebooks to +remove the extraneous stuff stored in the notebooks (e.g. which cells +you ran) which causes unnecessary merge conflicts. + +To run the tests in parallel, launch `nbdev_test`. + +Before submitting a PR, check that the local library and notebooks +match. + +- If you made a change to the notebooks in one of the exported cells, + you can export it to the library with `nbdev_prepare`. +- If you made a change to the library, you can export it back to the + notebooks with `nbdev_update`. diff --git a/llms-ctx-full.txt b/llms-ctx-full.txt new file mode 100644 index 00000000..bafbe93e --- /dev/null +++ b/llms-ctx-full.txt @@ -0,0 +1,12213 @@ +fastcore adds to Python features inspired by other languages, like multiple dispatch from Julia, mixins from Ruby, and currying, binding, and more from Haskell. It also adds some “missing features” and clean up some rough edges in the Python standard library, such as simplifying parallel processing, and bringing ideas from NumPy over to Python’s list type. + +Here are some tips on using fastcore: + +- **Liberal imports**: Utilize `from fastcore.module import *` freely. The library is designed for safe wildcard imports. +- **Enhanced list operations**: Substitute `list` with `L`. This provides advanced indexing, method chaining, and additional functionality while maintaining list-like behavior. +- **Extend existing classes**: Apply the `@patch` decorator to add methods to classes, including built-ins, without subclassing. This enables more flexible code organization. +- **Streamline class initialization**: In `__init__` methods, use `store_attr()` to efficiently set multiple attributes, reducing repetitive assignment code. +- **Explicit keyword arguments**: Apply the `delegates` decorator to functions to replace `**kwargs` with specific parameters, enhancing IDE support and documentation. +- **Optimize parallel execution**: Leverage fastcore's enhanced `ThreadPoolExecutor` and `ProcessPoolExecutor` for simplified concurrent processing. +- **Expressive testing**: Prefer fastcore's testing functions like `test_eq`, `test_ne`, `test_close` for more readable and informative test assertions. +- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to `pathlib.Path`. +- **Flexible data structures**: Convert between dictionaries and attribute-access objects using `dict2obj` and `obj2dict` for more intuitive data handling. +- **Data pipeline construction**: Employ `Transform` and `Pipeline` classes to create modular, composable data processing workflows. +- **Functional programming paradigms**: Utilize tools like `compose`, `maps`, and `filter_ex` to write more functional-style Python code. +- **Documentation**: Use `docments` where possible to document parameters of functions and methods. +- **Time-aware caching**: Apply the `timed_cache` decorator to add time-based expiration to the standard `lru_cache` functionality. +- **Simplified CLI creation**: Use fastcore's console script utilities to easily transform Python functions into command-line interfaces.# A tour of fastcore + + + +Here’s a (somewhat) quick tour of a few higlights from fastcore. + +### Documentation + +All fast.ai projects, including this one, are built with +[nbdev](https://nbdev.fast.ai), which is a full literate programming +environment built on Jupyter Notebooks. That means that every piece of +documentation, including the page you’re reading now, can be accessed as +interactive Jupyter notebooks. In fact, you can even grab a link +directly to a notebook running interactively on Google Colab - if you +want to follow along with this tour, click the link below: + +``` python +colab_link('index') +``` + +[Open `index` in +Colab](https://colab.research.google.com/github/fastai/fastcore/blob/master/nbs/index.ipynb) + +The full docs are available at +[fastcore.fast.ai](https://fastcore.fast.ai). The code in the examples +and in all fast.ai libraries follow the [fast.ai style +guide](https://docs.fast.ai/dev/style.html). In order to support +interactive programming, all fast.ai libraries are designed to allow for +`import *` to be used safely, particular by ensuring that +[`__all__`](https://riptutorial.com/python/example/2894/the---all---special-variable) +is defined in all packages. In order to see where a function is from, +just type it: + +``` python +coll_repr +``` + + + +For more details, including a link to the full documentation and source +code, use `doc`, which pops up a window with this information: + +``` python +doc(coll_repr) +``` + + + +The documentation also contains links to any related functions or +classes, which appear like this: +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) (in +the notebook itself you will just see a word with back-ticks around it; +the links are auto-generated in the documentation site). The +documentation will generally show one or more examples of use, along +with any background context necessary to understand them. As you’ll see, +the examples for each function and method are shown as tests, rather +than example outputs, so let’s start by explaining that. + +### Testing + +fastcore’s testing module is designed to work well with +[nbdev](https://nbdev.fast.ai), which is a full literate programming +environment built on Jupyter Notebooks. That means that your tests, +docs, and code all live together in the same notebook. fastcore and +nbdev’s approach to testing starts with the premise that all your tests +should pass. If one fails, no more tests in a notebook are run. + +Tests look like this: + +``` python +test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]') +``` + +That’s an example from the docs for +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr). As +you see, it’s not showing you the output directly. Here’s what that +would look like: + +``` python +coll_repr(range(1000), 5) +``` + + '(#1000) [0,1,2,3,4...]' + +So, the test is actually showing you what the output looks like, because +if the function call didn’t return `'(#1000) [0,1,2,3,4...]'`, then the +test would have failed. + +So every test shown in the docs is also showing you the behavior of the +library — and vice versa! + +Test functions always start with `test_`, and then follow with the +operation being tested. So +[`test_eq`](https://fastcore.fast.ai/test.html#test_eq) tests for +equality (as you saw in the example above). This includes tests for +equality of arrays and tensors, lists and generators, and many more: + +``` python +test_eq([0,1,2,3], np.arange(4)) +``` + +When a test fails, it prints out information about what was expected: + +``` python +test_eq([0,1,2,3], np.arange(3)) +``` + + ---- + AssertionError: ==: + [0, 1, 2, 3] + [0 1 2] + +If you want to check that objects are the same type, rather than the +just contain the same collection, use +[`test_eq_type`](https://fastcore.fast.ai/test.html#test_eq_type). + +You can test with any comparison function using +[`test`](https://fastcore.fast.ai/test.html#test), e.g test whether an +object is less than: + +``` python +test(2, 3, operator.lt) +``` + +You can even test that exceptions are raised: + +``` python +def divide_zero(): return 1/0 +test_fail(divide_zero) +``` + +…and test that things are printed to stdout: + +``` python +test_stdout(lambda: print('hi'), 'hi') +``` + +### Foundations + +fast.ai is unusual in that we often use +[mixins](https://en.wikipedia.org/wiki/Mixin) in our code. Mixins are +widely used in many programming languages, such as Ruby, but not so much +in Python. We use mixins to attach new behavior to existing libraries, +or to allow modules to add new behavior to our own classes, such as in +extension modules. One useful example of a mixin we define is +[`Path.ls`](https://fastcore.fast.ai/xtras.html#path.ls), which lists a +directory and returns an +[`L`](https://fastcore.fast.ai/foundation.html#l) (an extended list +class which we’ll discuss shortly): + +``` python +p = Path('images') +p.ls() +``` + + (#6) [Path('images/mnist3.png'),Path('images/att_00000.png'),Path('images/puppy.jpg'),Path('images/att_00005.png'),Path('images/att_00007.png'),Path('images/att_00006.png')] + +You can easily add you own mixins with the +[`patch`](https://fastcore.fast.ai/basics.html#patch) +[decorator](https://realpython.com/primer-on-python-decorators/), which +takes advantage of Python 3 [function +annotations](https://www.python.org/dev/peps/pep-3107/#parameters) to +say what class to patch: + +``` python +@patch +def num_items(self:Path): return len(self.ls()) + +p.num_items() +``` + + 6 + +We also use `**kwargs` frequently. In python `**kwargs` in a parameter +like means “*put any additional keyword arguments into a dict called +`kwargs`*”. Normally, using `kwargs` makes an API quite difficult to +work with, because it breaks things like tab-completion and popup lists +of signatures. `utils` provides +[`use_kwargs`](https://fastcore.fast.ai/meta.html#use_kwargs) and +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) to avoid +this problem. See our [detailed article on +delegation](https://www.fast.ai/2019/08/06/delegation/) on this topic. + +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) solves a +similar problem (and is also discussed in the article linked above): +it’s allows you to use Python’s exceptionally useful +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) magic +method, but avoids the problem that normally in Python tab-completion +and docs break when using this. For instance, you can see here that +Python’s `dir` function, which is used to find the attributes of a +python object, finds everything inside the `self.default` attribute +here: + +``` python +class Author: + def __init__(self, name): self.name = name + +class ProductPage(GetAttr): + _default = 'author' + def __init__(self,author,price,cost): self.author,self.price,self.cost = author,price,cost + +p = ProductPage(Author("Jeremy"), 1.50, 0.50) +[o for o in dir(p) if not o.startswith('_')] +``` + + ['author', 'cost', 'name', 'price'] + +Looking at that `ProductPage` example, it’s rather verbose and +duplicates a lot of attribute names, which can lead to bugs later if you +change them only in one place. `fastcore` provides +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr) to +simplify this common pattern. It also provides +[`basic_repr`](https://fastcore.fast.ai/basics.html#basic_repr) to give +simple objects a useful `repr`: + +``` python +class ProductPage: + def __init__(self,author,price,cost): store_attr() + __repr__ = basic_repr('author,price,cost') + +ProductPage("Jeremy", 1.50, 0.50) +``` + + __main__.ProductPage(author='Jeremy', price=1.5, cost=0.5) + +One of the most interesting `fastcore` functions is the +[`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs) +decorator. This allows class behavior to be modified without +sub-classing. This can allow folks that aren’t familiar with +object-oriented programming to customize your class more easily. Here’s +an example of a class that uses +[`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs): + +``` python +@funcs_kwargs +class T: + _methods=['some_method'] + def __init__(self, **kwargs): assert not kwargs, f'Passed unknown args: {kwargs}' + +p = T(some_method = print) +p.some_method("hello") +``` + + hello + +The `assert not kwargs` above is used to ensure that the user doesn’t +pass an unknown parameter (i.e one that’s not in `_methods`). `fastai` +uses [`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs) +in many places, for instance, you can customize any part of a +`DataLoader` by passing your own methods. + +`fastcore` also provides many utility functions that make a Python +programmer’s life easier, in `fastcore.utils`. We won’t look at many +here, since you can easily look at the docs yourself. To get you +started, have a look at the docs for +[`chunked`](https://fastcore.fast.ai/basics.html#chunked) (remember, if +you’re in a notebook, type `doc(chunked)`), which is a handy function +for creating lazily generated batches from a collection. + +Python’s +[`ProcessPoolExecutor`](https://fastcore.fast.ai/parallel.html#processpoolexecutor) +is extended to allow `max_workers` to be set to `0`, to easily turn off +parallel processing. This makes it easy to debug your code in serial, +then run it in parallel. It also allows you to pass arguments to your +parallel function, and to ensure there’s a pause between calls, in case +the process you are running has race conditions. +[`parallel`](https://fastcore.fast.ai/parallel.html#parallel) makes +parallel processing even easier to use, and even adds an optional +progress bar. + +### L + +Like most languages, Python allows for very concise syntax for some very +common types, such as `list`, which can be constructed with `[1,2,3]`. +Perl’s designer Larry Wall explained the reasoning for this kind of +syntax: + +> In metaphorical honor of Huffman’s compression code that assigns +> smaller numbers of bits to more common bytes. In terms of syntax, it +> simply means that commonly used things should be shorter, but you +> shouldn’t waste short sequences on less common constructs. + +On this basis, `fastcore` has just one type that has a single letter +name: [`L`](https://fastcore.fast.ai/foundation.html#l). The reason for +this is that it is designed to be a replacement for `list`, so we want +it to be just as easy to use as `[1,2,3]`. Here’s how to create that as +an [`L`](https://fastcore.fast.ai/foundation.html#l): + +``` python +L(1,2,3) +``` + + (#3) [1,2,3] + +The first thing to notice is that an +[`L`](https://fastcore.fast.ai/foundation.html#l) object includes in its +representation its number of elements; that’s the `(#3)` in the output +above. If there’s more than 10 elements, it will automatically truncate +the list: + +``` python +p = L.range(20).shuffle() +p +``` + + (#20) [5,1,9,10,18,13,6,17,3,16...] + +[`L`](https://fastcore.fast.ai/foundation.html#l) contains many of the +same indexing ideas that NumPy’s `array` does, including indexing with a +list of indexes, or a boolean mask list: + +``` python +p[2,4,6] +``` + + (#3) [9,18,6] + +It also contains other methods used in `array`, such as +[`L.argwhere`](https://fastcore.fast.ai/foundation.html#l.argwhere): + +``` python +p.argwhere(ge(15)) +``` + + (#5) [4,7,9,18,19] + +As you can see from this example, `fastcore` also includes a number of +features that make a functional style of programming easier, such as a +full range of boolean functions (e.g `ge`, `gt`, etc) which give the +same answer as the functions from Python’s `operator` module if given +two parameters, but return a [curried +function](https://en.wikipedia.org/wiki/Currying) if given one +parameter. + +There’s too much functionality to show it all here, so be sure to check +the docs. Many little things are added that we thought should have been +in `list` in the first place, such as making this do what you’d expect +(which is an error with `list`, but works fine with +[`L`](https://fastcore.fast.ai/foundation.html#l)): + +``` python +1 + L(2,3,4) +``` + + (#4) [1,2,3,4] + +### Transforms + +A [`Transform`](https://fastcore.fast.ai/transform.html#transform) is +the main building block of the fastai data pipelines. In the most +general terms a transform can be any function you want to apply to your +data, however the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) class +provides several mechanisms that make the process of building them easy +and flexible (see the docs for information about each of these): + +- Type dispatch +- Dispatch over tuples +- Reversability +- Type propagation +- Preprocessing +- Filtering based on the dataset type +- Ordering +- Appending new behavior with decorators + +[`Transform`](https://fastcore.fast.ai/transform.html#transform) looks +for three special methods, encodes, decodes, +and setups, which provide the implementation for +[`__call__`](https://www.python-course.eu/python3_magic_methods.php), +`decode`, and `setup` respectively. For instance: + +``` python +class A(Transform): + def encodes(self, x): return x+1 + +A()(1) +``` + + 2 + +For simple transforms like this, you can also use +[`Transform`](https://fastcore.fast.ai/transform.html#transform) as a +decorator: + +``` python +@Transform +def f(x): return x+1 + +f(1) +``` + + 2 + +Transforms can be composed into a +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline): + +``` python +@Transform +def g(x): return x/2 + +pipe = Pipeline([f,g]) +pipe(3) +``` + + 2.0 + +The power of +[`Transform`](https://fastcore.fast.ai/transform.html#transform) and +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) is best +understood by seeing how they’re used to create a complete data +processing pipeline. This is explained in [chapter +11](https://github.com/fastai/fastbook/blob/master/11_midlevel_data.ipynb) +of the [fastai +book](https://www.amazon.com/Deep-Learning-Coders-fastai-PyTorch/dp/1492045527), +which is [available for free](https://github.com/fastai/fastbook) in +Jupyter Notebook format.# fastcore: An Underrated Python Library + +A unique python library that extends the python programming language and provides utilities that enhance productivity. + +Sep 1, 2020 • Hamel Husain • 14 min read + +__fastcore fastai + +# Background __ + +I recently embarked on a journey to sharpen my python skills: I wanted to learn advanced patterns, idioms, and techniques. I started with reading books on advanced Python, however, the information didn't seem to stick without having somewhere to apply it. I also wanted the ability to ask questions from an expert while I was learning -- which is an arrangement that is hard to find! That's when it occurred to me: What if I could find an open source project that has fairly advanced python code and write documentation and tests? I made a bet that if I did this it would force me to learn everything very deeply, and the maintainers would be appreciative of my work and be willing to answer my questions. + +And that's exactly what I did over the past month! I'm pleased to report that it has been the most efficient learning experience I've ever experienced. I've discovered that writing documentation forced me to deeply understand not just what the code does but also _why the code works the way it does_ , and to explore edge cases while writing tests. Most importantly, I was able to ask questions when I was stuck, and maintainers were willing to devote extra time knowing that their mentorship was in service of making their code more accessible! It turns out the library I choose, fastcore is some of the most fascinating Python I have ever encountered as its purpose and goals are fairly unique. + +For the uninitiated, fastcore is a library on top of which many fast.ai projects are built on. Most importantly, fastcore extends the python programming language and strives to eliminate boilerplate and add useful functionality for common tasks. In this blog post, I'm going to highlight some of my favorite tools that fastcore provides, rather than sharing what I learned about python. My goal is to pique your interest in this library, and hopefully motivate you to check out the documentation after you are done to learn more! + +# Why fastcore is interesting __ + + 1. **Get exposed to ideas from other languages without leaving python:** I’ve always heard that it is beneficial to learn other languages in order to become a better programmer. From a pragmatic point of view, I’ve found it difficult to learn other languages because I could never use them at work. Fastcore extends python to include patterns found in languages as diverse as Julia, Ruby and Haskell. Now that I understand these tools I am motivated to learn other languages. + 2. **You get a new set of pragmatic tools** : fastcore includes utilities that will allow you to write more concise expressive code, and perhaps solve new problems. + 3. **Learn more about the Python programming language:** Because fastcore extends the python programming language, many advanced concepts are exposed during the process. For the motivated, this is a great way to see how many of the internals of python work. + +# A whirlwind tour through fastcore __ + +Here are some things you can do with fastcore that immediately caught my attention. + +* * * + +## Making **kwargs transparent __ + +Whenever I see a function that has the argument****kwargs** , I cringe a little. This is because it means the API is obfuscated and I have to read the source code to figure out what valid parameters might be. Consider the below example: + +``` +def baz(a, b=2, c=3, d=4): return a + b + c + +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) + +``` + +``` + +``` + +Without reading the source code, it might be hard for me to know that `foo` also accepts and additional parameters `b` and `d`. We can fix this with `delegates`: + +``` +def baz(a, b=2, c=3, d=4): return a + b + c + +@delegates(baz) # this decorator will pass down keyword arguments from baz +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) + +``` + +``` + +``` + +You can customize the behavior of this decorator. For example, you can have your cake and eat it too by passing down your arguments and also keeping `**kwargs`: + +``` +@delegates(baz, keep=True) +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) + +``` + +``` + +``` + +You can also exclude arguments. For example, we exclude argument `d` from delegation: + +``` +def basefoo(a, b=2, c=3, d=4): pass + +@delegates(basefoo, but=['d']) # exclude `d` +def foo(c, a, **kwargs): pass + +inspect.signature(foo) + +``` + +``` + +``` + +You can also delegate between classes: + +``` +class BaseFoo: + def __init__(self, e, c=2): pass + +@delegates()# since no argument was passsed here we delegate to the superclass +class Foo(BaseFoo): + def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs) + +inspect.signature(Foo) + +``` + +``` + +``` + +For more information, read the docs on delegates. + +* * * + +## Avoid boilerplate when setting instance attributes __ + +Have you ever wondered if it was possible to avoid the boilerplate involved with setting attributes in`__init__`? + +``` +class Test: + def __init__(self, a, b ,c): + self.a, self.b, self.c = a, b, c + +``` + +Ouch! That was painful. Look at all the repeated variable names. Do I really have to repeat myself like this when defining a class? Not Anymore! Checkout store_attr: + +``` +class Test: + def __init__(self, a, b, c): + store_attr() + +t = Test(5,4,3) +assert t.b == 4 + +``` + +You can also exclude certain attributes: + +``` +class Test: + def __init__(self, a, b, c): + store_attr(but=['c']) + +t = Test(5,4,3) +assert t.b == 4 +assert not hasattr(t, 'c') + +``` + +There are many more ways of customizing and using `store_attr` than I highlighted here. Check out the docs for more detail. + +P.S. you might be thinking that Python dataclasses also allow you to avoid this boilerplate. While true in some cases, `store_attr` is more flexible.1 + +1\. For example, store_attr does not rely on inheritance, which means you won't get stuck using multiple inheritance when using this with your own classes. Also, unlike dataclasses, store_attr does not require python 3.7 or higher. Furthermore, you can use store_attr anytime in the object lifecycle, and in any location in your class to customize the behavior of how and when variables are stored.↩ + +* * * + +## Avoiding subclassing boilerplate __ + +One thing I hate about python is the`__super__().__init__()` boilerplate associated with subclassing. For example: + +``` +class ParentClass: + def __init__(self): self.some_attr = 'hello' + +class ChildClass(ParentClass): + def __init__(self): + super().__init__() + +cc = ChildClass() +assert cc.some_attr == 'hello' # only accessible b/c you used super + +``` + +We can avoid this boilerplate by using the metaclass PrePostInitMeta. We define a new class called `NewParent` that is a wrapper around the `ParentClass`: + +``` +class NewParent(ParentClass, metaclass=PrePostInitMeta): + def __pre_init__(self, *args, **kwargs): super().__init__() + +class ChildClass(NewParent): + def __init__(self):pass + +sc = ChildClass() +assert sc.some_attr == 'hello' + +``` + +* * * + +## Type Dispatch __ + +Type dispatch, orMultiple dispatch, allows you to change the way a function behaves based upon the input types it receives. This is a prominent feature in some programming languages like Julia. For example, this is a conceptual example of how multiple dispatch works in Julia, returning different values depending on the input types of x and y: + +``` +collide_with(x::Asteroid, y::Asteroid) = ... +# deal with asteroid hitting asteroid + +collide_with(x::Asteroid, y::Spaceship) = ... +# deal with asteroid hitting spaceship + +collide_with(x::Spaceship, y::Asteroid) = ... +# deal with spaceship hitting asteroid + +collide_with(x::Spaceship, y::Spaceship) = ... +# deal with spaceship hitting spaceship + +``` + +Type dispatch can be especially useful in data science, where you might allow different input types (i.e. Numpy arrays and Pandas dataframes) to a function that processes data. Type dispatch allows you to have a common API for functions that do similar tasks. + +Unfortunately, Python does not support this out-of-the box. Fortunately, there is the @typedispatch decorator to the rescue. This decorator relies upon type hints in order to route inputs the correct version of the function: + +``` +@typedispatch +def f(x:str, y:str): return f'{x}{y}' + +@typedispatch +def f(x:np.ndarray): return x.sum() + +@typedispatch +def f(x:int, y:int): return x+y + +``` + +Below is a demonstration of type dispatch at work for the function `f`: + +``` +f('Hello ', 'World!') + +``` + +``` +'Hello World!' +``` + +``` +f(2,3) + +``` + +``` +5 +``` + +``` +f(np.array([5,5,5,5])) + +``` + +``` +20 +``` + +There are limitations of this feature, as well as other ways of using this functionality that you can read about here. In the process of learning about typed dispatch, I also found a python library called multipledispatch made by Mathhew Rocklin (the creator of Dask). + +After using this feature, I am now motivated to learn languages like Julia to discover what other paradigms I might be missing. + +* * * + +## A better version of functools.partial __ + +`functools.partial` is a great utility that creates functions from other functions that lets you set default values. Lets take this function for example that filters a list to only contain values >= `val`: + +``` +test_input = [1,2,3,4,5,6] +def f(arr, val): + "Filter a list to remove any values that are less than val." + return [x for x in arr if x >= val] + +f(test_input, 3) + +``` + +``` +[3, 4, 5, 6] +``` + +You can create a new function out of this function using `partial` that sets the default value to 5: + +``` +filter5 = partial(f, val=5) +filter5(test_input) + +``` + +``` +[5, 6] +``` + +One problem with `partial` is that it removes the original docstring and replaces it with a generic docstring: + +``` +filter5.__doc__ + +``` + +``` +'partial(func, *args, **keywords) - new function with partial application\n of the given arguments and keywords.\n' +``` + +fastcore.utils.partialler fixes this, and makes sure the docstring is retained such that the new API is transparent: + +``` +filter5 = partialler(f, val=5) +filter5.__doc__ + +``` + +``` +'Filter a list to remove any values that are less than val.' +``` + +* * * + +## Composition of functions __ + +A technique that is pervasive in functional programming languages is function composition, whereby you chain a bunch of functions together to achieve some kind of result. This is especially useful when applying various data transformations. Consider a toy example where I have three functions: (1) Removes elements of a list less than 5 (from the prior section) (2) adds 2 to each number (3) sums all the numbers: + +``` +def add(arr, val): return [x + val for x in arr] +def arrsum(arr): return sum(arr) + +# See the previous section on partialler +add2 = partialler(add, val=2) + +transform = compose(filter5, add2, arrsum) +transform([1,2,3,4,5,6]) + +``` + +``` +15 +``` + +But why is this useful? You might me thinking, I can accomplish the same thing with: + +``` +arrsum(add2(filter5([1,2,3,4,5,6]))) + +``` + +You are not wrong! However, composition gives you a convenient interface in case you want to do something like the following: + +``` +def fit(x, transforms:list): + "fit a model after performing transformations" + x = compose(*transforms)(x) + y = [np.mean(x)] * len(x) # its a dumb model. Don't judge me + return y + +# filters out elements < 5, adds 2, then predicts the mean +fit(x=[1,2,3,4,5,6], transforms=[filter5, add2]) + +``` + +``` +[7.5, 7.5] +``` + +For more information about `compose`, read the docs. + +* * * + +## A more useful `__repr__`__ + +In python,`__repr__` helps you get information about an object for logging and debugging. Below is what you get by default when you define a new class. (Note: we are using `store_attr`, which was discussed earlier). + +``` +class Test: + def __init__(self, a, b=2, c=3): store_attr() # `store_attr` was discussed previously + +Test(1) + +``` + +``` +<__main__.Test at 0x7ffcd766cee0> +``` + +We can use basic_repr to quickly give us a more sensible default: + +``` +class Test: + def __init__(self, a, b=2, c=3): store_attr() + __repr__ = basic_repr('a,b,c') + +Test(2) + +``` + +``` +Test(a=2, b=2, c=3) +``` + +* * * + +## Monkey Patching With A Decorator __ + +It can be convenient tomonkey patch with a decorator, which is especially helpful when you want to patch an external library you are importing. We can use the decorator @patch from `fastcore.foundation` along with type hints like so: + +``` +class MyClass(int): pass + +@patch +def func(self:MyClass, a): return self+a + +mc = MyClass(3) + +``` + +Now, `MyClass` has an additional method named `func`: + +``` +mc.func(10) + +``` + +``` +13 +``` + +Still not convinced? I'll show you another example of this kind of patching in the next section. + +* * * + +## A better pathlib.Path __ + +When you seethese extensions to pathlib.path you won't ever use vanilla pathlib again! A number of additional methods have been added to pathlib, such as: + + * `Path.readlines`: same as `with open('somefile', 'r') as f: f.readlines()` + * `Path.read`: same as `with open('somefile', 'r') as f: f.read()` + * `Path.save`: saves file as pickle + * `Path.load`: loads pickle file + * `Path.ls`: shows the contents of the path as a list. + * etc. + +Read more about this here. Here is a demonstration of `ls`: + +``` +from fastcore.utils import * +from pathlib import Path +p = Path('.') +p.ls() # you don't get this with vanilla Pathlib.Path!! + +``` + +``` +(#7) [Path('2020-09-01-fastcore.ipynb'),Path('README.md'),Path('fastcore_imgs'),Path('2020-02-20-test.ipynb'),Path('.ipynb_checkpoints'),Path('2020-02-21-introducing-fastpages.ipynb'),Path('my_icons')] +``` + +Wait! What's going on here? We just imported `pathlib.Path` \- why are we getting this new functionality? Thats because we imported the `fastcore.utils` module, which patches this module via the `@patch` decorator discussed earlier. Just to drive the point home on why the `@patch` decorator is useful, I'll go ahead and add another method to `Path` right now: + +``` +@patch +def fun(self:Path): return "This is fun!" + +p.fun() + +``` + +``` +'This is fun!' +``` + +That is magical, right? I know! That's why I'm writing about it! + +* * * + +## An Even More Concise Way To Create Lambdas __ + +`Self`, with an uppercase S, is an even more concise way to create lambdas that are calling methods on an object. For example, let's create a lambda for taking the sum of a Numpy array: + +``` +arr=np.array([5,4,3,2,1]) +f = lambda a: a.sum() +assert f(arr) == 15 + +``` + +You can use `Self` in the same way: + +``` +f = Self.sum() +assert f(arr) == 15 + +``` + +Let's create a lambda that does a groupby and max of a Pandas dataframe: + +``` +import pandas as pd +df=pd.DataFrame({'Some Column': ['a', 'a', 'b', 'b', ], + 'Another Column': [5, 7, 50, 70]}) + +f = Self.groupby('Some Column').mean() +f(df) + +``` + +| Another Column +---|--- +Some Column | +a | 6 +b | 60 + +Read more about `Self` in the docs). + +* * * + +## Notebook Functions __ + +These are simple but handy, and allow you to know whether or not code is executing in a Jupyter Notebook, Colab, or an Ipython Shell: + +``` +from fastcore.imports import in_notebook, in_colab, in_ipython +in_notebook(), in_colab(), in_ipython() + +``` + +``` +(True, False, True) +``` + +This is useful if you are displaying certain types of visualizations, progress bars or animations in your code that you may want to modify or toggle depending on the environment. + +* * * + +## A Drop-In Replacement For List __ + +You might be pretty happy with Python's`list`. This is one of those situations that you don't know you needed a better list until someone showed one to you. Enter `L`, a list like object with many extra goodies. + +The best way I can describe `L` is to pretend that `list` and `numpy` had a pretty baby: + +define a list (check out the nice `__repr__` that shows the length of the list!) + +``` +L(1,2,3) + +``` + +``` +(#3) [1,2,3] +``` + +Shuffle a list: + +``` +p = L.range(20).shuffle() +p + +``` + +``` +(#20) [8,7,5,12,14,16,2,15,19,6...] +``` + +Index into a list: + +``` +p[2,4,6] + +``` + +``` +(#3) [5,14,2] +``` + +L has sensible defaults, for example appending an element to a list: + +``` +1 + L(2,3,4) + +``` + +``` +(#4) [1,2,3,4] +``` + +There is much more `L` has to offer. Read the docs to learn more. + +# But Wait ... There's More!__ + +There are more things I would like to show you about fastcore, but there is no way they would reasonably fit into a blog post. Here is a list of some of my favorite things that I didn't demo in this blog post: + +## Utilities __ + +TheBasics section contain many shortcuts to perform common tasks or provide an additional interface to what standard python provides. + + * mk_class: quickly add a bunch of attributes to a class + * wrap_class: add new methods to a class with a simple decorator + * groupby: similar to Scala's groupby + * merge: merge dicts + * fasttuple: a tuple on steroids + * Infinite Lists: useful for padding and testing + * chunked: for batching and organizing stuff + +## Multiprocessing __ + +TheMultiprocessing section extends python's multiprocessing library by offering features like: + + * progress bars + * ability to pause to mitigate race conditions with external services + * processing things in batches on each worker, ex: if you have a vectorized operation to perform in chunks + +## Functional Programming __ + +Thefunctional programming section is my favorite part of this library. + + * maps: a map that also composes functions + * mapped: A more robust `map` + * using_attr: compose a function that operates on an attribute + +## Transforms __ + +Transforms is a collection of utilities for creating data transformations and associated pipelines. These transformation utilities build upon many of the building blocks discussed in this blog post. + +## Further Reading __ + +**It should be noted that you should read themain page of the docs first, followed by the section on tests to fully understand the documentation.** + + * The fastcore documentation site. + * The fastcore GitHub repo. + * Blog post on delegation. + +# Shameless plug: fastpages __ + +This blog post was written entirely in a Jupyter Notebook, which GitHub automatically converted into to a blog post! Sound interesting?Check out fastpages.# fastcore Module Documentation + +## fastcore.basics + +> Basic functionality used in the fastai library + +- `def ifnone(a, b)` + `b` if `a` is None else `a` + +- `def maybe_attr(o, attr)` + `getattr(o,attr,o)` + +- `def basic_repr(flds)` + Minimal `__repr__` + +- `class BasicRepr` + Base class for objects needing a basic `__repr__` + + +- `def is_array(x)` + `True` if `x` supports `__array__` or `iloc` + +- `def listify(o, *rest)` + Convert `o` to a `list` + +- `def tuplify(o, use_list, match)` + Make `o` a tuple + +- `def true(x)` + Test whether `x` is truthy; collections with >0 elements are considered `True` + +- `class NullType` + An object that is `False` and can be called, chained, and indexed + + - `def __getattr__(self, *args)` + - `def __call__(self, *args, **kwargs)` + - `def __getitem__(self, *args)` + - `def __bool__(self)` + +- `def tonull(x)` + Convert `None` to `null` + +- `def get_class(nm, *fld_names, **flds)` + Dynamically create a class, optionally inheriting from `sup`, containing `fld_names` + +- `def mk_class(nm, *fld_names, **flds)` + Create a class using `get_class` and add to the caller's module + +- `def wrap_class(nm, *fld_names, **flds)` + Decorator: makes function a method of a new class `nm` passing parameters to `mk_class` + +- `class ignore_exceptions` + Context manager to ignore exceptions + + - `def __enter__(self)` + - `def __exit__(self, *args)` + +- `def exec_local(code, var_name)` + Call `exec` on `code` and return the var `var_name` + +- `def risinstance(types, obj)` + Curried `isinstance` but with args reversed + +- `class Inf` + Infinite lists + + +- `def in_(x, a)` + `True` if `x in a` + +- `def ret_true(*args, **kwargs)` + Predicate: always `True` + +- `def ret_false(*args, **kwargs)` + Predicate: always `False` + +- `def stop(e)` + Raises exception `e` (by default `StopIteration`) + +- `def gen(func, seq, cond)` + Like `(func(o) for o in seq if cond(func(o)))` but handles `StopIteration` + +- `def chunked(it, chunk_sz, drop_last, n_chunks)` + Return batches from iterator `it` of size `chunk_sz` (or return `n_chunks` total) + +- `def otherwise(x, tst, y)` + `y if tst(x) else x` + +- `def custom_dir(c, add)` + Implement custom `__dir__`, adding `add` to `cls` + +- `class AttrDict` + `dict` subclass that also provides access to keys as attrs + + - `def __getattr__(self, k)` + - `def __setattr__(self, k, v)` + - `def __dir__(self)` + - `def copy(self)` + +- `class AttrDictDefault` + `AttrDict` subclass that returns `None` for missing attrs + + - `def __init__(self, *args, **kwargs)` + - `def __getattr__(self, k)` + +- `class NS` + `SimpleNamespace` subclass that also adds `iter` and `dict` support + + - `def __iter__(self)` + - `def __getitem__(self, x)` + - `def __setitem__(self, x, y)` + +- `def get_annotations_ex(obj)` + Backport of py3.10 `get_annotations` that returns globals/locals + +- `def eval_type(t, glb, loc)` + `eval` a type or collection of types, if needed, for annotations in py3.10+ + +- `def type_hints(f)` + Like `typing.get_type_hints` but returns `{}` if not allowed type + +- `def annotations(o)` + Annotations for `o`, or `type(o)` + +- `def anno_ret(func)` + Get the return annotation of `func` + +- `def signature_ex(obj, eval_str)` + Backport of `inspect.signature(..., eval_str=True` to `True`) + +- `def str2int(s)` + Convert `s` to an `int` + +- `def str2float(s)` + Convert `s` to a float + +- `def str2list(s)` + Convert `s` to a list + +- `def str2date(s)` + `date.fromisoformat` with empty string handling + +- `def typed(_func)` + Decorator to check param and return types at runtime, with optional casting + +- `def exec_new(code)` + Execute `code` in a new environment and return it + +- `def exec_import(mod, sym)` + Import `sym` from `mod` in a new environment + +## fastcore.dispatch + +> Basic single and dual parameter dispatch + +- `def lenient_issubclass(cls, types)` + If possible return whether `cls` is a subclass of `types`, otherwise return False. + +- `def sorted_topologically(iterable)` + Return a new list containing all items from the iterable sorted topologically + +- `class TypeDispatch` + Dictionary-like object; `__getitem__` matches keys of types using `issubclass` + + - `def __init__(self, funcs, bases)` + - `def add(self, f)` + Add type `t` and function `f` + + - `def first(self)` + Get first function in ordered dict of type:func. + + - `def returns(self, x)` + Get the return type of annotation of `x`. + + - `def __repr__(self)` + - `def __call__(self, *args, **kwargs)` + - `def __get__(self, inst, owner)` + - `def __getitem__(self, k)` + Find first matching type that is a super-class of `k` + + +- `class DispatchReg` + A global registry for `TypeDispatch` objects keyed by function name + + - `def __init__(self)` + - `def __call__(self, f)` + +- `def retain_meta(x, res, as_copy)` + Call `res.set_meta(x)`, if it exists + +- `def default_set_meta(self, x, as_copy)` + Copy over `_meta` from `x` to `res`, if it's missing + +- `@typedispatch def cast(x, typ)` + cast `x` to type `typ` (may also change `x` inplace) + +- `def retain_type(new, old, typ, as_copy)` + Cast `new` to type of `old` or `typ` if it's a superclass + +- `def retain_types(new, old, typs)` + Cast each item of `new` to type of matching item in `old` if it's a superclass + +- `def explode_types(o)` + Return the type of `o`, potentially in nested dictionaries for thing that are listy + +## fastcore.docments + +> Document parameters using comments. + +- `def docstring(sym)` + Get docstring for `sym` for functions ad classes + +- `def parse_docstring(sym)` + Parse a numpy-style docstring in `sym` + +- `def isdataclass(s)` + Check if `s` is a dataclass but not a dataclass' instance + +- `def get_dataclass_source(s)` + Get source code for dataclass `s` + +- `def get_source(s)` + Get source code for string, function object or dataclass `s` + +- `def get_name(obj)` + Get the name of `obj` + +- `def qual_name(obj)` + Get the qualified name of `obj` + +- `@delegates(_docments) def docments(elt, full, **kwargs)` + Generates a `docment` + +- `def extract_docstrings(code)` + Create a dict from function/class/method names to tuples of docstrings and param lists + +## fastcore.docscrape + +> Parse numpy-style docstrings + +- `def strip_blank_lines(l)` + Remove leading and trailing blank lines from a list of lines + +- `class Reader` + A line-based string reader. + + - `def __init__(self, data)` + - `def __getitem__(self, n)` + - `def reset(self)` + - `def read(self)` + - `def seek_next_non_empty_line(self)` + - `def eof(self)` + - `def read_to_condition(self, condition_func)` + - `def read_to_next_empty_line(self)` + - `def read_to_next_unindented_line(self)` + - `def peek(self, n)` + - `def is_empty(self)` + +- `class ParseError` + - `def __str__(self)` + +- `class NumpyDocString` + Parses a numpydoc string to an abstract representation + + - `def __init__(self, docstring, config)` + - `def __iter__(self)` + - `def __len__(self)` + - `def __getitem__(self, key)` + - `def __setitem__(self, key, val)` + +- `def dedent_lines(lines, split)` + Deindent a list of lines maximally + +## fastcore.foundation + +> The `L` class and helpers for it + +- `@contextmanager def working_directory(path)` + Change working directory to `path` and return to previous on exit. + +- `def add_docs(cls, cls_doc, **docs)` + Copy values from `docs` to `cls` docstrings, and confirm all public methods are documented + +- `def docs(cls)` + Decorator version of `add_docs`, using `_docs` dict + +- `def coll_repr(c, max_n)` + String repr of up to `max_n` items of (possibly lazy) collection `c` + +- `def is_bool(x)` + Check whether `x` is a bool or None + +- `def mask2idxs(mask)` + Convert bool mask or index list to index `L` + +- `def is_indexer(idx)` + Test whether `idx` will index a single item in a list + +- `class CollBase` + Base class for composing a list of `items` + + - `def __init__(self, items)` + - `def __len__(self)` + - `def __getitem__(self, k)` + - `def __setitem__(self, k, v)` + - `def __delitem__(self, i)` + - `def __repr__(self)` + - `def __iter__(self)` + +- `class L` + Behaves like a list of `items` but can also index with list of indices or masks + + - `def __init__(self, items, *rest)` + - `def __getitem__(self, idx)` + - `def copy(self)` + - `def __setitem__(self, idx, o)` + Set `idx` (can be list of indices, or mask, or int) items to `o` (which is broadcast if not iterable) + + - `def __eq__(self, b)` + - `def sorted(self, key, reverse)` + - `def __iter__(self)` + - `def __contains__(self, b)` + - `def __reversed__(self)` + - `def __invert__(self)` + - `def __repr__(self)` + - `def __mul__(a, b)` + - `def __add__(a, b)` + - `def __radd__(a, b)` + - `def __addi__(a, b)` + - `@classmethod def split(cls, s, sep, maxsplit)` + - `@classmethod def range(cls, a, b, step)` + - `def map(self, f, *args, **kwargs)` + - `def argwhere(self, f, negate, **kwargs)` + - `def argfirst(self, f, negate)` + - `def filter(self, f, negate, **kwargs)` + - `def enumerate(self)` + - `def renumerate(self)` + - `def unique(self, sort, bidir, start)` + - `def val2idx(self)` + - `def cycle(self)` + - `def map_dict(self, f, *args, **kwargs)` + - `def map_first(self, f, g, *args, **kwargs)` + - `def itemgot(self, *idxs)` + - `def attrgot(self, k, default)` + - `def starmap(self, f, *args, **kwargs)` + - `def zip(self, cycled)` + - `def zipwith(self, *rest)` + - `def map_zip(self, f, *args, **kwargs)` + - `def map_zipwith(self, f, *rest, **kwargs)` + - `def shuffle(self)` + - `def concat(self)` + - `def reduce(self, f, initial)` + - `def sum(self)` + - `def product(self)` + - `def setattrs(self, attr, val)` + +- `def save_config_file(file, d, **kwargs)` + Write settings dict to a new config file, or overwrite the existing one. + +- `class Config` + Reading and writing `ConfigParser` ini files + + - `def __init__(self, cfg_path, cfg_name, create, save, extra_files, types)` + - `def __repr__(self)` + - `def __setitem__(self, k, v)` + - `def __contains__(self, k)` + - `def save(self)` + - `def __getattr__(self, k)` + - `def __getitem__(self, k)` + - `def get(self, k, default)` + - `def path(self, k, default)` + - `@classmethod def find(cls, cfg_name, cfg_path, **kwargs)` + Search `cfg_path` and its parents to find `cfg_name` + + +## fastcore.imghdr + +> Recognize image file formats based on their first few bytes. + +- `def test_jpeg(h, f)` + JPEG data with JFIF or Exif markers; and raw JPEG + +- `def test_gif(h, f)` + GIF ('87 and '89 variants) + +- `def test_tiff(h, f)` + TIFF (can be in Motorola or Intel byte order) + +- `def test_rgb(h, f)` + SGI image library + +- `def test_pbm(h, f)` + PBM (portable bitmap) + +- `def test_pgm(h, f)` + PGM (portable graymap) + +- `def test_ppm(h, f)` + PPM (portable pixmap) + +- `def test_rast(h, f)` + Sun raster file + +- `def test_xbm(h, f)` + X bitmap (X10 or X11) + +## fastcore.imports + +- `def is_iter(o)` + Test whether `o` can be used in a `for` loop + +- `def is_coll(o)` + Test whether `o` is a collection (i.e. has a usable `len`) + +- `def all_equal(a, b)` + Compares whether `a` and `b` are the same length and have the same contents + +- `def noop(x, *args, **kwargs)` + Do nothing + +- `def noops(self, x, *args, **kwargs)` + Do nothing (method) + +- `def isinstance_str(x, cls_name)` + Like `isinstance`, except takes a type name instead of a type + +- `def equals(a, b)` + Compares `a` and `b` for equality; supports sublists, tensors and arrays too + +- `def ipython_shell()` + Same as `get_ipython` but returns `False` if not in IPython + +- `def in_ipython()` + Check if code is running in some kind of IPython environment + +- `def in_colab()` + Check if the code is running in Google Colaboratory + +- `def in_jupyter()` + Check if the code is running in a jupyter notebook + +- `def in_notebook()` + Check if the code is running in a jupyter notebook + +- `def remove_prefix(text, prefix)` + Temporary until py39 is a prereq + +- `def remove_suffix(text, suffix)` + Temporary until py39 is a prereq + +## fastcore.meta + +> Metaclasses + +- `def test_sig(f, b)` + Test the signature of an object + +- `class FixSigMeta` + A metaclass that fixes the signature on classes that override `__new__` + + - `def __new__(cls, name, bases, dict)` + +- `class PrePostInitMeta` + A metaclass that calls optional `__pre_init__` and `__post_init__` methods + + - `def __call__(cls, *args, **kwargs)` + +- `class AutoInit` + Same as `object`, but no need for subclasses to call `super().__init__` + + - `def __pre_init__(self, *args, **kwargs)` + +- `class NewChkMeta` + Metaclass to avoid recreating object passed to constructor + + - `def __call__(cls, x, *args, **kwargs)` + +- `class BypassNewMeta` + Metaclass: casts `x` to this class if it's of type `cls._bypass_type` + + - `def __call__(cls, x, *args, **kwargs)` + +- `def empty2none(p)` + Replace `Parameter.empty` with `None` + +- `def anno_dict(f)` + `__annotation__ dictionary with `empty` cast to `None`, returning empty if doesn't exist + +- `def use_kwargs_dict(keep, **kwargs)` + Decorator: replace `**kwargs` in signature with `names` params + +- `def use_kwargs(names, keep)` + Decorator: replace `**kwargs` in signature with `names` params + +- `def delegates(to, keep, but)` + Decorator: replace `**kwargs` in signature with params from `to` + +- `def method(f)` + Mark `f` as a method + +- `def funcs_kwargs(as_method)` + Replace methods in `cls._methods` with those from `kwargs` + +## fastcore.net + +> Network, HTTP, and URL functions + +- `def urlquote(url)` + Update url's path with `urllib.parse.quote` + +- `def urlwrap(url, data, headers)` + Wrap `url` in a urllib `Request` with `urlquote` + +- `class HTTP4xxClientError` + Base class for client exceptions (code 4xx) from `url*` functions + + +- `class HTTP5xxServerError` + Base class for server exceptions (code 5xx) from `url*` functions + + +- `def urlopen(url, data, headers, timeout, **kwargs)` + Like `urllib.request.urlopen`, but first `urlwrap` the `url`, and encode `data` + +- `def urlread(url, data, headers, decode, return_json, return_headers, timeout, **kwargs)` + Retrieve `url`, using `data` dict or `kwargs` to `POST` if present + +- `def urljson(url, data, timeout)` + Retrieve `url` and decode json + +- `def urlclean(url)` + Remove fragment, params, and querystring from `url` if present + +- `def urlsave(url, dest, reporthook, headers, timeout)` + Retrieve `url` and save based on its name + +- `def urlvalid(x)` + Test if `x` is a valid URL + +- `def urlrequest(url, verb, headers, route, query, data, json_data)` + `Request` for `url` with optional route params replaced by `route`, plus `query` string, and post `data` + +- `@patch def summary(self, skip)` + Summary containing full_url, headers, method, and data, removing `skip` from headers + +- `def urlsend(url, verb, headers, decode, route, query, data, json_data, return_json, return_headers, debug, timeout)` + Send request with `urlrequest`, converting result to json if `return_json` + +- `def do_request(url, post, headers, **data)` + Call GET or json-encoded POST on `url`, depending on `post` + +- `def start_server(port, host, dgram, reuse_addr, n_queue)` + Create a `socket` server on `port`, with optional `host`, of type `dgram` + +- `def start_client(port, host, dgram)` + Create a `socket` client on `port`, with optional `host`, of type `dgram` + +- `def tobytes(s)` + Convert `s` into HTTP-ready bytes format + +- `def http_response(body, status, hdrs, **kwargs)` + Create an HTTP-ready response, adding `kwargs` to `hdrs` + +- `@threaded def recv_once(host, port)` + Spawn a thread to receive a single HTTP request and store in `d['r']` + +## fastcore.parallel + +> Threading and multiprocessing functions + +- `def threaded(process)` + Run `f` in a `Thread` (or `Process` if `process=True`), and returns it + +- `def startthread(f)` + Like `threaded`, but start thread immediately + +- `def startproc(f)` + Like `threaded(True)`, but start Process immediately + +- `class ThreadPoolExecutor` + Same as Python's ThreadPoolExecutor, except can pass `max_workers==0` for serial execution + + - `def __init__(self, max_workers, on_exc, pause, **kwargs)` + - `def map(self, f, items, *args, **kwargs)` + +- `@delegates() class ProcessPoolExecutor` + Same as Python's ProcessPoolExecutor, except can pass `max_workers==0` for serial execution + + - `def __init__(self, max_workers, on_exc, pause, **kwargs)` + - `def map(self, f, items, *args, **kwargs)` + +- `def parallel(f, items, *args, **kwargs)` + Applies `func` in parallel to `items`, using `n_workers` + +- `def parallel_async(f, items, *args, **kwargs)` + Applies `f` to `items` in parallel using asyncio and a semaphore to limit concurrency. + +- `def run_procs(f, f_done, args)` + Call `f` for each item in `args` in parallel, yielding `f_done` + +- `def parallel_gen(cls, items, n_workers, **kwargs)` + Instantiate `cls` in `n_workers` procs & call each on a subset of `items` in parallel. + +## fastcore.py2pyi + +- `def imp_mod(module_path, package)` + Import dynamically the module referenced in `fn` + +- `def has_deco(node, name)` + Check if a function node `node` has a decorator named `name` + +- `def create_pyi(fn, package)` + Convert `fname.py` to `fname.pyi` by removing function bodies and expanding `delegates` kwargs + +- `@call_parse def py2pyi(fname, package)` + Convert `fname.py` to `fname.pyi` by removing function bodies and expanding `delegates` kwargs + +- `@call_parse def replace_wildcards(path)` + Expand wildcard imports in the specified Python file. + +## fastcore.script + +> A fast way to turn your python function into a script. + +- `def store_true()` + Placeholder to pass to `Param` for `store_true` action + +- `def store_false()` + Placeholder to pass to `Param` for `store_false` action + +- `def bool_arg(v)` + Use as `type` for `Param` to get `bool` behavior + +- `class Param` + A parameter in a function used in `anno_parser` or `call_parse` + + - `def __init__(self, help, type, opt, action, nargs, const, choices, required, default)` + - `def set_default(self, d)` + - `@property def pre(self)` + - `@property def kwargs(self)` + - `def __repr__(self)` + +- `def anno_parser(func, prog)` + Look at params (annotated with `Param`) in func and return an `ArgumentParser` + +- `def args_from_prog(func, prog)` + Extract args from `prog` + +- `def call_parse(func, nested)` + Decorator to create a simple CLI from `func` using `anno_parser` + +## fastcore.style + +> Fast styling for friendly CLIs. + +- `class StyleCode` + An escape sequence for styling terminal text. + + - `def __init__(self, name, code, typ)` + - `def __str__(self)` + +- `class Style` + A minimal terminal text styler. + + - `def __init__(self, codes)` + - `def __dir__(self)` + - `def __getattr__(self, k)` + - `def __call__(self, obj)` + - `def __repr__(self)` + +- `def demo()` + Demonstrate all available styles and their codes. + +## fastcore.test + +> Helper functions to quickly write tests in notebooks + +- `def test_fail(f, msg, contains, args, kwargs)` + Fails with `msg` unless `f()` raises an exception and (optionally) has `contains` in `e.args` + +- `def test(a, b, cmp, cname)` + `assert` that `cmp(a,b)`; display inputs and `cname or cmp.__name__` if it fails + +- `def nequals(a, b)` + Compares `a` and `b` for `not equals` + +- `def test_eq(a, b)` + `test` that `a==b` + +- `def test_eq_type(a, b)` + `test` that `a==b` and are same type + +- `def test_ne(a, b)` + `test` that `a!=b` + +- `def is_close(a, b, eps)` + Is `a` within `eps` of `b` + +- `def test_close(a, b, eps)` + `test` that `a` is within `eps` of `b` + +- `def test_is(a, b)` + `test` that `a is b` + +- `def test_shuffled(a, b)` + `test` that `a` and `b` are shuffled versions of the same sequence of items + +- `def test_stdout(f, exp, regex)` + Test that `f` prints `exp` to stdout, optionally checking as `regex` + +- `def test_fig_exists(ax)` + Test there is a figure displayed in `ax` + +- `class ExceptionExpected` + Context manager that tests if an exception is raised + + - `def __init__(self, ex, regex)` + - `def __enter__(self)` + - `def __exit__(self, type, value, traceback)` + +## fastcore.transform + +> Definition of `Transform` and `Pipeline` + +- `class Transform` + Delegates (`__call__`,`decode`,`setup`) to (encodes,decodes,setups) if `split_idx` matches + + - `def __init__(self, enc, dec, split_idx, order)` + - `@property def name(self)` + - `def __call__(self, x, **kwargs)` + - `def decode(self, x, **kwargs)` + - `def __repr__(self)` + - `def setup(self, items, train_setup)` + +- `class InplaceTransform` + A `Transform` that modifies in-place and just returns whatever it's passed + + +- `class DisplayedTransform` + A transform with a `__repr__` that shows its attrs + + - `@property def name(self)` + +- `class ItemTransform` + A transform that always take tuples as items + + - `def __call__(self, x, **kwargs)` + - `def decode(self, x, **kwargs)` + +- `def get_func(t, name, *args, **kwargs)` + Get the `t.name` (potentially partial-ized with `args` and `kwargs`) or `noop` if not defined + +- `class Func` + Basic wrapper around a `name` with `args` and `kwargs` to call on a given type + + - `def __init__(self, name, *args, **kwargs)` + - `def __repr__(self)` + - `def __call__(self, t)` + +- `def compose_tfms(x, tfms, is_enc, reverse, **kwargs)` + Apply all `func_nm` attribute of `tfms` on `x`, maybe in `reverse` order + +- `def mk_transform(f)` + Convert function `f` to `Transform` if it isn't already one + +- `def gather_attrs(o, k, nm)` + Used in __getattr__ to collect all attrs `k` from `self.{nm}` + +- `def gather_attr_names(o, nm)` + Used in __dir__ to collect all attrs `k` from `self.{nm}` + +- `class Pipeline` + A pipeline of composed (for encode/decode) transforms, setup with types + + - `def __init__(self, funcs, split_idx)` + - `def setup(self, items, train_setup)` + - `def add(self, ts, items, train_setup)` + - `def __call__(self, o)` + - `def __repr__(self)` + - `def __getitem__(self, i)` + - `def __setstate__(self, data)` + - `def __getattr__(self, k)` + - `def __dir__(self)` + - `def decode(self, o, full)` + - `def show(self, o, ctx, **kwargs)` + +## fastcore.xdg + +> XDG Base Directory Specification helpers. + +- `def xdg_cache_home()` + Path corresponding to `XDG_CACHE_HOME` + +- `def xdg_config_dirs()` + Paths corresponding to `XDG_CONFIG_DIRS` + +- `def xdg_config_home()` + Path corresponding to `XDG_CONFIG_HOME` + +- `def xdg_data_dirs()` + Paths corresponding to XDG_DATA_DIRS` + +- `def xdg_data_home()` + Path corresponding to `XDG_DATA_HOME` + +- `def xdg_runtime_dir()` + Path corresponding to `XDG_RUNTIME_DIR` + +- `def xdg_state_home()` + Path corresponding to `XDG_STATE_HOME` + +## fastcore.xml + +> Concise generation of XML. + +- `class FT` + A 'Fast Tag' structure, containing `tag`,`children`,and `attrs` + + - `def __init__(self, tag, cs, attrs, void_, **kwargs)` + - `def on(self, f)` + - `def changed(self)` + - `def __setattr__(self, k, v)` + - `def __getattr__(self, k)` + - `@property def list(self)` + - `def get(self, k, default)` + - `def __repr__(self)` + - `def __iter__(self)` + - `def __getitem__(self, idx)` + - `def __setitem__(self, i, o)` + - `def __call__(self, *c, **kw)` + - `def set(self, *c, **kw)` + Set children and/or attributes (chainable) + + +- `def ft(tag, *c, **kw)` + Create an `FT` structure for `to_xml()` + +- `def Html(*c, **kwargs)` + An HTML tag, optionally preceeded by `!DOCTYPE HTML` + +- `class Safe` + - `def __html__(self)` + +- `def to_xml(elm, lvl, indent, do_escape)` + Convert `ft` element tree into an XML string + +- `def highlight(s, lang)` + Markdown to syntax-highlight `s` in language `lang` + +## fastcore.xtras + +> Utility functions used in the fastai library + +- `def walk(path, symlinks, keep_file, keep_folder, skip_folder, func, ret_folders)` + Generator version of `os.walk`, using functions to filter files and folders + +- `def globtastic(path, recursive, symlinks, file_glob, file_re, folder_re, skip_file_glob, skip_file_re, skip_folder_re, func, ret_folders)` + A more powerful `glob`, including regex matches, symlink handling, and skip parameters + +- `@contextmanager def maybe_open(f, mode, **kwargs)` + Context manager: open `f` if it is a path (and close on exit) + +- `def mkdir(path, exist_ok, parents, overwrite, **kwargs)` + Creates and returns a directory defined by `path`, optionally removing previous existing directory if `overwrite` is `True` + +- `def image_size(fn)` + Tuple of (w,h) for png, gif, or jpg; `None` otherwise + +- `def bunzip(fn)` + bunzip `fn`, raising exception if output already exists + +- `def loads(s, **kw)` + Same as `json.loads`, but handles `None` + +- `def loads_multi(s)` + Generator of >=0 decoded json dicts, possibly with non-json ignored text at start and end + +- `def dumps(obj, **kw)` + Same as `json.dumps`, but uses `ujson` if available + +- `def untar_dir(fname, dest, rename, overwrite)` + untar `file` into `dest`, creating a directory if the root contains more than one item + +- `def repo_details(url)` + Tuple of `owner,name` from ssh or https git repo `url` + +- `def run(cmd, *rest)` + Pass `cmd` (splitting with `shlex` if string) to `subprocess.run`; return `stdout`; raise `IOError` if fails + +- `def open_file(fn, mode, **kwargs)` + Open a file, with optional compression if gz or bz2 suffix + +- `def save_pickle(fn, o)` + Save a pickle file, to a file name or opened file + +- `def load_pickle(fn)` + Load a pickle file from a file name or opened file + +- `def parse_env(s, fn)` + Parse a shell-style environment string or file + +- `def expand_wildcards(code)` + Expand all wildcard imports in the given code string. + +- `def dict2obj(d, list_func, dict_func)` + Convert (possibly nested) dicts (or lists of dicts) to `AttrDict` + +- `def obj2dict(d)` + Convert (possibly nested) AttrDicts (or lists of AttrDicts) to `dict` + +- `def repr_dict(d)` + Print nested dicts and lists, such as returned by `dict2obj` + +- `def is_listy(x)` + `isinstance(x, (tuple,list,L,slice,Generator))` + +- `def mapped(f, it)` + map `f` over `it`, unless it's not listy, in which case return `f(it)` + +- `@patch def readlines(self, hint, encoding)` + Read the content of `self` + +- `@patch def read_json(self, encoding, errors)` + Same as `read_text` followed by `loads` + +- `@patch def mk_write(self, data, encoding, errors, mode)` + Make all parent dirs of `self`, and write `data` + +- `@patch def relpath(self, start)` + Same as `os.path.relpath`, but returns a `Path`, and resolves symlinks + +- `@patch def ls(self, n_max, file_type, file_exts)` + Contents of path as a list + +- `@patch def delete(self)` + Delete a file, symlink, or directory tree + +- `class IterLen` + Base class to add iteration to anything supporting `__len__` and `__getitem__` + + - `def __iter__(self)` + +- `@docs class ReindexCollection` + Reindexes collection `coll` with indices `idxs` and optional LRU cache of size `cache` + + - `def __init__(self, coll, idxs, cache, tfm)` + - `def __getitem__(self, i)` + - `def __len__(self)` + - `def reindex(self, idxs)` + - `def shuffle(self)` + - `def cache_clear(self)` + - `def __getstate__(self)` + - `def __setstate__(self, s)` + +- `def get_source_link(func)` + Return link to `func` in source code + +- `def truncstr(s, maxlen, suf, space)` + Truncate `s` to length `maxlen`, adding suffix `suf` if truncated + +- `def sparkline(data, mn, mx, empty_zero)` + Sparkline for `data`, with `None`s (and zero, if `empty_zero`) shown as empty column + +- `def modify_exception(e, msg, replace)` + Modifies `e` with a custom message attached + +- `def round_multiple(x, mult, round_down)` + Round `x` to nearest multiple of `mult` + +- `def set_num_threads(nt)` + Get numpy (and others) to use `nt` threads + +- `def join_path_file(file, path, ext)` + Return `path/file` if file is a string or a `Path`, file otherwise + +- `def autostart(g)` + Decorator that automatically starts a generator + +- `class EventTimer` + An event timer with history of `store` items of time `span` + + - `def __init__(self, store, span)` + - `def add(self, n)` + Record `n` events + + - `@property def duration(self)` + - `@property def freq(self)` + +- `def stringfmt_names(s)` + Unique brace-delimited names in `s` + +- `class PartialFormatter` + A `string.Formatter` that doesn't error on missing fields, and tracks missing fields and unused args + + - `def __init__(self)` + - `def get_field(self, nm, args, kwargs)` + - `def check_unused_args(self, used, args, kwargs)` + +- `def partial_format(s, **kwargs)` + string format `s`, ignoring missing field errors, returning missing and extra fields + +- `def utc2local(dt)` + Convert `dt` from UTC to local time + +- `def local2utc(dt)` + Convert `dt` from local to UTC time + +- `def trace(f)` + Add `set_trace` to an existing function `f` + +- `@contextmanager def modified_env(*delete, **replace)` + Context manager temporarily modifying `os.environ` by deleting `delete` and replacing `replace` + +- `class ContextManagers` + Wrapper for `contextlib.ExitStack` which enters a collection of context managers + + - `def __init__(self, mgrs)` + - `def __enter__(self)` + - `def __exit__(self, *args, **kwargs)` + +- `def shufflish(x, pct)` + Randomly relocate items of `x` up to `pct` of `len(x)` from their starting location + +- `def console_help(libname)` + Show help for all console scripts from `libname` + +- `def hl_md(s, lang, show)` + Syntax highlight `s` using `lang`. + +- `def type2str(typ)` + Stringify `typ` + +- `class Unset` + - `def __repr__(self)` + - `def __str__(self)` + - `def __bool__(self)` + - `@property def name(self)` + +- `def nullable_dc(cls)` + Like `dataclass`, but default of `UNSET` added to fields without defaults + +- `def flexiclass(cls)` + Convert `cls` into a `dataclass` like `make_nullable`. Converts in place and also returns the result. + +- `def asdict(o)` + Convert `o` to a `dict`, supporting dataclasses, namedtuples, iterables, and `__dict__` attrs. + +- `def is_typeddict(cls)` + Check if `cls` is a `TypedDict` + +- `def is_namedtuple(cls)` + `True` if `cls` is a namedtuple type + +- `def flexicache(*funcs)` + Like `lru_cache`, but customisable with policy `funcs` + +- `def time_policy(seconds)` + A `flexicache` policy that expires cached items after `seconds` have passed + +- `def mtime_policy(filepath)` + A `flexicache` policy that expires cached items after `filepath` modified-time changes + +- `def timed_cache(seconds, maxsize)` + Like `lru_cache`, but also with time-based eviction +# Test + + + +## Simple test functions + +We can check that code raises an exception when that’s expected +([`test_fail`](https://fastcore.fast.ai/test.html#test_fail)). + +To test for equality or inequality (with different types of things) we +define a simple function +[`test`](https://fastcore.fast.ai/test.html#test) that compares two +objects with a given `cmp` operator. + +------------------------------------------------------------------------ + +source + +### test_fail + +> test_fail (f, msg='', contains='', args=None, kwargs=None) + +*Fails with `msg` unless `f()` raises an exception and (optionally) has +`contains` in `e.args`* + +``` python +def _fail(): raise Exception("foobar") +test_fail(_fail, contains="foo") + +def _fail(): raise Exception() +test_fail(_fail) +``` + +We can also pass `args` and `kwargs` to function to check if it fails +with special inputs. + +``` python +def _fail_args(a): + if a == 5: + raise ValueError +test_fail(_fail_args, args=(5,)) +test_fail(_fail_args, kwargs=dict(a=5)) +``` + +------------------------------------------------------------------------ + +source + +### test + +> test (a, b, cmp, cname=None) + +*`assert` that `cmp(a,b)`; display inputs and `cname or cmp.__name__` if +it fails* + +``` python +test([1,2],[1,2], operator.eq) +test_fail(lambda: test([1,2],[1], operator.eq)) +test([1,2],[1], operator.ne) +test_fail(lambda: test([1,2],[1,2], operator.ne)) +``` + +------------------------------------------------------------------------ + +### all_equal + +> all_equal (a, b) + +*Compares whether `a` and `b` are the same length and have the same +contents* + +``` python +test(['abc'], ['abc'], all_equal) +test_fail(lambda: test(['abc'],['cab'], all_equal)) +``` + +------------------------------------------------------------------------ + +### equals + +> equals (a, b) + +*Compares `a` and `b` for equality; supports sublists, tensors and +arrays too* + +``` python +test([['abc'],['a']], [['abc'],['a']], equals) +test([['abc'],['a'],'b', [['x']]], [['abc'],['a'],'b', [['x']]], equals) # supports any depth and nested structure +``` + +------------------------------------------------------------------------ + +source + +### nequals + +> nequals (a, b) + +*Compares `a` and `b` for `not equals`* + +``` python +test(['abc'], ['ab' ], nequals) +``` + +## test_eq test_ne, etc… + +Just use +[`test_eq`](https://fastcore.fast.ai/test.html#test_eq)/[`test_ne`](https://fastcore.fast.ai/test.html#test_ne) +to test for `==`/`!=`. +[`test_eq_type`](https://fastcore.fast.ai/test.html#test_eq_type) checks +things are equal and of the same type. We define them using +[`test`](https://fastcore.fast.ai/test.html#test): + +------------------------------------------------------------------------ + +source + +### test_eq + +> test_eq (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a==b`* + +``` python +test_eq([1,2],[1,2]) +test_eq([1,2],map(int,[1,2])) +test_eq(array([1,2]),array([1,2])) +test_eq(array([1,2]),array([1,2])) +test_eq([array([1,2]),3],[array([1,2]),3]) +test_eq(dict(a=1,b=2), dict(b=2,a=1)) +test_fail(lambda: test_eq([1,2], 1), contains="==") +test_fail(lambda: test_eq(None, np.array([1,2])), contains="==") +test_eq({'a', 'b', 'c'}, {'c', 'a', 'b'}) +``` + +``` python +df1 = pd.DataFrame(dict(a=[1,2],b=['a','b'])) +df2 = pd.DataFrame(dict(a=[1,2],b=['a','b'])) +df3 = pd.DataFrame(dict(a=[1,2],b=['a','c'])) + +test_eq(df1,df2) +test_eq(df1.a,df2.a) +test_fail(lambda: test_eq(df1,df3), contains='==') +class T(pd.Series): pass +test_eq(df1.iloc[0], T(df2.iloc[0])) # works with subclasses +``` + +``` python +test_eq(torch.zeros(10), torch.zeros(10, dtype=torch.float64)) +test_eq(torch.zeros(10), torch.ones(10)-1) +test_fail(lambda:test_eq(torch.zeros(10), torch.ones(1, 10)), contains='==') +test_eq(torch.zeros(3), [0,0,0]) +``` + +------------------------------------------------------------------------ + +source + +### test_eq_type + +> test_eq_type (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a==b` and are +same type* + +``` python +test_eq_type(1,1) +test_fail(lambda: test_eq_type(1,1.)) +test_eq_type([1,1],[1,1]) +test_fail(lambda: test_eq_type([1,1],(1,1))) +test_fail(lambda: test_eq_type([1,1],[1,1.])) +``` + +------------------------------------------------------------------------ + +source + +### test_ne + +> test_ne (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a!=b`* + +``` python +test_ne([1,2],[1]) +test_ne([1,2],[1,3]) +test_ne(array([1,2]),array([1,1])) +test_ne(array([1,2]),array([1,1])) +test_ne([array([1,2]),3],[array([1,2])]) +test_ne([3,4],array([3])) +test_ne([3,4],array([3,5])) +test_ne(dict(a=1,b=2), ['a', 'b']) +test_ne(['a', 'b'], dict(a=1,b=2)) +``` + +------------------------------------------------------------------------ + +source + +### is_close + +> is_close (a, b, eps=1e-05) + +*Is `a` within `eps` of `b`* + +------------------------------------------------------------------------ + +source + +### test_close + +> test_close (a, b, eps=1e-05) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a` is within +`eps` of `b`* + +``` python +test_close(1,1.001,eps=1e-2) +test_fail(lambda: test_close(1,1.001)) +test_close([-0.001,1.001], [0.,1.], eps=1e-2) +test_close(np.array([-0.001,1.001]), np.array([0.,1.]), eps=1e-2) +test_close(array([-0.001,1.001]), array([0.,1.]), eps=1e-2) +``` + +------------------------------------------------------------------------ + +source + +### test_is + +> test_is (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a is b`* + +``` python +test_fail(lambda: test_is([1], [1])) +a = [1] +test_is(a, a) +b = [2]; test_fail(lambda: test_is(a, b)) +``` + +------------------------------------------------------------------------ + +source + +### test_shuffled + +> test_shuffled (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a` and `b` are +shuffled versions of the same sequence of items* + +``` python +a = list(range(50)) +b = copy(a) +random.shuffle(b) +test_shuffled(a,b) +test_fail(lambda:test_shuffled(a,a)) +``` + +``` python +a = 'abc' +b = 'abcabc' +test_fail(lambda:test_shuffled(a,b)) +``` + +``` python +a = ['a', 42, True] +b = [42, True, 'a'] +test_shuffled(a,b) +``` + +------------------------------------------------------------------------ + +source + +### test_stdout + +> test_stdout (f, exp, regex=False) + +*Test that `f` prints `exp` to stdout, optionally checking as `regex`* + +``` python +test_stdout(lambda: print('hi'), 'hi') +test_fail(lambda: test_stdout(lambda: print('hi'), 'ho')) +test_stdout(lambda: 1+1, '') +test_stdout(lambda: print('hi there!'), r'^hi.*!$', regex=True) +``` + +------------------------------------------------------------------------ + +source + +### test_warns + +> test_warns (f, show=False) + +``` python +test_warns(lambda: warnings.warn("Oh no!")) +test_fail(lambda: test_warns(lambda: 2+2), contains='No warnings raised') +``` + +``` python +test_warns(lambda: warnings.warn("Oh no!"), show=True) +``` + + : Oh no! + +``` python +im = Image.open(TEST_IMAGE).resize((128,128)); im +``` + +![](00_test_files/figure-commonmark/cell-35-output-1.png) + +``` python +im = Image.open(TEST_IMAGE_BW).resize((128,128)); im +``` + +![](00_test_files/figure-commonmark/cell-36-output-1.png) + +------------------------------------------------------------------------ + +source + +### test_fig_exists + +> test_fig_exists (ax) + +*Test there is a figure displayed in `ax`* + +``` python +fig,ax = plt.subplots() +ax.imshow(array(im)); +``` + +![](00_test_files/figure-commonmark/cell-38-output-1.png) + +``` python +test_fig_exists(ax) +``` + +------------------------------------------------------------------------ + +source + +### ExceptionExpected + +> ExceptionExpected (ex=, regex='') + +*Context manager that tests if an exception is raised* + +``` python +def _tst_1(): assert False, "This is a test" +def _tst_2(): raise SyntaxError + +with ExceptionExpected(): _tst_1() +with ExceptionExpected(ex=AssertionError, regex="This is a test"): _tst_1() +with ExceptionExpected(ex=SyntaxError): _tst_2() +``` + +`exception` is an abbreviation for `ExceptionExpected()`. + +``` python +with exception: _tst_1() +```# Basic functionality + + + +## Basics + +------------------------------------------------------------------------ + +source + +### ifnone + +> ifnone (a, b) + +*`b` if `a` is None else `a`* + +Since `b if a is None else a` is such a common pattern, we wrap it in a +function. However, be careful, because python will evaluate *both* `a` +and `b` when calling +[`ifnone`](https://fastcore.fast.ai/basics.html#ifnone) (which it +doesn’t do if using the `if` version directly). + +``` python +test_eq(ifnone(None,1), 1) +test_eq(ifnone(2 ,1), 2) +``` + +------------------------------------------------------------------------ + +source + +### maybe_attr + +> maybe_attr (o, attr) + +*`getattr(o,attr,o)`* + +Return the attribute `attr` for object `o`. If the attribute doesn’t +exist, then return the object `o` instead. + +``` python +class myobj: myattr='foo' + +test_eq(maybe_attr(myobj, 'myattr'), 'foo') +test_eq(maybe_attr(myobj, 'another_attr'), myobj) +``` + +------------------------------------------------------------------------ + +source + +### basic_repr + +> basic_repr (flds=None) + +*Minimal `__repr__`* + +In types which provide rich display functionality in Jupyter, their +`__repr__` is also called in order to provide a fallback text +representation. Unfortunately, this includes a memory address which +changes on every invocation, making it non-deterministic. This causes +diffs to get messy and creates conflicts in git. To fix this, put +`__repr__=basic_repr()` inside your class. + +``` python +class SomeClass: __repr__=basic_repr() +repr(SomeClass()) +``` + + 'SomeClass()' + +If you pass a list of attributes (`flds`) of an object, then this will +generate a string with the name of each attribute and its corresponding +value. The format of this string is `key=value`, where `key` is the name +of the attribute, and `value` is the value of the attribute. For each +value, attempt to use the `__name__` attribute, otherwise fall back to +using the value’s `__repr__` when constructing the string. + +``` python +class SomeClass: + a=1 + b='foo' + __repr__=basic_repr('a,b') + __name__='some-class' + +repr(SomeClass()) +``` + + "SomeClass(a=1, b='foo')" + +Nested objects work too: + +``` python +class AnotherClass: + c=SomeClass() + d='bar' + __repr__=basic_repr(['c', 'd']) + +repr(AnotherClass()) +``` + + "AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')" + +Instance variables (but not class variables) are shown if +[`basic_repr`](https://fastcore.fast.ai/basics.html#basic_repr) is +called with no arguments: + +``` python +class SomeClass: + def __init__(self, a=1, b='foo'): self.a,self.b = a,b + __repr__=basic_repr() + +repr(SomeClass()) +``` + + "SomeClass(a=1, b='foo')" + +------------------------------------------------------------------------ + +source + +### BasicRepr + +> BasicRepr () + +*Base class for objects needing a basic `__repr__`* + +As a shortcut for creating a `__repr__` for instance variables, you can +inherit from +[`BasicRepr`](https://fastcore.fast.ai/basics.html#basicrepr): + +``` python +class SomeClass(BasicRepr): + def __init__(self, a=1, b='foo'): self.a,self.b = a,b + +repr(SomeClass()) +``` + + "SomeClass(a=1, b='foo')" + +------------------------------------------------------------------------ + +source + +### is_array + +> is_array (x) + +*`True` if `x` supports `__array__` or `iloc`* + +``` python +is_array(np.array(1)),is_array([1]) +``` + + (True, False) + +------------------------------------------------------------------------ + +source + +### listify + +> listify (o=None, *rest, use_list=False, match=None) + +*Convert `o` to a `list`* + +Conversion is designed to “do what you mean”, e.g: + +``` python +test_eq(listify('hi'), ['hi']) +test_eq(listify(b'hi'), [b'hi']) +test_eq(listify(array(1)), [array(1)]) +test_eq(listify(1), [1]) +test_eq(listify([1,2]), [1,2]) +test_eq(listify(range(3)), [0,1,2]) +test_eq(listify(None), []) +test_eq(listify(1,2), [1,2]) +``` + +``` python +arr = np.arange(9).reshape(3,3) +listify(arr) +``` + + [array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]])] + +``` python +listify(array([1,2])) +``` + + [array([1, 2])] + +Generators are turned into lists too: + +``` python +gen = (o for o in range(3)) +test_eq(listify(gen), [0,1,2]) +``` + +Use `match` to provide a length to match: + +``` python +test_eq(listify(1,match=3), [1,1,1]) +``` + +If `match` is a sequence, it’s length is used: + +``` python +test_eq(listify(1,match=range(3)), [1,1,1]) +``` + +If the listified item is not of length `1`, it must be the same length +as `match`: + +``` python +test_eq(listify([1,1,1],match=3), [1,1,1]) +test_fail(lambda: listify([1,1],match=3)) +``` + +------------------------------------------------------------------------ + +source + +### tuplify + +> tuplify (o, use_list=False, match=None) + +*Make `o` a tuple* + +``` python +test_eq(tuplify(None),()) +test_eq(tuplify([1,2,3]),(1,2,3)) +test_eq(tuplify(1,match=[1,2,3]),(1,1,1)) +``` + +------------------------------------------------------------------------ + +source + +### true + +> true (x) + +*Test whether `x` is truthy; collections with \>0 elements are +considered `True`* + +``` python +[(o,true(o)) for o in + (array(0),array(1),array([0]),array([0,1]),1,0,'',None)] +``` + + [(array(0), False), + (array(1), True), + (array([0]), True), + (array([0, 1]), True), + (1, True), + (0, False), + ('', False), + (None, False)] + +------------------------------------------------------------------------ + +source + +### NullType + +> NullType () + +*An object that is `False` and can be called, chained, and indexed* + +``` python +bool(null.hi().there[3]) +``` + + False + +------------------------------------------------------------------------ + +source + +### tonull + +> tonull (x) + +*Convert `None` to `null`* + +``` python +bool(tonull(None).hi().there[3]) +``` + + False + +------------------------------------------------------------------------ + +source + +### get_class + +> get_class (nm, *fld_names, sup=None, doc=None, funcs=None, anno=None, +> **flds) + +*Dynamically create a class, optionally inheriting from `sup`, +containing `fld_names`* + +``` python +_t = get_class('_t', 'a', b=2, anno={'b':int}) +t = _t() +test_eq(t.a, None) +test_eq(t.b, 2) +t = _t(1, b=3) +test_eq(t.a, 1) +test_eq(t.b, 3) +t = _t(1, 3) +test_eq(t.a, 1) +test_eq(t.b, 3) +test_eq(t, pickle.loads(pickle.dumps(t))) +test_eq(_t.__annotations__, {'b':int, 'a':typing.Any}) +repr(t) +``` + + '__main__._t(a=1, b=3)' + +Most often you’ll want to call +[`mk_class`](https://fastcore.fast.ai/basics.html#mk_class), since it +adds the class to your module. See +[`mk_class`](https://fastcore.fast.ai/basics.html#mk_class) for more +details and examples of use (which also apply to +[`get_class`](https://fastcore.fast.ai/basics.html#get_class)). + +------------------------------------------------------------------------ + +source + +### mk_class + +> mk_class (nm, *fld_names, sup=None, doc=None, funcs=None, mod=None, +> anno=None, **flds) + +*Create a class using +[`get_class`](https://fastcore.fast.ai/basics.html#get_class) and add to +the caller’s module* + +Any `kwargs` will be added as class attributes, and `sup` is an optional +(tuple of) base classes. + +``` python +mk_class('_t', a=1, sup=dict) +t = _t() +test_eq(t.a, 1) +assert(isinstance(t,dict)) +``` + +A `__init__` is provided that sets attrs for any `kwargs`, and for any +`args` (matching by position to fields), along with a `__repr__` which +prints all attrs. The docstring is set to `doc`. You can pass `funcs` +which will be added as attrs with the function names. + +``` python +def foo(self): return 1 +mk_class('_t', 'a', sup=dict, doc='test doc', funcs=foo) + +t = _t(3, b=2) +test_eq(t.a, 3) +test_eq(t.b, 2) +test_eq(t.foo(), 1) +test_eq(t.__doc__, 'test doc') +t +``` + + {} + +------------------------------------------------------------------------ + +source + +### wrap_class + +> wrap_class (nm, *fld_names, sup=None, doc=None, funcs=None, **flds) + +*Decorator: makes function a method of a new class `nm` passing +parameters to +[`mk_class`](https://fastcore.fast.ai/basics.html#mk_class)* + +``` python +@wrap_class('_t', a=2) +def bar(self,x): return x+1 + +t = _t() +test_eq(t.a, 2) +test_eq(t.bar(3), 4) +``` + +------------------------------------------------------------------------ + +source + +#### ignore_exceptions + +> ignore_exceptions () + +*Context manager to ignore exceptions* + +``` python +with ignore_exceptions(): + # Exception will be ignored + raise Exception +``` + +------------------------------------------------------------------------ + +source + +### exec_local + +> exec_local (code, var_name) + +*Call `exec` on `code` and return the var `var_name`* + +``` python +test_eq(exec_local("a=1", "a"), 1) +``` + +------------------------------------------------------------------------ + +source + +### risinstance + +> risinstance (types, obj=None) + +*Curried `isinstance` but with args reversed* + +``` python +assert risinstance(int, 1) +assert not risinstance(str, 0) +assert risinstance(int)(1) +assert not risinstance(int)(None) +``` + +`types` can also be strings: + +``` python +assert risinstance(('str','int'), 'a') +assert risinstance('str', 'a') +assert not risinstance('int', 'a') +``` + +------------------------------------------------------------------------ + +source + +### ver2tuple + +> ver2tuple (v:str) + +``` python +test_eq(ver2tuple('3.8.1'), (3,8,1)) +test_eq(ver2tuple('3.1'), (3,1,0)) +test_eq(ver2tuple('3.'), (3,0,0)) +test_eq(ver2tuple('3'), (3,0,0)) +``` + +## NoOp + +These are used when you need a pass-through function. + +------------------------------------------------------------------------ + +### noop + +> noop (x=None, *args, **kwargs) + +*Do nothing* + +``` python +noop() +test_eq(noop(1),1) +``` + +------------------------------------------------------------------------ + +### noops + +> noops (x=None, *args, **kwargs) + +*Do nothing (method)* + +``` python +class _t: foo=noops +test_eq(_t().foo(1),1) +``` + +## Infinite Lists + +These lists are useful for things like padding an array or adding index +column(s) to arrays. + +[`Inf`](https://fastcore.fast.ai/basics.html#inf) defines the following +properties: + +- `count: itertools.count()` +- `zeros: itertools.cycle([0])` +- `ones : itertools.cycle([1])` +- `nones: itertools.cycle([None])` + +``` python +test_eq([o for i,o in zip(range(5), Inf.count)], + [0, 1, 2, 3, 4]) + +test_eq([o for i,o in zip(range(5), Inf.zeros)], + [0]*5) + +test_eq([o for i,o in zip(range(5), Inf.ones)], + [1]*5) + +test_eq([o for i,o in zip(range(5), Inf.nones)], + [None]*5) +``` + +## Operator Functions + +------------------------------------------------------------------------ + +source + +### in\_ + +> in_ (x, a) + +*`True` if `x in a`* + +``` python +# test if element is in another +assert in_('c', ('b', 'c', 'a')) +assert in_(4, [2,3,4,5]) +assert in_('t', 'fastai') +test_fail(in_('h', 'fastai')) + +# use in_ as a partial +assert in_('fastai')('t') +assert in_([2,3,4,5])(4) +test_fail(in_('fastai')('h')) +``` + +In addition to [`in_`](https://fastcore.fast.ai/basics.html#in_), the +following functions are provided matching the behavior of the equivalent +versions in `operator`: *lt gt le ge eq ne add sub mul truediv is\_ +is_not mod*. + +``` python +lt(3,5),gt(3,5),is_(None,None),in_(0,[1,2]),mod(3,2) +``` + + (True, False, True, False, 1) + +Similarly to `_in`, they also have additional functionality: if you only +pass one param, they return a partial function that passes that param as +the second positional parameter. + +``` python +lt(5)(3),gt(5)(3),is_(None)(None),in_([1,2])(0),mod(2)(3) +``` + + (True, False, True, False, 1) + +------------------------------------------------------------------------ + +source + +### ret_true + +> ret_true (*args, **kwargs) + +*Predicate: always `True`* + +``` python +assert ret_true(1,2,3) +assert ret_true(False) +``` + +------------------------------------------------------------------------ + +source + +### ret_false + +> ret_false (*args, **kwargs) + +*Predicate: always `False`* + +------------------------------------------------------------------------ + +source + +### stop + +> stop (e=) + +*Raises exception `e` (by default `StopIteration`)* + +------------------------------------------------------------------------ + +source + +### gen + +> gen (func, seq, cond=) + +*Like `(func(o) for o in seq if cond(func(o)))` but handles +`StopIteration`* + +``` python +test_eq(gen(noop, Inf.count, lt(5)), + range(5)) +test_eq(gen(operator.neg, Inf.count, gt(-5)), + [0,-1,-2,-3,-4]) +test_eq(gen(lambda o:o if o<5 else stop(), Inf.count), + range(5)) +``` + +------------------------------------------------------------------------ + +source + +### chunked + +> chunked (it, chunk_sz=None, drop_last=False, n_chunks=None) + +*Return batches from iterator `it` of size `chunk_sz` (or return +`n_chunks` total)* + +Note that you must pass either `chunk_sz`, or `n_chunks`, but not both. + +``` python +t = list(range(10)) +test_eq(chunked(t,3), [[0,1,2], [3,4,5], [6,7,8], [9]]) +test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ]) + +t = map(lambda o:stop() if o==6 else o, Inf.count) +test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5]]) +t = map(lambda o:stop() if o==7 else o, Inf.count) +test_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5], [6]]) + +t = np.arange(10) +test_eq(chunked(t,3), [[0,1,2], [3,4,5], [6,7,8], [9]]) +test_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ]) + +test_eq(chunked([], 3), []) +test_eq(chunked([], n_chunks=3), []) +``` + +------------------------------------------------------------------------ + +source + +### otherwise + +> otherwise (x, tst, y) + +*`y if tst(x) else x`* + +``` python +test_eq(otherwise(2+1, gt(3), 4), 3) +test_eq(otherwise(2+1, gt(2), 4), 4) +``` + +## Attribute Helpers + +These functions reduce boilerplate when setting or manipulating +attributes or properties of objects. + +------------------------------------------------------------------------ + +source + +### custom_dir + +> custom_dir (c, add) + +*Implement custom `__dir__`, adding `add` to `cls`* + +[`custom_dir`](https://fastcore.fast.ai/basics.html#custom_dir) allows +you extract the [`__dict__` property of a +class](https://stackoverflow.com/questions/19907442/explain-dict-attribute) +and appends the list `add` to it. + +``` python +class _T: + def f(): pass + +s = custom_dir(_T(), add=['foo', 'bar']) +assert {'foo', 'bar', 'f'}.issubset(s) +``` + +------------------------------------------------------------------------ + +source + +### AttrDict + +*`dict` subclass that also provides access to keys as attrs* + +``` python +d = AttrDict(a=1,b="two") +test_eq(d.a, 1) +test_eq(d['b'], 'two') +test_eq(d.get('c','nope'), 'nope') +d.b = 2 +test_eq(d.b, 2) +test_eq(d['b'], 2) +d['b'] = 3 +test_eq(d['b'], 3) +test_eq(d.b, 3) +assert 'a' in dir(d) +``` + +[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict) will pretty +print in Jupyter Notebooks: + +``` python +_test_dict = {'a':1, 'b': {'c':1, 'd':2}, 'c': {'c':1, 'd':2}, 'd': {'c':1, 'd':2}, + 'e': {'c':1, 'd':2}, 'f': {'c':1, 'd':2, 'e': 4, 'f':[1,2,3,4,5]}} +AttrDict(_test_dict) +``` + +``` json +{ 'a': 1, + 'b': {'c': 1, 'd': 2}, + 'c': {'c': 1, 'd': 2}, + 'd': {'c': 1, 'd': 2}, + 'e': {'c': 1, 'd': 2}, + 'f': {'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}} +``` + +------------------------------------------------------------------------ + +source + +### AttrDictDefault + +> AttrDictDefault (*args, default_=None, **kwargs) + +*[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict) subclass +that returns `None` for missing attrs* + +``` python +d = AttrDictDefault(a=1,b="two", default_='nope') +test_eq(d.a, 1) +test_eq(d['b'], 'two') +test_eq(d.c, 'nope') +``` + +------------------------------------------------------------------------ + +source + +### NS + +*`SimpleNamespace` subclass that also adds `iter` and `dict` support* + +This is very similar to +[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict), but since +it starts with `SimpleNamespace`, it has some differences in behavior. +You can use it just like `SimpleNamespace`: + +``` python +d = NS(**_test_dict) +d +``` + + namespace(a=1, + b={'c': 1, 'd': 2}, + c={'c': 1, 'd': 2}, + d={'c': 1, 'd': 2}, + e={'c': 1, 'd': 2}, + f={'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}) + +…but you can also index it to get/set: + +``` python +d['a'] +``` + + 1 + +…and iterate t: + +``` python +list(d) +``` + + ['a', 'b', 'c', 'd', 'e', 'f'] + +------------------------------------------------------------------------ + +source + +### get_annotations_ex + +> get_annotations_ex (obj, globals=None, locals=None) + +*Backport of py3.10 `get_annotations` that returns globals/locals* + +In Python 3.10 `inspect.get_annotations` was added. However previous +versions of Python are unable to evaluate type annotations correctly if +`from future import __annotations__` is used. Furthermore, *all* +annotations are evaluated, even if only some subset are needed. +[`get_annotations_ex`](https://fastcore.fast.ai/basics.html#get_annotations_ex) +provides the same functionality as `inspect.get_annotations`, but works +on earlier versions of Python, and returns the `globals` and `locals` +needed to evaluate types. + +------------------------------------------------------------------------ + +source + +### eval_type + +> eval_type (t, glb, loc) + +*`eval` a type or collection of types, if needed, for annotations in +py3.10+* + +In py3.10, or if `from future import __annotations__` is used, `a` is a +`str`: + +``` python +class _T2a: pass +def func(a: _T2a): pass +ann,glb,loc = get_annotations_ex(func) + +eval_type(ann['a'], glb, loc) +``` + + __main__._T2a + +`|` is supported for defining `Union` types when using +[`eval_type`](https://fastcore.fast.ai/basics.html#eval_type) even for +python versions prior to 3.9: + +``` python +class _T2b: pass +def func(a: _T2a|_T2b): pass +ann,glb,loc = get_annotations_ex(func) + +eval_type(ann['a'], glb, loc) +``` + + typing.Union[__main__._T2a, __main__._T2b] + +------------------------------------------------------------------------ + +source + +### type_hints + +> type_hints (f) + +*Like `typing.get_type_hints` but returns `{}` if not allowed type* + +Below is a list of allowed types for type hints in python: + +``` python +list(typing._allowed_types) +``` + + [function, + builtin_function_or_method, + method, + module, + wrapper_descriptor, + method-wrapper, + method_descriptor] + +For example, type `func` is allowed so +[`type_hints`](https://fastcore.fast.ai/basics.html#type_hints) returns +the same value as `typing.get_hints`: + +``` python +def f(a:int)->bool: ... # a function with type hints (allowed) +exp = {'a':int,'return':bool} +test_eq(type_hints(f), typing.get_type_hints(f)) +test_eq(type_hints(f), exp) +``` + +However, `class` is not an allowed type, so +[`type_hints`](https://fastcore.fast.ai/basics.html#type_hints) returns +`{}`: + +``` python +class _T: + def __init__(self, a:int=0)->bool: ... +assert not type_hints(_T) +``` + +------------------------------------------------------------------------ + +source + +### annotations + +> annotations (o) + +*Annotations for `o`, or `type(o)`* + +This supports a wider range of situations than +[`type_hints`](https://fastcore.fast.ai/basics.html#type_hints), by +checking `type()` and `__init__` for annotations too: + +``` python +for o in _T,_T(),_T.__init__,f: test_eq(annotations(o), exp) +assert not annotations(int) +assert not annotations(print) +``` + +------------------------------------------------------------------------ + +source + +### anno_ret + +> anno_ret (func) + +*Get the return annotation of `func`* + +``` python +def f(x) -> float: return x +test_eq(anno_ret(f), float) + +def f(x) -> typing.Tuple[float,float]: return x +assert anno_ret(f)==typing.Tuple[float,float] +``` + +If your return annotation is `None`, +[`anno_ret`](https://fastcore.fast.ai/basics.html#anno_ret) will return +`NoneType` (and not `None`): + +``` python +def f(x) -> None: return x + +test_eq(anno_ret(f), NoneType) +assert anno_ret(f) is not None # returns NoneType instead of None +``` + +If your function does not have a return type, or if you pass in `None` +instead of a function, then +[`anno_ret`](https://fastcore.fast.ai/basics.html#anno_ret) returns +`None`: + +``` python +def f(x): return x + +test_eq(anno_ret(f), None) +test_eq(anno_ret(None), None) # instead of passing in a func, pass in None +``` + +------------------------------------------------------------------------ + +source + +### signature_ex + +> signature_ex (obj, eval_str:bool=False) + +*Backport of `inspect.signature(..., eval_str=True` to \source + +### union2tuple + +> union2tuple (t) + +``` python +test_eq(union2tuple(Union[int,str]), (int,str)) +test_eq(union2tuple(int), int) +assert union2tuple(Tuple[int,str])==Tuple[int,str] +test_eq(union2tuple((int,str)), (int,str)) +if UnionType: test_eq(union2tuple(int|str), (int,str)) +``` + +------------------------------------------------------------------------ + +source + +### argnames + +> argnames (f, frame=False) + +*Names of arguments to function or frame `f`* + +``` python +test_eq(argnames(f), ['x']) +``` + +------------------------------------------------------------------------ + +source + +### with_cast + +> with_cast (f) + +*Decorator which uses any parameter annotations as preprocessing +functions* + +``` python +@with_cast +def _f(a, b:Path, c:str='', d=0): return (a,b,c,d) + +test_eq(_f(1, '.', 3), (1,Path('.'),'3',0)) +test_eq(_f(1, '.'), (1,Path('.'),'',0)) + +@with_cast +def _g(a:int=0)->str: return a + +test_eq(_g(4.0), '4') +test_eq(_g(4.4), '4') +test_eq(_g(2), '2') +``` + +------------------------------------------------------------------------ + +source + +### store_attr + +> store_attr (names=None, but='', cast=False, store_args=None, **attrs) + +*Store params named in comma-separated `names` from calling context into +attrs in `self`* + +In it’s most basic form, you can use +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr) to +shorten code like this: + +``` python +class T: + def __init__(self, a,b,c): self.a,self.b,self.c = a,b,c +``` + +…to this: + +``` python +class T: + def __init__(self, a,b,c): store_attr('a,b,c', self) +``` + +This class behaves as if we’d used the first form: + +``` python +t = T(1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 +``` + +``` python +class T1: + def __init__(self, a,b,c): store_attr() +``` + +In addition, it stores the attrs as a `dict` in `__stored_args__`, which +you can use for display, logging, and so forth. + +``` python +test_eq(t.__stored_args__, {'a':1, 'b':3, 'c':2}) +``` + +Since you normally want to use the first argument (often called `self`) +for storing attributes, it’s optional: + +``` python +class T: + def __init__(self, a,b,c:str): store_attr('a,b,c') + +t = T(1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 +``` + +With `cast=True` any parameter annotations will be used as preprocessing +functions for the corresponding arguments: + +``` python +class T: + def __init__(self, a:listify, b, c:str): store_attr('a,b,c', cast=True) + +t = T(1,c=2,b=3) +assert t.a==[1] and t.b==3 and t.c=='2' +``` + +You can inherit from a class using +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr), and +just call it again to add in any new attributes added in the derived +class: + +``` python +class T2(T): + def __init__(self, d, **kwargs): + super().__init__(**kwargs) + store_attr('d') + +t = T2(d=1,a=2,b=3,c=4) +assert t.a==2 and t.b==3 and t.c==4 and t.d==1 +``` + +You can skip passing a list of attrs to store. In this case, all +arguments passed to the method are stored: + +``` python +class T: + def __init__(self, a,b,c): store_attr() + +t = T(1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 +``` + +``` python +class T4(T): + def __init__(self, d, **kwargs): + super().__init__(**kwargs) + store_attr() + +t = T4(4, a=1,c=2,b=3) +assert t.a==1 and t.b==3 and t.c==2 and t.d==4 +``` + +``` python +class T4: + def __init__(self, *, a: int, b: float = 1): + store_attr() + +t = T4(a=3) +assert t.a==3 and t.b==1 +t = T4(a=3, b=2) +assert t.a==3 and t.b==2 +``` + +You can skip some attrs by passing `but`: + +``` python +class T: + def __init__(self, a,b,c): store_attr(but='a') + +t = T(1,c=2,b=3) +assert t.b==3 and t.c==2 +assert not hasattr(t,'a') +``` + +You can also pass keywords to +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr), which +is identical to setting the attrs directly, but also stores them in +`__stored_args__`. + +``` python +class T: + def __init__(self): store_attr(a=1) + +t = T() +assert t.a==1 +``` + +You can also use store_attr inside functions. + +``` python +def create_T(a, b): + t = SimpleNamespace() + store_attr(self=t) + return t + +t = create_T(a=1, b=2) +assert t.a==1 and t.b==2 +``` + +------------------------------------------------------------------------ + +source + +### attrdict + +> attrdict (o, *ks, default=None) + +*Dict from each `k` in `ks` to `getattr(o,k)`* + +``` python +class T: + def __init__(self, a,b,c): store_attr() + +t = T(1,c=2,b=3) +test_eq(attrdict(t,'b','c'), {'b':3, 'c':2}) +``` + +------------------------------------------------------------------------ + +source + +### properties + +> properties (cls, *ps) + +*Change attrs in `cls` with names in `ps` to properties* + +``` python +class T: + def a(self): return 1 + def b(self): return 2 +properties(T,'a') + +test_eq(T().a,1) +test_eq(T().b(),2) +``` + +------------------------------------------------------------------------ + +source + +### camel2words + +> camel2words (s, space=' ') + +*Convert CamelCase to ‘spaced words’* + +``` python +test_eq(camel2words('ClassAreCamel'), 'Class Are Camel') +``` + +------------------------------------------------------------------------ + +source + +### camel2snake + +> camel2snake (name) + +*Convert CamelCase to snake_case* + +``` python +test_eq(camel2snake('ClassAreCamel'), 'class_are_camel') +test_eq(camel2snake('Already_Snake'), 'already__snake') +``` + +------------------------------------------------------------------------ + +source + +### snake2camel + +> snake2camel (s) + +*Convert snake_case to CamelCase* + +``` python +test_eq(snake2camel('a_b_cc'), 'ABCc') +``` + +------------------------------------------------------------------------ + +source + +### class2attr + +> class2attr (cls_name) + +*Return the snake-cased name of the class; strip ending `cls_name` if it +exists.* + +``` python +class Parent: + @property + def name(self): return class2attr(self, 'Parent') + +class ChildOfParent(Parent): pass +class ParentChildOf(Parent): pass + +p = Parent() +cp = ChildOfParent() +cp2 = ParentChildOf() + +test_eq(p.name, 'parent') +test_eq(cp.name, 'child_of') +test_eq(cp2.name, 'parent_child_of') +``` + +------------------------------------------------------------------------ + +source + +### getcallable + +> getcallable (o, attr) + +*Calls `getattr` with a default of `noop`* + +``` python +class Math: + def addition(self,a,b): return a+b + +m = Math() + +test_eq(getcallable(m, "addition")(a=1,b=2), 3) +test_eq(getcallable(m, "subtraction")(a=1,b=2), None) +``` + +------------------------------------------------------------------------ + +source + +### getattrs + +> getattrs (o, *attrs, default=None) + +*List of all `attrs` in `o`* + +``` python +from fractions import Fraction +``` + +``` python +getattrs(Fraction(1,2), 'numerator', 'denominator') +``` + + [1, 2] + +------------------------------------------------------------------------ + +source + +### hasattrs + +> hasattrs (o, attrs) + +*Test whether `o` contains all `attrs`* + +``` python +assert hasattrs(1,('imag','real')) +assert not hasattrs(1,('imag','foo')) +``` + +------------------------------------------------------------------------ + +source + +### setattrs + +> setattrs (dest, flds, src) + +``` python +d = dict(a=1,bb="2",ignore=3) +o = SimpleNamespace() +setattrs(o, "a,bb", d) +test_eq(o.a, 1) +test_eq(o.bb, "2") +``` + +``` python +d = SimpleNamespace(a=1,bb="2",ignore=3) +o = SimpleNamespace() +setattrs(o, "a,bb", d) +test_eq(o.a, 1) +test_eq(o.bb, "2") +``` + +------------------------------------------------------------------------ + +source + +### try_attrs + +> try_attrs (obj, *attrs) + +*Return first attr that exists in `obj`* + +``` python +test_eq(try_attrs(1, 'real'), 1) +test_eq(try_attrs(1, 'foobar', 'real'), 1) +``` + +## Attribute Delegation + +------------------------------------------------------------------------ + +source + +### GetAttrBase + +> GetAttrBase () + +*Basic delegation of +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) and +`__dir__`* + +------------------------------------------------------------------------ + +source + +#### GetAttr + +> GetAttr () + +*Inherit from this to have all attr accesses in `self._xtra` passed down +to `self.default`* + +Inherit from [`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) +to have attr access passed down to an instance attribute. This makes it +easy to create composites that don’t require callers to know about their +components. For a more detailed discussion of how this works as well as +relevant context, we suggest reading the [delegated composition section +of this blog article](https://www.fast.ai/2019/08/06/delegation/). + +You can customise the behaviour of +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) in subclasses +via; - `_default` - By default, this is set to `'default'`, so attr +access is passed down to `self.default` - `_default` can be set to the +name of any instance attribute that does not start with dunder `__` - +`_xtra` - By default, this is `None`, so all attr access is passed +down - You can limit which attrs get passed down by setting `_xtra` to a +list of attribute names + +To illuminate the utility of +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr), suppose we +have the following two classes, `_WebPage` which is a superclass of +`_ProductPage`, which we wish to compose like so: + +``` python +class _WebPage: + def __init__(self, title, author="Jeremy"): + self.title,self.author = title,author + +class _ProductPage: + def __init__(self, page, price): self.page,self.price = page,price + +page = _WebPage('Soap', author="Sylvain") +p = _ProductPage(page, 15.0) +``` + +How do we make it so we can just write `p.author`, instead of +`p.page.author` to access the `author` attribute? We can use +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr), of course! +First, we subclass +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) when defining +`_ProductPage`. Next, we set `self.default` to the object whose +attributes we want to be able to access directly, which in this case is +the `page` argument passed on initialization: + +``` python +class _ProductPage(GetAttr): + def __init__(self, page, price): self.default,self.price = page,price #self.default allows you to access page directly. + +p = _ProductPage(page, 15.0) +``` + +Now, we can access the `author` attribute directly from the instance: + +``` python +test_eq(p.author, 'Sylvain') +``` + +If you wish to store the object you are composing in an attribute other +than `self.default`, you can set the class attribute `_data` as shown +below. This is useful in the case where you might have a name collision +with `self.default`: + +``` python +class _C(GetAttr): + _default = '_data' # use different component name; `self._data` rather than `self.default` + def __init__(self,a): self._data = a + def foo(self): noop + +t = _C('Hi') +test_eq(t._data, 'Hi') +test_fail(lambda: t.default) # we no longer have self.default +test_eq(t.lower(), 'hi') +test_eq(t.upper(), 'HI') +assert 'lower' in dir(t) +assert 'upper' in dir(t) +``` + +By default, all attributes and methods of the object you are composing +are retained. In the below example, we compose a `str` object with the +class `_C`. This allows us to directly call string methods on instances +of class `_C`, such as `str.lower()` or `str.upper()`: + +``` python +class _C(GetAttr): + # allow all attributes and methods to get passed to `self.default` (by leaving _xtra=None) + def __init__(self,a): self.default = a + def foo(self): noop + +t = _C('Hi') +test_eq(t.lower(), 'hi') +test_eq(t.upper(), 'HI') +assert 'lower' in dir(t) +assert 'upper' in dir(t) +``` + +However, you can choose which attributes or methods to retain by +defining a class attribute `_xtra`, which is a list of allowed attribute +and method names to delegate. In the below example, we only delegate the +`lower` method from the composed `str` object when defining class `_C`: + +``` python +class _C(GetAttr): + _xtra = ['lower'] # specify which attributes get passed to `self.default` + def __init__(self,a): self.default = a + def foo(self): noop + +t = _C('Hi') +test_eq(t.default, 'Hi') +test_eq(t.lower(), 'hi') +test_fail(lambda: t.upper()) # upper wasn't in _xtra, so it isn't available to be called +assert 'lower' in dir(t) +assert 'upper' not in dir(t) +``` + +You must be careful to properly set an instance attribute in `__init__` +that corresponds to the class attribute `_default`. The below example +sets the class attribute `_default` to `data`, but erroneously fails to +define `self.data` (and instead defines `self.default`). + +Failing to properly set instance attributes leads to errors when you try +to access methods directly: + +``` python +class _C(GetAttr): + _default = 'data' # use a bad component name; i.e. self.data does not exist + def __init__(self,a): self.default = a + def foo(self): noop + +# TODO: should we raise an error when we create a new instance ... +t = _C('Hi') +test_eq(t.default, 'Hi') +# ... or is it enough for all GetAttr features to raise errors +test_fail(lambda: t.data) +test_fail(lambda: t.lower()) +test_fail(lambda: t.upper()) +test_fail(lambda: dir(t)) +``` + +------------------------------------------------------------------------ + +source + +### delegate_attr + +> delegate_attr (k, to) + +*Use in [`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) +to delegate to attr `to` without inheriting from +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr)* + +[`delegate_attr`](https://fastcore.fast.ai/basics.html#delegate_attr) is +a functional way to delegate attributes, and is an alternative to +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr). We recommend +reading the documentation of +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) for more +details around delegation. + +You can use achieve delegation when you define +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) by using +[`delegate_attr`](https://fastcore.fast.ai/basics.html#delegate_attr): + +``` python +class _C: + def __init__(self, o): self.o = o # self.o corresponds to the `to` argument in delegate_attr. + def __getattr__(self, k): return delegate_attr(self, k, to='o') + + +t = _C('HELLO') # delegates to a string +test_eq(t.lower(), 'hello') + +t = _C(np.array([5,4,3])) # delegates to a numpy array +test_eq(t.sum(), 12) + +t = _C(pd.DataFrame({'a': [1,2], 'b': [3,4]})) # delegates to a pandas.DataFrame +test_eq(t.b.max(), 4) +``` + +## Extensible Types + +[`ShowPrint`](https://fastcore.fast.ai/basics.html#showprint) is a base +class that defines a `show` method, which is used primarily for +callbacks in fastai that expect this method to be defined. + +[`Int`](https://fastcore.fast.ai/basics.html#int), +[`Float`](https://fastcore.fast.ai/basics.html#float), and +[`Str`](https://fastcore.fast.ai/basics.html#str) extend `int`, `float` +and `str` respectively by adding an additional `show` method by +inheriting from +[`ShowPrint`](https://fastcore.fast.ai/basics.html#showprint). + +The code for [`Int`](https://fastcore.fast.ai/basics.html#int) is shown +below: + +Examples: + +``` python +Int(0).show() +Float(2.0).show() +Str('Hello').show() +``` + + 0 + 2.0 + Hello + +## Collection functions + +Functions that manipulate popular python collections. + +------------------------------------------------------------------------ + +source + +### partition + +> partition (coll, f) + +*Partition a collection by a predicate* + +``` python +ts,fs = partition(range(10), mod(2)) +test_eq(fs, [0,2,4,6,8]) +test_eq(ts, [1,3,5,7,9]) +``` + +------------------------------------------------------------------------ + +source + +### flatten + +> flatten (o) + +*Concatenate all collections and items as a generator* + +------------------------------------------------------------------------ + +source + +### concat + +> concat (colls) + +*Concatenate all collections and items as a list* + +``` python +concat([(o for o in range(2)),[2,3,4], 5]) +``` + + [0, 1, 2, 3, 4, 5] + +``` python +concat([["abc", "xyz"], ["foo", "bar"]]) +``` + + ['abc', 'xyz', 'foo', 'bar'] + +------------------------------------------------------------------------ + +source + +### strcat + +> strcat (its, sep:str='') + +*Concatenate stringified items `its`* + +``` python +test_eq(strcat(['a',2]), 'a2') +test_eq(strcat(['a',2], ';'), 'a;2') +``` + +------------------------------------------------------------------------ + +source + +### detuplify + +> detuplify (x) + +*If `x` is a tuple with one thing, extract it* + +``` python +test_eq(detuplify(()),None) +test_eq(detuplify([1]),1) +test_eq(detuplify([1,2]), [1,2]) +test_eq(detuplify(np.array([[1,2]])), np.array([[1,2]])) +``` + +------------------------------------------------------------------------ + +source + +### replicate + +> replicate (item, match) + +*Create tuple of `item` copied `len(match)` times* + +``` python +t = [1,1] +test_eq(replicate([1,2], t),([1,2],[1,2])) +test_eq(replicate(1, t),(1,1)) +``` + +------------------------------------------------------------------------ + +source + +### setify + +> setify (o) + +*Turn any list like-object into a set.* + +``` python +# test +test_eq(setify(None),set()) +test_eq(setify('abc'),{'abc'}) +test_eq(setify([1,2,2]),{1,2}) +test_eq(setify(range(0,3)),{0,1,2}) +test_eq(setify({1,2}),{1,2}) +``` + +------------------------------------------------------------------------ + +source + +### merge + +> merge (*ds) + +*Merge all dictionaries in `ds`* + +``` python +test_eq(merge(), {}) +test_eq(merge(dict(a=1,b=2)), dict(a=1,b=2)) +test_eq(merge(dict(a=1,b=2), dict(b=3,c=4), None), dict(a=1, b=3, c=4)) +``` + +------------------------------------------------------------------------ + +source + +### range_of + +> range_of (x) + +*All indices of collection `x` (i.e. `list(range(len(x)))`)* + +``` python +test_eq(range_of([1,1,1,1]), [0,1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### groupby + +> groupby (x, key, val=) + +*Like `itertools.groupby` but doesn’t need to be sorted, and isn’t lazy, +plus some extensions* + +``` python +test_eq(groupby('aa ab bb'.split(), itemgetter(0)), {'a':['aa','ab'], 'b':['bb']}) +``` + +Here’s an example of how to *invert* a grouping, using an `int` as `key` +(which uses `itemgetter`; passing a `str` will use `attrgetter`), and +using a `val` function: + +``` python +d = {0: [1, 3, 7], 2: [3], 3: [5], 4: [8], 5: [4], 7: [5]} +groupby(((o,k) for k,v in d.items() for o in v), 0, 1) +``` + + {1: [0], 3: [0, 2], 7: [0], 5: [3, 7], 8: [4], 4: [5]} + +------------------------------------------------------------------------ + +source + +### last_index + +> last_index (x, o) + +*Finds the last index of occurence of `x` in `o` (returns -1 if no +occurence)* + +``` python +test_eq(last_index(9, [1, 2, 9, 3, 4, 9, 10]), 5) +test_eq(last_index(6, [1, 2, 9, 3, 4, 9, 10]), -1) +``` + +------------------------------------------------------------------------ + +source + +### filter_dict + +> filter_dict (d, func) + +*Filter a `dict` using `func`, applied to keys and values* + +``` python +letters = {o:chr(o) for o in range(65,73)} +letters +``` + + {65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H'} + +``` python +filter_dict(letters, lambda k,v: k<67 or v in 'FG') +``` + + {65: 'A', 66: 'B', 70: 'F', 71: 'G'} + +------------------------------------------------------------------------ + +source + +### filter_keys + +> filter_keys (d, func) + +*Filter a `dict` using `func`, applied to keys* + +``` python +filter_keys(letters, lt(67)) +``` + + {65: 'A', 66: 'B'} + +------------------------------------------------------------------------ + +source + +### filter_values + +> filter_values (d, func) + +*Filter a `dict` using `func`, applied to values* + +``` python +filter_values(letters, in_('FG')) +``` + + {70: 'F', 71: 'G'} + +------------------------------------------------------------------------ + +source + +### cycle + +> cycle (o) + +*Like `itertools.cycle` except creates list of `None`s if `o` is empty* + +``` python +test_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2]) +test_eq(itertools.islice(cycle([]),3), [None]*3) +test_eq(itertools.islice(cycle(None),3), [None]*3) +test_eq(itertools.islice(cycle(1),3), [1,1,1]) +``` + +------------------------------------------------------------------------ + +source + +### zip_cycle + +> zip_cycle (x, *args) + +*Like `itertools.zip_longest` but +[`cycle`](https://fastcore.fast.ai/foundation.html#cycle)s through +elements of all but first argument* + +``` python +test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')]) +``` + +------------------------------------------------------------------------ + +source + +### sorted_ex + +> sorted_ex (iterable, key=None, reverse=False) + +*Like `sorted`, but if key is str use `attrgetter`; if int use +`itemgetter`* + +------------------------------------------------------------------------ + +source + +### not\_ + +> not_ (f) + +*Create new function that negates result of `f`* + +``` python +def f(a): return a>0 +test_eq(f(1),True) +test_eq(not_(f)(1),False) +test_eq(not_(f)(a=-1),True) +``` + +------------------------------------------------------------------------ + +source + +### argwhere + +> argwhere (iterable, f, negate=False, **kwargs) + +*Like [`filter_ex`](https://fastcore.fast.ai/basics.html#filter_ex), but +return indices for matching items* + +------------------------------------------------------------------------ + +source + +### filter_ex + +> filter_ex (iterable, f=, negate=False, gen=False, +> **kwargs) + +*Like `filter`, but passing `kwargs` to `f`, defaulting `f` to `noop`, +and adding `negate` and +[`gen`](https://fastcore.fast.ai/basics.html#gen)* + +------------------------------------------------------------------------ + +source + +### range_of + +> range_of (a, b=None, step=None) + +*All indices of collection `a`, if `a` is a collection, otherwise +`range`* + +``` python +test_eq(range_of([1,1,1,1]), [0,1,2,3]) +test_eq(range_of(4), [0,1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### renumerate + +> renumerate (iterable, start=0) + +*Same as `enumerate`, but returns index as 2nd element instead of 1st* + +``` python +test_eq(renumerate('abc'), (('a',0),('b',1),('c',2))) +``` + +------------------------------------------------------------------------ + +source + +### first + +> first (x, f=None, negate=False, **kwargs) + +*First element of `x`, optionally filtered by `f`, or None if missing* + +``` python +test_eq(first(['a', 'b', 'c', 'd', 'e']), 'a') +test_eq(first([False]), False) +test_eq(first([False], noop), None) +``` + +------------------------------------------------------------------------ + +source + +### only + +> only (o) + +*Return the only item of `o`, raise if `o` doesn’t have exactly one +item* + +------------------------------------------------------------------------ + +source + +### nested_attr + +> nested_attr (o, attr, default=None) + +*Same as `getattr`, but if `attr` includes a `.`, then looks inside +nested objects* + +``` python +class CustomIndexable: + def __init__(self): self.data = {'a':1,'b':'v','c':{'d':5}} + def __getitem__(self, key): return self.data[key] + +custom_indexable = CustomIndexable() +test_eq(nested_attr(custom_indexable,'a'),1) +test_eq(nested_attr(custom_indexable,'c.d'),5) +test_eq(nested_attr(custom_indexable,'e'),None) +``` + +class TestObj: def **init**(self): self.nested = {‘key’: \[1, 2, +{‘inner’: ‘value’}\]} test_obj = TestObj() + +test_eq(nested_attr(test_obj, ‘nested.key.2.inner’),‘value’) +test_eq(nested_attr(\[1, 2, 3\], ‘1’),2) + +``` python +b = {'a':1,'b':'v','c':{'d':5}} +test_eq(nested_attr(b,'b'),'v') +test_eq(nested_attr(b,'c.d'),5) +``` + +``` python +a = SimpleNamespace(b=(SimpleNamespace(c=1))) +test_eq(nested_attr(a, 'b.c'), getattr(getattr(a, 'b'), 'c')) +test_eq(nested_attr(a, 'b.d'), None) +test_eq(nested_attr(b, 'a'), 1) +``` + +------------------------------------------------------------------------ + +source + +### nested_setdefault + +> nested_setdefault (o, attr, default) + +*Same as `setdefault`, but if `attr` includes a `.`, then looks inside +nested objects* + +------------------------------------------------------------------------ + +source + +### nested_callable + +> nested_callable (o, attr) + +*Same as +[`nested_attr`](https://fastcore.fast.ai/basics.html#nested_attr) but if +not found will return `noop`* + +``` python +a = SimpleNamespace(b=(SimpleNamespace(c=1))) +test_eq(nested_callable(a, 'b.c'), getattr(getattr(a, 'b'), 'c')) +test_eq(nested_callable(a, 'b.d'), noop) +``` + +------------------------------------------------------------------------ + +source + +### nested_idx + +> nested_idx (coll, *idxs) + +*Index into nested collections, dicts, etc, with `idxs`* + +``` python +a = {'b':[1,{'c':2}]} +test_eq(nested_idx(a, 'nope'), None) +test_eq(nested_idx(a, 'nope', 'nup'), None) +test_eq(nested_idx(a, 'b', 3), None) +test_eq(nested_idx(a), a) +test_eq(nested_idx(a, 'b'), [1,{'c':2}]) +test_eq(nested_idx(a, 'b', 1), {'c':2}) +test_eq(nested_idx(a, 'b', 1, 'c'), 2) +``` + +``` python +a = SimpleNamespace(b=[1,{'c':2}]) +test_eq(nested_idx(a, 'nope'), None) +test_eq(nested_idx(a, 'nope', 'nup'), None) +test_eq(nested_idx(a, 'b', 3), None) +test_eq(nested_idx(a), a) +test_eq(nested_idx(a, 'b'), [1,{'c':2}]) +test_eq(nested_idx(a, 'b', 1), {'c':2}) +test_eq(nested_idx(a, 'b', 1, 'c'), 2) +``` + +------------------------------------------------------------------------ + +source + +### set_nested_idx + +> set_nested_idx (coll, value, *idxs) + +*Set value indexed like \`nested_idx* + +``` python +set_nested_idx(a, 3, 'b', 0) +test_eq(nested_idx(a, 'b', 0), 3) +``` + +------------------------------------------------------------------------ + +source + +### val2idx + +> val2idx (x) + +*Dict from value to index* + +``` python +test_eq(val2idx([1,2,3]), {3:2,1:0,2:1}) +``` + +------------------------------------------------------------------------ + +source + +### uniqueify + +> uniqueify (x, sort=False, bidir=False, start=None) + +*Unique elements in `x`, optional `sort`, optional return reverse +correspondence, optional prepend with elements.* + +``` python +t = [1,1,0,5,0,3] +test_eq(uniqueify(t),[1,0,5,3]) +test_eq(uniqueify(t, sort=True),[0,1,3,5]) +test_eq(uniqueify(t, start=[7,8,6]), [7,8,6,1,0,5,3]) +v,o = uniqueify(t, bidir=True) +test_eq(v,[1,0,5,3]) +test_eq(o,{1:0, 0: 1, 5: 2, 3: 3}) +v,o = uniqueify(t, sort=True, bidir=True) +test_eq(v,[0,1,3,5]) +test_eq(o,{0:0, 1: 1, 3: 2, 5: 3}) +``` + +------------------------------------------------------------------------ + +source + +### loop_first_last + +> loop_first_last (values) + +*Iterate and generate a tuple with a flag for first and last value.* + +``` python +test_eq(loop_first_last(range(3)), [(True,False,0), (False,False,1), (False,True,2)]) +``` + +------------------------------------------------------------------------ + +source + +### loop_first + +> loop_first (values) + +*Iterate and generate a tuple with a flag for first value.* + +``` python +test_eq(loop_first(range(3)), [(True,0), (False,1), (False,2)]) +``` + +------------------------------------------------------------------------ + +source + +### loop_last + +> loop_last (values) + +*Iterate and generate a tuple with a flag for last value.* + +``` python +test_eq(loop_last(range(3)), [(False,0), (False,1), (True,2)]) +``` + +------------------------------------------------------------------------ + +source + +### first_match + +> first_match (lst, f, default=None) + +*First element of `lst` matching predicate `f`, or `default` if none* + +``` python +a = [0,2,4,5,6,7,10] +test_eq(first_match(a, lambda o:o%2), 3) +``` + +------------------------------------------------------------------------ + +source + +### last_match + +> last_match (lst, f, default=None) + +*Last element of `lst` matching predicate `f`, or `default` if none* + +``` python +test_eq(last_match(a, lambda o:o%2), 5) +``` + +## fastuple + +A tuple with extended functionality. + +------------------------------------------------------------------------ + +source + +#### fastuple + +> fastuple (x=None, *rest) + +*A `tuple` with elementwise ops and more friendly **init** behavior* + +#### Friendly init behavior + +Common failure modes when trying to initialize a tuple in python: + +``` py +tuple(3) +> TypeError: 'int' object is not iterable +``` + +or + +``` py +tuple(3, 4) +> TypeError: tuple expected at most 1 arguments, got 2 +``` + +However, [`fastuple`](https://fastcore.fast.ai/basics.html#fastuple) +allows you to define tuples like this and in the usual way: + +``` python +test_eq(fastuple(3), (3,)) +test_eq(fastuple(3,4), (3, 4)) +test_eq(fastuple((3,4)), (3, 4)) +``` + +#### Elementwise operations + +------------------------------------------------------------------------ + +source + +##### fastuple.add + +> fastuple.add (*args) + +*`+` is already defined in `tuple` for concat, so use `add` instead* + +``` python +test_eq(fastuple.add((1,1),(2,2)), (3,3)) +test_eq_type(fastuple(1,1).add(2), fastuple(3,3)) +test_eq(fastuple('1','2').add('2'), fastuple('12','22')) +``` + +------------------------------------------------------------------------ + +source + +##### fastuple.mul + +> fastuple.mul (*args) + +*`*` is already defined in `tuple` for replicating, so use `mul` +instead* + +``` python +test_eq_type(fastuple(1,1).mul(2), fastuple(2,2)) +``` + +#### Other Elementwise Operations + +Additionally, the following elementwise operations are available: - +`le`: less than or equal - `eq`: equal - `gt`: greater than - `min`: +minimum of + +``` python +test_eq(fastuple(3,1).le(1), (False, True)) +test_eq(fastuple(3,1).eq(1), (False, True)) +test_eq(fastuple(3,1).gt(1), (True, False)) +test_eq(fastuple(3,1).min(2), (2,1)) +``` + +You can also do other elementwise operations like negate a +[`fastuple`](https://fastcore.fast.ai/basics.html#fastuple), or subtract +two [`fastuple`](https://fastcore.fast.ai/basics.html#fastuple)s: + +``` python +test_eq(-fastuple(1,2), (-1,-2)) +test_eq(~fastuple(1,0,1), (False,True,False)) + +test_eq(fastuple(1,1)-fastuple(2,2), (-1,-1)) +``` + +``` python +test_eq(type(fastuple(1)), fastuple) +test_eq_type(fastuple(1,2), fastuple(1,2)) +test_ne(fastuple(1,2), fastuple(1,3)) +test_eq(fastuple(), ()) +``` + +## Functions on Functions + +Utilities for functional programming or for defining, modifying, or +debugging functions. + +------------------------------------------------------------------------ + +source + +### bind + +> bind (func, *pargs, **pkwargs) + +*Same as `partial`, except you can use `arg0` `arg1` etc param +placeholders* + +[`bind`](https://fastcore.fast.ai/basics.html#bind) is the same as +`partial`, but also allows you to reorder positional arguments using +variable name(s) `arg{i}` where i refers to the zero-indexed positional +argument. [`bind`](https://fastcore.fast.ai/basics.html#bind) as +implemented currently only supports reordering of up to the first 5 +positional arguments. + +Consider the function `myfunc` below, which has 3 positional arguments. +These arguments can be referenced as `arg0`, `arg1`, and `arg1`, +respectively. + +``` python +def myfn(a,b,c,d=1,e=2): return(a,b,c,d,e) +``` + +In the below example we bind the positional arguments of `myfn` as +follows: + +- The second input `14`, referenced by `arg1`, is substituted for the + first positional argument. +- We supply a default value of `17` for the second positional argument. +- The first input `19`, referenced by `arg0`, is subsituted for the + third positional argument. + +``` python +test_eq(bind(myfn, arg1, 17, arg0, e=3)(19,14), (14,17,19,1,3)) +``` + +In this next example: + +- We set the default value to `17` for the first positional argument. +- The first input `19` refrenced by `arg0`, becomes the second + positional argument. +- The second input `14` becomes the third positional argument. +- We override the default the value for named argument `e` to `3`. + +``` python +test_eq(bind(myfn, 17, arg0, e=3)(19,14), (17,19,14,1,3)) +``` + +This is an example of using +[`bind`](https://fastcore.fast.ai/basics.html#bind) like `partial` and +do not reorder any arguments: + +``` python +test_eq(bind(myfn)(17,19,14), (17,19,14,1,2)) +``` + +[`bind`](https://fastcore.fast.ai/basics.html#bind) can also be used to +change default values. In the below example, we use the first input `3` +to override the default value of the named argument `e`, and supply +default values for the first three positional arguments: + +``` python +test_eq(bind(myfn, 17,19,14,e=arg0)(3), (17,19,14,1,3)) +``` + +------------------------------------------------------------------------ + +source + +### mapt + +> mapt (func, *iterables) + +*Tuplified `map`* + +``` python +t = [0,1,2,3] +test_eq(mapt(operator.neg, t), (0,-1,-2,-3)) +``` + +------------------------------------------------------------------------ + +source + +### map_ex + +> map_ex (iterable, f, *args, gen=False, **kwargs) + +*Like `map`, but use +[`bind`](https://fastcore.fast.ai/basics.html#bind), and supports `str` +and indexing* + +``` python +test_eq(map_ex(t,operator.neg), [0,-1,-2,-3]) +``` + +If `f` is a string then it is treated as a format string to create the +mapping: + +``` python +test_eq(map_ex(t, '#{}#'), ['#0#','#1#','#2#','#3#']) +``` + +If `f` is a dictionary (or anything supporting `__getitem__`) then it is +indexed to create the mapping: + +``` python +test_eq(map_ex(t, list('abcd')), list('abcd')) +``` + +You can also pass the same `arg` params that +[`bind`](https://fastcore.fast.ai/basics.html#bind) accepts: + +``` python +def f(a=None,b=None): return b +test_eq(map_ex(t, f, b=arg0), range(4)) +``` + +------------------------------------------------------------------------ + +source + +### compose + +> compose (*funcs, order=None) + +*Create a function that composes all functions in `funcs`, passing along +remaining `*args` and `**kwargs` to all* + +``` python +f1 = lambda o,p=0: (o*2)+p +f2 = lambda o,p=1: (o+1)/p +test_eq(f2(f1(3)), compose(f1,f2)(3)) +test_eq(f2(f1(3,p=3),p=3), compose(f1,f2)(3,p=3)) +test_eq(f2(f1(3, 3), 3), compose(f1,f2)(3, 3)) + +f1.order = 1 +test_eq(f1(f2(3)), compose(f1,f2, order="order")(3)) +``` + +------------------------------------------------------------------------ + +source + +### maps + +> maps (*args, retain=) + +*Like `map`, except funcs are composed first* + +``` python +test_eq(maps([1]), [1]) +test_eq(maps(operator.neg, [1,2]), [-1,-2]) +test_eq(maps(operator.neg, operator.neg, [1,2]), [1,2]) +``` + +------------------------------------------------------------------------ + +source + +### partialler + +> partialler (f, *args, order=None, **kwargs) + +*Like `functools.partial` but also copies over docstring* + +``` python +def _f(x,a=1): + "test func" + return x-a +_f.order=1 + +f = partialler(_f, 2) +test_eq(f.order, 1) +test_eq(f(3), -1) +f = partialler(_f, a=2, order=3) +test_eq(f.__doc__, "test func") +test_eq(f.order, 3) +test_eq(f(3), _f(3,2)) +``` + +``` python +class partial0: + "Like `partialler`, but args passed to callable are inserted at started, instead of at end" + def __init__(self, f, *args, order=None, **kwargs): + self.f,self.args,self.kwargs = f,args,kwargs + self.order = ifnone(order, getattr(f,'order',None)) + self.__doc__ = f.__doc__ + + def __call__(self, *args, **kwargs): return self.f(*args, *self.args, **kwargs, **self.kwargs) +``` + +``` python +f = partial0(_f, 2) +test_eq(f.order, 1) +test_eq(f(3), 1) # NB: different to `partialler` example +``` + +------------------------------------------------------------------------ + +source + +### instantiate + +> instantiate (t) + +*Instantiate `t` if it’s a type, otherwise do nothing* + +``` python +test_eq_type(instantiate(int), 0) +test_eq_type(instantiate(1), 1) +``` + +------------------------------------------------------------------------ + +source + +### using_attr + +> using_attr (f, attr) + +*Construct a function which applies `f` to the argument’s attribute +`attr`* + +``` python +t = Path('/a/b.txt') +f = using_attr(str.upper, 'name') +test_eq(f(t), 'B.TXT') +``` + +### Self (with an *uppercase* S) + +A Concise Way To Create Lambdas + +This is a concise way to create lambdas that are calling methods on an +object (note the capitalization!) + +`Self.sum()`, for instance, is a shortcut for `lambda o: o.sum()`. + +``` python +f = Self.sum() +x = np.array([3.,1]) +test_eq(f(x), 4.) + +# This is equivalent to above +f = lambda o: o.sum() +x = np.array([3.,1]) +test_eq(f(x), 4.) + +f = Self.argmin() +arr = np.array([1,2,3,4,5]) +test_eq(f(arr), arr.argmin()) + +f = Self.sum().is_integer() +x = np.array([3.,1]) +test_eq(f(x), True) + +f = Self.sum().real.is_integer() +x = np.array([3.,1]) +test_eq(f(x), True) + +f = Self.imag() +test_eq(f(3), 0) + +f = Self[1] +test_eq(f(x), 1) +``` + +`Self` is also callable, which creates a function which calls any +function passed to it, using the arguments passed to `Self`: + +``` python +def f(a, b=3): return a+b+2 +def g(a, b=3): return a*b +fg = Self(1,b=2) +list(map(fg, [f,g])) +``` + + [5, 2] + +## Patching + +------------------------------------------------------------------------ + +source + +### copy_func + +> copy_func (f) + +*Copy a non-builtin function (NB `copy.copy` does not work for this)* + +Sometimes it may be desirable to make a copy of a function that doesn’t +point to the original object. When you use Python’s built in `copy.copy` +or `copy.deepcopy` to copy a function, you get a reference to the +original object: + +``` python +import copy as cp +``` + +``` python +def foo(): pass +a = cp.copy(foo) +b = cp.deepcopy(foo) + +a.someattr = 'hello' # since a and b point at the same object, updating a will update b +test_eq(b.someattr, 'hello') + +assert a is foo and b is foo +``` + +However, with +[`copy_func`](https://fastcore.fast.ai/basics.html#copy_func), you can +retrieve a copy of a function without a reference to the original +object: + +``` python +c = copy_func(foo) # c is an indpendent object +assert c is not foo +``` + +``` python +def g(x, *, y=3): return x+y +test_eq(copy_func(g)(4), 7) +``` + +------------------------------------------------------------------------ + +source + +### patch_to + +> patch_to (cls, as_prop=False, cls_method=False) + +*Decorator: add `f` to `cls`* + +The `@patch_to` decorator allows you to [monkey +patch](https://stackoverflow.com/questions/5626193/what-is-monkey-patching) +a function into a class as a method: + +``` python +class _T3(int): pass + +@patch_to(_T3) +def func1(self, a): return self+a + +t = _T3(1) # we initialized `t` to a type int = 1 +test_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3 +``` + +You can access instance properties in the usual way via `self`: + +``` python +class _T4(): + def __init__(self, g): self.g = g + +@patch_to(_T4) +def greet(self, x): return self.g + x + +t = _T4('hello ') # this sets self.g = 'hello ' +test_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello ' +``` + +You can instead specify that the method should be a class method by +setting `cls_method=True`: + +``` python +class _T5(int): attr = 3 # attr is a class attribute we will access in a later method + +@patch_to(_T5, cls_method=True) +def func(cls, x): return cls.attr + x # you can access class attributes in the normal way + +test_eq(_T5.func(4), 7) +``` + +Additionally you can specify that the function you want to patch should +be a class attribute with `as_prop=True`: + +``` python +@patch_to(_T5, as_prop=True) +def add_ten(self): return self + 10 + +t = _T5(4) +test_eq(t.add_ten, 14) +``` + +Instead of passing one class to the `@patch_to` decorator, you can pass +multiple classes in a tuple to simulteanously patch more than one class +with the same method: + +``` python +class _T6(int): pass +class _T7(int): pass + +@patch_to((_T6,_T7)) +def func_mult(self, a): return self*a + +t = _T6(2) +test_eq(t.func_mult(4), 8) +t = _T7(2) +test_eq(t.func_mult(4), 8) +``` + +------------------------------------------------------------------------ + +source + +### patch + +> patch (f=None, as_prop=False, cls_method=False) + +*Decorator: add `f` to the first parameter’s class (based on f’s type +annotations)* + +`@patch` is an alternative to `@patch_to` that allows you similarly +monkey patch class(es) by using [type +annotations](https://docs.python.org/3/library/typing.html): + +``` python +class _T8(int): pass + +@patch +def func(self:_T8, a): return self+a + +t = _T8(1) # we initilized `t` to a type int = 1 +test_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4 +test_eq(t.func.__qualname__, '_T8.func') +``` + +Similarly to +[`patch_to`](https://fastcore.fast.ai/basics.html#patch_to), you can +supply a union of classes instead of a single class in your type +annotations to patch multiple classes: + +``` python +class _T9(int): pass + +@patch +def func2(x:_T8|_T9, a): return x*a # will patch both _T8 and _T9 + +t = _T8(2) +test_eq(t.func2(4), 8) +test_eq(t.func2.__qualname__, '_T8.func2') + +t = _T9(2) +test_eq(t.func2(4), 8) +test_eq(t.func2.__qualname__, '_T9.func2') +``` + +Just like [`patch_to`](https://fastcore.fast.ai/basics.html#patch_to) +decorator you can use `as_prop` and `cls_method` parameters with +[`patch`](https://fastcore.fast.ai/basics.html#patch) decorator: + +``` python +@patch(as_prop=True) +def add_ten(self:_T5): return self + 10 + +t = _T5(4) +test_eq(t.add_ten, 14) +``` + +``` python +class _T5(int): attr = 3 # attr is a class attribute we will access in a later method + +@patch(cls_method=True) +def func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way + +test_eq(_T5.func(4), 7) +``` + +------------------------------------------------------------------------ + +source + +### patch_property + +> patch_property (f) + +*Deprecated; use `patch(as_prop=True)` instead* + +Patching `classmethod` shouldn’t affect how python’s inheritance works + +``` python +class FastParent: pass + +@patch(cls_method=True) +def type_cls(cls: FastParent): return cls + +class FastChild(FastParent): pass + +parent = FastParent() +test_eq(parent.type_cls(), FastParent) + +child = FastChild() +test_eq(child.type_cls(), FastChild) +``` + +## Other Helpers + +------------------------------------------------------------------------ + +source + +### compile_re + +> compile_re (pat) + +*Compile `pat` if it’s not None* + +``` python +assert compile_re(None) is None +assert compile_re('a').match('ab') +``` + +------------------------------------------------------------------------ + +source + +#### ImportEnum + +> ImportEnum (value, names=None, module=None, qualname=None, type=None, +> start=1) + +*An `Enum` that can have its values imported* + +``` python +_T = ImportEnum('_T', {'foobar':1, 'goobar':2}) +_T.imports() +test_eq(foobar, _T.foobar) +test_eq(goobar, _T.goobar) +``` + +------------------------------------------------------------------------ + +source + +#### StrEnum + +> StrEnum (value, names=None, module=None, qualname=None, type=None, +> start=1) + +*An [`ImportEnum`](https://fastcore.fast.ai/basics.html#importenum) that +behaves like a `str`* + +------------------------------------------------------------------------ + +source + +### str_enum + +> str_enum (name, *vals) + +*Simplified creation of +[`StrEnum`](https://fastcore.fast.ai/basics.html#strenum) types* + +------------------------------------------------------------------------ + +source + +#### ValEnum + +> ValEnum (value, names=None, module=None, qualname=None, type=None, +> start=1) + +*An [`ImportEnum`](https://fastcore.fast.ai/basics.html#importenum) that +stringifies using values* + +``` python +_T = str_enum('_T', 'a', 'b') +test_eq(f'{_T.a}', 'a') +test_eq(_T.a, 'a') +test_eq(list(_T.__members__), ['a','b']) +print(_T.a, _T.a.upper()) +``` + + a A + +------------------------------------------------------------------------ + +source + +#### Stateful + +> Stateful (*args, **kwargs) + +*A base class/mixin for objects that should not serialize all their +state* + +``` python +class _T(Stateful): + def __init__(self): + super().__init__() + self.a=1 + self._state['test']=2 + +t = _T() +t2 = pickle.loads(pickle.dumps(t)) +test_eq(t.a,1) +test_eq(t._state['test'],2) +test_eq(t2.a,1) +test_eq(t2._state,{}) +``` + +Override `_init_state` to do any necessary setup steps that are required +during `__init__` or during deserialization (e.g. `pickle.load`). Here’s +an example of how +[`Stateful`](https://fastcore.fast.ai/basics.html#stateful) simplifies +the official Python example for [Handling Stateful +Objects](https://docs.python.org/3/library/pickle.html#handling-stateful-objects). + +``` python +class TextReader(Stateful): + """Print and number lines in a text file.""" + _stateattrs=('file',) + def __init__(self, filename): + self.filename,self.lineno = filename,0 + super().__init__() + + def readline(self): + self.lineno += 1 + line = self.file.readline() + if line: return f"{self.lineno}: {line.strip()}" + + def _init_state(self): + self.file = open(self.filename) + for _ in range(self.lineno): self.file.readline() +``` + +``` python +reader = TextReader("00_test.ipynb") +print(reader.readline()) +print(reader.readline()) + +new_reader = pickle.loads(pickle.dumps(reader)) +print(reader.readline()) +``` + + 1: { + 2: "cells": [ + 3: { + +------------------------------------------------------------------------ + +source + +### NotStr + +> NotStr (s) + +*Behaves like a `str`, but isn’t an instance of one* + +``` python +s = NotStr("hello") +assert not isinstance(s, str) +test_eq(s, 'hello') +test_eq(s*2, 'hellohello') +test_eq(len(s), 5) +``` + +------------------------------------------------------------------------ + +source + +#### PrettyString + +*Little hack to get strings to show properly in Jupyter.* + +Allow strings with special characters to render properly in Jupyter. +Without calling `print()` strings with special characters are displayed +like so: + +``` python +with_special_chars='a string\nwith\nnew\nlines and\ttabs' +with_special_chars +``` + + 'a string\nwith\nnew\nlines and\ttabs' + +We can correct this with +[`PrettyString`](https://fastcore.fast.ai/basics.html#prettystring): + +``` python +PrettyString(with_special_chars) +``` + + a string + with + new + lines and tabs + +------------------------------------------------------------------------ + +source + +### even_mults + +> even_mults (start, stop, n) + +*Build log-stepped array from `start` to +[`stop`](https://fastcore.fast.ai/basics.html#stop) in `n` steps.* + +``` python +test_eq(even_mults(2,8,3), [2,4,8]) +test_eq(even_mults(2,32,5), [2,4,8,16,32]) +test_eq(even_mults(2,8,1), 8) +``` + +------------------------------------------------------------------------ + +source + +### num_cpus + +> num_cpus () + +*Get number of cpus* + +``` python +num_cpus() +``` + + 10 + +------------------------------------------------------------------------ + +source + +### add_props + +> add_props (f, g=None, n=2) + +*Create properties passing each of `range(n)` to f* + +``` python +class _T(): a,b = add_props(lambda i,x:i*2) + +t = _T() +test_eq(t.a,0) +test_eq(t.b,2) +``` + +``` python +class _T(): + def __init__(self, v): self.v=v + def _set(i, self, v): self.v[i] = v + a,b = add_props(lambda i,x: x.v[i], _set) + +t = _T([0,2]) +test_eq(t.a,0) +test_eq(t.b,2) +t.a = t.a+1 +t.b = 3 +test_eq(t.a,1) +test_eq(t.b,3) +``` + +------------------------------------------------------------------------ + +source + +### str2bool + +> str2bool (s) + +*Case-insensitive convert string `s` too a bool +(`y`,`yes`,`t`,[`true`](https://fastcore.fast.ai/basics.html#true),`on`,`1`-\>`True`)* + +True values are ‘y’, ‘yes’, ‘t’, ‘true’, ‘on’, and ‘1’; false values are +‘n’, ‘no’, ‘f’, ‘false’, ‘off’, and ‘0’. Raises `ValueError` if ‘val’ is +anything else. + +``` python +for o in "y YES t True on 1".split(): assert str2bool(o) +for o in "n no FALSE off 0".split(): assert not str2bool(o) +for o in 0,None,'',False: assert not str2bool(o) +for o in 1,True: assert str2bool(o) +``` + +------------------------------------------------------------------------ + +source + +### str2int + +> str2int (s) + +*Convert `s` to an `int`* + +------------------------------------------------------------------------ + +source + +### str2float + +> str2float (s:str) + +*Convert `s` to a float* + +------------------------------------------------------------------------ + +source + +### str2list + +> str2list (s:str) + +*Convert `s` to a list* + +------------------------------------------------------------------------ + +source + +### str2date + +> str2date (s:str) + +*`date.fromisoformat` with empty string handling* + +------------------------------------------------------------------------ + +source + +### to_date + +> to_date (arg) + +------------------------------------------------------------------------ + +source + +### to_list + +> to_list (arg) + +------------------------------------------------------------------------ + +source + +### to_float + +> to_float (arg) + +------------------------------------------------------------------------ + +source + +### to_int + +> to_int (arg) + +------------------------------------------------------------------------ + +source + +### to_bool + +> to_bool (arg) + +------------------------------------------------------------------------ + +source + +### typed + +> typed (_func=None, cast=False) + +*Decorator to check param and return types at runtime, with optional +casting* + +[`typed`](https://fastcore.fast.ai/basics.html#typed) validates argument +types at **runtime**. This is in contrast to +[MyPy](http://mypy-lang.org/) which only offers static type checking. + +For example, a `TypeError` will be raised if we try to pass an integer +into the first argument of the below function: + +``` python +@typed +def discount(price:int, pct:float) -> float: + return (1-pct) * price + +with ExceptionExpected(TypeError): discount(100.0, .1) +``` + +You can have automatic casting based on heuristics by specifying +`typed(cast=True)`. If casting is not possible, a `TypeError` is raised. + +``` python +@typed(cast=True) +def discount(price:int, pct:float) -> float: + return (1-pct) * price + +assert 90.0 == discount(100.5, .1) # will auto cast 100.5 to the int 100 +assert 90.0 == discount(' 100 ', .1) # will auto cast the str "100" to the int 100 +with ExceptionExpected(TypeError): discount("a", .1) +``` + +We can also optionally allow multiple types by enumarating the types in +a tuple as illustrated below: + +``` python +@typed +def discount(price:int|float, pct:float): + return (1-pct) * price + +assert 90.0 == discount(100.0, .1) + +@typed(cast=True) +def discount(price:int|None, pct:float): + return (1-pct) * price + +assert 90.0 == discount(100.0, .1) +``` + +We currently do not support union types when casting. + +``` python +@typed(cast=True) +def discount(price:int|float, pct:float): + return (1-pct) * price + +with ExceptionExpected(AssertionError): assert 90.0 == discount("100.0", .1) +``` + +[`typed`](https://fastcore.fast.ai/basics.html#typed) works with +classes, too: + +``` python +class Foo: + @typed + def __init__(self, a:int, b: int, c:str): pass + @typed(cast=True) + def test(cls, d:str): return d + +with ExceptionExpected(TypeError): Foo(1, 2, 3) +assert isinstance(Foo(1,2, 'a string').test(10), str) +``` + +It also works with custom types. + +``` python +@typed +def test_foo(foo: Foo): pass + +with ExceptionExpected(TypeError): test_foo(1) +test_foo(Foo(1, 2, 'a string')) +``` + +``` python +class Bar: + @typed + def __init__(self, a:int): self.a = a +@typed(cast=True) +def test_bar(bar: Bar): return bar + +assert isinstance(test_bar(1), Bar) +test_eq(test_bar(1).a, 1) +with ExceptionExpected(TypeError): test_bar("foobar") +``` + +------------------------------------------------------------------------ + +source + +### exec_new + +> exec_new (code) + +*Execute `code` in a new environment and return it* + +``` python +g = exec_new('a=1') +test_eq(g['a'], 1) +``` + +------------------------------------------------------------------------ + +source + +### exec_import + +> exec_import (mod, sym) + +*Import `sym` from `mod` in a new environment* + +## Notebook functions + +------------------------------------------------------------------------ + +### ipython_shell + +> ipython_shell () + +*Same as `get_ipython` but returns `False` if not in IPython* + +------------------------------------------------------------------------ + +### in_ipython + +> in_ipython () + +*Check if code is running in some kind of IPython environment* + +------------------------------------------------------------------------ + +### in_colab + +> in_colab () + +*Check if the code is running in Google Colaboratory* + +------------------------------------------------------------------------ + +### in_jupyter + +> in_jupyter () + +*Check if the code is running in a jupyter notebook* + +------------------------------------------------------------------------ + +### in_notebook + +> in_notebook () + +*Check if the code is running in a jupyter notebook* + +These variables are available as booleans in `fastcore.basics` as +`IN_IPYTHON`, `IN_JUPYTER`, `IN_COLAB` and `IN_NOTEBOOK`. + +``` python +IN_IPYTHON, IN_JUPYTER, IN_COLAB, IN_NOTEBOOK +``` + + (True, True, False, True)# Foundation + + + +## Foundational Functions + +------------------------------------------------------------------------ + +source + +### working_directory + +> working_directory (path) + +*Change working directory to `path` and return to previous on exit.* + +------------------------------------------------------------------------ + +source + +### add_docs + +> add_docs (cls, cls_doc=None, **docs) + +*Copy values from +[`docs`](https://fastcore.fast.ai/foundation.html#docs) to `cls` +docstrings, and confirm all public methods are documented* + +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs) allows +you to add docstrings to a class and its associated methods. This +function allows you to group docstrings together seperate from your +code, which enables you to define one-line functions as well as organize +your code more succintly. We believe this confers a number of benefits +which we discuss in [our style +guide](https://docs.fast.ai/dev/style.html). + +Suppose you have the following undocumented class: + +``` python +class T: + def foo(self): pass + def bar(self): pass +``` + +You can add documentation to this class like so: + +``` python +add_docs(T, cls_doc="A docstring for the class.", + foo="The foo method.", + bar="The bar method.") +``` + +Now, docstrings will appear as expected: + +``` python +test_eq(T.__doc__, "A docstring for the class.") +test_eq(T.foo.__doc__, "The foo method.") +test_eq(T.bar.__doc__, "The bar method.") +``` + +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs) also +validates that all of your public methods contain a docstring. If one of +your methods is not documented, it will raise an error: + +``` python +class T: + def foo(self): pass + def bar(self): pass + +f=lambda: add_docs(T, "A docstring for the class.", foo="The foo method.") +test_fail(f, contains="Missing docs") +``` + +------------------------------------------------------------------------ + +source + +### docs + +> docs (cls) + +*Decorator version of +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs), using +`_docs` dict* + +Instead of using +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs), you can +use the decorator +[`docs`](https://fastcore.fast.ai/foundation.html#docs) as shown below. +Note that the docstring for the class can be set with the argument +`cls_doc`: + +``` python +@docs +class _T: + def f(self): pass + def g(cls): pass + + _docs = dict(cls_doc="The class docstring", + f="The docstring for method f.", + g="A different docstring for method g.") + + +test_eq(_T.__doc__, "The class docstring") +test_eq(_T.f.__doc__, "The docstring for method f.") +test_eq(_T.g.__doc__, "A different docstring for method g.") +``` + +For either the [`docs`](https://fastcore.fast.ai/foundation.html#docs) +decorator or the +[`add_docs`](https://fastcore.fast.ai/foundation.html#add_docs) +function, you can still define your docstrings in the normal way. Below +we set the docstring for the class as usual, but define the method +docstrings through the `_docs` attribute: + +``` python +@docs +class _T: + "The class docstring" + def f(self): pass + _docs = dict(f="The docstring for method f.") + + +test_eq(_T.__doc__, "The class docstring") +test_eq(_T.f.__doc__, "The docstring for method f.") +``` + +------------------------------------------------------------------------ + +### is_iter + +> is_iter (o) + +*Test whether `o` can be used in a `for` loop* + +``` python +assert is_iter([1]) +assert not is_iter(array(1)) +assert is_iter(array([1,2])) +assert (o for o in range(3)) +``` + +------------------------------------------------------------------------ + +source + +### coll_repr + +> coll_repr (c, max_n=10) + +*String repr of up to `max_n` items of (possibly lazy) collection `c`* + +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) is +used to provide a more informative +[`__repr__`](https://stackoverflow.com/questions/1984162/purpose-of-pythons-repr) +about list-like objects. +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) and is +used by [`L`](https://fastcore.fast.ai/foundation.html#l) to build a +`__repr__` that displays the length of a list in addition to a preview +of a list. + +Below is an example of the `__repr__` string created for a list of 1000 +elements: + +``` python +test_eq(coll_repr(range(1000)), '(#1000) [0,1,2,3,4,5,6,7,8,9...]') +test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]') +test_eq(coll_repr(range(10), 5), '(#10) [0,1,2,3,4...]') +test_eq(coll_repr(range(5), 5), '(#5) [0,1,2,3,4]') +``` + +We can set the option `max_n` to optionally preview a specified number +of items instead of the default: + +``` python +test_eq(coll_repr(range(1000), max_n=5), '(#1000) [0,1,2,3,4...]') +``` + +------------------------------------------------------------------------ + +source + +### is_bool + +> is_bool (x) + +*Check whether `x` is a bool or None* + +------------------------------------------------------------------------ + +source + +### mask2idxs + +> mask2idxs (mask) + +*Convert bool mask or index list to index +[`L`](https://fastcore.fast.ai/foundation.html#l)* + +``` python +test_eq(mask2idxs([False,True,False,True]), [1,3]) +test_eq(mask2idxs(array([False,True,False,True])), [1,3]) +test_eq(mask2idxs(array([1,2,3])), [1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### cycle + +> cycle (o) + +*Like `itertools.cycle` except creates list of `None`s if `o` is empty* + +``` python +test_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2]) +test_eq(itertools.islice(cycle([]),3), [None]*3) +test_eq(itertools.islice(cycle(None),3), [None]*3) +test_eq(itertools.islice(cycle(1),3), [1,1,1]) +``` + +------------------------------------------------------------------------ + +source + +### zip_cycle + +> zip_cycle (x, *args) + +*Like `itertools.zip_longest` but +[`cycle`](https://fastcore.fast.ai/foundation.html#cycle)s through +elements of all but first argument* + +``` python +test_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')]) +``` + +------------------------------------------------------------------------ + +source + +### is_indexer + +> is_indexer (idx) + +*Test whether `idx` will index a single item in a list* + +You can, for example index a single item in a list with an integer or a +0-dimensional numpy array: + +``` python +assert is_indexer(1) +assert is_indexer(np.array(1)) +``` + +However, you cannot index into single item in a list with another list +or a numpy array with ndim \> 0. + +``` python +assert not is_indexer([1, 2]) +assert not is_indexer(np.array([[1, 2], [3, 4]])) +``` + +## [`L`](https://fastcore.fast.ai/foundation.html#l) helpers + +------------------------------------------------------------------------ + +source + +### CollBase + +> CollBase (items) + +*Base class for composing a list of `items`* + +`ColBase` is a base class that emulates the functionality of a python +`list`: + +``` python +class _T(CollBase): pass +l = _T([1,2,3,4,5]) + +test_eq(len(l), 5) # __len__ +test_eq(l[-1], 5); test_eq(l[0], 1) #__getitem__ +l[2] = 100; test_eq(l[2], 100) # __set_item__ +del l[0]; test_eq(len(l), 4) # __delitem__ +test_eq(str(l), '[2, 100, 4, 5]') # __repr__ +``` + +------------------------------------------------------------------------ + +source + +### L + +> L (x=None, *args, **kwargs) + +*Behaves like a list of `items` but can also index with list of indices +or masks* + +[`L`](https://fastcore.fast.ai/foundation.html#l) is a drop in +replacement for a python `list`. Inspired by +[NumPy](http://www.numpy.org/), +[`L`](https://fastcore.fast.ai/foundation.html#l), supports advanced +indexing and has additional methods (outlined below) that provide +additional functionality and encourage simple expressive code. For +example, the code below takes a list of pairs, selects the second item +of each pair, takes its absolute value, filters items greater than 4, +and adds them up: + +``` python +from fastcore.utils import gt +``` + +``` python +d = dict(a=1,b=-5,d=6,e=9).items() +test_eq(L(d).itemgot(1).map(abs).filter(gt(4)).sum(), 20) # abs(-5) + abs(6) + abs(9) = 20; 1 was filtered out. +``` + +Read [this overview section](https://fastcore.fast.ai/tour.html#L) for a +quick tutorial of [`L`](https://fastcore.fast.ai/foundation.html#l), as +well as background on the name. + +You can create an [`L`](https://fastcore.fast.ai/foundation.html#l) from +an existing iterable (e.g. a list, range, etc) and access or modify it +with an int list/tuple index, mask, int, or slice. All `list` methods +can also be used with [`L`](https://fastcore.fast.ai/foundation.html#l). + +``` python +t = L(range(12)) +test_eq(t, list(range(12))) +test_ne(t, list(range(11))) +t.reverse() +test_eq(t[0], 11) +t[3] = "h" +test_eq(t[3], "h") +t[3,5] = ("j","k") +test_eq(t[3,5], ["j","k"]) +test_eq(t, L(t)) +test_eq(L(L(1,2),[3,4]), ([1,2],[3,4])) +t +``` + + (#12) [11,10,9,'j',7,'k',5,4,3,2...] + +Any [`L`](https://fastcore.fast.ai/foundation.html#l) is a `Sequence` so +you can use it with methods like `random.sample`: + +``` python +assert isinstance(t, Sequence) +``` + +``` python +import random +``` + +``` python +random.seed(0) +random.sample(t, 3) +``` + + [5, 0, 11] + +There are optimized indexers for arrays, tensors, and DataFrames. + +``` python +import pandas as pd +``` + +``` python +arr = np.arange(9).reshape(3,3) +t = L(arr, use_list=None) +test_eq(t[1,2], arr[[1,2]]) + +df = pd.DataFrame({'a':[1,2,3]}) +t = L(df, use_list=None) +test_eq(t[1,2], L(pd.DataFrame({'a':[2,3]}, index=[1,2]), use_list=None)) +``` + +You can also modify an [`L`](https://fastcore.fast.ai/foundation.html#l) +with `append`, `+`, and `*`. + +``` python +t = L() +test_eq(t, []) +t.append(1) +test_eq(t, [1]) +t += [3,2] +test_eq(t, [1,3,2]) +t = t + [4] +test_eq(t, [1,3,2,4]) +t = 5 + t +test_eq(t, [5,1,3,2,4]) +test_eq(L(1,2,3), [1,2,3]) +test_eq(L(1,2,3), L(1,2,3)) +t = L(1)*5 +t = t.map(operator.neg) +test_eq(t,[-1]*5) +test_eq(~L([True,False,False]), L([False,True,True])) +t = L(range(4)) +test_eq(zip(t, L(1).cycle()), zip(range(4),(1,1,1,1))) +t = L.range(100) +test_shuffled(t,t.shuffle()) +``` + +``` python +test_eq(L([]).sum(), 0) +test_eq(L([]).product(), 1) +``` + +``` python +def _f(x,a=0): return x+a +t = L(1)*5 +test_eq(t.map(_f), t) +test_eq(t.map(_f,1), [2]*5) +test_eq(t.map(_f,a=2), [3]*5) +``` + +An [`L`](https://fastcore.fast.ai/foundation.html#l) can be constructed +from anything iterable, although tensors and arrays will not be iterated +over on construction, unless you pass `use_list` to the constructor. + +``` python +test_eq(L([1,2,3]),[1,2,3]) +test_eq(L(L([1,2,3])),[1,2,3]) +test_ne(L([1,2,3]),[1,2,]) +test_eq(L('abc'),['abc']) +test_eq(L(range(0,3)),[0,1,2]) +test_eq(L(o for o in range(0,3)),[0,1,2]) +test_eq(L(array(0)),[array(0)]) +test_eq(L([array(0),array(1)]),[array(0),array(1)]) +test_eq(L(array([0.,1.1]))[0],array([0.,1.1])) +test_eq(L(array([0.,1.1]), use_list=True), [array(0.),array(1.1)]) # `use_list=True` to unwrap arrays/arrays +``` + +If `match` is not `None` then the created list is same len as `match`, +either by: + +- If `len(items)==1` then `items` is replicated, +- Otherwise an error is raised if `match` and `items` are not already + the same size. + +``` python +test_eq(L(1,match=[1,2,3]),[1,1,1]) +test_eq(L([1,2],match=[2,3]),[1,2]) +test_fail(lambda: L([1,2],match=[1,2,3])) +``` + +If you create an [`L`](https://fastcore.fast.ai/foundation.html#l) from +an existing [`L`](https://fastcore.fast.ai/foundation.html#l) then +you’ll get back the original object (since +[`L`](https://fastcore.fast.ai/foundation.html#l) uses the +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) +metaclass). + +``` python +test_is(L(t), t) +``` + +An [`L`](https://fastcore.fast.ai/foundation.html#l) is considred equal +to a list if they have the same elements. It’s never considered equal to +a `str` a `set` or a `dict` even if they have the same elements/keys. + +``` python +test_eq(L(['a', 'b']), ['a', 'b']) +test_ne(L(['a', 'b']), 'ab') +test_ne(L(['a', 'b']), {'a':1, 'b':2}) +``` + +### [`L`](https://fastcore.fast.ai/foundation.html#l) Methods + +------------------------------------------------------------------------ + +source + +### L.\_\_getitem\_\_ + +> L.__getitem__ (idx) + +*Retrieve `idx` (can be list of indices, or mask, or int) items* + +``` python +t = L(range(12)) +test_eq(t[1,2], [1,2]) # implicit tuple +test_eq(t[[1,2]], [1,2]) # list +test_eq(t[:3], [0,1,2]) # slice +test_eq(t[[False]*11 + [True]], [11]) # mask +test_eq(t[array(3)], 3) +``` + +------------------------------------------------------------------------ + +source + +### L.\_\_setitem\_\_ + +> L.__setitem__ (idx, o) + +*Set `idx` (can be list of indices, or mask, or int) items to `o` (which +is broadcast if not iterable)* + +``` python +t[4,6] = 0 +test_eq(t[4,6], [0,0]) +t[4,6] = [1,2] +test_eq(t[4,6], [1,2]) +``` + +------------------------------------------------------------------------ + +source + +### L.unique + +> L.unique (sort=False, bidir=False, start=None) + +*Unique items, in stable order* + +``` python +test_eq(L(4,1,2,3,4,4).unique(), [4,1,2,3]) +``` + +------------------------------------------------------------------------ + +source + +### L.val2idx + +> L.val2idx () + +*Dict from value to index* + +``` python +test_eq(L(1,2,3).val2idx(), {3:2,1:0,2:1}) +``` + +------------------------------------------------------------------------ + +source + +### L.filter + +> L.filter (f=, negate=False, **kwargs) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) filtered +by predicate `f`, passing `args` and `kwargs` to `f`* + +``` python +list(t) +``` + + [0, 1, 2, 3, 1, 5, 2, 7, 8, 9, 10, 11] + +``` python +test_eq(t.filter(lambda o:o<5), [0,1,2,3,1,2]) +test_eq(t.filter(lambda o:o<5, negate=True), [5,7,8,9,10,11]) +``` + +------------------------------------------------------------------------ + +source + +### L.argwhere + +> L.argwhere (f, negate=False, **kwargs) + +*Like `filter`, but return indices for matching items* + +``` python +test_eq(t.argwhere(lambda o:o<5), [0,1,2,3,4,6]) +``` + +------------------------------------------------------------------------ + +source + +### L.argfirst + +> L.argfirst (f, negate=False) + +*Return index of first matching item* + +``` python +test_eq(t.argfirst(lambda o:o>4), 5) +test_eq(t.argfirst(lambda o:o>4,negate=True),0) +``` + +------------------------------------------------------------------------ + +source + +### L.map + +> L.map (f, *args, **kwargs) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with `f` +applied to all `items`, passing `args` and `kwargs` to `f`* + +``` python +test_eq(L.range(4).map(operator.neg), [0,-1,-2,-3]) +``` + +If `f` is a string then it is treated as a format string to create the +mapping: + +``` python +test_eq(L.range(4).map('#{}#'), ['#0#','#1#','#2#','#3#']) +``` + +If `f` is a dictionary (or anything supporting `__getitem__`) then it is +indexed to create the mapping: + +``` python +test_eq(L.range(4).map(list('abcd')), list('abcd')) +``` + +You can also pass the same `arg` params that +[`bind`](https://fastcore.fast.ai/basics.html#bind) accepts: + +``` python +def f(a=None,b=None): return b +test_eq(L.range(4).map(f, b=arg0), range(4)) +``` + +------------------------------------------------------------------------ + +source + +### L.map_dict + +> L.map_dict (f=, *args, **kwargs) + +*Like `map`, but creates a dict from `items` to function results* + +``` python +test_eq(L(range(1,5)).map_dict(), {1:1, 2:2, 3:3, 4:4}) +test_eq(L(range(1,5)).map_dict(operator.neg), {1:-1, 2:-2, 3:-3, 4:-4}) +``` + +------------------------------------------------------------------------ + +source + +### L.zip + +> L.zip (cycled=False) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with +`zip(*items)`* + +``` python +t = L([[1,2,3],'abc']) +test_eq(t.zip(), [(1, 'a'),(2, 'b'),(3, 'c')]) +``` + +``` python +t = L([[1,2,3,4],['a','b','c']]) +test_eq(t.zip(cycled=True ), [(1, 'a'),(2, 'b'),(3, 'c'),(4, 'a')]) +test_eq(t.zip(cycled=False), [(1, 'a'),(2, 'b'),(3, 'c')]) +``` + +------------------------------------------------------------------------ + +source + +### L.map_zip + +> L.map_zip (f, *args, cycled=False, **kwargs) + +*Combine `zip` and `starmap`* + +``` python +t = L([1,2,3],[2,3,4]) +test_eq(t.map_zip(operator.mul), [2,6,12]) +``` + +------------------------------------------------------------------------ + +source + +### L.zipwith + +> L.zipwith (*rest, cycled=False) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with +`self` zip with each of `*rest`* + +``` python +b = [[0],[1],[2,2]] +t = L([1,2,3]).zipwith(b) +test_eq(t, [(1,[0]), (2,[1]), (3,[2,2])]) +``` + +------------------------------------------------------------------------ + +source + +### L.map_zipwith + +> L.map_zipwith (f, *rest, cycled=False, **kwargs) + +*Combine `zipwith` and `starmap`* + +``` python +test_eq(L(1,2,3).map_zipwith(operator.mul, [2,3,4]), [2,6,12]) +``` + +------------------------------------------------------------------------ + +source + +### L.itemgot + +> L.itemgot (*idxs) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with item +`idx` of all `items`* + +``` python +test_eq(t.itemgot(1), b) +``` + +------------------------------------------------------------------------ + +source + +### L.attrgot + +> L.attrgot (k, default=None) + +*Create new [`L`](https://fastcore.fast.ai/foundation.html#l) with attr +`k` (or value `k` for dicts) of all `items`.* + +``` python +# Example when items are not a dict +a = [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)] +test_eq(L(a).attrgot('b'), [4,2]) + +#Example of when items are a dict +b =[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}] +test_eq(L(b).attrgot('id'), [15, 17]) +``` + +------------------------------------------------------------------------ + +source + +### L.sorted + +> L.sorted (key=None, reverse=False) + +*New [`L`](https://fastcore.fast.ai/foundation.html#l) sorted by `key`. +If key is str use `attrgetter`; if int use `itemgetter`* + +``` python +test_eq(L(a).sorted('a').attrgot('b'), [2,4]) +``` + +------------------------------------------------------------------------ + +source + +### L.split + +> L.split (s, sep=None, maxsplit=-1) + +*Class Method: Same as `str.split`, but returns an +[`L`](https://fastcore.fast.ai/foundation.html#l)* + +``` python +test_eq(L.split('a b c'), list('abc')) +``` + +------------------------------------------------------------------------ + +source + +### L.range + +> L.range (a, b=None, step=None) + +*Class Method: Same as `range`, but returns +[`L`](https://fastcore.fast.ai/foundation.html#l). Can pass collection +for `a`, to use `len(a)`* + +``` python +test_eq_type(L.range([1,1,1]), L(range(3))) +test_eq_type(L.range(5,2,2), L(range(5,2,2))) +``` + +------------------------------------------------------------------------ + +source + +### L.concat + +> L.concat () + +*Concatenate all elements of list* + +``` python +test_eq(L([0,1,2,3],4,L(5,6)).concat(), range(7)) +``` + +------------------------------------------------------------------------ + +source + +### L.copy + +> L.copy () + +*Same as `list.copy`, but returns an +[`L`](https://fastcore.fast.ai/foundation.html#l)* + +``` python +t = L([0,1,2,3],4,L(5,6)).copy() +test_eq(t.concat(), range(7)) +``` + +------------------------------------------------------------------------ + +source + +### L.map_first + +> L.map_first (f=, g=, *args, **kwargs) + +*First element of `map_filter`* + +``` python +t = L(0,1,2,3) +test_eq(t.map_first(lambda o:o*2 if o>2 else None), 6) +``` + +------------------------------------------------------------------------ + +source + +### L.setattrs + +> L.setattrs (attr, val) + +*Call `setattr` on all items* + +``` python +t = L(SimpleNamespace(),SimpleNamespace()) +t.setattrs('foo', 'bar') +test_eq(t.attrgot('foo'), ['bar','bar']) +``` + +## Config + +------------------------------------------------------------------------ + +source + +### save_config_file + +> save_config_file (file, d, **kwargs) + +*Write settings dict to a new config file, or overwrite the existing +one.* + +------------------------------------------------------------------------ + +source + +### read_config_file + +> read_config_file (file, **kwargs) + +Config files are saved and read using Python’s +`configparser.ConfigParser`, inside the `DEFAULT` section. + +``` python +_d = dict(user='fastai', lib_name='fastcore', some_path='test', some_bool=True, some_num=3) +try: + save_config_file('tmp.ini', _d) + res = read_config_file('tmp.ini') +finally: os.unlink('tmp.ini') +dict(res) +``` + + {'user': 'fastai', + 'lib_name': 'fastcore', + 'some_path': 'test', + 'some_bool': 'True', + 'some_num': '3'} + +------------------------------------------------------------------------ + +source + +### Config + +> Config (cfg_path, cfg_name, create=None, save=True, extra_files=None, +> types=None) + +*Reading and writing `ConfigParser` ini files* + +[`Config`](https://fastcore.fast.ai/foundation.html#config) is a +convenient wrapper around `ConfigParser` ini files with a single section +(`DEFAULT`). + +Instantiate a +[`Config`](https://fastcore.fast.ai/foundation.html#config) from an ini +file at `cfg_path/cfg_name`: + +``` python +save_config_file('../tmp.ini', _d) +try: cfg = Config('..', 'tmp.ini') +finally: os.unlink('../tmp.ini') +cfg +``` + + {'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'} + +You can create a new file if one doesn’t exist by providing a `create` +dict: + +``` python +try: cfg = Config('..', 'tmp.ini', create=_d) +finally: os.unlink('../tmp.ini') +cfg +``` + + {'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'} + +If you additionally pass `save=False`, the +[`Config`](https://fastcore.fast.ai/foundation.html#config) will contain +the items from `create` without writing a new file: + +``` python +cfg = Config('..', 'tmp.ini', create=_d, save=False) +test_eq(cfg.user,'fastai') +assert not Path('../tmp.ini').exists() +``` + +------------------------------------------------------------------------ + +source + +### Config.get + +> Config.get (k, default=None) + +Keys can be accessed as attributes, items, or with `get` and an optional +default: + +``` python +test_eq(cfg.user,'fastai') +test_eq(cfg['some_path'], 'test') +test_eq(cfg.get('foo','bar'),'bar') +``` + +Extra files can be read *before* `cfg_path/cfg_name` using +`extra_files`, in the order they appear: + +``` python +with tempfile.TemporaryDirectory() as d: + a = Config(d, 'a.ini', {'a':0,'b':0}) + b = Config(d, 'b.ini', {'a':1,'c':0}) + c = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file]) + test_eq(c.d, {'a':'2','b':'0','c':'0','d':'0'}) +``` + +If you pass a dict `types`, then the values of that dict will be used as +types to instantiate all values returned. `Path` is a special case – in +that case, the path returned will be relative to the path containing the +config file (assuming the value is relative). `bool` types use +[`str2bool`](https://fastcore.fast.ai/basics.html#str2bool) to convert +to boolean. + +``` python +_types = dict(some_path=Path, some_bool=bool, some_num=int) +cfg = Config('..', 'tmp.ini', create=_d, save=False, types=_types) + +test_eq(cfg.user,'fastai') +test_eq(cfg['some_path'].resolve(), (Path('..')/'test').resolve()) +test_eq(cfg.get('some_num'), 3) +``` + +------------------------------------------------------------------------ + +source + +### Config.find + +> Config.find (cfg_name, cfg_path=None, **kwargs) + +*Search `cfg_path` and its parents to find `cfg_name`* + +You can use +[`Config.find`](https://fastcore.fast.ai/foundation.html#config.find) to +search subdirectories for a config file, starting in the current path if +no path is specified: + +``` python +Config.find('settings.ini').repo +``` + + 'fastcore'# Utility functions + + + +## File Functions + +Utilities (other than extensions to Pathlib.Path) for dealing with IO. + +------------------------------------------------------------------------ + +source + +### walk + +> walk (path:pathlib.Path|str, symlinks:bool=True, keep_file: infunctioncallable>=, keep_folder: infunctioncallable>=, skip_folder: infunctioncallable>=, func: infunctioncallable>=, ret_folders:bool=False) + +*Generator version of `os.walk`, using functions to filter files and +folders* + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
pathpathlib.Path | strpath to start searching
symlinksboolTruefollow symlinks?
keep_filecallableret_truefunction that returns True for wanted files
keep_foldercallableret_truefunction that returns True for folders to enter
skip_foldercallableret_falsefunction that returns True for folders to skip
funccallablejoinfunction to apply to each matched file
ret_foldersboolFalsereturn folders, not just files
+ +------------------------------------------------------------------------ + +source + +### globtastic + +> globtastic (path:pathlib.Path|str, recursive:bool=True, +> symlinks:bool=True, file_glob:str=None, file_re:str=None, +> folder_re:str=None, skip_file_glob:str=None, +> skip_file_re:str=None, skip_folder_re:str=None, func: infunctioncallable>=, ret_folders:bool=False) + +*A more powerful `glob`, including regex matches, symlink handling, and +skip parameters* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
pathpathlib.Path | strpath to start searching
recursiveboolTruesearch subfolders
symlinksboolTruefollow symlinks?
file_globstrNoneOnly include files matching glob
file_restrNoneOnly include files matching regex
folder_restrNoneOnly enter folders matching regex
skip_file_globstrNoneSkip files matching glob
skip_file_restrNoneSkip files matching regex
skip_folder_restrNoneSkip folders matching regex,
funccallablejoinfunction to apply to each matched file
ret_foldersboolFalsereturn folders, not just files
ReturnsLPaths to matched files
+ +``` python +globtastic('.', skip_folder_re='^[_.]', folder_re='core', file_glob='*.*py*', file_re='c') +``` + + (#5) ['./fastcore/docments.py','./fastcore/dispatch.py','./fastcore/basics.py','./fastcore/docscrape.py','./fastcore/script.py'] + +------------------------------------------------------------------------ + +source + +### maybe_open + +> maybe_open (f, mode='r', **kwargs) + +*Context manager: open `f` if it is a path (and close on exit)* + +This is useful for functions where you want to accept a path *or* file. +[`maybe_open`](https://fastcore.fast.ai/xtras.html#maybe_open) will not +close your file handle if you pass one in. + +``` python +def _f(fn): + with maybe_open(fn) as f: return f.encoding + +fname = '00_test.ipynb' +sys_encoding = 'cp1252' if sys.platform == 'win32' else 'UTF-8' +test_eq(_f(fname), sys_encoding) +with open(fname) as fh: test_eq(_f(fh), sys_encoding) +``` + +For example, we can use this to reimplement +[`imghdr.what`](https://docs.python.org/3/library/imghdr.html#imghdr.what) +from the Python standard library, which is [written in Python +3.9](https://github.com/python/cpython/blob/3.9/Lib/imghdr.py#L11) as: + +``` python +from fastcore import imghdr +``` + +``` python +def what(file, h=None): + f = None + try: + if h is None: + if isinstance(file, (str,os.PathLike)): + f = open(file, 'rb') + h = f.read(32) + else: + location = file.tell() + h = file.read(32) + file.seek(location) + for tf in imghdr.tests: + res = tf(h, f) + if res: return res + finally: + if f: f.close() + return None +``` + +Here’s an example of the use of this function: + +``` python +fname = 'images/puppy.jpg' +what(fname) +``` + + 'jpeg' + +With [`maybe_open`](https://fastcore.fast.ai/xtras.html#maybe_open), +`Self`, and +[`L.map_first`](https://fastcore.fast.ai/foundation.html#l.map_first), +we can rewrite this in a much more concise and (in our opinion) clear +way: + +``` python +def what(file, h=None): + if h is None: + with maybe_open(file, 'rb') as f: h = f.peek(32) + return L(imghdr.tests).map_first(Self(h,file)) +``` + +…and we can check that it still works: + +``` python +test_eq(what(fname), 'jpeg') +``` + +…along with the version passing a file handle: + +``` python +with open(fname,'rb') as f: test_eq(what(f), 'jpeg') +``` + +…along with the `h` parameter version: + +``` python +with open(fname,'rb') as f: test_eq(what(None, h=f.read(32)), 'jpeg') +``` + +------------------------------------------------------------------------ + +source + +### mkdir + +> mkdir (path, exist_ok=False, parents=False, overwrite=False, **kwargs) + +*Creates and returns a directory defined by `path`, optionally removing +previous existing directory if `overwrite` is `True`* + +``` python +with tempfile.TemporaryDirectory() as d: + path = Path(os.path.join(d, 'new_dir')) + new_dir = mkdir(path) + assert new_dir.exists() + test_eq(new_dir, path) + + # test overwrite + with open(new_dir/'test.txt', 'w') as f: f.writelines('test') + test_eq(len(list(walk(new_dir))), 1) # assert file is present + new_dir = mkdir(new_dir, overwrite=True) + test_eq(len(list(walk(new_dir))), 0) # assert file was deleted +``` + +------------------------------------------------------------------------ + +source + +### image_size + +> image_size (fn) + +*Tuple of (w,h) for png, gif, or jpg; `None` otherwise* + +``` python +test_eq(image_size(fname), (1200,803)) +``` + +------------------------------------------------------------------------ + +source + +### bunzip + +> bunzip (fn) + +*bunzip `fn`, raising exception if output already exists* + +``` python +f = Path('files/test.txt') +if f.exists(): f.unlink() +bunzip('files/test.txt.bz2') +t = f.open().readlines() +test_eq(len(t),1) +test_eq(t[0], 'test\n') +f.unlink() +``` + +------------------------------------------------------------------------ + +source + +### loads + +> loads (s, **kw) + +*Same as `json.loads`, but handles `None`* + +------------------------------------------------------------------------ + +source + +### loads_multi + +> loads_multi (s:str) + +*Generator of \>=0 decoded json dicts, possibly with non-json ignored +text at start and end* + +``` python +tst = """ +# ignored +{ "a":1 } +hello +{ +"b":2 +} +""" + +test_eq(list(loads_multi(tst)), [{'a': 1}, {'b': 2}]) +``` + +------------------------------------------------------------------------ + +source + +### dumps + +> dumps (obj, **kw) + +*Same as `json.dumps`, but uses `ujson` if available* + +------------------------------------------------------------------------ + +source + +### untar_dir + +> untar_dir (fname, dest, rename=False, overwrite=False) + +*untar `file` into `dest`, creating a directory if the root contains +more than one item* + +``` python +def test_untar(foldername, rename=False, **kwargs): + with tempfile.TemporaryDirectory() as d: + nm = os.path.join(d, 'a') + shutil.make_archive(nm, 'gztar', **kwargs) + with tempfile.TemporaryDirectory() as d2: + d2 = Path(d2) + untar_dir(nm+'.tar.gz', d2, rename=rename) + test_eq(d2.ls(), [d2/foldername]) +``` + +If the contents of `fname` contain just one file or directory, it is +placed directly in `dest`: + +``` python +# using `base_dir` in `make_archive` results in `images` directory included in file names +test_untar('images', base_dir='images') +``` + +If `rename` then the directory created is named based on the archive, +without extension: + +``` python +test_untar('a', base_dir='images', rename=True) +``` + +If the contents of `fname` contain multiple files and directories, a new +folder in `dest` is created with the same name as `fname` (but without +extension): + +``` python +# using `root_dir` in `make_archive` results in `images` directory *not* included in file names +test_untar('a', root_dir='images') +``` + +------------------------------------------------------------------------ + +source + +### repo_details + +> repo_details (url) + +*Tuple of `owner,name` from ssh or https git repo `url`* + +``` python +test_eq(repo_details('https://github.com/fastai/fastai.git'), ['fastai', 'fastai']) +test_eq(repo_details('git@github.com:fastai/nbdev.git\n'), ['fastai', 'nbdev']) +``` + +------------------------------------------------------------------------ + +source + +### run + +> run (cmd, *rest, same_in_win=False, ignore_ex=False, as_bytes=False, +> stderr=False) + +*Pass `cmd` (splitting with `shlex` if string) to `subprocess.run`; +return `stdout`; raise `IOError` if fails* + +You can pass a string (which will be split based on standard shell +rules), a list, or pass args directly: + +``` python +run('echo', same_in_win=True) +run('pip', '--version', same_in_win=True) +run(['pip', '--version'], same_in_win=True) +``` + + 'pip 23.3.1 from /Users/jhoward/miniconda3/lib/python3.11/site-packages/pip (python 3.11)' + +``` python +if sys.platform == 'win32': + assert 'ipynb' in run('cmd /c dir /p') + assert 'ipynb' in run(['cmd', '/c', 'dir', '/p']) + assert 'ipynb' in run('cmd', '/c', 'dir', '/p') +else: + assert 'ipynb' in run('ls -ls') + assert 'ipynb' in run(['ls', '-l']) + assert 'ipynb' in run('ls', '-l') +``` + +Some commands fail in non-error situations, like `grep`. Use `ignore_ex` +in those cases, which will return a tuple of stdout and returncode: + +``` python +if sys.platform == 'win32': + test_eq(run('cmd /c findstr asdfds 00_test.ipynb', ignore_ex=True)[0], 1) +else: + test_eq(run('grep asdfds 00_test.ipynb', ignore_ex=True)[0], 1) +``` + +[`run`](https://fastcore.fast.ai/xtras.html#run) automatically decodes +returned bytes to a `str`. Use `as_bytes` to skip that: + +``` python +if sys.platform == 'win32': + test_eq(run('cmd /c echo hi'), 'hi') +else: + test_eq(run('echo hi', as_bytes=True), b'hi\n') +``` + +------------------------------------------------------------------------ + +source + +### open_file + +> open_file (fn, mode='r', **kwargs) + +*Open a file, with optional compression if gz or bz2 suffix* + +------------------------------------------------------------------------ + +source + +### save_pickle + +> save_pickle (fn, o) + +*Save a pickle file, to a file name or opened file* + +------------------------------------------------------------------------ + +source + +### load_pickle + +> load_pickle (fn) + +*Load a pickle file from a file name or opened file* + +``` python +for suf in '.pkl','.bz2','.gz': + # delete=False is added for Windows + # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file + with tempfile.NamedTemporaryFile(suffix=suf, delete=False) as f: + fn = Path(f.name) + save_pickle(fn, 't') + t = load_pickle(fn) + f.close() + test_eq(t,'t') +``` + +------------------------------------------------------------------------ + +source + +### parse_env + +> parse_env (s:str=None, fn:Union[str,pathlib.Path]=None) + +*Parse a shell-style environment string or file* + +``` python +testf = """# comment + # another comment + export FOO="bar#baz" +BAR=thing # comment "ok" + baz='thong' +QUX=quux +export ZAP = "zip" # more comments + FOOBAR = 42 # trailing space and comment""" + +exp = dict(FOO='bar#baz', BAR='thing', baz='thong', QUX='quux', ZAP='zip', FOOBAR='42') + +test_eq(parse_env(testf), exp) +``` + +------------------------------------------------------------------------ + +source + +### expand_wildcards + +> expand_wildcards (code) + +*Expand all wildcard imports in the given code string.* + +``` python +inp = """from math import * +from os import * +from random import * +def func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)""" + +exp = """from math import pi, sin +from os import path +from random import randint +def func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)""" + +test_eq(expand_wildcards(inp), exp) + +inp = """from itertools import * +def func(): pass""" +test_eq(expand_wildcards(inp), inp) + +inp = """def outer(): + from math import * + def inner(): + from os import * + return sin(pi) + path.join('a', 'b')""" + +exp = """def outer(): + from math import pi, sin + def inner(): + from os import path + return sin(pi) + path.join('a', 'b')""" + +test_eq(expand_wildcards(inp), exp) +``` + +## Collections + +------------------------------------------------------------------------ + +source + +### dict2obj + +> dict2obj (d, list_func=, dict_func= 'fastcore.basics.AttrDict'>) + +*Convert (possibly nested) dicts (or lists of dicts) to +[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict)* + +This is a convenience to give you “dotted” access to (possibly nested) +dictionaries, e.g: + +``` python +d1 = dict(a=1, b=dict(c=2,d=3)) +d2 = dict2obj(d1) +test_eq(d2.b.c, 2) +test_eq(d2.b['c'], 2) +``` + +It can also be used on lists of dicts. + +``` python +_list_of_dicts = [d1, d1] +ds = dict2obj(_list_of_dicts) +test_eq(ds[0].b.c, 2) +``` + +------------------------------------------------------------------------ + +source + +### obj2dict + +> obj2dict (d) + +*Convert (possibly nested) AttrDicts (or lists of AttrDicts) to `dict`* + +[`obj2dict`](https://fastcore.fast.ai/xtras.html#obj2dict) can be used +to reverse what is done by +[`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj): + +``` python +test_eq(obj2dict(d2), d1) +test_eq(obj2dict(ds), _list_of_dicts) +``` + +------------------------------------------------------------------------ + +source + +### repr_dict + +> repr_dict (d) + +*Print nested dicts and lists, such as returned by +[`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj)* + +``` python +print(repr_dict(d2)) +``` + + - a: 1 + - b: + - c: 2 + - d: 3 + +------------------------------------------------------------------------ + +source + +### is_listy + +> is_listy (x) + +*`isinstance(x, (tuple,list,L,slice,Generator))`* + +``` python +assert is_listy((1,)) +assert is_listy([1]) +assert is_listy(L([1])) +assert is_listy(slice(2)) +assert not is_listy(array([1])) +``` + +------------------------------------------------------------------------ + +source + +### mapped + +> mapped (f, it) + +*map `f` over `it`, unless it’s not listy, in which case return `f(it)`* + +``` python +def _f(x,a=1): return x-a + +test_eq(mapped(_f,1),0) +test_eq(mapped(_f,[1,2]),[0,1]) +test_eq(mapped(_f,(1,)),(0,)) +``` + +## Extensions to Pathlib.Path + +The following methods are added to the standard python libary +[Pathlib.Path](https://docs.python.org/3/library/pathlib.html#basic-use). + +------------------------------------------------------------------------ + +source + +### Path.readlines + +> Path.readlines (hint=-1, encoding='utf8') + +*Read the content of `self`* + +------------------------------------------------------------------------ + +source + +### Path.read_json + +> Path.read_json (encoding=None, errors=None) + +*Same as `read_text` followed by +[`loads`](https://fastcore.fast.ai/xtras.html#loads)* + +------------------------------------------------------------------------ + +source + +### Path.mk_write + +> Path.mk_write (data, encoding=None, errors=None, mode=511) + +*Make all parent dirs of `self`, and write `data`* + +------------------------------------------------------------------------ + +source + +### Path.relpath + +> Path.relpath (start=None) + +*Same as `os.path.relpath`, but returns a `Path`, and resolves symlinks* + +``` python +p = Path('../fastcore/').resolve() +p +``` + + Path('/Users/daniel.roy.greenfeld/fh/fastcore/fastcore') + +``` python +p.relpath(Path.cwd()) +``` + + Path('../fastcore') + +------------------------------------------------------------------------ + +source + +### Path.ls + +> Path.ls (n_max=None, file_type=None, file_exts=None) + +*Contents of path as a list* + +We add an `ls()` method to `pathlib.Path` which is simply defined as +`list(Path.iterdir())`, mainly for convenience in REPL environments such +as notebooks. + +``` python +path = Path() +t = path.ls() +assert len(t)>0 +t1 = path.ls(10) +test_eq(len(t1), 10) +t2 = path.ls(file_exts='.ipynb') +assert len(t)>len(t2) +t[0] +``` + + Path('000_tour.ipynb') + +You can also pass an optional `file_type` MIME prefix and/or a list of +file extensions. + +``` python +lib_path = (path/'../fastcore') +txt_files=lib_path.ls(file_type='text') +assert len(txt_files) > 0 and txt_files[0].suffix=='.py' +ipy_files=path.ls(file_exts=['.ipynb']) +assert len(ipy_files) > 0 and ipy_files[0].suffix=='.ipynb' +txt_files[0],ipy_files[0] +``` + + (Path('../fastcore/shutil.py'), Path('000_tour.ipynb')) + +------------------------------------------------------------------------ + +source + +### Path.\_\_repr\_\_ + +> Path.__repr__ () + +*Return repr(self).* + +fastai also updates the `repr` of `Path` such that, if `Path.BASE_PATH` +is defined, all paths are printed relative to that path (as long as they +are contained in `Path.BASE_PATH`: + +``` python +t = ipy_files[0].absolute() +try: + Path.BASE_PATH = t.parent.parent + test_eq(repr(t), f"Path('nbs/{t.name}')") +finally: Path.BASE_PATH = None +``` + +------------------------------------------------------------------------ + +source + +### Path.delete + +> Path.delete () + +*Delete a file, symlink, or directory tree* + +## Reindexing Collections + +------------------------------------------------------------------------ + +source + +#### ReindexCollection + +> ReindexCollection (coll, idxs=None, cache=None, tfm=) + +*Reindexes collection `coll` with indices `idxs` and optional LRU cache +of size `cache`* + +This is useful when constructing batches or organizing data in a +particular manner (i.e. for deep learning). This class is primarly used +in organizing data for language models in fastai. + +You can supply a custom index upon instantiation with the `idxs` +argument, or you can call the `reindex` method to supply a new index for +your collection. + +Here is how you can reindex a list such that the elements are reversed: + +``` python +rc=ReindexCollection(['a', 'b', 'c', 'd', 'e'], idxs=[4,3,2,1,0]) +list(rc) +``` + + ['e', 'd', 'c', 'b', 'a'] + +Alternatively, you can use the `reindex` method: + +------------------------------------------------------------------------ + +source + +###### ReindexCollection.reindex + +> ReindexCollection.reindex (idxs) + +*Replace `self.idxs` with idxs* + +``` python +rc=ReindexCollection(['a', 'b', 'c', 'd', 'e']) +rc.reindex([4,3,2,1,0]) +list(rc) +``` + + ['e', 'd', 'c', 'b', 'a'] + +You can optionally specify a LRU cache, which uses +[functools.lru_cache](https://docs.python.org/3/library/functools.html#functools.lru_cache) +upon instantiation: + +``` python +sz = 50 +t = ReindexCollection(L.range(sz), cache=2) + +#trigger a cache hit by indexing into the same element multiple times +t[0], t[0] +t._get.cache_info() +``` + + CacheInfo(hits=1, misses=1, maxsize=2, currsize=1) + +You can optionally clear the LRU cache by calling the `cache_clear` +method: + +------------------------------------------------------------------------ + +source + +##### ReindexCollection.cache_clear + +> ReindexCollection.cache_clear () + +*Clear LRU cache* + +``` python +sz = 50 +t = ReindexCollection(L.range(sz), cache=2) + +#trigger a cache hit by indexing into the same element multiple times +t[0], t[0] +t.cache_clear() +t._get.cache_info() +``` + + CacheInfo(hits=0, misses=0, maxsize=2, currsize=0) + +------------------------------------------------------------------------ + +source + +##### ReindexCollection.shuffle + +> ReindexCollection.shuffle () + +*Randomly shuffle indices* + +Note that an ordered index is automatically constructed for the data +structure even if one is not supplied. + +``` python +rc=ReindexCollection(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']) +rc.shuffle() +list(rc) +``` + + ['a', 'd', 'h', 'c', 'e', 'b', 'f', 'g'] + +``` python +sz = 50 +t = ReindexCollection(L.range(sz), cache=2) +test_eq(list(t), range(sz)) +test_eq(t[sz-1], sz-1) +test_eq(t._get.cache_info().hits, 1) +t.shuffle() +test_eq(t._get.cache_info().hits, 1) +test_ne(list(t), range(sz)) +test_eq(set(t), set(range(sz))) +t.cache_clear() +test_eq(t._get.cache_info().hits, 0) +test_eq(t.count(0), 1) +``` + +## Other Helpers + +------------------------------------------------------------------------ + +source + +### get_source_link + +> get_source_link (func) + +*Return link to `func` in source code* + +[`get_source_link`](https://fastcore.fast.ai/xtras.html#get_source_link) +allows you get a link to source code related to an object. For +[nbdev](https://github.com/fastai/nbdev) related projects such as +fastcore, we can get the full link to a GitHub repo. For `nbdev` +projects, be sure to properly set the `git_url` in `settings.ini` +(derived from `lib_name` and `branch` on top of the prefix you will need +to adapt) so that those links are correct. + +For example, below we get the link to +[`fastcore.test.test_eq`](https://fastcore.fast.ai/test.html#test_eq): + +``` python +from fastcore.test import test_eq +``` + +``` python +assert 'fastcore/test.py' in get_source_link(test_eq) +assert get_source_link(test_eq).startswith('https://github.com/fastai/fastcore') +get_source_link(test_eq) +``` + + 'https://github.com/fastai/fastcore/tree/master/fastcore/test.py#L35' + +------------------------------------------------------------------------ + +source + +### truncstr + +> truncstr (s:str, maxlen:int, suf:str='…', space='') + +*Truncate `s` to length `maxlen`, adding suffix `suf` if truncated* + +``` python +w = 'abacadabra' +test_eq(truncstr(w, 10), w) +test_eq(truncstr(w, 5), 'abac…') +test_eq(truncstr(w, 5, suf=''), 'abaca') +test_eq(truncstr(w, 11, space='_'), w+"_") +test_eq(truncstr(w, 10, space='_'), w[:-1]+'…') +test_eq(truncstr(w, 5, suf='!!'), 'aba!!') +``` + +------------------------------------------------------------------------ + +source + +### sparkline + +> sparkline (data, mn=None, mx=None, empty_zero=False) + +*Sparkline for `data`, with `None`s (and zero, if `empty_zero`) shown as +empty column* + +``` python +data = [9,6,None,1,4,0,8,15,10] +print(f'without "empty_zero": {sparkline(data, empty_zero=False)}') +print(f' with "empty_zero": {sparkline(data, empty_zero=True )}') +``` + + without "empty_zero": ▅▂ ▁▂▁▃▇▅ + with "empty_zero": ▅▂ ▁▂ ▃▇▅ + +You can set a maximum and minimum for the y-axis of the sparkline with +the arguments `mn` and `mx` respectively: + +``` python +sparkline([1,2,3,400], mn=0, mx=3) +``` + + '▂▅▇▇' + +------------------------------------------------------------------------ + +source + +### modify_exception + +> modify_exception (e:Exception, msg:str=None, replace:bool=False) + +*Modifies `e` with a custom message attached* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
eExceptionAn exception
msgstrNoneA custom message
replaceboolFalseWhether to replace e.args with [msg]
ReturnsException
+ +``` python +msg = "This is my custom message!" + +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), None)), contains='') +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), msg)), contains=msg) +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception("The first message"), msg)), contains="The first message This is my custom message!") +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception("The first message"), msg, True)), contains="This is my custom message!") +``` + +------------------------------------------------------------------------ + +source + +### round_multiple + +> round_multiple (x, mult, round_down=False) + +*Round `x` to nearest multiple of `mult`* + +``` python +test_eq(round_multiple(63,32), 64) +test_eq(round_multiple(50,32), 64) +test_eq(round_multiple(40,32), 32) +test_eq(round_multiple( 0,32), 0) +test_eq(round_multiple(63,32, round_down=True), 32) +test_eq(round_multiple((63,40),32), (64,32)) +``` + +------------------------------------------------------------------------ + +source + +### set_num_threads + +> set_num_threads (nt) + +*Get numpy (and others) to use `nt` threads* + +This sets the number of threads consistently for many tools, by: + +1. Set the following environment variables equal to `nt`: + `OPENBLAS_NUM_THREADS`,`NUMEXPR_NUM_THREADS`,`OMP_NUM_THREADS`,`MKL_NUM_THREADS` +2. Sets `nt` threads for numpy and pytorch. + +------------------------------------------------------------------------ + +source + +### join_path_file + +> join_path_file (file, path, ext='') + +*Return `path/file` if file is a string or a `Path`, file otherwise* + +``` python +path = Path.cwd()/'_tmp'/'tst' +f = join_path_file('tst.txt', path) +assert path.exists() +test_eq(f, path/'tst.txt') +with open(f, 'w') as f_: assert join_path_file(f_, path) == f_ +shutil.rmtree(Path.cwd()/'_tmp') +``` + +------------------------------------------------------------------------ + +source + +### autostart + +> autostart (g) + +*Decorator that automatically starts a generator* + +------------------------------------------------------------------------ + +source + +#### EventTimer + +> EventTimer (store=5, span=60) + +*An event timer with history of `store` items of time `span`* + +Add events with `add`, and get number of `events` and their frequency +(`freq`). + +``` python +# Random wait function for testing +def _randwait(): yield from (sleep(random.random()/200) for _ in range(100)) + +c = EventTimer(store=5, span=0.03) +for o in _randwait(): c.add(1) +print(f'Num Events: {c.events}, Freq/sec: {c.freq:.01f}') +print('Most recent: ', sparkline(c.hist), *L(c.hist).map('{:.01f}')) +``` + + Num Events: 3, Freq/sec: 205.6 + Most recent: ▁▁▃▁▇ 254.1 263.2 284.5 259.9 315.7 + +------------------------------------------------------------------------ + +source + +### stringfmt_names + +> stringfmt_names (s:str) + +*Unique brace-delimited names in `s`* + +``` python +s = '/pulls/{pull_number}/reviews/{review_id}' +test_eq(stringfmt_names(s), ['pull_number','review_id']) +``` + +------------------------------------------------------------------------ + +source + +#### PartialFormatter + +> PartialFormatter () + +*A `string.Formatter` that doesn’t error on missing fields, and tracks +missing fields and unused args* + +------------------------------------------------------------------------ + +source + +### partial_format + +> partial_format (s:str, **kwargs) + +*string format `s`, ignoring missing field errors, returning missing and +extra fields* + +The result is a tuple of +`(formatted_string,missing_fields,extra_fields)`, e.g: + +``` python +res,missing,xtra = partial_format(s, pull_number=1, foo=2) +test_eq(res, '/pulls/1/reviews/{review_id}') +test_eq(missing, ['review_id']) +test_eq(xtra, {'foo':2}) +``` + +------------------------------------------------------------------------ + +source + +### utc2local + +> utc2local (dt:datetime.datetime) + +*Convert `dt` from UTC to local time* + +``` python +dt = datetime(2000,1,1,12) +print(f'{dt} UTC is {utc2local(dt)} local time') +``` + + 2000-01-01 12:00:00 UTC is 2000-01-01 22:00:00+10:00 local time + +------------------------------------------------------------------------ + +source + +### local2utc + +> local2utc (dt:datetime.datetime) + +*Convert `dt` from local to UTC time* + +``` python +print(f'{dt} local is {local2utc(dt)} UTC time') +``` + + 2000-01-01 12:00:00 local is 2000-01-01 02:00:00+00:00 UTC time + +------------------------------------------------------------------------ + +source + +### trace + +> trace (f) + +*Add `set_trace` to an existing function `f`* + +You can add a breakpoint to an existing function, e.g: + +``` python +Path.cwd = trace(Path.cwd) +Path.cwd() +``` + +Now, when the function is called it will drop you into the debugger. +Note, you must issue the `s` command when you begin to step into the +function that is being traced. + +------------------------------------------------------------------------ + +source + +### modified_env + +> modified_env (*delete, **replace) + +*Context manager temporarily modifying `os.environ` by deleting `delete` +and replacing `replace`* + +``` python +# USER isn't in Cloud Linux Environments +env_test = 'USERNAME' if sys.platform == "win32" else 'SHELL' +oldusr = os.environ[env_test] + +replace_param = {env_test: 'a'} +with modified_env('PATH', **replace_param): + test_eq(os.environ[env_test], 'a') + assert 'PATH' not in os.environ + +assert 'PATH' in os.environ +test_eq(os.environ[env_test], oldusr) +``` + +------------------------------------------------------------------------ + +source + +#### ContextManagers + +> ContextManagers (mgrs) + +*Wrapper for `contextlib.ExitStack` which enters a collection of context +managers* + +------------------------------------------------------------------------ + +source + +### shufflish + +> shufflish (x, pct=0.04) + +*Randomly relocate items of `x` up to `pct` of `len(x)` from their +starting location* + +------------------------------------------------------------------------ + +source + +### console_help + +> console_help (libname:str) + +*Show help for all console scripts from `libname`* + + + + + + + + + + + + + + + + +
TypeDetails
libnamestrname of library for console script listing
+ +------------------------------------------------------------------------ + +source + +### hl_md + +> hl_md (s, lang='xml', show=True) + +*Syntax highlight `s` using `lang`.* + +When we display code in a notebook, it’s nice to highlight it, so we +create a function to simplify that: + +``` python +hl_md('a child') +``` + +``` xml +a child +``` + +------------------------------------------------------------------------ + +source + +### type2str + +> type2str (typ:type) + +*Stringify `typ`* + +``` python +test_eq(type2str(Optional[float]), 'Union[float, None]') +``` + +------------------------------------------------------------------------ + +source + +### dataclass_src + +> dataclass_src (cls) + +``` python +DC = make_dataclass('DC', [('x', int), ('y', Optional[float], None), ('z', float, None)]) +print(dataclass_src(DC)) +``` + + @dataclass + class DC: + x: int + y: Union[float, None] = None + z: float = None + +------------------------------------------------------------------------ + +source + +### Unset + +> Unset (value, names=None, module=None, qualname=None, type=None, start=1) + +*An enumeration.* + +------------------------------------------------------------------------ + +source + +### nullable_dc + +> nullable_dc (cls) + +*Like `dataclass`, but default of `UNSET` added to fields without +defaults* + +``` python +@nullable_dc +class Person: name: str; age: int; city: str = "Unknown" +Person(name="Bob") +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +------------------------------------------------------------------------ + +source + +### make_nullable + +> make_nullable (clas) + +``` python +@dataclass +class Person: name: str; age: int; city: str = "Unknown" + +make_nullable(Person) +Person("Bob", city='NY') +``` + + Person(name='Bob', age=UNSET, city='NY') + +``` python +Person(name="Bob") +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +``` python +Person("Bob", 34) +``` + + Person(name='Bob', age=34, city='Unknown') + +------------------------------------------------------------------------ + +source + +### flexiclass + +> flexiclass (cls) + +*Convert `cls` into a `dataclass` like +[`make_nullable`](https://fastcore.fast.ai/xtras.html#make_nullable). +Converts in place and also returns the result.* + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
clsThe class to convert
Returnsdataclass
+ +This can be used as a decorator… + +``` python +@flexiclass +class Person: name: str; age: int; city: str = "Unknown" + +bob = Person(name="Bob") +bob +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +…or can update the behavior of an existing class (or dataclass): + +``` python +class Person: name: str; age: int; city: str = "Unknown" + +flexiclass(Person) +bob = Person(name="Bob") +bob +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +Action occurs in-place: + +``` python +class Person: name: str; age: int; city: str = "Unknown" + +flexiclass(Person) +is_dataclass(Person) +``` + + True + +------------------------------------------------------------------------ + +source + +### asdict + +> asdict (o) + +*Convert `o` to a `dict`, supporting dataclasses, namedtuples, +iterables, and `__dict__` attrs.* + +Any `UNSET` values are not included. + +``` python +asdict(bob) +``` + + {'name': 'Bob', 'city': 'Unknown'} + +To customise dict conversion behavior for a class, implement the +`_asdict` method (this is used in the Python stdlib for named tuples). + +------------------------------------------------------------------------ + +source + +### is_typeddict + +> is_typeddict (cls:type) + +*Check if `cls` is a `TypedDict`* + +``` python +class MyDict(TypedDict): name:str + +assert is_typeddict(MyDict) +assert not is_typeddict({'a':1}) +``` + +------------------------------------------------------------------------ + +source + +### is_namedtuple + +> is_namedtuple (cls) + +*`True` if `cls` is a namedtuple type* + +``` python +assert is_namedtuple(namedtuple('tst', ['a'])) +assert not is_namedtuple(tuple) +``` + +------------------------------------------------------------------------ + +source + +### flexicache + +> flexicache (*funcs, maxsize=128) + +*Like `lru_cache`, but customisable with policy `funcs`* + +This is a flexible lru cache function that you can pass a list of +functions to. Those functions define the cache eviction policy. For +instance, +[`time_policy`](https://fastcore.fast.ai/xtras.html#time_policy) is +provided for time-based cache eviction, and +[`mtime_policy`](https://fastcore.fast.ai/xtras.html#mtime_policy) +evicts based on a file’s modified-time changing. The policy functions +are passed the last value that function returned was (initially `None`), +and return a new value to indicate the cache has expired. When the cache +expires, all functions are called with `None` to force getting new +values. + +------------------------------------------------------------------------ + +source + +### time_policy + +> time_policy (seconds) + +*A [`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) policy +that expires cached items after `seconds` have passed* + +------------------------------------------------------------------------ + +source + +### mtime_policy + +> mtime_policy (filepath) + +*A [`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) policy +that expires cached items after `filepath` modified-time changes* + +``` python +@flexicache(time_policy(10), mtime_policy('000_tour.ipynb')) +def cached_func(x, y): return x+y + +cached_func(1,2) +``` + + 3 + +``` python +@flexicache(time_policy(10), mtime_policy('000_tour.ipynb')) +async def cached_func(x, y): return x+y + +await cached_func(1,2) +await cached_func(1,2) +``` + + 3 + +------------------------------------------------------------------------ + +source + +### timed_cache + +> timed_cache (seconds=60, maxsize=128) + +*Like `lru_cache`, but also with time-based eviction* + +This function is a small convenience wrapper for using +[`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) with +[`time_policy`](https://fastcore.fast.ai/xtras.html#time_policy). + +``` python +@timed_cache(seconds=0.05, maxsize=2) +def cached_func(x): return x * 2, time() + +# basic caching +result1, time1 = cached_func(2) +test_eq(result1, 4) +sleep(0.001) +result2, time2 = cached_func(2) +test_eq(result2, 4) +test_eq(time1, time2) + +# caching different values +result3, _ = cached_func(3) +test_eq(result3, 6) + +# maxsize +_, time4 = cached_func(4) +_, time2_new = cached_func(2) +test_close(time2, time2_new, eps=0.1) +_, time3_new = cached_func(3) +test_ne(time3_new, time()) + +# time expiration +sleep(0.05) +_, time4_new = cached_func(4) +test_ne(time4_new, time()) +```
# Parallel + + + +``` python +from fastcore.test import * +from nbdev.showdoc import * +from fastcore.nb_imports import * +``` + +------------------------------------------------------------------------ + +source + +### threaded + +> threaded (process=False) + +*Run `f` in a `Thread` (or `Process` if `process=True`), and returns it* + +``` python +@threaded +def _1(): + time.sleep(0.05) + print("second") + return 5 + +@threaded +def _2(): + time.sleep(0.01) + print("first") + +a = _1() +_2() +time.sleep(0.1) +``` + + first + second + +After the thread is complete, the return value is stored in the `result` +attr. + +``` python +a.result +``` + + 5 + +------------------------------------------------------------------------ + +source + +### startthread + +> startthread (f) + +*Like [`threaded`](https://fastcore.fast.ai/parallel.html#threaded), but +start thread immediately* + +``` python +@startthread +def _(): + time.sleep(0.05) + print("second") + +@startthread +def _(): + time.sleep(0.01) + print("first") + +time.sleep(0.1) +``` + + first + second + +------------------------------------------------------------------------ + +source + +### startproc + +> startproc (f) + +*Like `threaded(True)`, but start Process immediately* + +``` python +@startproc +def _(): + time.sleep(0.05) + print("second") + +@startproc +def _(): + time.sleep(0.01) + print("first") + +time.sleep(0.1) +``` + + first + second + +------------------------------------------------------------------------ + +source + +### parallelable + +> parallelable (param_name, num_workers, f=None) + +------------------------------------------------------------------------ + +source + +#### ThreadPoolExecutor + +> ThreadPoolExecutor (max_workers=4, on_exc=, +> pause=0, **kwargs) + +*Same as Python’s ThreadPoolExecutor, except can pass `max_workers==0` +for serial execution* + +------------------------------------------------------------------------ + +source + +#### ProcessPoolExecutor + +> ProcessPoolExecutor (max_workers=4, on_exc=, +> pause=0, mp_context=None, initializer=None, +> initargs=()) + +*Same as Python’s ProcessPoolExecutor, except can pass `max_workers==0` +for serial execution* + +------------------------------------------------------------------------ + +source + +### parallel + +> parallel (f, items, *args, n_workers=4, total=None, progress=None, +> pause=0, method=None, threadpool=False, timeout=None, +> chunksize=1, **kwargs) + +*Applies `func` in parallel to `items`, using `n_workers`* + +``` python +inp,exp = range(50),range(1,51) + +test_eq(parallel(_add_one, inp, n_workers=2), exp) +test_eq(parallel(_add_one, inp, threadpool=True, n_workers=2), exp) +test_eq(parallel(_add_one, inp, n_workers=1, a=2), range(2,52)) +test_eq(parallel(_add_one, inp, n_workers=0), exp) +test_eq(parallel(_add_one, inp, n_workers=0, a=2), range(2,52)) +``` + +Use the `pause` parameter to ensure a pause of `pause` seconds between +processes starting. This is in case there are race conditions in +starting some process, or to stagger the time each process starts, for +example when making many requests to a webserver. Set `threadpool=True` +to use +[`ThreadPoolExecutor`](https://fastcore.fast.ai/parallel.html#threadpoolexecutor) +instead of +[`ProcessPoolExecutor`](https://fastcore.fast.ai/parallel.html#processpoolexecutor). + +``` python +from datetime import datetime +``` + +``` python +def print_time(i): + time.sleep(random.random()/1000) + print(i, datetime.now()) + +parallel(print_time, range(5), n_workers=2, pause=0.25); +``` + + 0 2024-10-11 23:06:05.920741 + 1 2024-10-11 23:06:06.171470 + 2 2024-10-11 23:06:06.431925 + 3 2024-10-11 23:06:06.689940 + 4 2024-10-11 23:06:06.937109 + +------------------------------------------------------------------------ + +source + +### parallel_async + +> parallel_async (f, items, *args, n_workers=16, timeout=None, chunksize=1, +> on_exc=, **kwargs) + +*Applies `f` to `items` in parallel using asyncio and a semaphore to +limit concurrency.* + +``` python +import asyncio +``` + +``` python +async def print_time_async(i): + wait = random.random() + await asyncio.sleep(wait) + print(i, datetime.now(), wait) + +await parallel_async(print_time_async, range(6), n_workers=3); +``` + + 0 2024-10-11 23:06:39.545583 0.10292732609738675 + 3 2024-10-11 23:06:39.900393 0.3516179734831676 + 4 2024-10-11 23:06:39.941094 0.03699593757956876 + 2 2024-10-11 23:06:39.957677 0.5148658606540902 + 1 2024-10-11 23:06:40.099716 0.6574035385815227 + 5 2024-10-11 23:06:40.654097 0.7116319667399102 + +------------------------------------------------------------------------ + +source + +### run_procs + +> run_procs (f, f_done, args) + +*Call `f` for each item in `args` in parallel, yielding `f_done`* + +------------------------------------------------------------------------ + +source + +### parallel_gen + +> parallel_gen (cls, items, n_workers=4, **kwargs) + +*Instantiate `cls` in `n_workers` procs & call each on a subset of +`items` in parallel.* + +``` python +# class _C: +# def __call__(self, o): return ((i+1) for i in o) + +# items = range(5) + +# res = L(parallel_gen(_C, items, n_workers=0)) +# idxs,dat1 = zip(*res.sorted(itemgetter(0))) +# test_eq(dat1, range(1,6)) + +# res = L(parallel_gen(_C, items, n_workers=3)) +# idxs,dat2 = zip(*res.sorted(itemgetter(0))) +# test_eq(dat2, dat1) +``` + +`cls` is any class with `__call__`. It will be passed `args` and +`kwargs` when initialized. Note that `n_workers` instances of `cls` are +created, one in each process. `items` are then split in `n_workers` +batches and one is sent to each `cls`. The function then returns a +generator of tuples of item indices and results. + +``` python +class TestSleepyBatchFunc: + "For testing parallel processes that run at different speeds" + def __init__(self): self.a=1 + def __call__(self, batch): + for k in batch: + time.sleep(random.random()/4) + yield k+self.a + +x = np.linspace(0,0.99,20) + +res = L(parallel_gen(TestSleepyBatchFunc, x, n_workers=2)) +test_eq(res.sorted().itemgot(1), x+1) +``` + + + +``` python +# #|hide +# from subprocess import Popen, PIPE +# # test num_workers > 0 in scripts works when python process start method is spawn +# process = Popen(["python", "parallel_test.py"], stdout=PIPE) +# _, err = process.communicate(timeout=10) +# exit_code = process.wait() +# test_eq(exit_code, 0) +```# Network functionality + + + +``` python +from fastcore.test import * +from nbdev.showdoc import * +from fastcore.nb_imports import * +``` + +## URLs + +------------------------------------------------------------------------ + +source + +### urlquote + +> urlquote (url) + +*Update url’s path with `urllib.parse.quote`* + +``` python +urlquote("https://github.com/fastai/fastai/compare/master@{1.day.ago}…master") +``` + + 'https://github.com/fastai/fastai/compare/master@%7B1.day.ago%7D%E2%80%A6master' + +``` python +urlquote("https://www.google.com/search?q=你好") +``` + + 'https://www.google.com/search?q=%E4%BD%A0%E5%A5%BD' + +------------------------------------------------------------------------ + +source + +### urlwrap + +> urlwrap (url, data=None, headers=None) + +*Wrap `url` in a urllib `Request` with +[`urlquote`](https://fastcore.fast.ai/net.html#urlquote)* + +------------------------------------------------------------------------ + +source + +#### HTTP4xxClientError + +> HTTP4xxClientError (url, code, msg, hdrs, fp) + +*Base class for client exceptions (code 4xx) from `url*` functions* + +------------------------------------------------------------------------ + +source + +#### HTTP5xxServerError + +> HTTP5xxServerError (url, code, msg, hdrs, fp) + +*Base class for server exceptions (code 5xx) from `url*` functions* + +------------------------------------------------------------------------ + +source + +### urlopener + +> urlopener () + +------------------------------------------------------------------------ + +source + +### urlopen + +> urlopen (url, data=None, headers=None, timeout=None, **kwargs) + +*Like `urllib.request.urlopen`, but first +[`urlwrap`](https://fastcore.fast.ai/net.html#urlwrap) the `url`, and +encode `data`* + +With [`urlopen`](https://fastcore.fast.ai/net.html#urlopen), the body of +the response will also be returned in addition to the message if there +is an error: + +``` python +try: urlopen('https://api.github.com/v3') +except HTTPError as e: + print(e.code, e.msg) + assert 'documentation_url' in e.msg +``` + + 404 Not Found + ====Error Body==== + { + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest" + } + +------------------------------------------------------------------------ + +source + +### urlread + +> urlread (url, data=None, headers=None, decode=True, return_json=False, +> return_headers=False, timeout=None, **kwargs) + +*Retrieve `url`, using `data` dict or `kwargs` to `POST` if present* + +------------------------------------------------------------------------ + +source + +### urljson + +> urljson (url, data=None, timeout=None) + +*Retrieve `url` and decode json* + +``` python +test_eq(urljson('https://httpbin.org/get')['headers']['User-Agent'], url_default_headers['User-Agent']) +``` + +------------------------------------------------------------------------ + +source + +### urlcheck + +> urlcheck (url, headers=None, timeout=10) + +------------------------------------------------------------------------ + +source + +### urlclean + +> urlclean (url) + +*Remove fragment, params, and querystring from `url` if present* + +``` python +test_eq(urlclean('http://a.com/b?c=1#d'), 'http://a.com/b') +``` + +------------------------------------------------------------------------ + +source + +### urlretrieve + +> urlretrieve (url, filename=None, reporthook=None, data=None, +> headers=None, timeout=None) + +*Same as `urllib.request.urlretrieve` but also works with `Request` +objects* + +------------------------------------------------------------------------ + +source + +### urldest + +> urldest (url, dest=None) + +------------------------------------------------------------------------ + +source + +### urlsave + +> urlsave (url, dest=None, reporthook=None, headers=None, timeout=None) + +*Retrieve `url` and save based on its name* + +``` python +#skip +with tempfile.TemporaryDirectory() as d: urlsave('http://www.google.com/index.html', d) +``` + +------------------------------------------------------------------------ + +source + +### urlvalid + +> urlvalid (x) + +*Test if `x` is a valid URL* + +``` python +assert urlvalid('http://www.google.com/') +assert not urlvalid('www.google.com/') +assert not urlvalid(1) +``` + +------------------------------------------------------------------------ + +source + +### urlrequest + +> urlrequest (url, verb, headers=None, route=None, query=None, data=None, +> json_data=True) + +*`Request` for `url` with optional route params replaced by `route`, +plus `query` string, and post `data`* + +``` python +hdr = {'Hdr1':'1', 'Hdr2':'2'} +req = urlrequest('http://example.com/{foo}/1', 'POST', + headers=hdr, route={'foo':'3'}, query={'q':'4'}, data={'d':'5'}) + +test_eq(req.headers, hdr) +test_eq(req.full_url, 'http://example.com/3/1?q=4') +test_eq(req.method, 'POST') +test_eq(req.data, b'{"d": "5"}') +``` + +``` python +req = urlrequest('http://example.com/{foo}/1', 'POST', data={'d':'5','e':'6'}, headers=hdr, json_data=False) +test_eq(req.data, b'd=5&e=6') +``` + +------------------------------------------------------------------------ + +source + +### Request.summary + +> Request.summary (skip=None) + +*Summary containing full_url, headers, method, and data, removing `skip` +from headers* + +``` python +req.summary(skip='Hdr1') +``` + + {'full_url': 'http://example.com/{foo}/1', + 'method': 'POST', + 'data': b'd=5&e=6', + 'headers': {'Hdr2': '2'}} + +------------------------------------------------------------------------ + +source + +### urlsend + +> urlsend (url, verb, headers=None, decode=True, route=None, query=None, +> data=None, json_data=True, return_json=True, +> return_headers=False, debug=None, timeout=None) + +*Send request with +[`urlrequest`](https://fastcore.fast.ai/net.html#urlrequest), converting +result to json if `return_json`* + +------------------------------------------------------------------------ + +source + +### do_request + +> do_request (url, post=False, headers=None, **data) + +*Call GET or json-encoded POST on `url`, depending on `post`* + +## Basic client/server + +------------------------------------------------------------------------ + +source + +### start_server + +> start_server (port, host=None, dgram=False, reuse_addr=True, +> n_queue=None) + +*Create a `socket` server on `port`, with optional `host`, of type +`dgram`* + +You can create a TCP client and server pass an int as `port` and +optional `host`. `host` defaults to your main network interface if not +provided. You can create a Unix socket client and server by passing a +string to `port`. A `SOCK_STREAM` socket is created by default, unless +you pass `dgram=True`, in which case a `SOCK_DGRAM` socket is created. +`n_queue` sets the listening queue size. + +------------------------------------------------------------------------ + +source + +### start_client + +> start_client (port, host=None, dgram=False) + +*Create a `socket` client on `port`, with optional `host`, of type +`dgram`* + +------------------------------------------------------------------------ + +source + +### tobytes + +> tobytes (s:str) + +*Convert `s` into HTTP-ready bytes format* + +``` python +test_eq(tobytes('foo\nbar'), b'foo\r\nbar') +``` + +------------------------------------------------------------------------ + +source + +### http_response + +> http_response (body=None, status=200, hdrs=None, **kwargs) + +*Create an HTTP-ready response, adding `kwargs` to `hdrs`* + +``` python +exp = b'HTTP/1.1 200 OK\r\nUser-Agent: me\r\nContent-Length: 4\r\n\r\nbody' +test_eq(http_response('body', 200, User_Agent='me'), exp) +``` + +------------------------------------------------------------------------ + +source + +### recv_once + +> recv_once (host:str='localhost', port:int=8000) + +*Spawn a thread to receive a single HTTP request and store in `d['r']`*# Docments + + + +[`docments`](https://fastcore.fast.ai/docments.html#docments) provides +programmatic access to comments in function parameters and return types. +It can be used to create more developer-friendly documentation, CLI, etc +tools. + +## Why? + +Without docments, if you want to document your parameters, you have to +repeat param names in docstrings, since they’re already in the function +signature. The parameters have to be kept synchronized in the two places +as you change your code. Readers of your code have to look back and +forth between two places to understand what’s happening. So it’s more +work for you, and for your users. + +Furthermore, to have parameter documentation formatted nicely without +docments, you have to use special magic docstring formatting, often with +[odd +quirks](https://stackoverflow.com/questions/62167540/why-do-definitions-have-a-space-before-the-colon-in-numpy-docstring-sections), +which is a pain to create and maintain, and awkward to read in code. For +instance, using [numpy-style +documentation](https://numpydoc.readthedocs.io/en/latest/format.html): + +``` python +def add_np(a:int, b:int=0)->int: + """The sum of two numbers. + + Used to demonstrate numpy-style docstrings. + +Parameters +---------- +a : int + the 1st number to add +b : int + the 2nd number to add (default: 0) + +Returns +------- +int + the result of adding `a` to `b`""" + return a+b +``` + +By comparison, here’s the same thing using docments: + +``` python +def add( + a:int, # the 1st number to add + b=0, # the 2nd number to add +)->int: # the result of adding `a` to `b` + "The sum of two numbers." + return a+b +``` + +## Numpy docstring helper functions + +[`docments`](https://fastcore.fast.ai/docments.html#docments) also +supports numpy-style docstrings, or a mix or numpy-style and docments +parameter documentation. The functions in this section help get and +parse this information. + +------------------------------------------------------------------------ + +source + +### docstring + +> docstring (sym) + +*Get docstring for `sym` for functions ad classes* + +``` python +test_eq(docstring(add), "The sum of two numbers.") +``` + +------------------------------------------------------------------------ + +source + +### parse_docstring + +> parse_docstring (sym) + +*Parse a numpy-style docstring in `sym`* + +``` python +# parse_docstring(add_np) +``` + +------------------------------------------------------------------------ + +source + +### isdataclass + +> isdataclass (s) + +*Check if `s` is a dataclass but not a dataclass’ instance* + +------------------------------------------------------------------------ + +source + +### get_dataclass_source + +> get_dataclass_source (s) + +*Get source code for dataclass `s`* + +------------------------------------------------------------------------ + +source + +### get_source + +> get_source (s) + +*Get source code for string, function object or dataclass `s`* + +------------------------------------------------------------------------ + +source + +### get_name + +> get_name (obj) + +*Get the name of `obj`* + +``` python +test_eq(get_name(in_ipython), 'in_ipython') +test_eq(get_name(L.map), 'map') +``` + +------------------------------------------------------------------------ + +source + +### qual_name + +> qual_name (obj) + +*Get the qualified name of `obj`* + +``` python +assert qual_name(docscrape) == 'fastcore.docscrape' +``` + +## Docments + +------------------------------------------------------------------------ + +source + +### docments + +> docments (elt, full=False, returns=True, eval_str=False) + +*Generates a `docment`* + +The returned `dict` has parameter names as keys, docments as values. The +return value comment appears in the `return`, unless `returns=False`. +Using the `add` definition above, we get: + +``` python +def add( + a:int, # the 1st number to add + b=0, # the 2nd number to add +)->int: # the result of adding `a` to `b` + "The sum of two numbers." + return a+b + +docments(add) +``` + +``` json +{ 'a': 'the 1st number to add', + 'b': 'the 2nd number to add', + 'return': 'the result of adding `a` to `b`'} +``` + +If you pass `full=True`, the values are `dict` of defaults, types, and +docments as values. Note that the type annotation is inferred from the +default value, if the annotation is empty and a default is supplied. + +``` python +docments(add, full=True) +``` + +``` json +{ 'a': { 'anno': , + 'default': , + 'docment': 'the 1st number to add'}, + 'b': { 'anno': , + 'default': 0, + 'docment': 'the 2nd number to add'}, + 'return': { 'anno': , + 'default': , + 'docment': 'the result of adding `a` to `b`'}} +``` + +To evaluate stringified annotations (from python 3.10), use `eval_str`: + +``` python +docments(add, full=True, eval_str=True)['a'] +``` + +``` json +{ 'anno': , + 'default': , + 'docment': 'the 1st number to add'} +``` + +If you need more space to document a parameter, place one or more lines +of comments above the parameter, or above the return type. You can +mix-and-match these docment styles: + +``` python +def add( + # The first operand + a:int, + # This is the second of the operands to the *addition* operator. + # Note that passing a negative value here is the equivalent of the *subtraction* operator. + b:int, +)->int: # The result is calculated using Python's builtin `+` operator. + "Add `a` to `b`" + return a+b +``` + +``` python +docments(add) +``` + +``` json +{ 'a': 'The first operand', + 'b': 'This is the second of the operands to the *addition* operator.\n' + 'Note that passing a negative value here is the equivalent of the ' + '*subtraction* operator.', + 'return': "The result is calculated using Python's builtin `+` operator."} +``` + +Docments works with async functions, too: + +``` python +async def add_async( + # The first operand + a:int, + # This is the second of the operands to the *addition* operator. + # Note that passing a negative value here is the equivalent of the *subtraction* operator. + b:int, +)->int: # The result is calculated using Python's builtin `+` operator. + "Add `a` to `b`" + return a+b +``` + +``` python +test_eq(docments(add_async), docments(add)) +``` + +You can also use docments with classes and methods: + +``` python +class Adder: + "An addition calculator" + def __init__(self, + a:int, # First operand + b:int, # 2nd operand + ): self.a,self.b = a,b + + def calculate(self + )->int: # Integral result of addition operator + "Add `a` to `b`" + return a+b +``` + +``` python +docments(Adder) +``` + +``` json +{'a': 'First operand', 'b': '2nd operand', 'return': None} +``` + +``` python +docments(Adder.calculate) +``` + +``` json +{'return': 'Integral result of addition operator', 'self': None} +``` + +docments can also be extracted from numpy-style docstrings: + +``` python +print(add_np.__doc__) +``` + + The sum of two numbers. + + Used to demonstrate numpy-style docstrings. + + Parameters + ---------- + a : int + the 1st number to add + b : int + the 2nd number to add (default: 0) + + Returns + ------- + int + the result of adding `a` to `b` + +``` python +docments(add_np) +``` + +``` json +{ 'a': 'the 1st number to add', + 'b': 'the 2nd number to add (default: 0)', + 'return': 'the result of adding `a` to `b`'} +``` + +You can even mix and match docments and numpy parameters: + +``` python +def add_mixed(a:int, # the first number to add + b + )->int: # the result + """The sum of two numbers. + +Parameters +---------- +b : int + the 2nd number to add (default: 0)""" + return a+b +``` + +``` python +docments(add_mixed, full=True) +``` + +``` json +{ 'a': { 'anno': , + 'default': , + 'docment': 'the first number to add'}, + 'b': { 'anno': 'int', + 'default': , + 'docment': 'the 2nd number to add (default: 0)'}, + 'return': { 'anno': , + 'default': , + 'docment': 'the result'}} +``` + +You can use docments with dataclasses, however if the class was defined +in online notebook, docments will not contain parameters’ comments. This +is because the source code is not available in the notebook. After +converting the notebook to a module, the docments will be available. +Thus, documentation will have correct parameters’ comments. + +Docments even works with +[`delegates`](https://fastcore.fast.ai/meta.html#delegates): + +``` python +from fastcore.meta import delegates +``` + +``` python +def _a(a:int=2): return a # First + +@delegates(_a) +def _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second + +docments(_b) +``` + +``` json +{'a': 'First', 'b': 'Second', 'return': None} +``` + +## Extract docstrings + +------------------------------------------------------------------------ + +source + +### extract_docstrings + +> extract_docstrings (code) + +*Create a dict from function/class/method names to tuples of docstrings +and param lists* + +``` python +sample_code = """ +"This is a module." + +def top_func(a, b, *args, **kw): + "This is top-level." + pass + +class SampleClass: + "This is a class." + + def __init__(self, x, y): + "Constructor for SampleClass." + pass + + def method1(self, param1): + "This is method1." + pass + + def _private_method(self): + "This should not be included." + pass + +class AnotherClass: + def __init__(self, a, b): + "This class has no separate docstring." + pass""" + +exp = {'_module': ('This is a module.', ''), + 'top_func': ('This is top-level.', 'a, b, *args, **kw'), + 'SampleClass': ('This is a class.', 'self, x, y'), + 'SampleClass.method1': ('This is method1.', 'self, param1'), + 'AnotherClass': ('This class has no separate docstring.', 'self, a, b')} +test_eq(extract_docstrings(sample_code), exp) +```# Meta + + + +``` python +from fastcore.foundation import * +from nbdev.showdoc import * +from fastcore.nb_imports import * +``` + +See this [blog post](https://realpython.com/python-metaclasses/) for +more information about metaclasses. + +- [`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) + preserves information that enables [intropsection of + signatures](https://www.python.org/dev/peps/pep-0362/#:~:text=Python%20has%20always%20supported%20powerful,fully%20reconstruct%20the%20function's%20signature.) + (i.e. tab completion in IDEs) when certain types of inheritence would + otherwise obfuscate this introspection. +- [`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta) + ensures that the classes defined with it run `__pre_init__` and + `__post_init__` (without having to write `self.__pre_init__()` and + `self.__post_init__()` in the actual `init` +- [`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) gives + the + [`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta) + functionality and ensures classes defined with it don’t re-create an + object of their type whenever it’s passed to the constructor +- [`BypassNewMeta`](https://fastcore.fast.ai/meta.html#bypassnewmeta) + ensures classes defined with it can easily be casted form objects they + subclass. + +------------------------------------------------------------------------ + +source + +### test_sig + +> test_sig (f, b) + +*Test the signature of an object* + +``` python +def func_1(h,i,j): pass +def func_2(h,i=3, j=[5,6]): pass + +class T: + def __init__(self, a, b): pass + +test_sig(func_1, '(h, i, j)') +test_sig(func_2, '(h, i=3, j=[5, 6])') +test_sig(T, '(a, b)') +``` + +------------------------------------------------------------------------ + +source + +### FixSigMeta + +> FixSigMeta (name, bases, dict) + +*A metaclass that fixes the signature on classes that override +`__new__`* + +When you inherit from a class that defines `__new__`, or a metaclass +that defines `__call__`, the signature of your `__init__` method is +obfuscated such that tab completion no longer works. +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) fixes this +issue and restores signatures. + +To understand what +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) does, it +is useful to inspect an object’s signature. You can inspect the +signature of an object with `inspect.signature`: + +``` python +class T: + def __init__(self, a, b, c): pass + +inspect.signature(T) +``` + + + +This corresponds to tab completion working in the normal way: + +Tab completion in a Jupyter Notebook. + +However, when you inherhit from a class that defines `__new__` or a +metaclass that defines `__call__` this obfuscates the signature by +overriding your class with the signature of `__new__`, which prevents +tab completion from displaying useful information: + +``` python +class Foo: + def __new__(self, **args): pass + +class Bar(Foo): + def __init__(self, d, e, f): pass + +inspect.signature(Bar) +``` + + + +Tab completion in a Jupyter Notebook. + +Finally, the signature and tab completion can be restored by inheriting +from the metaclass +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) as shown +below: + +``` python +class Bar(Foo, metaclass=FixSigMeta): + def __init__(self, d, e, f): pass + +test_sig(Bar, '(d, e, f)') +inspect.signature(Bar) +``` + + + +Tab completion in a Jupyter Notebook. + +If you need to define a metaclass that overrides `__call__` (as done in +[`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta)), +you need to inherit from +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) instead of +`type` when constructing the metaclass to preserve the signature in +`__init__`. Be careful not to override `__new__` when doing this: + +``` python +class TestMeta(FixSigMeta): + # __new__ comes from FixSigMeta + def __call__(cls, *args, **kwargs): pass + +class T(metaclass=TestMeta): + def __init__(self, a, b): pass + +test_sig(T, '(a, b)') +``` + +On the other hand, if you fail to inherit from +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) when +inheriting from a metaclass that overrides `__call__`, your signature +will reflect that of `__call__` instead (which is often undesirable): + +``` python +class GenericMeta(type): + "A boilerplate metaclass that doesn't do anything for testing." + def __new__(cls, name, bases, dict): + return super().__new__(cls, name, bases, dict) + def __call__(cls, *args, **kwargs): pass + +class T2(metaclass=GenericMeta): + def __init__(self, a, b): pass + +# We can avoid this by inheriting from the metaclass `FixSigMeta` +test_sig(T2, '(*args, **kwargs)') +``` + +------------------------------------------------------------------------ + +source + +### PrePostInitMeta + +> PrePostInitMeta (name, bases, dict) + +*A metaclass that calls optional `__pre_init__` and `__post_init__` +methods* + +`__pre_init__` and `__post_init__` are useful for initializing variables +or performing tasks prior to or after `__init__` being called, +respectively. Fore example: + +``` python +class _T(metaclass=PrePostInitMeta): + def __pre_init__(self): self.a = 0; + def __init__(self,b=0): self.b = self.a + 1; assert self.b==1 + def __post_init__(self): self.c = self.b + 2; assert self.c==3 + +t = _T() +test_eq(t.a, 0) # set with __pre_init__ +test_eq(t.b, 1) # set with __init__ +test_eq(t.c, 3) # set with __post_init__ +``` + +One use for +[`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta) +is avoiding the `__super__().__init__()` boilerplate associated with +subclassing, such as used in +[`AutoInit`](https://fastcore.fast.ai/meta.html#autoinit). + +------------------------------------------------------------------------ + +source + +### AutoInit + +> AutoInit (*args, **kwargs) + +*Same as `object`, but no need for subclasses to call +`super().__init__`* + +This is normally used as a +[mixin](https://www.residentmar.io/2019/07/07/python-mixins.html), eg: + +``` python +class TestParent(): + def __init__(self): self.h = 10 + +class TestChild(AutoInit, TestParent): + def __init__(self): self.k = self.h + 2 + +t = TestChild() +test_eq(t.h, 10) # h=10 is initialized in the parent class +test_eq(t.k, 12) +``` + +------------------------------------------------------------------------ + +source + +### NewChkMeta + +> NewChkMeta (name, bases, dict) + +*Metaclass to avoid recreating object passed to constructor* + +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) is used +when an object of the same type is the first argument to your class’s +constructor (i.e. the `__init__` function), and you would rather it not +create a new object but point to the same exact object. + +This is used in [`L`](https://fastcore.fast.ai/foundation.html#l), for +example, to avoid creating a new object when the object is already of +type [`L`](https://fastcore.fast.ai/foundation.html#l). This allows the +users to defenisvely instantiate an +[`L`](https://fastcore.fast.ai/foundation.html#l) object and just return +a reference to the same object if it already happens to be of type +[`L`](https://fastcore.fast.ai/foundation.html#l). + +For example, the below class `_T` **optionally** accepts an object `o` +as its first argument. A new object is returned upon instantiation per +usual: + +``` python +class _T(): + "Testing" + def __init__(self, o): + # if `o` is not an object without an attribute `foo`, set foo = 1 + self.foo = getattr(o,'foo',1) +``` + +``` python +t = _T(3) +test_eq(t.foo,1) # 1 was not of type _T, so foo = 1 + +t2 = _T(t) #t1 is of type _T +assert t is not t2 # t1 and t2 are different objects +``` + +However, if we want `_T` to return a reference to the same object when +passed an an object of type `_T` we can inherit from the +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) class as +illustrated below: + +``` python +class _T(metaclass=NewChkMeta): + "Testing with metaclass NewChkMeta" + def __init__(self, o=None, b=1): + # if `o` is not an object without an attribute `foo`, set foo = 1 + self.foo = getattr(o,'foo',1) + self.b = b +``` + +We can now test `t` and `t2` are now pointing at the same object when +using this new definition of `_T`: + +``` python +t = _T(3) +test_eq(t.foo,1) # 1 was not of type _T, so foo = 1 + +t2 = _T(t) # t2 will now reference t + +test_is(t, t2) # t and t2 are the same object +t2.foo = 5 # this will also change t.foo to 5 because it is the same object +test_eq(t.foo, 5) +test_eq(t2.foo, 5) +``` + +However, there is one exception to how +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) works. +**If you pass any additional arguments in the constructor a new object +is returned**, even if the first object is of the same type. For +example, consider the below example where we pass the additional +argument `b` into the constructor: + +``` python +t3 = _T(t, b=1) +assert t3 is not t + +t4 = _T(t) # without any arguments the constructor will return a reference to the same object +assert t4 is t +``` + +Finally, it should be noted that +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) as well as +all other metaclases in this section, inherit from +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta). This +means class signatures will always be preserved when inheriting from +this metaclass (see docs for +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) for more +details): + +``` python +test_sig(_T, '(o=None, b=1)') +``` + +------------------------------------------------------------------------ + +source + +### BypassNewMeta + +> BypassNewMeta (name, bases, dict) + +*Metaclass: casts `x` to this class if it’s of type `cls._bypass_type`* + +[`BypassNewMeta`](https://fastcore.fast.ai/meta.html#bypassnewmeta) is +identical to +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta), except +for checking for a class as the same type, we instead check for a class +of type specified in attribute `_bypass_type`. + +In NewChkMeta, objects of the same type passed to the constructor +(without arguments) would result into a new variable referencing the +same object. However, with +[`BypassNewMeta`](https://fastcore.fast.ai/meta.html#bypassnewmeta) this +only occurs if the type matches the `_bypass_type` of the class you are +defining: + +``` python +class _TestA: pass +class _TestB: pass + +class _T(_TestA, metaclass=BypassNewMeta): + _bypass_type=_TestB + def __init__(self,x): self.x=x +``` + +In the below example, `t` does not refer to `t2` because `t` is of type +`_TestA` while `_T._bypass_type` is of type `TestB`: + +``` python +t = _TestA() +t2 = _T(t) +assert t is not t2 +``` + +However, if `t` is set to `_TestB` to match `_T._bypass_type`, then both +`t` and `t2` will refer to the same object. + +``` python +t = _TestB() +t2 = _T(t) +t2.new_attr = 15 + +test_is(t, t2) +# since t2 just references t these will be the same +test_eq(t.new_attr, t2.new_attr) + +# likewise, chaning an attribute on t will also affect t2 because they both point to the same object. +t.new_attr = 9 +test_eq(t2.new_attr, 9) +``` + +## Metaprogramming + +------------------------------------------------------------------------ + +source + +### empty2none + +> empty2none (p) + +*Replace `Parameter.empty` with `None`* + +------------------------------------------------------------------------ + +source + +### anno_dict + +> anno_dict (f) + +*`__annotation__ dictionary with`empty`cast to`None\`, returning empty +if doesn’t exist* + +``` python +def _f(a:int, b:L)->str: ... +test_eq(anno_dict(_f), {'a': int, 'b': L, 'return': str}) +``` + +------------------------------------------------------------------------ + +source + +### use_kwargs_dict + +> use_kwargs_dict (keep=False, **kwargs) + +*Decorator: replace `**kwargs` in signature with `names` params* + +Replace all `**kwargs` with named arguments like so: + +``` python +@use_kwargs_dict(y=1,z=None) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, y=1, z=None)') +``` + +Add named arguments, but optionally keep `**kwargs` by setting +`keep=True`: + +``` python +@use_kwargs_dict(y=1,z=None, keep=True) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, y=1, z=None, **kwargs)') +``` + +------------------------------------------------------------------------ + +source + +### use_kwargs + +> use_kwargs (names, keep=False) + +*Decorator: replace `**kwargs` in signature with `names` params* + +[`use_kwargs`](https://fastcore.fast.ai/meta.html#use_kwargs) is +different than +[`use_kwargs_dict`](https://fastcore.fast.ai/meta.html#use_kwargs_dict) +as it only replaces `**kwargs` with named parameters without any default +values: + +``` python +@use_kwargs(['y', 'z']) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, y=None, z=None)') +``` + +You may optionally keep the `**kwargs` argument in your signature by +setting `keep=True`: + +``` python +@use_kwargs(['y', 'z'], keep=True) +def foo(a, *args, b=1, **kwargs): pass +test_sig(foo, '(a, *args, b=1, y=None, z=None, **kwargs)') +``` + +------------------------------------------------------------------------ + +source + +### delegates + +> delegates (to:function=None, keep=False, but:list=None) + +*Decorator: replace `**kwargs` in signature with params from `to`* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
tofunctionNoneDelegatee
keepboolFalseKeep kwargs in decorated function?
butlistNoneExclude these parameters from signature
+ +A common Python idiom is to accept `**kwargs` in addition to named +parameters that are passed onto other function calls. It is especially +common to use `**kwargs` when you want to give the user an option to +override default parameters of any functions or methods being called by +the parent function. + +For example, suppose we have have a function `foo` that passes arguments +to `baz` like so: + +``` python +def baz(a, b:int=2, c:int=3): return a + b + c + +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +assert foo(c=1, a=1) == 7 +``` + +The problem with this approach is the api for `foo` is obfuscated. Users +cannot introspect what the valid arguments for `**kwargs` are without +reading the source code. When a user tries tries to introspect the +signature of `foo`, they are presented with this: + +``` python +inspect.signature(foo) +``` + + + +We can address this issue by using the decorator +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) to include +parameters from other functions. For example, if we apply the +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) decorator to +`foo` to include parameters from `baz`: + +``` python +@delegates(baz) +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +test_sig(foo, '(c, a, *, b: int = 2)') +inspect.signature(foo) +``` + + + +We can optionally decide to keep `**kwargs` by setting `keep=True`: + +``` python +@delegates(baz, keep=True) +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) +``` + + + +It is important to note that **only parameters with default parameters +are included**. For example, in the below scenario only `c`, but NOT `e` +and `d` are included in the signature of `foo` after applying +[`delegates`](https://fastcore.fast.ai/meta.html#delegates): + +``` python +def basefoo(e, d, c=2): pass + +@delegates(basefoo) +def foo(a, b=1, **kwargs): pass +inspect.signature(foo) # e and d are not included b/c they don't have default parameters. +``` + + + +The reason that required arguments (i.e. those without default +parameters) are automatically excluded is that you should be explicitly +implementing required arguments into your function’s signature rather +than relying on +[`delegates`](https://fastcore.fast.ai/meta.html#delegates). + +Additionally, you can exclude specific parameters from being included in +the signature with the `but` parameter. In the example below, we exclude +the parameter `d`: + +``` python +def basefoo(e, c=2, d=3): pass + +@delegates(basefoo, but= ['d']) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, c=2)') +inspect.signature(foo) +``` + + + +You can also use +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) between +methods in a class. Here is an example of +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) with class +methods: + +``` python +# example 1: class methods +class _T(): + @classmethod + def foo(cls, a=1, b=2): + pass + + @classmethod + @delegates(foo) + def bar(cls, c=3, **kwargs): + pass + +test_sig(_T.bar, '(c=3, *, a=1, b=2)') +``` + +Here is the same example with instance methods: + +``` python +# example 2: instance methods +class _T(): + def foo(self, a=1, b=2): + pass + + @delegates(foo) + def bar(self, c=3, **kwargs): + pass + +t = _T() +test_sig(t.bar, '(c=3, *, a=1, b=2)') +``` + +You can also delegate between classes. By default, the +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) decorator +will delegate to the superclass: + +``` python +class BaseFoo: + def __init__(self, e, c=2): pass + +@delegates()# since no argument was passsed here we delegate to the superclass +class Foo(BaseFoo): + def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs) + +test_sig(Foo, '(a, b=1, *, c=2)') +``` + +------------------------------------------------------------------------ + +source + +### method + +> method (f) + +*Mark `f` as a method* + +The [`method`](https://fastcore.fast.ai/meta.html#method) function is +used to change a function’s type to a method. In the below example we +change the type of `a` from a function to a method: + +``` python +def a(x=2): return x + 1 +assert type(a).__name__ == 'function' + +a = method(a) +assert type(a).__name__ == 'method' +``` + +------------------------------------------------------------------------ + +source + +### funcs_kwargs + +> funcs_kwargs (as_method=False) + +*Replace methods in `cls._methods` with those from `kwargs`* + +The `func_kwargs` decorator allows you to add a list of functions or +methods to an existing class. You must set this list as a class +attribute named `_methods` when defining your class. Additionally, you +must incldue the `**kwargs` argument in the `___init__` method of your +class. + +After defining your class this way, you can add functions to your class +upon instantation as illusrated below. + +For example, we define class `T` to allow adding the function `b` to +class `T` as follows (note that this function is stored as an attribute +of `T` and doesn’t have access to `cls` or `self`): + +``` python +@funcs_kwargs +class T: + _methods=['b'] # allows you to add method b upon instantiation + def __init__(self, f=1, **kwargs): pass # don't forget to include **kwargs in __init__ + def a(self): return 1 + def b(self): return 2 + +t = T() +test_eq(t.a(), 1) +test_eq(t.b(), 2) +``` + +Because we defined the class `T` this way, the signature of `T` +indicates the option to add the function or method(s) specified in +`_methods`. In this example, `b` is added to the signature: + +``` python +test_sig(T, '(f=1, *, b=None)') +inspect.signature(T) +``` + + + +You can now add the function `b` to class `T` upon instantiation: + +``` python +def _new_func(): return 5 + +t = T(b = _new_func) +test_eq(t.b(), 5) +``` + +If you try to add a function with a name not listed in `_methods` it +will be ignored. In the below example, the attempt to add a function +named `a` is ignored: + +``` python +t = T(a = lambda:3) +test_eq(t.a(), 1) # the attempt to add a is ignored and uses the original method instead. +``` + +Note that you can also add methods not defined in the original class as +long it is specified in the `_methods` attribute: + +``` python +@funcs_kwargs +class T: + _methods=['c'] + def __init__(self, f=1, **kwargs): pass + +t = T(c = lambda: 4) +test_eq(t.c(), 4) +``` + +Until now, these examples showed how to add functions stored as an +instance attribute without access to `self`. However, if you need access +to `self` you can set `as_method=True` in the `func_kwargs` decorator to +add a method instead: + +``` python +def _f(self,a=1): return self.num + a # access the num attribute from the instance + +@funcs_kwargs(as_method=True) +class T: + _methods=['b'] + num = 5 + +t = T(b = _f) # adds method b +test_eq(t.b(5), 10) # self.num + 5 = 10 +``` + +Here is an example of how you might use this functionality with +inheritence: + +``` python +def _f(self,a=1): return self.num * a #multiply instead of add + +class T2(T): + def __init__(self,num): + super().__init__(b = _f) # add method b from the super class + self.num=num + +t = T2(num=3) +test_eq(t.b(a=5), 15) # 3 * 5 = 15 +test_sig(T2, '(num)') +```
# Script - CLI + + + +Part of [fast.ai](https://www.fast.ai)’s toolkit for delightful +developer experiences. + +## Overview + +Sometimes, you want to create a quick script, either for yourself, or +for others. But in Python, that involves a whole lot of boilerplate and +ceremony, especially if you want to support command line arguments, +provide help, and other niceties. You can use +[argparse](https://docs.python.org/3/library/argparse.html) for this +purpose, which comes with Python, but it’s complex and verbose. + +`fastcore.script` makes life easier. There are much fancier modules to +help you write scripts (we recommend [Python +Fire](https://github.com/google/python-fire), and +[Click](https://click.palletsprojects.com/en/7.x/) is also popular), but +fastcore.script is very fast and very simple. In fact, it’s \<50 lines +of code! Basically, it’s just a little wrapper around `argparse` that +uses modern Python features and some thoughtful defaults to get rid of +the boilerplate. + +For full details, see the [docs](https://fastcore.script.fast.ai) for +`core`. + +## Example + +Here’s a complete example (available in `examples/test_fastcore.py`): + +``` python +from fastcore.script import * +@call_parse +def main(msg:str, # The message + upper:bool): # Convert to uppercase? + "Print `msg`, optionally converting to uppercase" + print(msg.upper() if upper else msg) +``` + +If you copy that info a file and run it, you’ll see: + + $ examples/test_fastcore.py --help + usage: test_fastcore.py [-h] [--upper] msg + + Print `msg`, optionally converting to uppercase + + positional arguments: + msg The message + + optional arguments: + -h, --help show this help message and exit + --upper Convert to uppercase? (default: False) + +As you see, we didn’t need any `if __name__ == "__main__"`, we didn’t +have to parse arguments, we just wrote a function, added a decorator to +it, and added some annotations to our function’s parameters. As a bonus, +we can also use this function directly from a REPL such as Jupyter +Notebook - it’s not just for command line scripts! + +You should provide a default (after the `=`) for any *optional* +parameters. If you don’t provide a default for a parameter, then it will +be a *positional* parameter. + +## Param annotations + +If you want to use the full power of `argparse`, you can do so by using +[`Param`](https://fastcore.fast.ai/script.html#param) annotations +instead of type annotations and +[docments](https://fastcore.fast.ai/docments.html), like so: + +``` python +from fastcore.script import * +@call_parse +def main(msg:Param("The message", str), + upper:Param("Convert to uppercase?", store_true)): + "Print `msg`, optionally converting to uppercase" + print(msg.upper() if upper else msg) +``` + +If you use this approach, then each parameter in your function should +have an annotation `Param(...)` (as in the example above). You can pass +the following when calling +[`Param`](https://fastcore.fast.ai/script.html#param): +`help`,`type`,`opt`,`action`,`nargs`,`const`,`choices`,`required` . +Except for `opt`, all of these are just passed directly to `argparse`, +so you have all the power of that module at your disposal. Generally +you’ll want to pass at least `help` (since this is provided as the help +string for that parameter) and `type` (to ensure that you get the type +of data you expect). `opt` is a bool that defines whether a param is +optional or required (positional) - but you’ll generally not need to set +this manually, because fastcore.script will set it for you automatically +based on *default* values. + +## setuptools scripts + +There’s a really nice feature of pip/setuptools that lets you create +commandline scripts directly from functions, makes them available in the +`PATH`, and even makes your scripts cross-platform (e.g. in Windows it +creates an exe). fastcore.script supports this feature too. The trick to +making a function available as a script is to add a `console_scripts` +section to your setup file, of the form: +`script_name=module:function_name`. E.g. in this case we use: +`test_fastcore.script=fastcore.script.test_cli:main`. With this, you can +then just type `test_fastcore.script` at any time, from any directory, +and your script will be called (once it’s installed using one of the +methods below). + +You don’t actually have to write a `setup.py` yourself. Instead, just +use [nbdev](https://nbdev.fast.ai). Then modify `settings.ini` as +appropriate for your module/script. To install your script directly, you +can type `pip install -e .`. Your script, when installed this way (it’s +called an [editable +install](http://codumentary.blogspot.com/2014/11/python-tip-of-year-pip-install-editable.html)), +will automatically be up to date even if you edit it - there’s no need +to reinstall it after editing. With nbdev you can even make your module +and script available for installation directly from pip and conda by +running `make release`. + +## API details + +------------------------------------------------------------------------ + +source + +### store_true + +> store_true () + +*Placeholder to pass to +[`Param`](https://fastcore.fast.ai/script.html#param) for +[`store_true`](https://fastcore.fast.ai/script.html#store_true) action* + +------------------------------------------------------------------------ + +source + +### store_false + +> store_false () + +*Placeholder to pass to +[`Param`](https://fastcore.fast.ai/script.html#param) for +[`store_false`](https://fastcore.fast.ai/script.html#store_false) +action* + +------------------------------------------------------------------------ + +source + +### bool_arg + +> bool_arg (v) + +*Use as `type` for [`Param`](https://fastcore.fast.ai/script.html#param) +to get `bool` behavior* + +------------------------------------------------------------------------ + +source + +### clean_type_str + +> clean_type_str (x:str) + +``` python +class Test: pass + +test_eq(clean_type_str(argparse.ArgumentParser), 'argparse.ArgumentParser') +test_eq(clean_type_str(Test), 'Test') +test_eq(clean_type_str(int), 'int') +test_eq(clean_type_str(float), 'float') +test_eq(clean_type_str(store_false), 'store_false') +``` + +------------------------------------------------------------------------ + +source + +### Param + +> Param (help='', type=None, opt=True, action=None, nargs=None, const=None, +> choices=None, required=None, default=None) + +*A parameter in a function used in +[`anno_parser`](https://fastcore.fast.ai/script.html#anno_parser) or +[`call_parse`](https://fastcore.fast.ai/script.html#call_parse)* + +``` python +test_eq(repr(Param("Help goes here")), '') +test_eq(repr(Param("Help", int)), 'int ') +test_eq(repr(Param(help=None, type=int)), 'int') +test_eq(repr(Param(help=None, type=None)), '') +``` + +Each parameter in your function should have an annotation `Param(...)`. +You can pass the following when calling +[`Param`](https://fastcore.fast.ai/script.html#param): +`help`,`type`,`opt`,`action`,`nargs`,`const`,`choices`,`required` +(i.e. it takes the same parameters as +`argparse.ArgumentParser.add_argument`, plus `opt`). Except for `opt`, +all of these are just passed directly to `argparse`, so you have all the +power of that module at your disposal. Generally you’ll want to pass at +least `help` (since this is provided as the help string for that +parameter) and `type` (to ensure that you get the type of data you +expect). + +`opt` is a bool that defines whether a param is optional or required +(positional) - but you’ll generally not need to set this manually, +because fastcore.script will set it for you automatically based on +*default* values. You should provide a default (after the `=`) for any +*optional* parameters. If you don’t provide a default for a parameter, +then it will be a *positional* parameter. + +Param’s `__repr__` also allows for more informative function annotation +when looking up the function’s doc using shift+tab. You see the type +annotation (if there is one) and the accompanying help documentation +with it. + +``` python +def f(required:Param("Required param", int), + a:Param("param 1", bool_arg), + b:Param("param 2", str)="test"): + "my docs" + ... +``` + +``` python +help(f) +``` + + Help on function f in module __main__: + + f(required: int , a: bool_arg , b: str = 'test') + my docs + +``` python +p = Param(help="help", type=int) +p.set_default(1) +test_eq(p.kwargs, {'help': 'help (default: 1)', 'type': int, 'default': 1}) +``` + +------------------------------------------------------------------------ + +source + +### anno_parser + +> anno_parser (func, prog:str=None) + +*Look at params (annotated with +[`Param`](https://fastcore.fast.ai/script.html#param)) in func and +return an `ArgumentParser`* + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
funcFunction to get arguments from
progstrNoneThe name of the program
+ +This converts a function with parameter annotations of type +[`Param`](https://fastcore.fast.ai/script.html#param) into an +`argparse.ArgumentParser` object. Function arguments with a default +provided are optional, and other arguments are positional. + +``` python +_en = str_enum('_en', 'aa','bb','cc') +def f(required:Param("Required param", int), + a:Param("param 1", bool_arg), + b:Param("param 2", str)="test", + c:Param("param 3", _en)=_en.aa): + "my docs" + ... + +p = anno_parser(f, 'progname') +p.print_help() +``` + + usage: progname [-h] [--b B] [--c {aa,bb,cc}] required a + + my docs + + positional arguments: + required Required param + a param 1 + + optional arguments: + -h, --help show this help message and exit + --b B param 2 (default: test) + --c {aa,bb,cc} param 3 (default: aa) + +It also works with type annotations and docments: + +``` python +def g(required:int, # Required param + a:bool_arg, # param 1 + b="test", # param 2 + c:_en=_en.aa): # param 3 + "my docs" + ... + +p = anno_parser(g, 'progname') +p.print_help() +``` + + usage: progname [-h] [--b B] [--c {aa,bb,cc}] required a + + my docs + + positional arguments: + required Required param + a param 1 + + optional arguments: + -h, --help show this help message and exit + --b B param 2 (default: test) + --c {aa,bb,cc} param 3 (default: aa) + +------------------------------------------------------------------------ + +source + +### args_from_prog + +> args_from_prog (func, prog) + +*Extract args from `prog`* + +Sometimes it’s convenient to extract arguments from the actual name of +the called program. +[`args_from_prog`](https://fastcore.fast.ai/script.html#args_from_prog) +will do this, assuming that names and values of the params are separated +by a `#`. Optionally there can also be a prefix separated by `##` +(double underscore). + +``` python +exp = {'a': False, 'b': 'baa'} +test_eq(args_from_prog(f, 'foo##a#0#b#baa'), exp) +test_eq(args_from_prog(f, 'a#0#b#baa'), exp) +``` + +------------------------------------------------------------------------ + +source + +### call_parse + +> call_parse (func=None, nested=False) + +*Decorator to create a simple CLI from `func` using +[`anno_parser`](https://fastcore.fast.ai/script.html#anno_parser)* + +``` python +@call_parse +def test_add( + a:int=0, # param a + b:int=0 # param 1 +): + "Add up `a` and `b`" + return a + b +``` + +[`call_parse`](https://fastcore.fast.ai/script.html#call_parse) +decorated functions work as regular functions and also as command-line +interface functions. + +``` python +test_eq(test_add(1,2), 3) +``` + +This is the main way to use `fastcore.script`; decorate your function +with [`call_parse`](https://fastcore.fast.ai/script.html#call_parse), +add [`Param`](https://fastcore.fast.ai/script.html#param) annotations +(as shown above) or type annotations and docments, and it can then be +used as a script. + +Use the `nested` keyword argument to create nested parsers, where +earlier parsers consume only their known args from `sys.argv` before +later parsers are used. This is useful to create one command line +application that executes another. For example: + +``` sh +myrunner --keyword 1 script.py -- +``` + +A separating `--` after the first application’s args is recommended +though not always required, otherwise args may be parsed in unexpected +ways. For example: + +``` sh +myrunner script.py -h +``` + +would display `myrunner`’s help and not `script.py`’s.
# XDG + + + +See the [XDG Base Directory +Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) +for more information. + +## Overview + +[`xdg_cache_home`](https://fastcore.fast.ai/xdg.html#xdg_cache_home), +[`xdg_config_home`](https://fastcore.fast.ai/xdg.html#xdg_config_home), +[`xdg_data_home`](https://fastcore.fast.ai/xdg.html#xdg_data_home), and +[`xdg_state_home`](https://fastcore.fast.ai/xdg.html#xdg_state_home) +return `pathlib.Path` objects containing the value of the environment +variable named `XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, and +`XDG_STATE_HOME` respectively, or the default defined in the +specification if the environment variable is unset, empty, or contains a +relative path rather than absolute path. + +[`xdg_config_dirs`](https://fastcore.fast.ai/xdg.html#xdg_config_dirs) +and [`xdg_data_dirs`](https://fastcore.fast.ai/xdg.html#xdg_data_dirs) +return a list of `pathlib.Path` objects containing the value, split on +colons, of the environment variable named `XDG_CONFIG_DIRS` and +`XDG_DATA_DIRS` respectively, or the default defined in the +specification if the environment variable is unset or empty. Relative +paths are ignored, as per the specification. + +[`xdg_runtime_dir`](https://fastcore.fast.ai/xdg.html#xdg_runtime_dir) +returns a `pathlib.Path` object containing the value of the +`XDG_RUNTIME_DIR` environment variable, or `None` if the environment +variable is not set, or contains a relative path rather than absolute +path. + +## Helpers + +We’ll start by defining a context manager that temporarily sets an +environment variable to demonstrate the behaviour of each helper +function: + +``` python +from contextlib import contextmanager +``` + +``` python +@contextmanager +def env(variable, value): + old = os.environ.get(variable, None) + try: + os.environ[variable] = value + yield + finally: + if old is None: del os.environ[variable] + else: os.environ[variable] = old +``` + +------------------------------------------------------------------------ + +source + +### xdg_cache_home + +> xdg_cache_home () + +*Path corresponding to `XDG_CACHE_HOME`* + +``` python +from fastcore.test import * +``` + +``` python +test_eq(xdg_cache_home(), Path.home()/'.cache') +with env('XDG_CACHE_HOME', '/home/fastai/.cache'): + test_eq(xdg_cache_home(), Path('/home/fastai/.cache')) +``` + +------------------------------------------------------------------------ + +source + +### xdg_config_dirs + +> xdg_config_dirs () + +*Paths corresponding to `XDG_CONFIG_DIRS`* + +``` python +test_eq(xdg_config_dirs(), [Path('/etc/xdg')]) +with env('XDG_CONFIG_DIRS', '/home/fastai/.xdg:/home/fastai/.config'): + test_eq(xdg_config_dirs(), [Path('/home/fastai/.xdg'), Path('/home/fastai/.config')]) +``` + +------------------------------------------------------------------------ + +source + +### xdg_config_home + +> xdg_config_home () + +*Path corresponding to `XDG_CONFIG_HOME`* + +``` python +test_eq(xdg_config_home(), Path.home()/'.config') +with env('XDG_CONFIG_HOME', '/home/fastai/.config'): + test_eq(xdg_config_home(), Path('/home/fastai/.config')) +``` + +------------------------------------------------------------------------ + +source + +### xdg_data_dirs + +> xdg_data_dirs () + +*Paths corresponding to XDG_DATA_DIRS\`* + +------------------------------------------------------------------------ + +source + +### xdg_data_home + +> xdg_data_home () + +*Path corresponding to `XDG_DATA_HOME`* + +``` python +test_eq(xdg_data_home(), Path.home()/'.local/share') +with env('XDG_DATA_HOME', '/home/fastai/.data'): + test_eq(xdg_data_home(), Path('/home/fastai/.data')) +``` + +------------------------------------------------------------------------ + +source + +### xdg_runtime_dir + +> xdg_runtime_dir () + +*Path corresponding to `XDG_RUNTIME_DIR`* + +------------------------------------------------------------------------ + +source + +### xdg_state_home + +> xdg_state_home () + +*Path corresponding to `XDG_STATE_HOME`* + +``` python +test_eq(xdg_state_home(), Path.home()/'.local/state') +with env('XDG_STATE_HOME', '/home/fastai/.state'): + test_eq(xdg_state_home(), Path('/home/fastai/.state')) +``` + +------------------------------------------------------------------------ + +Copyright © 2016-2021 Scott Stevenson + +Modifications copyright © 2022 onwards Jeremy Howard# XML + + + +``` python +from IPython.display import Markdown +from pprint import pprint + +from fastcore.test import test_eq +``` + +## FT functions + +------------------------------------------------------------------------ + +source + +### attrmap + +> attrmap (o) + +------------------------------------------------------------------------ + +source + +### valmap + +> valmap (o) + +------------------------------------------------------------------------ + +source + +### FT + +> FT (tag:str, cs:tuple, attrs:dict=None, void_=False, **kwargs) + +*A ‘Fast Tag’ structure, containing `tag`,`children`,and `attrs`* + +------------------------------------------------------------------------ + +source + +### ft + +> ft (tag:str, *c, void_:bool=False, attrmap: infunctioncallable>=, valmap: infunctioncallable>=, ft_cls=, +> **kw) + +*Create an [`FT`](https://fastcore.fast.ai/xml.html#ft) structure for +`to_xml()`* + +The main HTML tags are exported as +[`ft`](https://fastcore.fast.ai/xml.html#ft) partials. + +Attributes are passed as keywords. Use ‘klass’ and ‘fr’ instead of +‘class’ and ‘for’, to avoid Python reserved word clashes. + +------------------------------------------------------------------------ + +source + +### Html + +> Html (*c, doctype=True, **kwargs) + +*An HTML tag, optionally preceeded by `!DOCTYPE HTML`* + +``` python +samp = Html( + Head(Title('Some page')), + Body(Div('Some text\nanother line', (Input(name="jph's"), Img(src="filename", data=1)), + cls=['myclass', 'another'], + style={'padding':1, 'margin':2})) +) +pprint(samp) +``` + + (!doctype((),{'html': True}), + html((head((title(('Some page',),{}),),{}), body((div(('Some text\nanother line', input((),{'name': "jph's"}), img((),{'src': 'filename', 'data': 1})),{'class': 'myclass another', 'style': 'padding:1; margin:2'}),),{})),{})) + +``` python +elem = P('Some text', id="myid") +print(elem.tag) +print(elem.children) +print(elem.attrs) +``` + + p + ('Some text',) + {'id': 'myid'} + +You can get and set attrs directly: + +``` python +elem.id = 'newid' +print(elem.id, elem.get('id'), elem.get('foo', 'missing')) +elem +``` + + newid newid missing + + p(('Some text',),{'id': 'newid'}) + +------------------------------------------------------------------------ + +source + +### Safe + +\*str(object=’’) -\> str str(bytes_or_buffer\[, encoding\[, errors\]\]) +-\> str + +Create a new string object from the given object. If encoding or errors +is specified, then the object must expose a data buffer that will be +decoded using the given encoding and error handler. Otherwise, returns +the result of object.\_\_str\_\_() (if defined) or repr(object). +encoding defaults to sys.getdefaultencoding(). errors defaults to +‘strict’.\* + +## Conversion to XML/HTML + +------------------------------------------------------------------------ + +source + +### to_xml + +> to_xml (elm, lvl=0, indent=True, do_escape=True) + +*Convert [`ft`](https://fastcore.fast.ai/xml.html#ft) element tree into +an XML string* + +``` python +h = to_xml(samp, do_escape=False) +print(h) +``` + + + + + Some page + + +
+ Some text + another line +
+ + + +``` python +class PageTitle: + def __ft__(self): return H1("Hello") + +class HomePage: + def __ft__(self): return Div(PageTitle(), Div('hello')) + +h = to_xml(Div(HomePage())) +expected_output = """
+
+

Hello

+
hello
+
+
+""" +assert h == expected_output +``` + +``` python +print(h) +``` + +
+
+

Hello

+
hello
+
+
+ +``` python +h = to_xml(samp, indent=False) +print(h) +``` + + Some page
Some text + another line
+ +Interoperability both directions with Django and Jinja using the +[**html**() +protocol](https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.escape): + +``` python +def _esc(s): return s.__html__() if hasattr(s, '__html__') else Safe(escape(s)) + +r = Safe('Hello from Django') +print(to_xml(Div(r))) +print(_esc(Div(P('Hello from fastcore <3')))) +``` + +
Hello from Django
+ +
+

Hello from fastcore <3

+
+ +## Display + +------------------------------------------------------------------------ + +source + +### highlight + +> highlight (s, lang='html') + +*Markdown to syntax-highlight `s` in language `lang`* + +------------------------------------------------------------------------ + +source + +### showtags + +> showtags (s) + +You can also reorder the children to come *after* the attrs, if you use +this alternative syntax for [`FT`](https://fastcore.fast.ai/xml.html#ft) +where the children are in a second pair of `()` (behind the scenes this +is because [`FT`](https://fastcore.fast.ai/xml.html#ft) implements +`__call__` to add children). + +``` python +Body(klass='myclass')( + Div(style='padding:3px')( + 'Some text 1<2', + I(spurious=True)('in italics'), + Input(name='me'), + Img(src="filename", data=1) + ) +) +``` + +``` html + +
+Some text 1<2in italics +
+ +``` + +------------------------------------------------------------------------ + +source + +### **getattr** + +> __getattr__ (tag)
\ No newline at end of file diff --git a/llms-ctx.txt b/llms-ctx.txt new file mode 100644 index 00000000..9b549b05 --- /dev/null +++ b/llms-ctx.txt @@ -0,0 +1,2418 @@ +fastcore adds to Python features inspired by other languages, like multiple dispatch from Julia, mixins from Ruby, and currying, binding, and more from Haskell. It also adds some “missing features” and clean up some rough edges in the Python standard library, such as simplifying parallel processing, and bringing ideas from NumPy over to Python’s list type. + +Here are some tips on using fastcore: + +- **Liberal imports**: Utilize `from fastcore.module import *` freely. The library is designed for safe wildcard imports. +- **Enhanced list operations**: Substitute `list` with `L`. This provides advanced indexing, method chaining, and additional functionality while maintaining list-like behavior. +- **Extend existing classes**: Apply the `@patch` decorator to add methods to classes, including built-ins, without subclassing. This enables more flexible code organization. +- **Streamline class initialization**: In `__init__` methods, use `store_attr()` to efficiently set multiple attributes, reducing repetitive assignment code. +- **Explicit keyword arguments**: Apply the `delegates` decorator to functions to replace `**kwargs` with specific parameters, enhancing IDE support and documentation. +- **Optimize parallel execution**: Leverage fastcore's enhanced `ThreadPoolExecutor` and `ProcessPoolExecutor` for simplified concurrent processing. +- **Expressive testing**: Prefer fastcore's testing functions like `test_eq`, `test_ne`, `test_close` for more readable and informative test assertions. +- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to `pathlib.Path`. +- **Flexible data structures**: Convert between dictionaries and attribute-access objects using `dict2obj` and `obj2dict` for more intuitive data handling. +- **Data pipeline construction**: Employ `Transform` and `Pipeline` classes to create modular, composable data processing workflows. +- **Functional programming paradigms**: Utilize tools like `compose`, `maps`, and `filter_ex` to write more functional-style Python code. +- **Documentation**: Use `docments` where possible to document parameters of functions and methods. +- **Time-aware caching**: Apply the `timed_cache` decorator to add time-based expiration to the standard `lru_cache` functionality. +- **Simplified CLI creation**: Use fastcore's console script utilities to easily transform Python functions into command-line interfaces.# A tour of fastcore + + + +Here’s a (somewhat) quick tour of a few higlights from fastcore. + +### Documentation + +All fast.ai projects, including this one, are built with +[nbdev](https://nbdev.fast.ai), which is a full literate programming +environment built on Jupyter Notebooks. That means that every piece of +documentation, including the page you’re reading now, can be accessed as +interactive Jupyter notebooks. In fact, you can even grab a link +directly to a notebook running interactively on Google Colab - if you +want to follow along with this tour, click the link below: + +``` python +colab_link('index') +``` + +[Open `index` in +Colab](https://colab.research.google.com/github/fastai/fastcore/blob/master/nbs/index.ipynb) + +The full docs are available at +[fastcore.fast.ai](https://fastcore.fast.ai). The code in the examples +and in all fast.ai libraries follow the [fast.ai style +guide](https://docs.fast.ai/dev/style.html). In order to support +interactive programming, all fast.ai libraries are designed to allow for +`import *` to be used safely, particular by ensuring that +[`__all__`](https://riptutorial.com/python/example/2894/the---all---special-variable) +is defined in all packages. In order to see where a function is from, +just type it: + +``` python +coll_repr +``` + + + +For more details, including a link to the full documentation and source +code, use `doc`, which pops up a window with this information: + +``` python +doc(coll_repr) +``` + + + +The documentation also contains links to any related functions or +classes, which appear like this: +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) (in +the notebook itself you will just see a word with back-ticks around it; +the links are auto-generated in the documentation site). The +documentation will generally show one or more examples of use, along +with any background context necessary to understand them. As you’ll see, +the examples for each function and method are shown as tests, rather +than example outputs, so let’s start by explaining that. + +### Testing + +fastcore’s testing module is designed to work well with +[nbdev](https://nbdev.fast.ai), which is a full literate programming +environment built on Jupyter Notebooks. That means that your tests, +docs, and code all live together in the same notebook. fastcore and +nbdev’s approach to testing starts with the premise that all your tests +should pass. If one fails, no more tests in a notebook are run. + +Tests look like this: + +``` python +test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]') +``` + +That’s an example from the docs for +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr). As +you see, it’s not showing you the output directly. Here’s what that +would look like: + +``` python +coll_repr(range(1000), 5) +``` + + '(#1000) [0,1,2,3,4...]' + +So, the test is actually showing you what the output looks like, because +if the function call didn’t return `'(#1000) [0,1,2,3,4...]'`, then the +test would have failed. + +So every test shown in the docs is also showing you the behavior of the +library — and vice versa! + +Test functions always start with `test_`, and then follow with the +operation being tested. So +[`test_eq`](https://fastcore.fast.ai/test.html#test_eq) tests for +equality (as you saw in the example above). This includes tests for +equality of arrays and tensors, lists and generators, and many more: + +``` python +test_eq([0,1,2,3], np.arange(4)) +``` + +When a test fails, it prints out information about what was expected: + +``` python +test_eq([0,1,2,3], np.arange(3)) +``` + + ---- + AssertionError: ==: + [0, 1, 2, 3] + [0 1 2] + +If you want to check that objects are the same type, rather than the +just contain the same collection, use +[`test_eq_type`](https://fastcore.fast.ai/test.html#test_eq_type). + +You can test with any comparison function using +[`test`](https://fastcore.fast.ai/test.html#test), e.g test whether an +object is less than: + +``` python +test(2, 3, operator.lt) +``` + +You can even test that exceptions are raised: + +``` python +def divide_zero(): return 1/0 +test_fail(divide_zero) +``` + +…and test that things are printed to stdout: + +``` python +test_stdout(lambda: print('hi'), 'hi') +``` + +### Foundations + +fast.ai is unusual in that we often use +[mixins](https://en.wikipedia.org/wiki/Mixin) in our code. Mixins are +widely used in many programming languages, such as Ruby, but not so much +in Python. We use mixins to attach new behavior to existing libraries, +or to allow modules to add new behavior to our own classes, such as in +extension modules. One useful example of a mixin we define is +[`Path.ls`](https://fastcore.fast.ai/xtras.html#path.ls), which lists a +directory and returns an +[`L`](https://fastcore.fast.ai/foundation.html#l) (an extended list +class which we’ll discuss shortly): + +``` python +p = Path('images') +p.ls() +``` + + (#6) [Path('images/mnist3.png'),Path('images/att_00000.png'),Path('images/puppy.jpg'),Path('images/att_00005.png'),Path('images/att_00007.png'),Path('images/att_00006.png')] + +You can easily add you own mixins with the +[`patch`](https://fastcore.fast.ai/basics.html#patch) +[decorator](https://realpython.com/primer-on-python-decorators/), which +takes advantage of Python 3 [function +annotations](https://www.python.org/dev/peps/pep-3107/#parameters) to +say what class to patch: + +``` python +@patch +def num_items(self:Path): return len(self.ls()) + +p.num_items() +``` + + 6 + +We also use `**kwargs` frequently. In python `**kwargs` in a parameter +like means “*put any additional keyword arguments into a dict called +`kwargs`*”. Normally, using `kwargs` makes an API quite difficult to +work with, because it breaks things like tab-completion and popup lists +of signatures. `utils` provides +[`use_kwargs`](https://fastcore.fast.ai/meta.html#use_kwargs) and +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) to avoid +this problem. See our [detailed article on +delegation](https://www.fast.ai/2019/08/06/delegation/) on this topic. + +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) solves a +similar problem (and is also discussed in the article linked above): +it’s allows you to use Python’s exceptionally useful +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) magic +method, but avoids the problem that normally in Python tab-completion +and docs break when using this. For instance, you can see here that +Python’s `dir` function, which is used to find the attributes of a +python object, finds everything inside the `self.default` attribute +here: + +``` python +class Author: + def __init__(self, name): self.name = name + +class ProductPage(GetAttr): + _default = 'author' + def __init__(self,author,price,cost): self.author,self.price,self.cost = author,price,cost + +p = ProductPage(Author("Jeremy"), 1.50, 0.50) +[o for o in dir(p) if not o.startswith('_')] +``` + + ['author', 'cost', 'name', 'price'] + +Looking at that `ProductPage` example, it’s rather verbose and +duplicates a lot of attribute names, which can lead to bugs later if you +change them only in one place. `fastcore` provides +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr) to +simplify this common pattern. It also provides +[`basic_repr`](https://fastcore.fast.ai/basics.html#basic_repr) to give +simple objects a useful `repr`: + +``` python +class ProductPage: + def __init__(self,author,price,cost): store_attr() + __repr__ = basic_repr('author,price,cost') + +ProductPage("Jeremy", 1.50, 0.50) +``` + + __main__.ProductPage(author='Jeremy', price=1.5, cost=0.5) + +One of the most interesting `fastcore` functions is the +[`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs) +decorator. This allows class behavior to be modified without +sub-classing. This can allow folks that aren’t familiar with +object-oriented programming to customize your class more easily. Here’s +an example of a class that uses +[`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs): + +``` python +@funcs_kwargs +class T: + _methods=['some_method'] + def __init__(self, **kwargs): assert not kwargs, f'Passed unknown args: {kwargs}' + +p = T(some_method = print) +p.some_method("hello") +``` + + hello + +The `assert not kwargs` above is used to ensure that the user doesn’t +pass an unknown parameter (i.e one that’s not in `_methods`). `fastai` +uses [`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs) +in many places, for instance, you can customize any part of a +`DataLoader` by passing your own methods. + +`fastcore` also provides many utility functions that make a Python +programmer’s life easier, in `fastcore.utils`. We won’t look at many +here, since you can easily look at the docs yourself. To get you +started, have a look at the docs for +[`chunked`](https://fastcore.fast.ai/basics.html#chunked) (remember, if +you’re in a notebook, type `doc(chunked)`), which is a handy function +for creating lazily generated batches from a collection. + +Python’s +[`ProcessPoolExecutor`](https://fastcore.fast.ai/parallel.html#processpoolexecutor) +is extended to allow `max_workers` to be set to `0`, to easily turn off +parallel processing. This makes it easy to debug your code in serial, +then run it in parallel. It also allows you to pass arguments to your +parallel function, and to ensure there’s a pause between calls, in case +the process you are running has race conditions. +[`parallel`](https://fastcore.fast.ai/parallel.html#parallel) makes +parallel processing even easier to use, and even adds an optional +progress bar. + +### L + +Like most languages, Python allows for very concise syntax for some very +common types, such as `list`, which can be constructed with `[1,2,3]`. +Perl’s designer Larry Wall explained the reasoning for this kind of +syntax: + +> In metaphorical honor of Huffman’s compression code that assigns +> smaller numbers of bits to more common bytes. In terms of syntax, it +> simply means that commonly used things should be shorter, but you +> shouldn’t waste short sequences on less common constructs. + +On this basis, `fastcore` has just one type that has a single letter +name: [`L`](https://fastcore.fast.ai/foundation.html#l). The reason for +this is that it is designed to be a replacement for `list`, so we want +it to be just as easy to use as `[1,2,3]`. Here’s how to create that as +an [`L`](https://fastcore.fast.ai/foundation.html#l): + +``` python +L(1,2,3) +``` + + (#3) [1,2,3] + +The first thing to notice is that an +[`L`](https://fastcore.fast.ai/foundation.html#l) object includes in its +representation its number of elements; that’s the `(#3)` in the output +above. If there’s more than 10 elements, it will automatically truncate +the list: + +``` python +p = L.range(20).shuffle() +p +``` + + (#20) [5,1,9,10,18,13,6,17,3,16...] + +[`L`](https://fastcore.fast.ai/foundation.html#l) contains many of the +same indexing ideas that NumPy’s `array` does, including indexing with a +list of indexes, or a boolean mask list: + +``` python +p[2,4,6] +``` + + (#3) [9,18,6] + +It also contains other methods used in `array`, such as +[`L.argwhere`](https://fastcore.fast.ai/foundation.html#l.argwhere): + +``` python +p.argwhere(ge(15)) +``` + + (#5) [4,7,9,18,19] + +As you can see from this example, `fastcore` also includes a number of +features that make a functional style of programming easier, such as a +full range of boolean functions (e.g `ge`, `gt`, etc) which give the +same answer as the functions from Python’s `operator` module if given +two parameters, but return a [curried +function](https://en.wikipedia.org/wiki/Currying) if given one +parameter. + +There’s too much functionality to show it all here, so be sure to check +the docs. Many little things are added that we thought should have been +in `list` in the first place, such as making this do what you’d expect +(which is an error with `list`, but works fine with +[`L`](https://fastcore.fast.ai/foundation.html#l)): + +``` python +1 + L(2,3,4) +``` + + (#4) [1,2,3,4] + +### Transforms + +A [`Transform`](https://fastcore.fast.ai/transform.html#transform) is +the main building block of the fastai data pipelines. In the most +general terms a transform can be any function you want to apply to your +data, however the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) class +provides several mechanisms that make the process of building them easy +and flexible (see the docs for information about each of these): + +- Type dispatch +- Dispatch over tuples +- Reversability +- Type propagation +- Preprocessing +- Filtering based on the dataset type +- Ordering +- Appending new behavior with decorators + +[`Transform`](https://fastcore.fast.ai/transform.html#transform) looks +for three special methods, encodes, decodes, +and setups, which provide the implementation for +[`__call__`](https://www.python-course.eu/python3_magic_methods.php), +`decode`, and `setup` respectively. For instance: + +``` python +class A(Transform): + def encodes(self, x): return x+1 + +A()(1) +``` + + 2 + +For simple transforms like this, you can also use +[`Transform`](https://fastcore.fast.ai/transform.html#transform) as a +decorator: + +``` python +@Transform +def f(x): return x+1 + +f(1) +``` + + 2 + +Transforms can be composed into a +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline): + +``` python +@Transform +def g(x): return x/2 + +pipe = Pipeline([f,g]) +pipe(3) +``` + + 2.0 + +The power of +[`Transform`](https://fastcore.fast.ai/transform.html#transform) and +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) is best +understood by seeing how they’re used to create a complete data +processing pipeline. This is explained in [chapter +11](https://github.com/fastai/fastbook/blob/master/11_midlevel_data.ipynb) +of the [fastai +book](https://www.amazon.com/Deep-Learning-Coders-fastai-PyTorch/dp/1492045527), +which is [available for free](https://github.com/fastai/fastbook) in +Jupyter Notebook format.# fastcore: An Underrated Python Library + +A unique python library that extends the python programming language and provides utilities that enhance productivity. + +Sep 1, 2020 • Hamel Husain • 14 min read + +__fastcore fastai + +# Background __ + +I recently embarked on a journey to sharpen my python skills: I wanted to learn advanced patterns, idioms, and techniques. I started with reading books on advanced Python, however, the information didn't seem to stick without having somewhere to apply it. I also wanted the ability to ask questions from an expert while I was learning -- which is an arrangement that is hard to find! That's when it occurred to me: What if I could find an open source project that has fairly advanced python code and write documentation and tests? I made a bet that if I did this it would force me to learn everything very deeply, and the maintainers would be appreciative of my work and be willing to answer my questions. + +And that's exactly what I did over the past month! I'm pleased to report that it has been the most efficient learning experience I've ever experienced. I've discovered that writing documentation forced me to deeply understand not just what the code does but also _why the code works the way it does_ , and to explore edge cases while writing tests. Most importantly, I was able to ask questions when I was stuck, and maintainers were willing to devote extra time knowing that their mentorship was in service of making their code more accessible! It turns out the library I choose, fastcore is some of the most fascinating Python I have ever encountered as its purpose and goals are fairly unique. + +For the uninitiated, fastcore is a library on top of which many fast.ai projects are built on. Most importantly, fastcore extends the python programming language and strives to eliminate boilerplate and add useful functionality for common tasks. In this blog post, I'm going to highlight some of my favorite tools that fastcore provides, rather than sharing what I learned about python. My goal is to pique your interest in this library, and hopefully motivate you to check out the documentation after you are done to learn more! + +# Why fastcore is interesting __ + + 1. **Get exposed to ideas from other languages without leaving python:** I’ve always heard that it is beneficial to learn other languages in order to become a better programmer. From a pragmatic point of view, I’ve found it difficult to learn other languages because I could never use them at work. Fastcore extends python to include patterns found in languages as diverse as Julia, Ruby and Haskell. Now that I understand these tools I am motivated to learn other languages. + 2. **You get a new set of pragmatic tools** : fastcore includes utilities that will allow you to write more concise expressive code, and perhaps solve new problems. + 3. **Learn more about the Python programming language:** Because fastcore extends the python programming language, many advanced concepts are exposed during the process. For the motivated, this is a great way to see how many of the internals of python work. + +# A whirlwind tour through fastcore __ + +Here are some things you can do with fastcore that immediately caught my attention. + +* * * + +## Making **kwargs transparent __ + +Whenever I see a function that has the argument****kwargs** , I cringe a little. This is because it means the API is obfuscated and I have to read the source code to figure out what valid parameters might be. Consider the below example: + +``` +def baz(a, b=2, c=3, d=4): return a + b + c + +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) + +``` + +``` + +``` + +Without reading the source code, it might be hard for me to know that `foo` also accepts and additional parameters `b` and `d`. We can fix this with `delegates`: + +``` +def baz(a, b=2, c=3, d=4): return a + b + c + +@delegates(baz) # this decorator will pass down keyword arguments from baz +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) + +``` + +``` + +``` + +You can customize the behavior of this decorator. For example, you can have your cake and eat it too by passing down your arguments and also keeping `**kwargs`: + +``` +@delegates(baz, keep=True) +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) + +``` + +``` + +``` + +You can also exclude arguments. For example, we exclude argument `d` from delegation: + +``` +def basefoo(a, b=2, c=3, d=4): pass + +@delegates(basefoo, but=['d']) # exclude `d` +def foo(c, a, **kwargs): pass + +inspect.signature(foo) + +``` + +``` + +``` + +You can also delegate between classes: + +``` +class BaseFoo: + def __init__(self, e, c=2): pass + +@delegates()# since no argument was passsed here we delegate to the superclass +class Foo(BaseFoo): + def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs) + +inspect.signature(Foo) + +``` + +``` + +``` + +For more information, read the docs on delegates. + +* * * + +## Avoid boilerplate when setting instance attributes __ + +Have you ever wondered if it was possible to avoid the boilerplate involved with setting attributes in`__init__`? + +``` +class Test: + def __init__(self, a, b ,c): + self.a, self.b, self.c = a, b, c + +``` + +Ouch! That was painful. Look at all the repeated variable names. Do I really have to repeat myself like this when defining a class? Not Anymore! Checkout store_attr: + +``` +class Test: + def __init__(self, a, b, c): + store_attr() + +t = Test(5,4,3) +assert t.b == 4 + +``` + +You can also exclude certain attributes: + +``` +class Test: + def __init__(self, a, b, c): + store_attr(but=['c']) + +t = Test(5,4,3) +assert t.b == 4 +assert not hasattr(t, 'c') + +``` + +There are many more ways of customizing and using `store_attr` than I highlighted here. Check out the docs for more detail. + +P.S. you might be thinking that Python dataclasses also allow you to avoid this boilerplate. While true in some cases, `store_attr` is more flexible.1 + +1\. For example, store_attr does not rely on inheritance, which means you won't get stuck using multiple inheritance when using this with your own classes. Also, unlike dataclasses, store_attr does not require python 3.7 or higher. Furthermore, you can use store_attr anytime in the object lifecycle, and in any location in your class to customize the behavior of how and when variables are stored.↩ + +* * * + +## Avoiding subclassing boilerplate __ + +One thing I hate about python is the`__super__().__init__()` boilerplate associated with subclassing. For example: + +``` +class ParentClass: + def __init__(self): self.some_attr = 'hello' + +class ChildClass(ParentClass): + def __init__(self): + super().__init__() + +cc = ChildClass() +assert cc.some_attr == 'hello' # only accessible b/c you used super + +``` + +We can avoid this boilerplate by using the metaclass PrePostInitMeta. We define a new class called `NewParent` that is a wrapper around the `ParentClass`: + +``` +class NewParent(ParentClass, metaclass=PrePostInitMeta): + def __pre_init__(self, *args, **kwargs): super().__init__() + +class ChildClass(NewParent): + def __init__(self):pass + +sc = ChildClass() +assert sc.some_attr == 'hello' + +``` + +* * * + +## Type Dispatch __ + +Type dispatch, orMultiple dispatch, allows you to change the way a function behaves based upon the input types it receives. This is a prominent feature in some programming languages like Julia. For example, this is a conceptual example of how multiple dispatch works in Julia, returning different values depending on the input types of x and y: + +``` +collide_with(x::Asteroid, y::Asteroid) = ... +# deal with asteroid hitting asteroid + +collide_with(x::Asteroid, y::Spaceship) = ... +# deal with asteroid hitting spaceship + +collide_with(x::Spaceship, y::Asteroid) = ... +# deal with spaceship hitting asteroid + +collide_with(x::Spaceship, y::Spaceship) = ... +# deal with spaceship hitting spaceship + +``` + +Type dispatch can be especially useful in data science, where you might allow different input types (i.e. Numpy arrays and Pandas dataframes) to a function that processes data. Type dispatch allows you to have a common API for functions that do similar tasks. + +Unfortunately, Python does not support this out-of-the box. Fortunately, there is the @typedispatch decorator to the rescue. This decorator relies upon type hints in order to route inputs the correct version of the function: + +``` +@typedispatch +def f(x:str, y:str): return f'{x}{y}' + +@typedispatch +def f(x:np.ndarray): return x.sum() + +@typedispatch +def f(x:int, y:int): return x+y + +``` + +Below is a demonstration of type dispatch at work for the function `f`: + +``` +f('Hello ', 'World!') + +``` + +``` +'Hello World!' +``` + +``` +f(2,3) + +``` + +``` +5 +``` + +``` +f(np.array([5,5,5,5])) + +``` + +``` +20 +``` + +There are limitations of this feature, as well as other ways of using this functionality that you can read about here. In the process of learning about typed dispatch, I also found a python library called multipledispatch made by Mathhew Rocklin (the creator of Dask). + +After using this feature, I am now motivated to learn languages like Julia to discover what other paradigms I might be missing. + +* * * + +## A better version of functools.partial __ + +`functools.partial` is a great utility that creates functions from other functions that lets you set default values. Lets take this function for example that filters a list to only contain values >= `val`: + +``` +test_input = [1,2,3,4,5,6] +def f(arr, val): + "Filter a list to remove any values that are less than val." + return [x for x in arr if x >= val] + +f(test_input, 3) + +``` + +``` +[3, 4, 5, 6] +``` + +You can create a new function out of this function using `partial` that sets the default value to 5: + +``` +filter5 = partial(f, val=5) +filter5(test_input) + +``` + +``` +[5, 6] +``` + +One problem with `partial` is that it removes the original docstring and replaces it with a generic docstring: + +``` +filter5.__doc__ + +``` + +``` +'partial(func, *args, **keywords) - new function with partial application\n of the given arguments and keywords.\n' +``` + +fastcore.utils.partialler fixes this, and makes sure the docstring is retained such that the new API is transparent: + +``` +filter5 = partialler(f, val=5) +filter5.__doc__ + +``` + +``` +'Filter a list to remove any values that are less than val.' +``` + +* * * + +## Composition of functions __ + +A technique that is pervasive in functional programming languages is function composition, whereby you chain a bunch of functions together to achieve some kind of result. This is especially useful when applying various data transformations. Consider a toy example where I have three functions: (1) Removes elements of a list less than 5 (from the prior section) (2) adds 2 to each number (3) sums all the numbers: + +``` +def add(arr, val): return [x + val for x in arr] +def arrsum(arr): return sum(arr) + +# See the previous section on partialler +add2 = partialler(add, val=2) + +transform = compose(filter5, add2, arrsum) +transform([1,2,3,4,5,6]) + +``` + +``` +15 +``` + +But why is this useful? You might me thinking, I can accomplish the same thing with: + +``` +arrsum(add2(filter5([1,2,3,4,5,6]))) + +``` + +You are not wrong! However, composition gives you a convenient interface in case you want to do something like the following: + +``` +def fit(x, transforms:list): + "fit a model after performing transformations" + x = compose(*transforms)(x) + y = [np.mean(x)] * len(x) # its a dumb model. Don't judge me + return y + +# filters out elements < 5, adds 2, then predicts the mean +fit(x=[1,2,3,4,5,6], transforms=[filter5, add2]) + +``` + +``` +[7.5, 7.5] +``` + +For more information about `compose`, read the docs. + +* * * + +## A more useful `__repr__`__ + +In python,`__repr__` helps you get information about an object for logging and debugging. Below is what you get by default when you define a new class. (Note: we are using `store_attr`, which was discussed earlier). + +``` +class Test: + def __init__(self, a, b=2, c=3): store_attr() # `store_attr` was discussed previously + +Test(1) + +``` + +``` +<__main__.Test at 0x7ffcd766cee0> +``` + +We can use basic_repr to quickly give us a more sensible default: + +``` +class Test: + def __init__(self, a, b=2, c=3): store_attr() + __repr__ = basic_repr('a,b,c') + +Test(2) + +``` + +``` +Test(a=2, b=2, c=3) +``` + +* * * + +## Monkey Patching With A Decorator __ + +It can be convenient tomonkey patch with a decorator, which is especially helpful when you want to patch an external library you are importing. We can use the decorator @patch from `fastcore.foundation` along with type hints like so: + +``` +class MyClass(int): pass + +@patch +def func(self:MyClass, a): return self+a + +mc = MyClass(3) + +``` + +Now, `MyClass` has an additional method named `func`: + +``` +mc.func(10) + +``` + +``` +13 +``` + +Still not convinced? I'll show you another example of this kind of patching in the next section. + +* * * + +## A better pathlib.Path __ + +When you seethese extensions to pathlib.path you won't ever use vanilla pathlib again! A number of additional methods have been added to pathlib, such as: + + * `Path.readlines`: same as `with open('somefile', 'r') as f: f.readlines()` + * `Path.read`: same as `with open('somefile', 'r') as f: f.read()` + * `Path.save`: saves file as pickle + * `Path.load`: loads pickle file + * `Path.ls`: shows the contents of the path as a list. + * etc. + +Read more about this here. Here is a demonstration of `ls`: + +``` +from fastcore.utils import * +from pathlib import Path +p = Path('.') +p.ls() # you don't get this with vanilla Pathlib.Path!! + +``` + +``` +(#7) [Path('2020-09-01-fastcore.ipynb'),Path('README.md'),Path('fastcore_imgs'),Path('2020-02-20-test.ipynb'),Path('.ipynb_checkpoints'),Path('2020-02-21-introducing-fastpages.ipynb'),Path('my_icons')] +``` + +Wait! What's going on here? We just imported `pathlib.Path` \- why are we getting this new functionality? Thats because we imported the `fastcore.utils` module, which patches this module via the `@patch` decorator discussed earlier. Just to drive the point home on why the `@patch` decorator is useful, I'll go ahead and add another method to `Path` right now: + +``` +@patch +def fun(self:Path): return "This is fun!" + +p.fun() + +``` + +``` +'This is fun!' +``` + +That is magical, right? I know! That's why I'm writing about it! + +* * * + +## An Even More Concise Way To Create Lambdas __ + +`Self`, with an uppercase S, is an even more concise way to create lambdas that are calling methods on an object. For example, let's create a lambda for taking the sum of a Numpy array: + +``` +arr=np.array([5,4,3,2,1]) +f = lambda a: a.sum() +assert f(arr) == 15 + +``` + +You can use `Self` in the same way: + +``` +f = Self.sum() +assert f(arr) == 15 + +``` + +Let's create a lambda that does a groupby and max of a Pandas dataframe: + +``` +import pandas as pd +df=pd.DataFrame({'Some Column': ['a', 'a', 'b', 'b', ], + 'Another Column': [5, 7, 50, 70]}) + +f = Self.groupby('Some Column').mean() +f(df) + +``` + +| Another Column +---|--- +Some Column | +a | 6 +b | 60 + +Read more about `Self` in the docs). + +* * * + +## Notebook Functions __ + +These are simple but handy, and allow you to know whether or not code is executing in a Jupyter Notebook, Colab, or an Ipython Shell: + +``` +from fastcore.imports import in_notebook, in_colab, in_ipython +in_notebook(), in_colab(), in_ipython() + +``` + +``` +(True, False, True) +``` + +This is useful if you are displaying certain types of visualizations, progress bars or animations in your code that you may want to modify or toggle depending on the environment. + +* * * + +## A Drop-In Replacement For List __ + +You might be pretty happy with Python's`list`. This is one of those situations that you don't know you needed a better list until someone showed one to you. Enter `L`, a list like object with many extra goodies. + +The best way I can describe `L` is to pretend that `list` and `numpy` had a pretty baby: + +define a list (check out the nice `__repr__` that shows the length of the list!) + +``` +L(1,2,3) + +``` + +``` +(#3) [1,2,3] +``` + +Shuffle a list: + +``` +p = L.range(20).shuffle() +p + +``` + +``` +(#20) [8,7,5,12,14,16,2,15,19,6...] +``` + +Index into a list: + +``` +p[2,4,6] + +``` + +``` +(#3) [5,14,2] +``` + +L has sensible defaults, for example appending an element to a list: + +``` +1 + L(2,3,4) + +``` + +``` +(#4) [1,2,3,4] +``` + +There is much more `L` has to offer. Read the docs to learn more. + +# But Wait ... There's More!__ + +There are more things I would like to show you about fastcore, but there is no way they would reasonably fit into a blog post. Here is a list of some of my favorite things that I didn't demo in this blog post: + +## Utilities __ + +TheBasics section contain many shortcuts to perform common tasks or provide an additional interface to what standard python provides. + + * mk_class: quickly add a bunch of attributes to a class + * wrap_class: add new methods to a class with a simple decorator + * groupby: similar to Scala's groupby + * merge: merge dicts + * fasttuple: a tuple on steroids + * Infinite Lists: useful for padding and testing + * chunked: for batching and organizing stuff + +## Multiprocessing __ + +TheMultiprocessing section extends python's multiprocessing library by offering features like: + + * progress bars + * ability to pause to mitigate race conditions with external services + * processing things in batches on each worker, ex: if you have a vectorized operation to perform in chunks + +## Functional Programming __ + +Thefunctional programming section is my favorite part of this library. + + * maps: a map that also composes functions + * mapped: A more robust `map` + * using_attr: compose a function that operates on an attribute + +## Transforms __ + +Transforms is a collection of utilities for creating data transformations and associated pipelines. These transformation utilities build upon many of the building blocks discussed in this blog post. + +## Further Reading __ + +**It should be noted that you should read themain page of the docs first, followed by the section on tests to fully understand the documentation.** + + * The fastcore documentation site. + * The fastcore GitHub repo. + * Blog post on delegation. + +# Shameless plug: fastpages __ + +This blog post was written entirely in a Jupyter Notebook, which GitHub automatically converted into to a blog post! Sound interesting?Check out fastpages.# fastcore Module Documentation + +## fastcore.basics + +> Basic functionality used in the fastai library + +- `def ifnone(a, b)` + `b` if `a` is None else `a` + +- `def maybe_attr(o, attr)` + `getattr(o,attr,o)` + +- `def basic_repr(flds)` + Minimal `__repr__` + +- `class BasicRepr` + Base class for objects needing a basic `__repr__` + + +- `def is_array(x)` + `True` if `x` supports `__array__` or `iloc` + +- `def listify(o, *rest)` + Convert `o` to a `list` + +- `def tuplify(o, use_list, match)` + Make `o` a tuple + +- `def true(x)` + Test whether `x` is truthy; collections with >0 elements are considered `True` + +- `class NullType` + An object that is `False` and can be called, chained, and indexed + + - `def __getattr__(self, *args)` + - `def __call__(self, *args, **kwargs)` + - `def __getitem__(self, *args)` + - `def __bool__(self)` + +- `def tonull(x)` + Convert `None` to `null` + +- `def get_class(nm, *fld_names, **flds)` + Dynamically create a class, optionally inheriting from `sup`, containing `fld_names` + +- `def mk_class(nm, *fld_names, **flds)` + Create a class using `get_class` and add to the caller's module + +- `def wrap_class(nm, *fld_names, **flds)` + Decorator: makes function a method of a new class `nm` passing parameters to `mk_class` + +- `class ignore_exceptions` + Context manager to ignore exceptions + + - `def __enter__(self)` + - `def __exit__(self, *args)` + +- `def exec_local(code, var_name)` + Call `exec` on `code` and return the var `var_name` + +- `def risinstance(types, obj)` + Curried `isinstance` but with args reversed + +- `class Inf` + Infinite lists + + +- `def in_(x, a)` + `True` if `x in a` + +- `def ret_true(*args, **kwargs)` + Predicate: always `True` + +- `def ret_false(*args, **kwargs)` + Predicate: always `False` + +- `def stop(e)` + Raises exception `e` (by default `StopIteration`) + +- `def gen(func, seq, cond)` + Like `(func(o) for o in seq if cond(func(o)))` but handles `StopIteration` + +- `def chunked(it, chunk_sz, drop_last, n_chunks)` + Return batches from iterator `it` of size `chunk_sz` (or return `n_chunks` total) + +- `def otherwise(x, tst, y)` + `y if tst(x) else x` + +- `def custom_dir(c, add)` + Implement custom `__dir__`, adding `add` to `cls` + +- `class AttrDict` + `dict` subclass that also provides access to keys as attrs + + - `def __getattr__(self, k)` + - `def __setattr__(self, k, v)` + - `def __dir__(self)` + - `def copy(self)` + +- `class AttrDictDefault` + `AttrDict` subclass that returns `None` for missing attrs + + - `def __init__(self, *args, **kwargs)` + - `def __getattr__(self, k)` + +- `class NS` + `SimpleNamespace` subclass that also adds `iter` and `dict` support + + - `def __iter__(self)` + - `def __getitem__(self, x)` + - `def __setitem__(self, x, y)` + +- `def get_annotations_ex(obj)` + Backport of py3.10 `get_annotations` that returns globals/locals + +- `def eval_type(t, glb, loc)` + `eval` a type or collection of types, if needed, for annotations in py3.10+ + +- `def type_hints(f)` + Like `typing.get_type_hints` but returns `{}` if not allowed type + +- `def annotations(o)` + Annotations for `o`, or `type(o)` + +- `def anno_ret(func)` + Get the return annotation of `func` + +- `def signature_ex(obj, eval_str)` + Backport of `inspect.signature(..., eval_str=True` to `True`) + +- `def str2int(s)` + Convert `s` to an `int` + +- `def str2float(s)` + Convert `s` to a float + +- `def str2list(s)` + Convert `s` to a list + +- `def str2date(s)` + `date.fromisoformat` with empty string handling + +- `def typed(_func)` + Decorator to check param and return types at runtime, with optional casting + +- `def exec_new(code)` + Execute `code` in a new environment and return it + +- `def exec_import(mod, sym)` + Import `sym` from `mod` in a new environment + +## fastcore.dispatch + +> Basic single and dual parameter dispatch + +- `def lenient_issubclass(cls, types)` + If possible return whether `cls` is a subclass of `types`, otherwise return False. + +- `def sorted_topologically(iterable)` + Return a new list containing all items from the iterable sorted topologically + +- `class TypeDispatch` + Dictionary-like object; `__getitem__` matches keys of types using `issubclass` + + - `def __init__(self, funcs, bases)` + - `def add(self, f)` + Add type `t` and function `f` + + - `def first(self)` + Get first function in ordered dict of type:func. + + - `def returns(self, x)` + Get the return type of annotation of `x`. + + - `def __repr__(self)` + - `def __call__(self, *args, **kwargs)` + - `def __get__(self, inst, owner)` + - `def __getitem__(self, k)` + Find first matching type that is a super-class of `k` + + +- `class DispatchReg` + A global registry for `TypeDispatch` objects keyed by function name + + - `def __init__(self)` + - `def __call__(self, f)` + +- `def retain_meta(x, res, as_copy)` + Call `res.set_meta(x)`, if it exists + +- `def default_set_meta(self, x, as_copy)` + Copy over `_meta` from `x` to `res`, if it's missing + +- `@typedispatch def cast(x, typ)` + cast `x` to type `typ` (may also change `x` inplace) + +- `def retain_type(new, old, typ, as_copy)` + Cast `new` to type of `old` or `typ` if it's a superclass + +- `def retain_types(new, old, typs)` + Cast each item of `new` to type of matching item in `old` if it's a superclass + +- `def explode_types(o)` + Return the type of `o`, potentially in nested dictionaries for thing that are listy + +## fastcore.docments + +> Document parameters using comments. + +- `def docstring(sym)` + Get docstring for `sym` for functions ad classes + +- `def parse_docstring(sym)` + Parse a numpy-style docstring in `sym` + +- `def isdataclass(s)` + Check if `s` is a dataclass but not a dataclass' instance + +- `def get_dataclass_source(s)` + Get source code for dataclass `s` + +- `def get_source(s)` + Get source code for string, function object or dataclass `s` + +- `def get_name(obj)` + Get the name of `obj` + +- `def qual_name(obj)` + Get the qualified name of `obj` + +- `@delegates(_docments) def docments(elt, full, **kwargs)` + Generates a `docment` + +- `def extract_docstrings(code)` + Create a dict from function/class/method names to tuples of docstrings and param lists + +## fastcore.docscrape + +> Parse numpy-style docstrings + +- `def strip_blank_lines(l)` + Remove leading and trailing blank lines from a list of lines + +- `class Reader` + A line-based string reader. + + - `def __init__(self, data)` + - `def __getitem__(self, n)` + - `def reset(self)` + - `def read(self)` + - `def seek_next_non_empty_line(self)` + - `def eof(self)` + - `def read_to_condition(self, condition_func)` + - `def read_to_next_empty_line(self)` + - `def read_to_next_unindented_line(self)` + - `def peek(self, n)` + - `def is_empty(self)` + +- `class ParseError` + - `def __str__(self)` + +- `class NumpyDocString` + Parses a numpydoc string to an abstract representation + + - `def __init__(self, docstring, config)` + - `def __iter__(self)` + - `def __len__(self)` + - `def __getitem__(self, key)` + - `def __setitem__(self, key, val)` + +- `def dedent_lines(lines, split)` + Deindent a list of lines maximally + +## fastcore.foundation + +> The `L` class and helpers for it + +- `@contextmanager def working_directory(path)` + Change working directory to `path` and return to previous on exit. + +- `def add_docs(cls, cls_doc, **docs)` + Copy values from `docs` to `cls` docstrings, and confirm all public methods are documented + +- `def docs(cls)` + Decorator version of `add_docs`, using `_docs` dict + +- `def coll_repr(c, max_n)` + String repr of up to `max_n` items of (possibly lazy) collection `c` + +- `def is_bool(x)` + Check whether `x` is a bool or None + +- `def mask2idxs(mask)` + Convert bool mask or index list to index `L` + +- `def is_indexer(idx)` + Test whether `idx` will index a single item in a list + +- `class CollBase` + Base class for composing a list of `items` + + - `def __init__(self, items)` + - `def __len__(self)` + - `def __getitem__(self, k)` + - `def __setitem__(self, k, v)` + - `def __delitem__(self, i)` + - `def __repr__(self)` + - `def __iter__(self)` + +- `class L` + Behaves like a list of `items` but can also index with list of indices or masks + + - `def __init__(self, items, *rest)` + - `def __getitem__(self, idx)` + - `def copy(self)` + - `def __setitem__(self, idx, o)` + Set `idx` (can be list of indices, or mask, or int) items to `o` (which is broadcast if not iterable) + + - `def __eq__(self, b)` + - `def sorted(self, key, reverse)` + - `def __iter__(self)` + - `def __contains__(self, b)` + - `def __reversed__(self)` + - `def __invert__(self)` + - `def __repr__(self)` + - `def __mul__(a, b)` + - `def __add__(a, b)` + - `def __radd__(a, b)` + - `def __addi__(a, b)` + - `@classmethod def split(cls, s, sep, maxsplit)` + - `@classmethod def range(cls, a, b, step)` + - `def map(self, f, *args, **kwargs)` + - `def argwhere(self, f, negate, **kwargs)` + - `def argfirst(self, f, negate)` + - `def filter(self, f, negate, **kwargs)` + - `def enumerate(self)` + - `def renumerate(self)` + - `def unique(self, sort, bidir, start)` + - `def val2idx(self)` + - `def cycle(self)` + - `def map_dict(self, f, *args, **kwargs)` + - `def map_first(self, f, g, *args, **kwargs)` + - `def itemgot(self, *idxs)` + - `def attrgot(self, k, default)` + - `def starmap(self, f, *args, **kwargs)` + - `def zip(self, cycled)` + - `def zipwith(self, *rest)` + - `def map_zip(self, f, *args, **kwargs)` + - `def map_zipwith(self, f, *rest, **kwargs)` + - `def shuffle(self)` + - `def concat(self)` + - `def reduce(self, f, initial)` + - `def sum(self)` + - `def product(self)` + - `def setattrs(self, attr, val)` + +- `def save_config_file(file, d, **kwargs)` + Write settings dict to a new config file, or overwrite the existing one. + +- `class Config` + Reading and writing `ConfigParser` ini files + + - `def __init__(self, cfg_path, cfg_name, create, save, extra_files, types)` + - `def __repr__(self)` + - `def __setitem__(self, k, v)` + - `def __contains__(self, k)` + - `def save(self)` + - `def __getattr__(self, k)` + - `def __getitem__(self, k)` + - `def get(self, k, default)` + - `def path(self, k, default)` + - `@classmethod def find(cls, cfg_name, cfg_path, **kwargs)` + Search `cfg_path` and its parents to find `cfg_name` + + +## fastcore.imghdr + +> Recognize image file formats based on their first few bytes. + +- `def test_jpeg(h, f)` + JPEG data with JFIF or Exif markers; and raw JPEG + +- `def test_gif(h, f)` + GIF ('87 and '89 variants) + +- `def test_tiff(h, f)` + TIFF (can be in Motorola or Intel byte order) + +- `def test_rgb(h, f)` + SGI image library + +- `def test_pbm(h, f)` + PBM (portable bitmap) + +- `def test_pgm(h, f)` + PGM (portable graymap) + +- `def test_ppm(h, f)` + PPM (portable pixmap) + +- `def test_rast(h, f)` + Sun raster file + +- `def test_xbm(h, f)` + X bitmap (X10 or X11) + +## fastcore.imports + +- `def is_iter(o)` + Test whether `o` can be used in a `for` loop + +- `def is_coll(o)` + Test whether `o` is a collection (i.e. has a usable `len`) + +- `def all_equal(a, b)` + Compares whether `a` and `b` are the same length and have the same contents + +- `def noop(x, *args, **kwargs)` + Do nothing + +- `def noops(self, x, *args, **kwargs)` + Do nothing (method) + +- `def isinstance_str(x, cls_name)` + Like `isinstance`, except takes a type name instead of a type + +- `def equals(a, b)` + Compares `a` and `b` for equality; supports sublists, tensors and arrays too + +- `def ipython_shell()` + Same as `get_ipython` but returns `False` if not in IPython + +- `def in_ipython()` + Check if code is running in some kind of IPython environment + +- `def in_colab()` + Check if the code is running in Google Colaboratory + +- `def in_jupyter()` + Check if the code is running in a jupyter notebook + +- `def in_notebook()` + Check if the code is running in a jupyter notebook + +- `def remove_prefix(text, prefix)` + Temporary until py39 is a prereq + +- `def remove_suffix(text, suffix)` + Temporary until py39 is a prereq + +## fastcore.meta + +> Metaclasses + +- `def test_sig(f, b)` + Test the signature of an object + +- `class FixSigMeta` + A metaclass that fixes the signature on classes that override `__new__` + + - `def __new__(cls, name, bases, dict)` + +- `class PrePostInitMeta` + A metaclass that calls optional `__pre_init__` and `__post_init__` methods + + - `def __call__(cls, *args, **kwargs)` + +- `class AutoInit` + Same as `object`, but no need for subclasses to call `super().__init__` + + - `def __pre_init__(self, *args, **kwargs)` + +- `class NewChkMeta` + Metaclass to avoid recreating object passed to constructor + + - `def __call__(cls, x, *args, **kwargs)` + +- `class BypassNewMeta` + Metaclass: casts `x` to this class if it's of type `cls._bypass_type` + + - `def __call__(cls, x, *args, **kwargs)` + +- `def empty2none(p)` + Replace `Parameter.empty` with `None` + +- `def anno_dict(f)` + `__annotation__ dictionary with `empty` cast to `None`, returning empty if doesn't exist + +- `def use_kwargs_dict(keep, **kwargs)` + Decorator: replace `**kwargs` in signature with `names` params + +- `def use_kwargs(names, keep)` + Decorator: replace `**kwargs` in signature with `names` params + +- `def delegates(to, keep, but)` + Decorator: replace `**kwargs` in signature with params from `to` + +- `def method(f)` + Mark `f` as a method + +- `def funcs_kwargs(as_method)` + Replace methods in `cls._methods` with those from `kwargs` + +## fastcore.net + +> Network, HTTP, and URL functions + +- `def urlquote(url)` + Update url's path with `urllib.parse.quote` + +- `def urlwrap(url, data, headers)` + Wrap `url` in a urllib `Request` with `urlquote` + +- `class HTTP4xxClientError` + Base class for client exceptions (code 4xx) from `url*` functions + + +- `class HTTP5xxServerError` + Base class for server exceptions (code 5xx) from `url*` functions + + +- `def urlopen(url, data, headers, timeout, **kwargs)` + Like `urllib.request.urlopen`, but first `urlwrap` the `url`, and encode `data` + +- `def urlread(url, data, headers, decode, return_json, return_headers, timeout, **kwargs)` + Retrieve `url`, using `data` dict or `kwargs` to `POST` if present + +- `def urljson(url, data, timeout)` + Retrieve `url` and decode json + +- `def urlclean(url)` + Remove fragment, params, and querystring from `url` if present + +- `def urlsave(url, dest, reporthook, headers, timeout)` + Retrieve `url` and save based on its name + +- `def urlvalid(x)` + Test if `x` is a valid URL + +- `def urlrequest(url, verb, headers, route, query, data, json_data)` + `Request` for `url` with optional route params replaced by `route`, plus `query` string, and post `data` + +- `@patch def summary(self, skip)` + Summary containing full_url, headers, method, and data, removing `skip` from headers + +- `def urlsend(url, verb, headers, decode, route, query, data, json_data, return_json, return_headers, debug, timeout)` + Send request with `urlrequest`, converting result to json if `return_json` + +- `def do_request(url, post, headers, **data)` + Call GET or json-encoded POST on `url`, depending on `post` + +- `def start_server(port, host, dgram, reuse_addr, n_queue)` + Create a `socket` server on `port`, with optional `host`, of type `dgram` + +- `def start_client(port, host, dgram)` + Create a `socket` client on `port`, with optional `host`, of type `dgram` + +- `def tobytes(s)` + Convert `s` into HTTP-ready bytes format + +- `def http_response(body, status, hdrs, **kwargs)` + Create an HTTP-ready response, adding `kwargs` to `hdrs` + +- `@threaded def recv_once(host, port)` + Spawn a thread to receive a single HTTP request and store in `d['r']` + +## fastcore.parallel + +> Threading and multiprocessing functions + +- `def threaded(process)` + Run `f` in a `Thread` (or `Process` if `process=True`), and returns it + +- `def startthread(f)` + Like `threaded`, but start thread immediately + +- `def startproc(f)` + Like `threaded(True)`, but start Process immediately + +- `class ThreadPoolExecutor` + Same as Python's ThreadPoolExecutor, except can pass `max_workers==0` for serial execution + + - `def __init__(self, max_workers, on_exc, pause, **kwargs)` + - `def map(self, f, items, *args, **kwargs)` + +- `@delegates() class ProcessPoolExecutor` + Same as Python's ProcessPoolExecutor, except can pass `max_workers==0` for serial execution + + - `def __init__(self, max_workers, on_exc, pause, **kwargs)` + - `def map(self, f, items, *args, **kwargs)` + +- `def parallel(f, items, *args, **kwargs)` + Applies `func` in parallel to `items`, using `n_workers` + +- `def parallel_async(f, items, *args, **kwargs)` + Applies `f` to `items` in parallel using asyncio and a semaphore to limit concurrency. + +- `def run_procs(f, f_done, args)` + Call `f` for each item in `args` in parallel, yielding `f_done` + +- `def parallel_gen(cls, items, n_workers, **kwargs)` + Instantiate `cls` in `n_workers` procs & call each on a subset of `items` in parallel. + +## fastcore.py2pyi + +- `def imp_mod(module_path, package)` + Import dynamically the module referenced in `fn` + +- `def has_deco(node, name)` + Check if a function node `node` has a decorator named `name` + +- `def create_pyi(fn, package)` + Convert `fname.py` to `fname.pyi` by removing function bodies and expanding `delegates` kwargs + +- `@call_parse def py2pyi(fname, package)` + Convert `fname.py` to `fname.pyi` by removing function bodies and expanding `delegates` kwargs + +- `@call_parse def replace_wildcards(path)` + Expand wildcard imports in the specified Python file. + +## fastcore.script + +> A fast way to turn your python function into a script. + +- `def store_true()` + Placeholder to pass to `Param` for `store_true` action + +- `def store_false()` + Placeholder to pass to `Param` for `store_false` action + +- `def bool_arg(v)` + Use as `type` for `Param` to get `bool` behavior + +- `class Param` + A parameter in a function used in `anno_parser` or `call_parse` + + - `def __init__(self, help, type, opt, action, nargs, const, choices, required, default)` + - `def set_default(self, d)` + - `@property def pre(self)` + - `@property def kwargs(self)` + - `def __repr__(self)` + +- `def anno_parser(func, prog)` + Look at params (annotated with `Param`) in func and return an `ArgumentParser` + +- `def args_from_prog(func, prog)` + Extract args from `prog` + +- `def call_parse(func, nested)` + Decorator to create a simple CLI from `func` using `anno_parser` + +## fastcore.style + +> Fast styling for friendly CLIs. + +- `class StyleCode` + An escape sequence for styling terminal text. + + - `def __init__(self, name, code, typ)` + - `def __str__(self)` + +- `class Style` + A minimal terminal text styler. + + - `def __init__(self, codes)` + - `def __dir__(self)` + - `def __getattr__(self, k)` + - `def __call__(self, obj)` + - `def __repr__(self)` + +- `def demo()` + Demonstrate all available styles and their codes. + +## fastcore.test + +> Helper functions to quickly write tests in notebooks + +- `def test_fail(f, msg, contains, args, kwargs)` + Fails with `msg` unless `f()` raises an exception and (optionally) has `contains` in `e.args` + +- `def test(a, b, cmp, cname)` + `assert` that `cmp(a,b)`; display inputs and `cname or cmp.__name__` if it fails + +- `def nequals(a, b)` + Compares `a` and `b` for `not equals` + +- `def test_eq(a, b)` + `test` that `a==b` + +- `def test_eq_type(a, b)` + `test` that `a==b` and are same type + +- `def test_ne(a, b)` + `test` that `a!=b` + +- `def is_close(a, b, eps)` + Is `a` within `eps` of `b` + +- `def test_close(a, b, eps)` + `test` that `a` is within `eps` of `b` + +- `def test_is(a, b)` + `test` that `a is b` + +- `def test_shuffled(a, b)` + `test` that `a` and `b` are shuffled versions of the same sequence of items + +- `def test_stdout(f, exp, regex)` + Test that `f` prints `exp` to stdout, optionally checking as `regex` + +- `def test_fig_exists(ax)` + Test there is a figure displayed in `ax` + +- `class ExceptionExpected` + Context manager that tests if an exception is raised + + - `def __init__(self, ex, regex)` + - `def __enter__(self)` + - `def __exit__(self, type, value, traceback)` + +## fastcore.transform + +> Definition of `Transform` and `Pipeline` + +- `class Transform` + Delegates (`__call__`,`decode`,`setup`) to (encodes,decodes,setups) if `split_idx` matches + + - `def __init__(self, enc, dec, split_idx, order)` + - `@property def name(self)` + - `def __call__(self, x, **kwargs)` + - `def decode(self, x, **kwargs)` + - `def __repr__(self)` + - `def setup(self, items, train_setup)` + +- `class InplaceTransform` + A `Transform` that modifies in-place and just returns whatever it's passed + + +- `class DisplayedTransform` + A transform with a `__repr__` that shows its attrs + + - `@property def name(self)` + +- `class ItemTransform` + A transform that always take tuples as items + + - `def __call__(self, x, **kwargs)` + - `def decode(self, x, **kwargs)` + +- `def get_func(t, name, *args, **kwargs)` + Get the `t.name` (potentially partial-ized with `args` and `kwargs`) or `noop` if not defined + +- `class Func` + Basic wrapper around a `name` with `args` and `kwargs` to call on a given type + + - `def __init__(self, name, *args, **kwargs)` + - `def __repr__(self)` + - `def __call__(self, t)` + +- `def compose_tfms(x, tfms, is_enc, reverse, **kwargs)` + Apply all `func_nm` attribute of `tfms` on `x`, maybe in `reverse` order + +- `def mk_transform(f)` + Convert function `f` to `Transform` if it isn't already one + +- `def gather_attrs(o, k, nm)` + Used in __getattr__ to collect all attrs `k` from `self.{nm}` + +- `def gather_attr_names(o, nm)` + Used in __dir__ to collect all attrs `k` from `self.{nm}` + +- `class Pipeline` + A pipeline of composed (for encode/decode) transforms, setup with types + + - `def __init__(self, funcs, split_idx)` + - `def setup(self, items, train_setup)` + - `def add(self, ts, items, train_setup)` + - `def __call__(self, o)` + - `def __repr__(self)` + - `def __getitem__(self, i)` + - `def __setstate__(self, data)` + - `def __getattr__(self, k)` + - `def __dir__(self)` + - `def decode(self, o, full)` + - `def show(self, o, ctx, **kwargs)` + +## fastcore.xdg + +> XDG Base Directory Specification helpers. + +- `def xdg_cache_home()` + Path corresponding to `XDG_CACHE_HOME` + +- `def xdg_config_dirs()` + Paths corresponding to `XDG_CONFIG_DIRS` + +- `def xdg_config_home()` + Path corresponding to `XDG_CONFIG_HOME` + +- `def xdg_data_dirs()` + Paths corresponding to XDG_DATA_DIRS` + +- `def xdg_data_home()` + Path corresponding to `XDG_DATA_HOME` + +- `def xdg_runtime_dir()` + Path corresponding to `XDG_RUNTIME_DIR` + +- `def xdg_state_home()` + Path corresponding to `XDG_STATE_HOME` + +## fastcore.xml + +> Concise generation of XML. + +- `class FT` + A 'Fast Tag' structure, containing `tag`,`children`,and `attrs` + + - `def __init__(self, tag, cs, attrs, void_, **kwargs)` + - `def on(self, f)` + - `def changed(self)` + - `def __setattr__(self, k, v)` + - `def __getattr__(self, k)` + - `@property def list(self)` + - `def get(self, k, default)` + - `def __repr__(self)` + - `def __iter__(self)` + - `def __getitem__(self, idx)` + - `def __setitem__(self, i, o)` + - `def __call__(self, *c, **kw)` + - `def set(self, *c, **kw)` + Set children and/or attributes (chainable) + + +- `def ft(tag, *c, **kw)` + Create an `FT` structure for `to_xml()` + +- `def Html(*c, **kwargs)` + An HTML tag, optionally preceeded by `!DOCTYPE HTML` + +- `class Safe` + - `def __html__(self)` + +- `def to_xml(elm, lvl, indent, do_escape)` + Convert `ft` element tree into an XML string + +- `def highlight(s, lang)` + Markdown to syntax-highlight `s` in language `lang` + +## fastcore.xtras + +> Utility functions used in the fastai library + +- `def walk(path, symlinks, keep_file, keep_folder, skip_folder, func, ret_folders)` + Generator version of `os.walk`, using functions to filter files and folders + +- `def globtastic(path, recursive, symlinks, file_glob, file_re, folder_re, skip_file_glob, skip_file_re, skip_folder_re, func, ret_folders)` + A more powerful `glob`, including regex matches, symlink handling, and skip parameters + +- `@contextmanager def maybe_open(f, mode, **kwargs)` + Context manager: open `f` if it is a path (and close on exit) + +- `def mkdir(path, exist_ok, parents, overwrite, **kwargs)` + Creates and returns a directory defined by `path`, optionally removing previous existing directory if `overwrite` is `True` + +- `def image_size(fn)` + Tuple of (w,h) for png, gif, or jpg; `None` otherwise + +- `def bunzip(fn)` + bunzip `fn`, raising exception if output already exists + +- `def loads(s, **kw)` + Same as `json.loads`, but handles `None` + +- `def loads_multi(s)` + Generator of >=0 decoded json dicts, possibly with non-json ignored text at start and end + +- `def dumps(obj, **kw)` + Same as `json.dumps`, but uses `ujson` if available + +- `def untar_dir(fname, dest, rename, overwrite)` + untar `file` into `dest`, creating a directory if the root contains more than one item + +- `def repo_details(url)` + Tuple of `owner,name` from ssh or https git repo `url` + +- `def run(cmd, *rest)` + Pass `cmd` (splitting with `shlex` if string) to `subprocess.run`; return `stdout`; raise `IOError` if fails + +- `def open_file(fn, mode, **kwargs)` + Open a file, with optional compression if gz or bz2 suffix + +- `def save_pickle(fn, o)` + Save a pickle file, to a file name or opened file + +- `def load_pickle(fn)` + Load a pickle file from a file name or opened file + +- `def parse_env(s, fn)` + Parse a shell-style environment string or file + +- `def expand_wildcards(code)` + Expand all wildcard imports in the given code string. + +- `def dict2obj(d, list_func, dict_func)` + Convert (possibly nested) dicts (or lists of dicts) to `AttrDict` + +- `def obj2dict(d)` + Convert (possibly nested) AttrDicts (or lists of AttrDicts) to `dict` + +- `def repr_dict(d)` + Print nested dicts and lists, such as returned by `dict2obj` + +- `def is_listy(x)` + `isinstance(x, (tuple,list,L,slice,Generator))` + +- `def mapped(f, it)` + map `f` over `it`, unless it's not listy, in which case return `f(it)` + +- `@patch def readlines(self, hint, encoding)` + Read the content of `self` + +- `@patch def read_json(self, encoding, errors)` + Same as `read_text` followed by `loads` + +- `@patch def mk_write(self, data, encoding, errors, mode)` + Make all parent dirs of `self`, and write `data` + +- `@patch def relpath(self, start)` + Same as `os.path.relpath`, but returns a `Path`, and resolves symlinks + +- `@patch def ls(self, n_max, file_type, file_exts)` + Contents of path as a list + +- `@patch def delete(self)` + Delete a file, symlink, or directory tree + +- `class IterLen` + Base class to add iteration to anything supporting `__len__` and `__getitem__` + + - `def __iter__(self)` + +- `@docs class ReindexCollection` + Reindexes collection `coll` with indices `idxs` and optional LRU cache of size `cache` + + - `def __init__(self, coll, idxs, cache, tfm)` + - `def __getitem__(self, i)` + - `def __len__(self)` + - `def reindex(self, idxs)` + - `def shuffle(self)` + - `def cache_clear(self)` + - `def __getstate__(self)` + - `def __setstate__(self, s)` + +- `def get_source_link(func)` + Return link to `func` in source code + +- `def truncstr(s, maxlen, suf, space)` + Truncate `s` to length `maxlen`, adding suffix `suf` if truncated + +- `def sparkline(data, mn, mx, empty_zero)` + Sparkline for `data`, with `None`s (and zero, if `empty_zero`) shown as empty column + +- `def modify_exception(e, msg, replace)` + Modifies `e` with a custom message attached + +- `def round_multiple(x, mult, round_down)` + Round `x` to nearest multiple of `mult` + +- `def set_num_threads(nt)` + Get numpy (and others) to use `nt` threads + +- `def join_path_file(file, path, ext)` + Return `path/file` if file is a string or a `Path`, file otherwise + +- `def autostart(g)` + Decorator that automatically starts a generator + +- `class EventTimer` + An event timer with history of `store` items of time `span` + + - `def __init__(self, store, span)` + - `def add(self, n)` + Record `n` events + + - `@property def duration(self)` + - `@property def freq(self)` + +- `def stringfmt_names(s)` + Unique brace-delimited names in `s` + +- `class PartialFormatter` + A `string.Formatter` that doesn't error on missing fields, and tracks missing fields and unused args + + - `def __init__(self)` + - `def get_field(self, nm, args, kwargs)` + - `def check_unused_args(self, used, args, kwargs)` + +- `def partial_format(s, **kwargs)` + string format `s`, ignoring missing field errors, returning missing and extra fields + +- `def utc2local(dt)` + Convert `dt` from UTC to local time + +- `def local2utc(dt)` + Convert `dt` from local to UTC time + +- `def trace(f)` + Add `set_trace` to an existing function `f` + +- `@contextmanager def modified_env(*delete, **replace)` + Context manager temporarily modifying `os.environ` by deleting `delete` and replacing `replace` + +- `class ContextManagers` + Wrapper for `contextlib.ExitStack` which enters a collection of context managers + + - `def __init__(self, mgrs)` + - `def __enter__(self)` + - `def __exit__(self, *args, **kwargs)` + +- `def shufflish(x, pct)` + Randomly relocate items of `x` up to `pct` of `len(x)` from their starting location + +- `def console_help(libname)` + Show help for all console scripts from `libname` + +- `def hl_md(s, lang, show)` + Syntax highlight `s` using `lang`. + +- `def type2str(typ)` + Stringify `typ` + +- `class Unset` + - `def __repr__(self)` + - `def __str__(self)` + - `def __bool__(self)` + - `@property def name(self)` + +- `def nullable_dc(cls)` + Like `dataclass`, but default of `UNSET` added to fields without defaults + +- `def flexiclass(cls)` + Convert `cls` into a `dataclass` like `make_nullable`. Converts in place and also returns the result. + +- `def asdict(o)` + Convert `o` to a `dict`, supporting dataclasses, namedtuples, iterables, and `__dict__` attrs. + +- `def is_typeddict(cls)` + Check if `cls` is a `TypedDict` + +- `def is_namedtuple(cls)` + `True` if `cls` is a namedtuple type + +- `def flexicache(*funcs)` + Like `lru_cache`, but customisable with policy `funcs` + +- `def time_policy(seconds)` + A `flexicache` policy that expires cached items after `seconds` have passed + +- `def mtime_policy(filepath)` + A `flexicache` policy that expires cached items after `filepath` modified-time changes + +- `def timed_cache(seconds, maxsize)` + Like `lru_cache`, but also with time-based eviction + \ No newline at end of file diff --git a/llms.txt b/llms.txt new file mode 100644 index 00000000..034ac932 --- /dev/null +++ b/llms.txt @@ -0,0 +1,43 @@ +# fastcore + +fastcore adds to Python features inspired by other languages, like multiple dispatch from Julia, mixins from Ruby, and currying, binding, and more from Haskell. It also adds some “missing features” and clean up some rough edges in the Python standard library, such as simplifying parallel processing, and bringing ideas from NumPy over to Python’s list type. + +Here are some tips on using fastcore: + +- **Liberal imports**: Utilize `from fastcore.module import *` freely. The library is designed for safe wildcard imports. +- **Enhanced list operations**: Substitute `list` with `L`. This provides advanced indexing, method chaining, and additional functionality while maintaining list-like behavior. +- **Extend existing classes**: Apply the `@patch` decorator to add methods to classes, including built-ins, without subclassing. This enables more flexible code organization. +- **Streamline class initialization**: In `__init__` methods, use `store_attr()` to efficiently set multiple attributes, reducing repetitive assignment code. +- **Explicit keyword arguments**: Apply the `delegates` decorator to functions to replace `**kwargs` with specific parameters, enhancing IDE support and documentation. +- **Optimize parallel execution**: Leverage fastcore's enhanced `ThreadPoolExecutor` and `ProcessPoolExecutor` for simplified concurrent processing. +- **Expressive testing**: Prefer fastcore's testing functions like `test_eq`, `test_ne`, `test_close` for more readable and informative test assertions. +- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to `pathlib.Path`. +- **Flexible data structures**: Convert between dictionaries and attribute-access objects using `dict2obj` and `obj2dict` for more intuitive data handling. +- **Data pipeline construction**: Employ `Transform` and `Pipeline` classes to create modular, composable data processing workflows. +- **Functional programming paradigms**: Utilize tools like `compose`, `maps`, and `filter_ex` to write more functional-style Python code. +- **Documentation**: Use `docments` where possible to document parameters of functions and methods. +- **Time-aware caching**: Apply the `timed_cache` decorator to add time-based expiration to the standard `lru_cache` functionality. +- **Simplified CLI creation**: Use fastcore's console script utilities to easily transform Python functions into command-line interfaces. + +## Tutorials + +- [Fastcore Quick Tour](https://fastcore.fast.ai/tour.html.md): A quick tour of a few higlights from fastcore. +- [Blog Post](https://gist.githubusercontent.com/hamelsmu/ea9e0519d9a94a4203bcc36043eb01c5/raw/6c0c96a2823d67aecc103206d6ab21c05dcd520a/fastcore:_an_underrated_python_library.md): A tour of some of the features of fastcore. + +## API + +- [API List](https://fastcore.fast.ai/apilist.txt): A succint list of all functions and methods in fastcore. + +## Optional + +- [fastcore.test](https://fastcore.fast.ai/test.html.md): Simple testing functions +- [fastcore.basics](https://fastcore.fast.ai/basics.html.md): Basic functionality used in the fastai library. +- [fastcore.foundation](https://fastcore.fast.ai/foundation.html.md): The L class and helpers for it +- [fastcore.xtras](https://fastcore.fast.ai/xtras.html.md): Utility functions used in the fastai library +- [fastcore.parallel](https://fastcore.fast.ai/parallel.html.md):parallel processing +- [fastcore.net](https://fastcore.fast.ai/net.html.md): testing utilities +- [fastcore.docments](https://fastcore.fast.ai/docments.html.md): documentation utilities +- [fastcore.meta](https://fastcore.fast.ai/meta.html.md): metaclasses +- [fastcore.script](https://fastcore.fast.ai/script.html.md): CLI script utilities +- [fastcore.xdg](https://fastcore.fast.ai/xdg.html.md): XDG Base Directory Specification helpers. +- [fastcore.xml](https://fastcore.fast.ai/xml.html.md): concise generation of XML \ No newline at end of file diff --git a/meta.html b/meta.html new file mode 100644 index 00000000..aa4e1a79 --- /dev/null +++ b/meta.html @@ -0,0 +1,1309 @@ + + + + + + + + + + +Meta – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Meta

+
+ +
+
+ Metaclasses +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from fastcore.foundation import *
+from nbdev.showdoc import *
+from fastcore.nb_imports import *
+
+

See this blog post for more information about metaclasses.

+
    +
  • FixSigMeta preserves information that enables intropsection of signatures (i.e. tab completion in IDEs) when certain types of inheritence would otherwise obfuscate this introspection.
  • +
  • PrePostInitMeta ensures that the classes defined with it run __pre_init__ and __post_init__ (without having to write self.__pre_init__() and self.__post_init__() in the actual init
  • +
  • NewChkMeta gives the PrePostInitMeta functionality and ensures classes defined with it don’t re-create an object of their type whenever it’s passed to the constructor
  • +
  • BypassNewMeta ensures classes defined with it can easily be casted form objects they subclass.
  • +
+
+

source

+
+

test_sig

+
+
 test_sig (f, b)
+
+

Test the signature of an object

+
+
def func_1(h,i,j): pass
+def func_2(h,i=3, j=[5,6]): pass
+
+class T:
+    def __init__(self, a, b): pass
+
+test_sig(func_1, '(h, i, j)')
+test_sig(func_2, '(h, i=3, j=[5, 6])')
+test_sig(T, '(a, b)')
+
+
+

source

+
+
+

FixSigMeta

+
+
 FixSigMeta (name, bases, dict)
+
+

A metaclass that fixes the signature on classes that override __new__

+

When you inherit from a class that defines __new__, or a metaclass that defines __call__, the signature of your __init__ method is obfuscated such that tab completion no longer works. FixSigMeta fixes this issue and restores signatures.

+

To understand what FixSigMeta does, it is useful to inspect an object’s signature. You can inspect the signature of an object with inspect.signature:

+
+
class T:
+    def __init__(self, a, b, c): pass
+    
+inspect.signature(T)
+
+
<Signature (a, b, c)>
+
+
+

This corresponds to tab completion working in the normal way:

+

Tab completion in a Jupyter Notebook.

+

However, when you inherhit from a class that defines __new__ or a metaclass that defines __call__ this obfuscates the signature by overriding your class with the signature of __new__, which prevents tab completion from displaying useful information:

+
+
class Foo:
+    def __new__(self, **args): pass
+
+class Bar(Foo):
+    def __init__(self, d, e, f): pass
+    
+inspect.signature(Bar)
+
+
<Signature (d, e, f)>
+
+
+

Tab completion in a Jupyter Notebook.

+

Finally, the signature and tab completion can be restored by inheriting from the metaclass FixSigMeta as shown below:

+
+
class Bar(Foo, metaclass=FixSigMeta):
+    def __init__(self, d, e, f): pass
+    
+test_sig(Bar, '(d, e, f)')
+inspect.signature(Bar)
+
+
<Signature (d, e, f)>
+
+
+

Tab completion in a Jupyter Notebook.

+

If you need to define a metaclass that overrides __call__ (as done in PrePostInitMeta), you need to inherit from FixSigMeta instead of type when constructing the metaclass to preserve the signature in __init__. Be careful not to override __new__ when doing this:

+
+
class TestMeta(FixSigMeta):
+    # __new__ comes from FixSigMeta
+    def __call__(cls, *args, **kwargs): pass
+    
+class T(metaclass=TestMeta):
+    def __init__(self, a, b): pass
+    
+test_sig(T, '(a, b)')
+
+

On the other hand, if you fail to inherit from FixSigMeta when inheriting from a metaclass that overrides __call__, your signature will reflect that of __call__ instead (which is often undesirable):

+
+
class GenericMeta(type):
+    "A boilerplate metaclass that doesn't do anything for testing."
+    def __new__(cls, name, bases, dict):
+        return super().__new__(cls, name, bases, dict)
+    def __call__(cls, *args, **kwargs): pass
+
+class T2(metaclass=GenericMeta):
+    def __init__(self, a, b): pass
+
+# We can avoid this by inheriting from the metaclass `FixSigMeta`
+test_sig(T2, '(*args, **kwargs)')
+
+
+

source

+
+
+

PrePostInitMeta

+
+
 PrePostInitMeta (name, bases, dict)
+
+

A metaclass that calls optional __pre_init__ and __post_init__ methods

+

__pre_init__ and __post_init__ are useful for initializing variables or performing tasks prior to or after __init__ being called, respectively. Fore example:

+
+
class _T(metaclass=PrePostInitMeta):
+    def __pre_init__(self):  self.a  = 0; 
+    def __init__(self,b=0):  self.b = self.a + 1; assert self.b==1
+    def __post_init__(self): self.c = self.b + 2; assert self.c==3
+
+t = _T()
+test_eq(t.a, 0) # set with __pre_init__
+test_eq(t.b, 1) # set with __init__
+test_eq(t.c, 3) # set with __post_init__
+
+

One use for PrePostInitMeta is avoiding the __super__().__init__() boilerplate associated with subclassing, such as used in AutoInit.

+
+

source

+
+
+

AutoInit

+
+
 AutoInit (*args, **kwargs)
+
+

Same as object, but no need for subclasses to call super().__init__

+

This is normally used as a mixin, eg:

+
+
class TestParent():
+    def __init__(self): self.h = 10
+        
+class TestChild(AutoInit, TestParent):
+    def __init__(self): self.k = self.h + 2
+    
+t = TestChild()
+test_eq(t.h, 10) # h=10 is initialized in the parent class
+test_eq(t.k, 12)
+
+
+

source

+
+
+

NewChkMeta

+
+
 NewChkMeta (name, bases, dict)
+
+

Metaclass to avoid recreating object passed to constructor

+

NewChkMeta is used when an object of the same type is the first argument to your class’s constructor (i.e. the __init__ function), and you would rather it not create a new object but point to the same exact object.

+

This is used in L, for example, to avoid creating a new object when the object is already of type L. This allows the users to defenisvely instantiate an L object and just return a reference to the same object if it already happens to be of type L.

+

For example, the below class _T optionally accepts an object o as its first argument. A new object is returned upon instantiation per usual:

+
+
class _T():
+    "Testing"
+    def __init__(self, o): 
+        # if `o` is not an object without an attribute `foo`, set foo = 1
+        self.foo = getattr(o,'foo',1)
+
+
+
t = _T(3)
+test_eq(t.foo,1) # 1 was not of type _T, so foo = 1
+
+t2 = _T(t) #t1 is of type _T
+assert t is not t2 # t1 and t2 are different objects
+
+

However, if we want _T to return a reference to the same object when passed an an object of type _T we can inherit from the NewChkMeta class as illustrated below:

+
+
class _T(metaclass=NewChkMeta):
+    "Testing with metaclass NewChkMeta"
+    def __init__(self, o=None, b=1):
+        # if `o` is not an object without an attribute `foo`, set foo = 1
+        self.foo = getattr(o,'foo',1)
+        self.b = b
+
+

We can now test t and t2 are now pointing at the same object when using this new definition of _T:

+
+
t = _T(3)
+test_eq(t.foo,1) # 1 was not of type _T, so foo = 1
+
+t2 = _T(t) # t2 will now reference t
+
+test_is(t, t2) # t and t2 are the same object
+t2.foo = 5 # this will also change t.foo to 5 because it is the same object
+test_eq(t.foo, 5)
+test_eq(t2.foo, 5)
+
+

However, there is one exception to how NewChkMeta works. If you pass any additional arguments in the constructor a new object is returned, even if the first object is of the same type. For example, consider the below example where we pass the additional argument b into the constructor:

+
+
t3 = _T(t, b=1)
+assert t3 is not t
+
+t4 = _T(t) # without any arguments the constructor will return a reference to the same object
+assert t4 is t
+
+

Finally, it should be noted that NewChkMeta as well as all other metaclases in this section, inherit from FixSigMeta. This means class signatures will always be preserved when inheriting from this metaclass (see docs for FixSigMeta for more details):

+
+
test_sig(_T, '(o=None, b=1)')
+
+
+

source

+
+
+

BypassNewMeta

+
+
 BypassNewMeta (name, bases, dict)
+
+

Metaclass: casts x to this class if it’s of type cls._bypass_type

+

BypassNewMeta is identical to NewChkMeta, except for checking for a class as the same type, we instead check for a class of type specified in attribute _bypass_type.

+

In NewChkMeta, objects of the same type passed to the constructor (without arguments) would result into a new variable referencing the same object. However, with BypassNewMeta this only occurs if the type matches the _bypass_type of the class you are defining:

+
+
class _TestA: pass
+class _TestB: pass
+
+class _T(_TestA, metaclass=BypassNewMeta):
+    _bypass_type=_TestB
+    def __init__(self,x): self.x=x
+
+

In the below example, t does not refer to t2 because t is of type _TestA while _T._bypass_type is of type TestB:

+
+
t = _TestA()
+t2 = _T(t)
+assert t is not t2
+
+

However, if t is set to _TestB to match _T._bypass_type, then both t and t2 will refer to the same object.

+
+
t = _TestB()
+t2 = _T(t)
+t2.new_attr = 15
+
+test_is(t, t2)
+# since t2 just references t these will be the same
+test_eq(t.new_attr, t2.new_attr)
+
+# likewise, chaning an attribute on t will also affect t2 because they both point to the same object.
+t.new_attr = 9
+test_eq(t2.new_attr, 9)
+
+
+
+

Metaprogramming

+
+

source

+
+

empty2none

+
+
 empty2none (p)
+
+

Replace Parameter.empty with None

+
+

source

+
+
+

anno_dict

+
+
 anno_dict (f)
+
+

__annotation__ dictionary withemptycast toNone`, returning empty if doesn’t exist

+
+
def _f(a:int, b:L)->str: ...
+test_eq(anno_dict(_f), {'a': int, 'b': L, 'return': str})
+
+
+

source

+
+
+

use_kwargs_dict

+
+
 use_kwargs_dict (keep=False, **kwargs)
+
+

Decorator: replace **kwargs in signature with names params

+

Replace all **kwargs with named arguments like so:

+
+
@use_kwargs_dict(y=1,z=None)
+def foo(a, b=1, **kwargs): pass
+
+test_sig(foo, '(a, b=1, *, y=1, z=None)')
+
+

Add named arguments, but optionally keep **kwargs by setting keep=True:

+
+
@use_kwargs_dict(y=1,z=None, keep=True)
+def foo(a, b=1, **kwargs): pass
+
+test_sig(foo, '(a, b=1, *, y=1, z=None, **kwargs)')
+
+
+

source

+
+
+

use_kwargs

+
+
 use_kwargs (names, keep=False)
+
+

Decorator: replace **kwargs in signature with names params

+

use_kwargs is different than use_kwargs_dict as it only replaces **kwargs with named parameters without any default values:

+
+
@use_kwargs(['y', 'z'])
+def foo(a, b=1, **kwargs): pass
+
+test_sig(foo, '(a, b=1, *, y=None, z=None)')
+
+

You may optionally keep the **kwargs argument in your signature by setting keep=True:

+
+
@use_kwargs(['y', 'z'], keep=True)
+def foo(a, *args, b=1, **kwargs): pass
+test_sig(foo, '(a, *args, b=1, y=None, z=None, **kwargs)')
+
+
+

source

+
+
+

delegates

+
+
 delegates (to:function=None, keep=False, but:list=None)
+
+

Decorator: replace **kwargs in signature with params from to

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
tofunctionNoneDelegatee
keepboolFalseKeep kwargs in decorated function?
butlistNoneExclude these parameters from signature
+

A common Python idiom is to accept **kwargs in addition to named parameters that are passed onto other function calls. It is especially common to use **kwargs when you want to give the user an option to override default parameters of any functions or methods being called by the parent function.

+

For example, suppose we have have a function foo that passes arguments to baz like so:

+
+
def baz(a, b:int=2, c:int=3): return a + b + c
+
+def foo(c, a, **kwargs):
+    return c + baz(a, **kwargs)
+
+assert foo(c=1, a=1) == 7
+
+

The problem with this approach is the api for foo is obfuscated. Users cannot introspect what the valid arguments for **kwargs are without reading the source code. When a user tries tries to introspect the signature of foo, they are presented with this:

+
+
inspect.signature(foo)
+
+
<Signature (c, a, **kwargs)>
+
+
+

We can address this issue by using the decorator delegates to include parameters from other functions. For example, if we apply the delegates decorator to foo to include parameters from baz:

+
+
@delegates(baz)
+def foo(c, a, **kwargs):
+    return c + baz(a, **kwargs)
+
+test_sig(foo, '(c, a, *, b: int = 2)')
+inspect.signature(foo)
+
+
<Signature (c, a, *, b: int = 2)>
+
+
+

We can optionally decide to keep **kwargs by setting keep=True:

+
+
@delegates(baz, keep=True)
+def foo(c, a, **kwargs):
+    return c + baz(a, **kwargs)
+
+inspect.signature(foo)
+
+
<Signature (c, a, *, b: int = 2, **kwargs)>
+
+
+

It is important to note that only parameters with default parameters are included. For example, in the below scenario only c, but NOT e and d are included in the signature of foo after applying delegates:

+
+
def basefoo(e, d, c=2): pass
+
+@delegates(basefoo)
+def foo(a, b=1, **kwargs): pass
+inspect.signature(foo) # e and d are not included b/c they don't have default parameters.
+
+
<Signature (a, b=1, *, c=2)>
+
+
+

The reason that required arguments (i.e. those without default parameters) are automatically excluded is that you should be explicitly implementing required arguments into your function’s signature rather than relying on delegates.

+

Additionally, you can exclude specific parameters from being included in the signature with the but parameter. In the example below, we exclude the parameter d:

+
+
def basefoo(e, c=2, d=3): pass
+
+@delegates(basefoo, but= ['d'])
+def foo(a, b=1, **kwargs): pass
+
+test_sig(foo, '(a, b=1, *, c=2)')
+inspect.signature(foo)
+
+
<Signature (a, b=1, *, c=2)>
+
+
+

You can also use delegates between methods in a class. Here is an example of delegates with class methods:

+
+
# example 1: class methods
+class _T():
+    @classmethod
+    def foo(cls, a=1, b=2):
+        pass
+    
+    @classmethod
+    @delegates(foo)
+    def bar(cls, c=3, **kwargs):
+        pass
+
+test_sig(_T.bar, '(c=3, *, a=1, b=2)')
+
+

Here is the same example with instance methods:

+
+
# example 2: instance methods
+class _T():
+    def foo(self, a=1, b=2):
+        pass
+    
+    @delegates(foo)
+    def bar(self, c=3, **kwargs):
+        pass
+
+t = _T()
+test_sig(t.bar, '(c=3, *, a=1, b=2)')
+
+

You can also delegate between classes. By default, the delegates decorator will delegate to the superclass:

+
+
class BaseFoo:
+    def __init__(self, e, c=2): pass
+
+@delegates()# since no argument was passsed here we delegate to the superclass
+class Foo(BaseFoo):
+    def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs)
+
+test_sig(Foo, '(a, b=1, *, c=2)')
+
+
+

source

+
+
+

method

+
+
 method (f)
+
+

Mark f as a method

+

The method function is used to change a function’s type to a method. In the below example we change the type of a from a function to a method:

+
+
def a(x=2): return x + 1
+assert type(a).__name__ == 'function'
+
+a = method(a)
+assert type(a).__name__ == 'method'
+
+
+

source

+
+
+

funcs_kwargs

+
+
 funcs_kwargs (as_method=False)
+
+

Replace methods in cls._methods with those from kwargs

+

The func_kwargs decorator allows you to add a list of functions or methods to an existing class. You must set this list as a class attribute named _methods when defining your class. Additionally, you must incldue the **kwargs argument in the ___init__ method of your class.

+

After defining your class this way, you can add functions to your class upon instantation as illusrated below.

+

For example, we define class T to allow adding the function b to class T as follows (note that this function is stored as an attribute of T and doesn’t have access to cls or self):

+
+
@funcs_kwargs
+class T:
+    _methods=['b'] # allows you to add method b upon instantiation
+    def __init__(self, f=1, **kwargs): pass # don't forget to include **kwargs in __init__
+    def a(self): return 1
+    def b(self): return 2
+    
+t = T()
+test_eq(t.a(), 1)
+test_eq(t.b(), 2)
+
+

Because we defined the class T this way, the signature of T indicates the option to add the function or method(s) specified in _methods. In this example, b is added to the signature:

+
+
test_sig(T, '(f=1, *, b=None)')
+inspect.signature(T)
+
+
<Signature (f=1, *, b=None)>
+
+
+

You can now add the function b to class T upon instantiation:

+
+
def _new_func(): return 5
+
+t = T(b = _new_func)
+test_eq(t.b(), 5)
+
+

If you try to add a function with a name not listed in _methods it will be ignored. In the below example, the attempt to add a function named a is ignored:

+
+
t = T(a = lambda:3)
+test_eq(t.a(), 1) # the attempt to add a is ignored and uses the original method instead.
+
+

Note that you can also add methods not defined in the original class as long it is specified in the _methods attribute:

+
+
@funcs_kwargs
+class T:
+    _methods=['c']
+    def __init__(self, f=1, **kwargs): pass
+
+t = T(c = lambda: 4)
+test_eq(t.c(), 4)
+
+

Until now, these examples showed how to add functions stored as an instance attribute without access to self. However, if you need access to self you can set as_method=True in the func_kwargs decorator to add a method instead:

+
+
def _f(self,a=1): return self.num + a # access the num attribute from the instance
+
+@funcs_kwargs(as_method=True)
+class T: 
+    _methods=['b']
+    num = 5
+    
+t = T(b = _f) # adds method b
+test_eq(t.b(5), 10) # self.num + 5 = 10
+
+

Here is an example of how you might use this functionality with inheritence:

+
+
def _f(self,a=1): return self.num * a #multiply instead of add 
+
+class T2(T):
+    def __init__(self,num):
+        super().__init__(b = _f) # add method b from the super class
+        self.num=num
+        
+t = T2(num=3)
+test_eq(t.b(a=5), 15) # 3 * 5 = 15
+test_sig(T2, '(num)')
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/meta.html.md b/meta.html.md new file mode 100644 index 00000000..a0d4c388 --- /dev/null +++ b/meta.html.md @@ -0,0 +1,814 @@ +# Meta + + + + +``` python +from fastcore.foundation import * +from nbdev.showdoc import * +from fastcore.nb_imports import * +``` + +See this [blog post](https://realpython.com/python-metaclasses/) for +more information about metaclasses. + +- [`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) + preserves information that enables [intropsection of + signatures](https://www.python.org/dev/peps/pep-0362/#:~:text=Python%20has%20always%20supported%20powerful,fully%20reconstruct%20the%20function's%20signature.) + (i.e. tab completion in IDEs) when certain types of inheritence would + otherwise obfuscate this introspection. +- [`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta) + ensures that the classes defined with it run `__pre_init__` and + `__post_init__` (without having to write `self.__pre_init__()` and + `self.__post_init__()` in the actual `init` +- [`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) gives + the + [`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta) + functionality and ensures classes defined with it don’t re-create an + object of their type whenever it’s passed to the constructor +- [`BypassNewMeta`](https://fastcore.fast.ai/meta.html#bypassnewmeta) + ensures classes defined with it can easily be casted form objects they + subclass. + +------------------------------------------------------------------------ + +source + +### test_sig + +> test_sig (f, b) + +*Test the signature of an object* + +``` python +def func_1(h,i,j): pass +def func_2(h,i=3, j=[5,6]): pass + +class T: + def __init__(self, a, b): pass + +test_sig(func_1, '(h, i, j)') +test_sig(func_2, '(h, i=3, j=[5, 6])') +test_sig(T, '(a, b)') +``` + +------------------------------------------------------------------------ + +source + +### FixSigMeta + +> FixSigMeta (name, bases, dict) + +*A metaclass that fixes the signature on classes that override +`__new__`* + +When you inherit from a class that defines `__new__`, or a metaclass +that defines `__call__`, the signature of your `__init__` method is +obfuscated such that tab completion no longer works. +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) fixes this +issue and restores signatures. + +To understand what +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) does, it +is useful to inspect an object’s signature. You can inspect the +signature of an object with `inspect.signature`: + +``` python +class T: + def __init__(self, a, b, c): pass + +inspect.signature(T) +``` + + + +This corresponds to tab completion working in the normal way: + +Tab completion in a Jupyter Notebook. + +However, when you inherhit from a class that defines `__new__` or a +metaclass that defines `__call__` this obfuscates the signature by +overriding your class with the signature of `__new__`, which prevents +tab completion from displaying useful information: + +``` python +class Foo: + def __new__(self, **args): pass + +class Bar(Foo): + def __init__(self, d, e, f): pass + +inspect.signature(Bar) +``` + + + +Tab completion in a Jupyter Notebook. + +Finally, the signature and tab completion can be restored by inheriting +from the metaclass +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) as shown +below: + +``` python +class Bar(Foo, metaclass=FixSigMeta): + def __init__(self, d, e, f): pass + +test_sig(Bar, '(d, e, f)') +inspect.signature(Bar) +``` + + + +Tab completion in a Jupyter Notebook. + +If you need to define a metaclass that overrides `__call__` (as done in +[`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta)), +you need to inherit from +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) instead of +`type` when constructing the metaclass to preserve the signature in +`__init__`. Be careful not to override `__new__` when doing this: + +``` python +class TestMeta(FixSigMeta): + # __new__ comes from FixSigMeta + def __call__(cls, *args, **kwargs): pass + +class T(metaclass=TestMeta): + def __init__(self, a, b): pass + +test_sig(T, '(a, b)') +``` + +On the other hand, if you fail to inherit from +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) when +inheriting from a metaclass that overrides `__call__`, your signature +will reflect that of `__call__` instead (which is often undesirable): + +``` python +class GenericMeta(type): + "A boilerplate metaclass that doesn't do anything for testing." + def __new__(cls, name, bases, dict): + return super().__new__(cls, name, bases, dict) + def __call__(cls, *args, **kwargs): pass + +class T2(metaclass=GenericMeta): + def __init__(self, a, b): pass + +# We can avoid this by inheriting from the metaclass `FixSigMeta` +test_sig(T2, '(*args, **kwargs)') +``` + +------------------------------------------------------------------------ + +source + +### PrePostInitMeta + +> PrePostInitMeta (name, bases, dict) + +*A metaclass that calls optional `__pre_init__` and `__post_init__` +methods* + +`__pre_init__` and `__post_init__` are useful for initializing variables +or performing tasks prior to or after `__init__` being called, +respectively. Fore example: + +``` python +class _T(metaclass=PrePostInitMeta): + def __pre_init__(self): self.a = 0; + def __init__(self,b=0): self.b = self.a + 1; assert self.b==1 + def __post_init__(self): self.c = self.b + 2; assert self.c==3 + +t = _T() +test_eq(t.a, 0) # set with __pre_init__ +test_eq(t.b, 1) # set with __init__ +test_eq(t.c, 3) # set with __post_init__ +``` + +One use for +[`PrePostInitMeta`](https://fastcore.fast.ai/meta.html#prepostinitmeta) +is avoiding the `__super__().__init__()` boilerplate associated with +subclassing, such as used in +[`AutoInit`](https://fastcore.fast.ai/meta.html#autoinit). + +------------------------------------------------------------------------ + +source + +### AutoInit + +> AutoInit (*args, **kwargs) + +*Same as `object`, but no need for subclasses to call +`super().__init__`* + +This is normally used as a +[mixin](https://www.residentmar.io/2019/07/07/python-mixins.html), eg: + +``` python +class TestParent(): + def __init__(self): self.h = 10 + +class TestChild(AutoInit, TestParent): + def __init__(self): self.k = self.h + 2 + +t = TestChild() +test_eq(t.h, 10) # h=10 is initialized in the parent class +test_eq(t.k, 12) +``` + +------------------------------------------------------------------------ + +source + +### NewChkMeta + +> NewChkMeta (name, bases, dict) + +*Metaclass to avoid recreating object passed to constructor* + +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) is used +when an object of the same type is the first argument to your class’s +constructor (i.e. the `__init__` function), and you would rather it not +create a new object but point to the same exact object. + +This is used in [`L`](https://fastcore.fast.ai/foundation.html#l), for +example, to avoid creating a new object when the object is already of +type [`L`](https://fastcore.fast.ai/foundation.html#l). This allows the +users to defenisvely instantiate an +[`L`](https://fastcore.fast.ai/foundation.html#l) object and just return +a reference to the same object if it already happens to be of type +[`L`](https://fastcore.fast.ai/foundation.html#l). + +For example, the below class `_T` **optionally** accepts an object `o` +as its first argument. A new object is returned upon instantiation per +usual: + +``` python +class _T(): + "Testing" + def __init__(self, o): + # if `o` is not an object without an attribute `foo`, set foo = 1 + self.foo = getattr(o,'foo',1) +``` + +``` python +t = _T(3) +test_eq(t.foo,1) # 1 was not of type _T, so foo = 1 + +t2 = _T(t) #t1 is of type _T +assert t is not t2 # t1 and t2 are different objects +``` + +However, if we want `_T` to return a reference to the same object when +passed an an object of type `_T` we can inherit from the +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) class as +illustrated below: + +``` python +class _T(metaclass=NewChkMeta): + "Testing with metaclass NewChkMeta" + def __init__(self, o=None, b=1): + # if `o` is not an object without an attribute `foo`, set foo = 1 + self.foo = getattr(o,'foo',1) + self.b = b +``` + +We can now test `t` and `t2` are now pointing at the same object when +using this new definition of `_T`: + +``` python +t = _T(3) +test_eq(t.foo,1) # 1 was not of type _T, so foo = 1 + +t2 = _T(t) # t2 will now reference t + +test_is(t, t2) # t and t2 are the same object +t2.foo = 5 # this will also change t.foo to 5 because it is the same object +test_eq(t.foo, 5) +test_eq(t2.foo, 5) +``` + +However, there is one exception to how +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) works. +**If you pass any additional arguments in the constructor a new object +is returned**, even if the first object is of the same type. For +example, consider the below example where we pass the additional +argument `b` into the constructor: + +``` python +t3 = _T(t, b=1) +assert t3 is not t + +t4 = _T(t) # without any arguments the constructor will return a reference to the same object +assert t4 is t +``` + +Finally, it should be noted that +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta) as well as +all other metaclases in this section, inherit from +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta). This +means class signatures will always be preserved when inheriting from +this metaclass (see docs for +[`FixSigMeta`](https://fastcore.fast.ai/meta.html#fixsigmeta) for more +details): + +``` python +test_sig(_T, '(o=None, b=1)') +``` + +------------------------------------------------------------------------ + +source + +### BypassNewMeta + +> BypassNewMeta (name, bases, dict) + +*Metaclass: casts `x` to this class if it’s of type `cls._bypass_type`* + +[`BypassNewMeta`](https://fastcore.fast.ai/meta.html#bypassnewmeta) is +identical to +[`NewChkMeta`](https://fastcore.fast.ai/meta.html#newchkmeta), except +for checking for a class as the same type, we instead check for a class +of type specified in attribute `_bypass_type`. + +In NewChkMeta, objects of the same type passed to the constructor +(without arguments) would result into a new variable referencing the +same object. However, with +[`BypassNewMeta`](https://fastcore.fast.ai/meta.html#bypassnewmeta) this +only occurs if the type matches the `_bypass_type` of the class you are +defining: + +``` python +class _TestA: pass +class _TestB: pass + +class _T(_TestA, metaclass=BypassNewMeta): + _bypass_type=_TestB + def __init__(self,x): self.x=x +``` + +In the below example, `t` does not refer to `t2` because `t` is of type +`_TestA` while `_T._bypass_type` is of type `TestB`: + +``` python +t = _TestA() +t2 = _T(t) +assert t is not t2 +``` + +However, if `t` is set to `_TestB` to match `_T._bypass_type`, then both +`t` and `t2` will refer to the same object. + +``` python +t = _TestB() +t2 = _T(t) +t2.new_attr = 15 + +test_is(t, t2) +# since t2 just references t these will be the same +test_eq(t.new_attr, t2.new_attr) + +# likewise, chaning an attribute on t will also affect t2 because they both point to the same object. +t.new_attr = 9 +test_eq(t2.new_attr, 9) +``` + +## Metaprogramming + +------------------------------------------------------------------------ + +source + +### empty2none + +> empty2none (p) + +*Replace `Parameter.empty` with `None`* + +------------------------------------------------------------------------ + +source + +### anno_dict + +> anno_dict (f) + +*`__annotation__ dictionary with`empty`cast to`None\`, returning empty +if doesn’t exist* + +``` python +def _f(a:int, b:L)->str: ... +test_eq(anno_dict(_f), {'a': int, 'b': L, 'return': str}) +``` + +------------------------------------------------------------------------ + +source + +### use_kwargs_dict + +> use_kwargs_dict (keep=False, **kwargs) + +*Decorator: replace `**kwargs` in signature with `names` params* + +Replace all `**kwargs` with named arguments like so: + +``` python +@use_kwargs_dict(y=1,z=None) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, y=1, z=None)') +``` + +Add named arguments, but optionally keep `**kwargs` by setting +`keep=True`: + +``` python +@use_kwargs_dict(y=1,z=None, keep=True) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, y=1, z=None, **kwargs)') +``` + +------------------------------------------------------------------------ + +source + +### use_kwargs + +> use_kwargs (names, keep=False) + +*Decorator: replace `**kwargs` in signature with `names` params* + +[`use_kwargs`](https://fastcore.fast.ai/meta.html#use_kwargs) is +different than +[`use_kwargs_dict`](https://fastcore.fast.ai/meta.html#use_kwargs_dict) +as it only replaces `**kwargs` with named parameters without any default +values: + +``` python +@use_kwargs(['y', 'z']) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, y=None, z=None)') +``` + +You may optionally keep the `**kwargs` argument in your signature by +setting `keep=True`: + +``` python +@use_kwargs(['y', 'z'], keep=True) +def foo(a, *args, b=1, **kwargs): pass +test_sig(foo, '(a, *args, b=1, y=None, z=None, **kwargs)') +``` + +------------------------------------------------------------------------ + +source + +### delegates + +> delegates (to:function=None, keep=False, but:list=None) + +*Decorator: replace `**kwargs` in signature with params from `to`* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
tofunctionNoneDelegatee
keepboolFalseKeep kwargs in decorated function?
butlistNoneExclude these parameters from signature
+ +A common Python idiom is to accept `**kwargs` in addition to named +parameters that are passed onto other function calls. It is especially +common to use `**kwargs` when you want to give the user an option to +override default parameters of any functions or methods being called by +the parent function. + +For example, suppose we have have a function `foo` that passes arguments +to `baz` like so: + +``` python +def baz(a, b:int=2, c:int=3): return a + b + c + +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +assert foo(c=1, a=1) == 7 +``` + +The problem with this approach is the api for `foo` is obfuscated. Users +cannot introspect what the valid arguments for `**kwargs` are without +reading the source code. When a user tries tries to introspect the +signature of `foo`, they are presented with this: + +``` python +inspect.signature(foo) +``` + + + +We can address this issue by using the decorator +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) to include +parameters from other functions. For example, if we apply the +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) decorator to +`foo` to include parameters from `baz`: + +``` python +@delegates(baz) +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +test_sig(foo, '(c, a, *, b: int = 2)') +inspect.signature(foo) +``` + + + +We can optionally decide to keep `**kwargs` by setting `keep=True`: + +``` python +@delegates(baz, keep=True) +def foo(c, a, **kwargs): + return c + baz(a, **kwargs) + +inspect.signature(foo) +``` + + + +It is important to note that **only parameters with default parameters +are included**. For example, in the below scenario only `c`, but NOT `e` +and `d` are included in the signature of `foo` after applying +[`delegates`](https://fastcore.fast.ai/meta.html#delegates): + +``` python +def basefoo(e, d, c=2): pass + +@delegates(basefoo) +def foo(a, b=1, **kwargs): pass +inspect.signature(foo) # e and d are not included b/c they don't have default parameters. +``` + + + +The reason that required arguments (i.e. those without default +parameters) are automatically excluded is that you should be explicitly +implementing required arguments into your function’s signature rather +than relying on +[`delegates`](https://fastcore.fast.ai/meta.html#delegates). + +Additionally, you can exclude specific parameters from being included in +the signature with the `but` parameter. In the example below, we exclude +the parameter `d`: + +``` python +def basefoo(e, c=2, d=3): pass + +@delegates(basefoo, but= ['d']) +def foo(a, b=1, **kwargs): pass + +test_sig(foo, '(a, b=1, *, c=2)') +inspect.signature(foo) +``` + + + +You can also use +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) between +methods in a class. Here is an example of +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) with class +methods: + +``` python +# example 1: class methods +class _T(): + @classmethod + def foo(cls, a=1, b=2): + pass + + @classmethod + @delegates(foo) + def bar(cls, c=3, **kwargs): + pass + +test_sig(_T.bar, '(c=3, *, a=1, b=2)') +``` + +Here is the same example with instance methods: + +``` python +# example 2: instance methods +class _T(): + def foo(self, a=1, b=2): + pass + + @delegates(foo) + def bar(self, c=3, **kwargs): + pass + +t = _T() +test_sig(t.bar, '(c=3, *, a=1, b=2)') +``` + +You can also delegate between classes. By default, the +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) decorator +will delegate to the superclass: + +``` python +class BaseFoo: + def __init__(self, e, c=2): pass + +@delegates()# since no argument was passsed here we delegate to the superclass +class Foo(BaseFoo): + def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs) + +test_sig(Foo, '(a, b=1, *, c=2)') +``` + +------------------------------------------------------------------------ + +source + +### method + +> method (f) + +*Mark `f` as a method* + +The [`method`](https://fastcore.fast.ai/meta.html#method) function is +used to change a function’s type to a method. In the below example we +change the type of `a` from a function to a method: + +``` python +def a(x=2): return x + 1 +assert type(a).__name__ == 'function' + +a = method(a) +assert type(a).__name__ == 'method' +``` + +------------------------------------------------------------------------ + +source + +### funcs_kwargs + +> funcs_kwargs (as_method=False) + +*Replace methods in `cls._methods` with those from `kwargs`* + +The `func_kwargs` decorator allows you to add a list of functions or +methods to an existing class. You must set this list as a class +attribute named `_methods` when defining your class. Additionally, you +must incldue the `**kwargs` argument in the `___init__` method of your +class. + +After defining your class this way, you can add functions to your class +upon instantation as illusrated below. + +For example, we define class `T` to allow adding the function `b` to +class `T` as follows (note that this function is stored as an attribute +of `T` and doesn’t have access to `cls` or `self`): + +``` python +@funcs_kwargs +class T: + _methods=['b'] # allows you to add method b upon instantiation + def __init__(self, f=1, **kwargs): pass # don't forget to include **kwargs in __init__ + def a(self): return 1 + def b(self): return 2 + +t = T() +test_eq(t.a(), 1) +test_eq(t.b(), 2) +``` + +Because we defined the class `T` this way, the signature of `T` +indicates the option to add the function or method(s) specified in +`_methods`. In this example, `b` is added to the signature: + +``` python +test_sig(T, '(f=1, *, b=None)') +inspect.signature(T) +``` + + + +You can now add the function `b` to class `T` upon instantiation: + +``` python +def _new_func(): return 5 + +t = T(b = _new_func) +test_eq(t.b(), 5) +``` + +If you try to add a function with a name not listed in `_methods` it +will be ignored. In the below example, the attempt to add a function +named `a` is ignored: + +``` python +t = T(a = lambda:3) +test_eq(t.a(), 1) # the attempt to add a is ignored and uses the original method instead. +``` + +Note that you can also add methods not defined in the original class as +long it is specified in the `_methods` attribute: + +``` python +@funcs_kwargs +class T: + _methods=['c'] + def __init__(self, f=1, **kwargs): pass + +t = T(c = lambda: 4) +test_eq(t.c(), 4) +``` + +Until now, these examples showed how to add functions stored as an +instance attribute without access to `self`. However, if you need access +to `self` you can set `as_method=True` in the `func_kwargs` decorator to +add a method instead: + +``` python +def _f(self,a=1): return self.num + a # access the num attribute from the instance + +@funcs_kwargs(as_method=True) +class T: + _methods=['b'] + num = 5 + +t = T(b = _f) # adds method b +test_eq(t.b(5), 10) # self.num + 5 = 10 +``` + +Here is an example of how you might use this functionality with +inheritence: + +``` python +def _f(self,a=1): return self.num * a #multiply instead of add + +class T2(T): + def __init__(self,num): + super().__init__(b = _f) # add method b from the super class + self.num=num + +t = T2(num=3) +test_eq(t.b(a=5), 15) # 3 * 5 = 15 +test_sig(T2, '(num)') +``` diff --git a/net.html b/net.html new file mode 100644 index 00000000..9cf8afe7 --- /dev/null +++ b/net.html @@ -0,0 +1,1059 @@ + + + + + + + + + + +Network functionality – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Network functionality

+
+ +
+
+ Network, HTTP, and URL functions +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from fastcore.test import *
+from nbdev.showdoc import *
+from fastcore.nb_imports import *
+
+
+

URLs

+
+

source

+
+

urlquote

+
+
 urlquote (url)
+
+

Update url’s path with urllib.parse.quote

+
+
urlquote("https://github.com/fastai/fastai/compare/master@{1.day.ago}…master")
+
+
'https://github.com/fastai/fastai/compare/master@%7B1.day.ago%7D%E2%80%A6master'
+
+
+
+
urlquote("https://www.google.com/search?q=你好")
+
+
'https://www.google.com/search?q=%E4%BD%A0%E5%A5%BD'
+
+
+
+

source

+
+
+

urlwrap

+
+
 urlwrap (url, data=None, headers=None)
+
+

Wrap url in a urllib Request with urlquote

+
+

source

+
+

HTTP4xxClientError

+
+
 HTTP4xxClientError (url, code, msg, hdrs, fp)
+
+

Base class for client exceptions (code 4xx) from url* functions

+
+

source

+
+
+

HTTP5xxServerError

+
+
 HTTP5xxServerError (url, code, msg, hdrs, fp)
+
+

Base class for server exceptions (code 5xx) from url* functions

+
+

source

+
+
+
+

urlopener

+
+
 urlopener ()
+
+
+

source

+
+
+

urlopen

+
+
 urlopen (url, data=None, headers=None, timeout=None, **kwargs)
+
+

Like urllib.request.urlopen, but first urlwrap the url, and encode data

+

With urlopen, the body of the response will also be returned in addition to the message if there is an error:

+
+
try: urlopen('https://api.github.com/v3')
+except HTTPError as e: 
+    print(e.code, e.msg)
+    assert 'documentation_url' in e.msg
+
+
404 Not Found
+====Error Body====
+{
+  "message": "Not Found",
+  "documentation_url": "https://docs.github.com/rest"
+}
+
+
+
+
+

source

+
+
+

urlread

+
+
 urlread (url, data=None, headers=None, decode=True, return_json=False,
+          return_headers=False, timeout=None, **kwargs)
+
+

Retrieve url, using data dict or kwargs to POST if present

+
+

source

+
+
+

urljson

+
+
 urljson (url, data=None, timeout=None)
+
+

Retrieve url and decode json

+
+
test_eq(urljson('https://httpbin.org/get')['headers']['User-Agent'], url_default_headers['User-Agent'])
+
+
+

source

+
+
+

urlcheck

+
+
 urlcheck (url, headers=None, timeout=10)
+
+
+

source

+
+
+

urlclean

+
+
 urlclean (url)
+
+

Remove fragment, params, and querystring from url if present

+
+
test_eq(urlclean('http://a.com/b?c=1#d'), 'http://a.com/b')
+
+
+

source

+
+
+

urlretrieve

+
+
 urlretrieve (url, filename=None, reporthook=None, data=None,
+              headers=None, timeout=None)
+
+

Same as urllib.request.urlretrieve but also works with Request objects

+
+

source

+
+
+

urldest

+
+
 urldest (url, dest=None)
+
+
+

source

+
+
+

urlsave

+
+
 urlsave (url, dest=None, reporthook=None, headers=None, timeout=None)
+
+

Retrieve url and save based on its name

+
+
#skip
+with tempfile.TemporaryDirectory() as d: urlsave('http://www.google.com/index.html', d)
+
+
+

source

+
+
+

urlvalid

+
+
 urlvalid (x)
+
+

Test if x is a valid URL

+
+
assert urlvalid('http://www.google.com/')
+assert not urlvalid('www.google.com/')
+assert not urlvalid(1)
+
+
+

source

+
+
+

urlrequest

+
+
 urlrequest (url, verb, headers=None, route=None, query=None, data=None,
+             json_data=True)
+
+

Request for url with optional route params replaced by route, plus query string, and post data

+
+
hdr = {'Hdr1':'1', 'Hdr2':'2'}
+req = urlrequest('http://example.com/{foo}/1', 'POST',
+                 headers=hdr, route={'foo':'3'}, query={'q':'4'}, data={'d':'5'})
+
+test_eq(req.headers, hdr)
+test_eq(req.full_url, 'http://example.com/3/1?q=4')
+test_eq(req.method, 'POST')
+test_eq(req.data, b'{"d": "5"}')
+
+
+
req = urlrequest('http://example.com/{foo}/1', 'POST', data={'d':'5','e':'6'}, headers=hdr, json_data=False)
+test_eq(req.data, b'd=5&e=6')
+
+
+

source

+
+
+

Request.summary

+
+
 Request.summary (skip=None)
+
+

Summary containing full_url, headers, method, and data, removing skip from headers

+
+
req.summary(skip='Hdr1')
+
+
{'full_url': 'http://example.com/{foo}/1',
+ 'method': 'POST',
+ 'data': b'd=5&e=6',
+ 'headers': {'Hdr2': '2'}}
+
+
+
+

source

+
+
+

urlsend

+
+
 urlsend (url, verb, headers=None, decode=True, route=None, query=None,
+          data=None, json_data=True, return_json=True,
+          return_headers=False, debug=None, timeout=None)
+
+

Send request with urlrequest, converting result to json if return_json

+
+

source

+
+
+

do_request

+
+
 do_request (url, post=False, headers=None, **data)
+
+

Call GET or json-encoded POST on url, depending on post

+
+
+
+

Basic client/server

+
+

source

+
+

start_server

+
+
 start_server (port, host=None, dgram=False, reuse_addr=True,
+               n_queue=None)
+
+

Create a socket server on port, with optional host, of type dgram

+

You can create a TCP client and server pass an int as port and optional host. host defaults to your main network interface if not provided. You can create a Unix socket client and server by passing a string to port. A SOCK_STREAM socket is created by default, unless you pass dgram=True, in which case a SOCK_DGRAM socket is created. n_queue sets the listening queue size.

+
+

source

+
+
+

start_client

+
+
 start_client (port, host=None, dgram=False)
+
+

Create a socket client on port, with optional host, of type dgram

+
+

source

+
+
+

tobytes

+
+
 tobytes (s:str)
+
+

Convert s into HTTP-ready bytes format

+
+
test_eq(tobytes('foo\nbar'), b'foo\r\nbar')
+
+
+

source

+
+
+

http_response

+
+
 http_response (body=None, status=200, hdrs=None, **kwargs)
+
+

Create an HTTP-ready response, adding kwargs to hdrs

+
+
exp = b'HTTP/1.1 200 OK\r\nUser-Agent: me\r\nContent-Length: 4\r\n\r\nbody'
+test_eq(http_response('body', 200, User_Agent='me'), exp)
+
+
+

source

+
+
+

recv_once

+
+
 recv_once (host:str='localhost', port:int=8000)
+
+

Spawn a thread to receive a single HTTP request and store in d['r']

+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/net.html.md b/net.html.md new file mode 100644 index 00000000..8b00bdb1 --- /dev/null +++ b/net.html.md @@ -0,0 +1,390 @@ +# Network functionality + + + + +``` python +from fastcore.test import * +from nbdev.showdoc import * +from fastcore.nb_imports import * +``` + +## URLs + +------------------------------------------------------------------------ + +source + +### urlquote + +> urlquote (url) + +*Update url’s path with `urllib.parse.quote`* + +``` python +urlquote("https://github.com/fastai/fastai/compare/master@{1.day.ago}…master") +``` + + 'https://github.com/fastai/fastai/compare/master@%7B1.day.ago%7D%E2%80%A6master' + +``` python +urlquote("https://www.google.com/search?q=你好") +``` + + 'https://www.google.com/search?q=%E4%BD%A0%E5%A5%BD' + +------------------------------------------------------------------------ + +source + +### urlwrap + +> urlwrap (url, data=None, headers=None) + +*Wrap `url` in a urllib `Request` with +[`urlquote`](https://fastcore.fast.ai/net.html#urlquote)* + +------------------------------------------------------------------------ + +source + +#### HTTP4xxClientError + +> HTTP4xxClientError (url, code, msg, hdrs, fp) + +*Base class for client exceptions (code 4xx) from `url*` functions* + +------------------------------------------------------------------------ + +source + +#### HTTP5xxServerError + +> HTTP5xxServerError (url, code, msg, hdrs, fp) + +*Base class for server exceptions (code 5xx) from `url*` functions* + +------------------------------------------------------------------------ + +source + +### urlopener + +> urlopener () + +------------------------------------------------------------------------ + +source + +### urlopen + +> urlopen (url, data=None, headers=None, timeout=None, **kwargs) + +*Like `urllib.request.urlopen`, but first +[`urlwrap`](https://fastcore.fast.ai/net.html#urlwrap) the `url`, and +encode `data`* + +With [`urlopen`](https://fastcore.fast.ai/net.html#urlopen), the body of +the response will also be returned in addition to the message if there +is an error: + +``` python +try: urlopen('https://api.github.com/v3') +except HTTPError as e: + print(e.code, e.msg) + assert 'documentation_url' in e.msg +``` + + 404 Not Found + ====Error Body==== + { + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest" + } + +------------------------------------------------------------------------ + +source + +### urlread + +> urlread (url, data=None, headers=None, decode=True, return_json=False, +> return_headers=False, timeout=None, **kwargs) + +*Retrieve `url`, using `data` dict or `kwargs` to `POST` if present* + +------------------------------------------------------------------------ + +source + +### urljson + +> urljson (url, data=None, timeout=None) + +*Retrieve `url` and decode json* + +``` python +test_eq(urljson('https://httpbin.org/get')['headers']['User-Agent'], url_default_headers['User-Agent']) +``` + +------------------------------------------------------------------------ + +source + +### urlcheck + +> urlcheck (url, headers=None, timeout=10) + +------------------------------------------------------------------------ + +source + +### urlclean + +> urlclean (url) + +*Remove fragment, params, and querystring from `url` if present* + +``` python +test_eq(urlclean('http://a.com/b?c=1#d'), 'http://a.com/b') +``` + +------------------------------------------------------------------------ + +source + +### urlretrieve + +> urlretrieve (url, filename=None, reporthook=None, data=None, +> headers=None, timeout=None) + +*Same as `urllib.request.urlretrieve` but also works with `Request` +objects* + +------------------------------------------------------------------------ + +source + +### urldest + +> urldest (url, dest=None) + +------------------------------------------------------------------------ + +source + +### urlsave + +> urlsave (url, dest=None, reporthook=None, headers=None, timeout=None) + +*Retrieve `url` and save based on its name* + +``` python +#skip +with tempfile.TemporaryDirectory() as d: urlsave('http://www.google.com/index.html', d) +``` + +------------------------------------------------------------------------ + +source + +### urlvalid + +> urlvalid (x) + +*Test if `x` is a valid URL* + +``` python +assert urlvalid('http://www.google.com/') +assert not urlvalid('www.google.com/') +assert not urlvalid(1) +``` + +------------------------------------------------------------------------ + +source + +### urlrequest + +> urlrequest (url, verb, headers=None, route=None, query=None, data=None, +> json_data=True) + +*`Request` for `url` with optional route params replaced by `route`, +plus `query` string, and post `data`* + +``` python +hdr = {'Hdr1':'1', 'Hdr2':'2'} +req = urlrequest('http://example.com/{foo}/1', 'POST', + headers=hdr, route={'foo':'3'}, query={'q':'4'}, data={'d':'5'}) + +test_eq(req.headers, hdr) +test_eq(req.full_url, 'http://example.com/3/1?q=4') +test_eq(req.method, 'POST') +test_eq(req.data, b'{"d": "5"}') +``` + +``` python +req = urlrequest('http://example.com/{foo}/1', 'POST', data={'d':'5','e':'6'}, headers=hdr, json_data=False) +test_eq(req.data, b'd=5&e=6') +``` + +------------------------------------------------------------------------ + +source + +### Request.summary + +> Request.summary (skip=None) + +*Summary containing full_url, headers, method, and data, removing `skip` +from headers* + +``` python +req.summary(skip='Hdr1') +``` + + {'full_url': 'http://example.com/{foo}/1', + 'method': 'POST', + 'data': b'd=5&e=6', + 'headers': {'Hdr2': '2'}} + +------------------------------------------------------------------------ + +source + +### urlsend + +> urlsend (url, verb, headers=None, decode=True, route=None, query=None, +> data=None, json_data=True, return_json=True, +> return_headers=False, debug=None, timeout=None) + +*Send request with +[`urlrequest`](https://fastcore.fast.ai/net.html#urlrequest), converting +result to json if `return_json`* + +------------------------------------------------------------------------ + +source + +### do_request + +> do_request (url, post=False, headers=None, **data) + +*Call GET or json-encoded POST on `url`, depending on `post`* + +## Basic client/server + +------------------------------------------------------------------------ + +source + +### start_server + +> start_server (port, host=None, dgram=False, reuse_addr=True, +> n_queue=None) + +*Create a `socket` server on `port`, with optional `host`, of type +`dgram`* + +You can create a TCP client and server pass an int as `port` and +optional `host`. `host` defaults to your main network interface if not +provided. You can create a Unix socket client and server by passing a +string to `port`. A `SOCK_STREAM` socket is created by default, unless +you pass `dgram=True`, in which case a `SOCK_DGRAM` socket is created. +`n_queue` sets the listening queue size. + +------------------------------------------------------------------------ + +source + +### start_client + +> start_client (port, host=None, dgram=False) + +*Create a `socket` client on `port`, with optional `host`, of type +`dgram`* + +------------------------------------------------------------------------ + +source + +### tobytes + +> tobytes (s:str) + +*Convert `s` into HTTP-ready bytes format* + +``` python +test_eq(tobytes('foo\nbar'), b'foo\r\nbar') +``` + +------------------------------------------------------------------------ + +source + +### http_response + +> http_response (body=None, status=200, hdrs=None, **kwargs) + +*Create an HTTP-ready response, adding `kwargs` to `hdrs`* + +``` python +exp = b'HTTP/1.1 200 OK\r\nUser-Agent: me\r\nContent-Length: 4\r\n\r\nbody' +test_eq(http_response('body', 200, User_Agent='me'), exp) +``` + +------------------------------------------------------------------------ + +source + +### recv_once + +> recv_once (host:str='localhost', port:int=8000) + +*Spawn a thread to receive a single HTTP request and store in `d['r']`* diff --git a/parallel.html b/parallel.html new file mode 100644 index 00000000..1e12a98b --- /dev/null +++ b/parallel.html @@ -0,0 +1,1014 @@ + + + + + + + + + + +Parallel – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Parallel

+
+ +
+
+ Threading and multiprocessing functions +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from fastcore.test import *
+from nbdev.showdoc import *
+from fastcore.nb_imports import *
+
+
+

source

+
+

threaded

+
+
 threaded (process=False)
+
+

Run f in a Thread (or Process if process=True), and returns it

+
+
@threaded
+def _1():
+    time.sleep(0.05)
+    print("second")
+    return 5
+
+@threaded
+def _2():
+    time.sleep(0.01)
+    print("first")
+
+a = _1()
+_2()
+time.sleep(0.1)
+
+
first
+second
+
+
+

After the thread is complete, the return value is stored in the result attr.

+
+
a.result
+
+
5
+
+
+
+

source

+
+
+

startthread

+
+
 startthread (f)
+
+

Like threaded, but start thread immediately

+
+
@startthread
+def _():
+    time.sleep(0.05)
+    print("second")
+
+@startthread
+def _():
+    time.sleep(0.01)
+    print("first")
+
+time.sleep(0.1)
+
+
first
+second
+
+
+
+

source

+
+
+

startproc

+
+
 startproc (f)
+
+

Like threaded(True), but start Process immediately

+
+
@startproc
+def _():
+    time.sleep(0.05)
+    print("second")
+
+@startproc
+def _():
+    time.sleep(0.01)
+    print("first")
+
+time.sleep(0.1)
+
+
first
+second
+
+
+
+

source

+
+
+

parallelable

+
+
 parallelable (param_name, num_workers, f=None)
+
+
+

source

+
+

ThreadPoolExecutor

+
+
 ThreadPoolExecutor (max_workers=4, on_exc=<built-in function print>,
+                     pause=0, **kwargs)
+
+

Same as Python’s ThreadPoolExecutor, except can pass max_workers==0 for serial execution

+
+

source

+
+
+

ProcessPoolExecutor

+
+
 ProcessPoolExecutor (max_workers=4, on_exc=<built-in function print>,
+                      pause=0, mp_context=None, initializer=None,
+                      initargs=())
+
+

Same as Python’s ProcessPoolExecutor, except can pass max_workers==0 for serial execution

+
+

source

+
+
+
+

parallel

+
+
 parallel (f, items, *args, n_workers=4, total=None, progress=None,
+           pause=0, method=None, threadpool=False, timeout=None,
+           chunksize=1, **kwargs)
+
+

Applies func in parallel to items, using n_workers

+
+
inp,exp = range(50),range(1,51)
+
+test_eq(parallel(_add_one, inp, n_workers=2), exp)
+test_eq(parallel(_add_one, inp, threadpool=True, n_workers=2), exp)
+test_eq(parallel(_add_one, inp, n_workers=1, a=2), range(2,52))
+test_eq(parallel(_add_one, inp, n_workers=0), exp)
+test_eq(parallel(_add_one, inp, n_workers=0, a=2), range(2,52))
+
+

Use the pause parameter to ensure a pause of pause seconds between processes starting. This is in case there are race conditions in starting some process, or to stagger the time each process starts, for example when making many requests to a webserver. Set threadpool=True to use ThreadPoolExecutor instead of ProcessPoolExecutor.

+
+
from datetime import datetime
+
+
+
def print_time(i): 
+    time.sleep(random.random()/1000)
+    print(i, datetime.now())
+
+parallel(print_time, range(5), n_workers=2, pause=0.25);
+
+
0 2024-10-11 23:06:05.920741
+1 2024-10-11 23:06:06.171470
+2 2024-10-11 23:06:06.431925
+3 2024-10-11 23:06:06.689940
+4 2024-10-11 23:06:06.937109
+
+
+
+

source

+
+
+

parallel_async

+
+
 parallel_async (f, items, *args, n_workers=16, timeout=None, chunksize=1,
+                 on_exc=<built-in function print>, **kwargs)
+
+

Applies f to items in parallel using asyncio and a semaphore to limit concurrency.

+
+
import asyncio
+
+
+
async def print_time_async(i): 
+    wait = random.random()
+    await asyncio.sleep(wait)
+    print(i, datetime.now(), wait)
+
+await parallel_async(print_time_async, range(6), n_workers=3);
+
+
0 2024-10-11 23:06:39.545583 0.10292732609738675
+3 2024-10-11 23:06:39.900393 0.3516179734831676
+4 2024-10-11 23:06:39.941094 0.03699593757956876
+2 2024-10-11 23:06:39.957677 0.5148658606540902
+1 2024-10-11 23:06:40.099716 0.6574035385815227
+5 2024-10-11 23:06:40.654097 0.7116319667399102
+
+
+
+

source

+
+
+

run_procs

+
+
 run_procs (f, f_done, args)
+
+

Call f for each item in args in parallel, yielding f_done

+
+

source

+
+
+

parallel_gen

+
+
 parallel_gen (cls, items, n_workers=4, **kwargs)
+
+

Instantiate cls in n_workers procs & call each on a subset of items in parallel.

+
+
# class _C:
+#     def __call__(self, o): return ((i+1) for i in o)
+
+# items = range(5)
+
+# res = L(parallel_gen(_C, items, n_workers=0))
+# idxs,dat1 = zip(*res.sorted(itemgetter(0)))
+# test_eq(dat1, range(1,6))
+
+# res = L(parallel_gen(_C, items, n_workers=3))
+# idxs,dat2 = zip(*res.sorted(itemgetter(0)))
+# test_eq(dat2, dat1)
+
+

cls is any class with __call__. It will be passed args and kwargs when initialized. Note that n_workers instances of cls are created, one in each process. items are then split in n_workers batches and one is sent to each cls. The function then returns a generator of tuples of item indices and results.

+
+
class TestSleepyBatchFunc:
+    "For testing parallel processes that run at different speeds"
+    def __init__(self): self.a=1
+    def __call__(self, batch):
+        for k in batch:
+            time.sleep(random.random()/4)
+            yield k+self.a
+
+x = np.linspace(0,0.99,20)
+
+res = L(parallel_gen(TestSleepyBatchFunc, x, n_workers=2))
+test_eq(res.sorted().itemgot(1), x+1)
+
+ + +
+
+ +
+
+
+
# #|hide
+# from subprocess import Popen, PIPE
+# # test num_workers > 0 in scripts works when python process start method is spawn
+# process = Popen(["python", "parallel_test.py"], stdout=PIPE)
+# _, err = process.communicate(timeout=10)
+# exit_code = process.wait()
+# test_eq(exit_code, 0)
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/parallel.html.md b/parallel.html.md new file mode 100644 index 00000000..efcf9b15 --- /dev/null +++ b/parallel.html.md @@ -0,0 +1,321 @@ +# Parallel + + + + +``` python +from fastcore.test import * +from nbdev.showdoc import * +from fastcore.nb_imports import * +``` + +------------------------------------------------------------------------ + +source + +### threaded + +> threaded (process=False) + +*Run `f` in a `Thread` (or `Process` if `process=True`), and returns it* + +``` python +@threaded +def _1(): + time.sleep(0.05) + print("second") + return 5 + +@threaded +def _2(): + time.sleep(0.01) + print("first") + +a = _1() +_2() +time.sleep(0.1) +``` + + first + second + +After the thread is complete, the return value is stored in the `result` +attr. + +``` python +a.result +``` + + 5 + +------------------------------------------------------------------------ + +source + +### startthread + +> startthread (f) + +*Like [`threaded`](https://fastcore.fast.ai/parallel.html#threaded), but +start thread immediately* + +``` python +@startthread +def _(): + time.sleep(0.05) + print("second") + +@startthread +def _(): + time.sleep(0.01) + print("first") + +time.sleep(0.1) +``` + + first + second + +------------------------------------------------------------------------ + +source + +### startproc + +> startproc (f) + +*Like `threaded(True)`, but start Process immediately* + +``` python +@startproc +def _(): + time.sleep(0.05) + print("second") + +@startproc +def _(): + time.sleep(0.01) + print("first") + +time.sleep(0.1) +``` + + first + second + +------------------------------------------------------------------------ + +source + +### parallelable + +> parallelable (param_name, num_workers, f=None) + +------------------------------------------------------------------------ + +source + +#### ThreadPoolExecutor + +> ThreadPoolExecutor (max_workers=4, on_exc=, +> pause=0, **kwargs) + +*Same as Python’s ThreadPoolExecutor, except can pass `max_workers==0` +for serial execution* + +------------------------------------------------------------------------ + +source + +#### ProcessPoolExecutor + +> ProcessPoolExecutor (max_workers=4, on_exc=, +> pause=0, mp_context=None, initializer=None, +> initargs=()) + +*Same as Python’s ProcessPoolExecutor, except can pass `max_workers==0` +for serial execution* + +------------------------------------------------------------------------ + +source + +### parallel + +> parallel (f, items, *args, n_workers=4, total=None, progress=None, +> pause=0, method=None, threadpool=False, timeout=None, +> chunksize=1, **kwargs) + +*Applies `func` in parallel to `items`, using `n_workers`* + +``` python +inp,exp = range(50),range(1,51) + +test_eq(parallel(_add_one, inp, n_workers=2), exp) +test_eq(parallel(_add_one, inp, threadpool=True, n_workers=2), exp) +test_eq(parallel(_add_one, inp, n_workers=1, a=2), range(2,52)) +test_eq(parallel(_add_one, inp, n_workers=0), exp) +test_eq(parallel(_add_one, inp, n_workers=0, a=2), range(2,52)) +``` + +Use the `pause` parameter to ensure a pause of `pause` seconds between +processes starting. This is in case there are race conditions in +starting some process, or to stagger the time each process starts, for +example when making many requests to a webserver. Set `threadpool=True` +to use +[`ThreadPoolExecutor`](https://fastcore.fast.ai/parallel.html#threadpoolexecutor) +instead of +[`ProcessPoolExecutor`](https://fastcore.fast.ai/parallel.html#processpoolexecutor). + +``` python +from datetime import datetime +``` + +``` python +def print_time(i): + time.sleep(random.random()/1000) + print(i, datetime.now()) + +parallel(print_time, range(5), n_workers=2, pause=0.25); +``` + + 0 2024-10-11 23:06:05.920741 + 1 2024-10-11 23:06:06.171470 + 2 2024-10-11 23:06:06.431925 + 3 2024-10-11 23:06:06.689940 + 4 2024-10-11 23:06:06.937109 + +------------------------------------------------------------------------ + +source + +### parallel_async + +> parallel_async (f, items, *args, n_workers=16, timeout=None, chunksize=1, +> on_exc=, **kwargs) + +*Applies `f` to `items` in parallel using asyncio and a semaphore to +limit concurrency.* + +``` python +import asyncio +``` + +``` python +async def print_time_async(i): + wait = random.random() + await asyncio.sleep(wait) + print(i, datetime.now(), wait) + +await parallel_async(print_time_async, range(6), n_workers=3); +``` + + 0 2024-10-11 23:06:39.545583 0.10292732609738675 + 3 2024-10-11 23:06:39.900393 0.3516179734831676 + 4 2024-10-11 23:06:39.941094 0.03699593757956876 + 2 2024-10-11 23:06:39.957677 0.5148658606540902 + 1 2024-10-11 23:06:40.099716 0.6574035385815227 + 5 2024-10-11 23:06:40.654097 0.7116319667399102 + +------------------------------------------------------------------------ + +source + +### run_procs + +> run_procs (f, f_done, args) + +*Call `f` for each item in `args` in parallel, yielding `f_done`* + +------------------------------------------------------------------------ + +source + +### parallel_gen + +> parallel_gen (cls, items, n_workers=4, **kwargs) + +*Instantiate `cls` in `n_workers` procs & call each on a subset of +`items` in parallel.* + +``` python +# class _C: +# def __call__(self, o): return ((i+1) for i in o) + +# items = range(5) + +# res = L(parallel_gen(_C, items, n_workers=0)) +# idxs,dat1 = zip(*res.sorted(itemgetter(0))) +# test_eq(dat1, range(1,6)) + +# res = L(parallel_gen(_C, items, n_workers=3)) +# idxs,dat2 = zip(*res.sorted(itemgetter(0))) +# test_eq(dat2, dat1) +``` + +`cls` is any class with `__call__`. It will be passed `args` and +`kwargs` when initialized. Note that `n_workers` instances of `cls` are +created, one in each process. `items` are then split in `n_workers` +batches and one is sent to each `cls`. The function then returns a +generator of tuples of item indices and results. + +``` python +class TestSleepyBatchFunc: + "For testing parallel processes that run at different speeds" + def __init__(self): self.a=1 + def __call__(self, batch): + for k in batch: + time.sleep(random.random()/4) + yield k+self.a + +x = np.linspace(0,0.99,20) + +res = L(parallel_gen(TestSleepyBatchFunc, x, n_workers=2)) +test_eq(res.sorted().itemgot(1), x+1) +``` + + + +``` python +# #|hide +# from subprocess import Popen, PIPE +# # test num_workers > 0 in scripts works when python process start method is spawn +# process = Popen(["python", "parallel_test.py"], stdout=PIPE) +# _, err = process.communicate(timeout=10) +# exit_code = process.wait() +# test_eq(exit_code, 0) +``` diff --git a/py2pyi.html b/py2pyi.html new file mode 100644 index 00000000..c44de310 --- /dev/null +++ b/py2pyi.html @@ -0,0 +1,1236 @@ + + + + + + + + + +Create delegated pyi – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Create delegated pyi

+
+ + + +
+ + + + +
+ + + +
+ + + +
+

Setup

+
+
+

Basics

+
+

source

+
+

imp_mod

+
+
 imp_mod (module_path, package=None)
+
+

Import dynamically the module referenced in fn

+
+
fn = Path('test_py2pyi.py')
+
+
+
mod = imp_mod(fn)
+a = mod.A()
+a.h()
+
+
1
+
+
+
+
tree = _get_tree(mod)
+
+
+
+
+

AST.__repr__

+
+
 AST.__repr__ ()
+
+
+
# for o in enumerate(tree.body): print(o)
+
+
+
node = tree.body[4]
+node
+
+
def f(a: int, b: str='a') -> str:
+    """I am f"""
+    return 1
+
+
+
+
isinstance(node, functypes)
+
+
True
+
+
+
+

source

+
+
+

has_deco

+
+
 has_deco (node:Union[ast.FunctionDef,ast.AsyncFunctionDef], name:str)
+
+

Check if a function node node has a decorator named name

+
+
nm = 'delegates'
+has_deco(node, nm)
+
+
False
+
+
+
+
node = tree.body[5]
+node
+
+
@delegates(f)
+def g(c, d: X, **kwargs) -> str:
+    """I am g"""
+    return 2
+
+
+
+
has_deco(node, nm)
+
+
True
+
+
+
+
+
+

Function processing

+
+
def _proc_body   (node, mod): print('_proc_body', type(node))
+def _proc_func   (node, mod): print('_proc_func', type(node))
+def _proc_class  (node, mod): print('_proc_class', type(node))
+def _proc_patched(node, mod): print('_proc_patched', type(node))
+
+
+
parent_node = copy.deepcopy(tree.body[7])
+patched_node = copy.deepcopy(tree.body[10])
+test_is(has_deco(patched_node, "patch"), True)
+test_eq(str(patched_node.args.args[0].annotation), parent_node.name)
+
+_clean_patched_node(patched_node)
+test_is(has_deco(patched_node, "patch"), False)
+test_eq(patched_node.args.args[0].annotation, None)
+
+
+
empty_cls1, empty_cls2, empty_cls3 = ast.parse('''
+class A: 
+    """An empty class."""
+class B: 
+    pass
+class C: 
+    ...
+''').body
+
+test_is(_is_empty_class(empty_cls1), True)
+test_is(_is_empty_class(empty_cls2), True)
+test_is(_is_empty_class(empty_cls3), True)
+
+non_empty_cls, empty_func = ast.parse('''
+class A: 
+    a = 1
+def f():
+    ...
+''').body
+test_is(_is_empty_class(non_empty_cls), False)
+test_is(_is_empty_class(empty_func), False)
+
+
+
# we could have reused `parent_node` and `patched_node` from the previous cells.
+# copying them here allows us to run this cell multiple times which makes it a little easier to write tests.
+
+parent_node = copy.deepcopy(tree.body[7])
+patched_node = copy.deepcopy(tree.body[11])
+test_eq(len(parent_node.body),1)
+_add_patched_node_to_parent(patched_node, parent_node)
+test_eq(len(parent_node.body),2)
+test_eq(parent_node.body[-1], patched_node)
+
+# patched node replaces an existing class method (A.h)
+patched_h_node = ast.parse("""
+@patch
+def h(self: A, *args, **kwargs):
+    ...
+""", mode='single').body[0]
+
+_add_patched_node_to_parent(patched_h_node, parent_node)
+test_eq(len(parent_node.body), 2)
+test_eq(parent_node.body[0], patched_h_node)
+
+# patched node is added to an empty class
+empty_cls, patched_node = ast.parse('''
+class Z: 
+    """An empty class."""
+
+@patch
+def a(self: Z, *args, **kwargs):
+    ...
+''').body
+
+test_eq(len(empty_cls.body), 1)
+test_ne(empty_cls.body[0], patched_node)
+_add_patched_node_to_parent(patched_node, empty_cls)
+test_eq(len(empty_cls.body), 1)
+test_eq(empty_cls.body[0], patched_node)
+
+
+
raw_tree = _get_tree(mod)
+processed_tree = _proc_mod(mod)
+n_raw_tree_nodes = len(raw_tree.body)
+# mod contains 3 patch methods so our processed_tree should have 3 less nodes 
+test_eq(len(processed_tree.body), n_raw_tree_nodes-3)
+
+
_proc_class <class 'ast.ClassDef'>
+_proc_body <class 'ast.FunctionDef'>
+_proc_func <class 'ast.FunctionDef'>
+_proc_body <class 'ast.FunctionDef'>
+_proc_class <class 'ast.ClassDef'>
+_proc_class <class 'ast.ClassDef'>
+_proc_patched <class 'ast.FunctionDef'>
+_proc_patched <class 'ast.FunctionDef'>
+_proc_body <class 'ast.FunctionDef'>
+
+
+
+
_proc_mod(mod);
+
+
_proc_class <class 'ast.ClassDef'>
+_proc_body <class 'ast.FunctionDef'>
+_proc_func <class 'ast.FunctionDef'>
+_proc_body <class 'ast.FunctionDef'>
+_proc_class <class 'ast.ClassDef'>
+_proc_class <class 'ast.ClassDef'>
+_proc_patched <class 'ast.FunctionDef'>
+_proc_patched <class 'ast.FunctionDef'>
+_proc_body <class 'ast.FunctionDef'>
+
+
+
+
node.name
+
+
'g'
+
+
+
+
sym = getattr(mod, node.name)
+sym
+
+
<function test_py2pyi.g(c, d: test_py2pyi.X, *, b: str = 'a') -> str>
+
+
+
+
sig = signature(sym)
+print(sig)
+
+
(c, d: test_py2pyi.X, *, b: str = 'a') -> str
+
+
+
+

source

+
+

sig2str

+
+
 sig2str (sig)
+
+
+

source

+
+
+

ast_args

+
+
 ast_args (func)
+
+
+
newargs = ast_args(sym)
+newargs
+
+
c, d: test_py2pyi.X, *, b: str='a'
+
+
+
+
node.args
+
+
c, d: X, **kwargs
+
+
+
+
node.args = newargs
+node
+
+
@delegates(f)
+def g(c, d: test_py2pyi.X, *, b: str='a') -> str:
+    """I am g"""
+    return 2
+
+
+
+
_body_ellip(node)
+node
+
+
@delegates(f)
+def g(c, d: test_py2pyi.X, *, b: str='a') -> str:
+    """I am g"""
+    ...
+
+
+
+
tree = _get_tree(mod)
+node = tree.body[5]
+node
+
+
@delegates(f)
+def g(c, d: X, **kwargs) -> str:
+    """I am g"""
+    return 2
+
+
+
+
_update_func(node, sym)
+node
+
+
def g(c, d: X, *, b: str='a') -> str:
+    """I am g"""
+    ...
+
+
+
+
tree = _proc_mod(mod)
+tree.body[5]
+
+
_proc_class <class 'ast.ClassDef'>
+_proc_class <class 'ast.ClassDef'>
+_proc_class <class 'ast.ClassDef'>
+_proc_patched <class 'ast.FunctionDef'>
+_proc_patched <class 'ast.FunctionDef'>
+
+
+
def g(c, d: X, *, b: str='a') -> str:
+    """I am g"""
+    ...
+
+
+
+
+
+

Patch

+
+
tree = _get_tree(mod)
+node = tree.body[9]
+node
+
+
@patch
+@delegates(j)
+def k(self: (A, B), b: bool=False, **kwargs):
+    return 1
+
+
+
+
ann = node.args.args[0].annotation
+
+
+
if hasattr(ann, 'elts'): ann = ann.elts[0]
+
+
+
nm = ann.id
+nm
+
+
'A'
+
+
+
+
cls = getattr(mod, nm)
+sym = getattr(cls, node.name)
+
+
+
sig2str(signature(sym))
+
+
"(self: (test_py2pyi.A, test_py2pyi.B), b: bool = False, *, d: str = 'a')"
+
+
+
+
_update_func(node, sym)
+
+
+
node
+
+
@patch
+def k(self: (A, B), b: bool=False, *, d: str='a'):
+    ...
+
+
+
+
tree = _get_tree(mod)
+tree.body[9]
+
+
@patch
+@delegates(j)
+def k(self: (A, B), b: bool=False, **kwargs):
+    return 1
+
+
+
+
+

Class and file

+
+
tree = _get_tree(mod)
+node = tree.body[7]
+node
+
+
class A:
+
+    @delegates(j)
+    def h(self, b: bool=False, **kwargs):
+        a = 1
+        return a
+
+
+
+
node.body
+
+
[@delegates(j)
+ def h(self, b: bool=False, **kwargs):
+     a = 1
+     return a]
+
+
+
+
tree = _proc_mod(mod)
+tree.body[7]
+
+
class A:
+
+    def h(self, b: bool=False, *, d: str='a'):
+        ...
+
+    def k(self, b: bool=False, *, d: str='a'):
+        ...
+
+    def m(self, b: bool=False, *, d: str='a'):
+        ...
+
+    def n(self, b: bool=False, **kwargs):
+        """No delegates here mmm'k?"""
+        ...
+
+
+
+

source

+
+

create_pyi

+
+
 create_pyi (fn, package=None)
+
+

Convert fname.py to fname.pyi by removing function bodies and expanding delegates kwargs

+
+
create_pyi(fn)
+
+
+
# fn = Path('/Users/jhoward/git/fastcore/fastcore/docments.py')
+# create_pyi(fn, 'fastcore')
+
+
+
+
+

Script

+
+

source

+
+

py2pyi

+
+
 py2pyi (fname:str, package:str=None)
+
+

Convert fname.py to fname.pyi by removing function bodies and expanding delegates kwargs

+ + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
fnamestrThe file name to convert
packagestrNoneThe parent package
+
+

source

+
+
+

replace_wildcards

+
+
 replace_wildcards (path:str)
+
+

Expand wildcard imports in the specified Python file.

+ + + + + + + + + + + + + + + +
TypeDetails
pathstrPath to the Python file to process
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/py2pyi.html.md b/py2pyi.html.md new file mode 100644 index 00000000..ce22e5ce --- /dev/null +++ b/py2pyi.html.md @@ -0,0 +1,545 @@ +# Create delegated pyi + + + + +## Setup + +## Basics + +------------------------------------------------------------------------ + +source + +### imp_mod + +> imp_mod (module_path, package=None) + +*Import dynamically the module referenced in `fn`* + +``` python +fn = Path('test_py2pyi.py') +``` + +``` python +mod = imp_mod(fn) +a = mod.A() +a.h() +``` + + 1 + +``` python +tree = _get_tree(mod) +``` + +------------------------------------------------------------------------ + +### AST.\_\_repr\_\_ + +> AST.__repr__ () + +``` python +# for o in enumerate(tree.body): print(o) +``` + +``` python +node = tree.body[4] +node +``` + +``` python +def f(a: int, b: str='a') -> str: + """I am f""" + return 1 +``` + +``` python +isinstance(node, functypes) +``` + + True + +------------------------------------------------------------------------ + +source + +### has_deco + +> has_deco (node:Union[ast.FunctionDef,ast.AsyncFunctionDef], name:str) + +*Check if a function node `node` has a decorator named `name`* + +``` python +nm = 'delegates' +has_deco(node, nm) +``` + + False + +``` python +node = tree.body[5] +node +``` + +``` python +@delegates(f) +def g(c, d: X, **kwargs) -> str: + """I am g""" + return 2 +``` + +``` python +has_deco(node, nm) +``` + + True + +## Function processing + +``` python +def _proc_body (node, mod): print('_proc_body', type(node)) +def _proc_func (node, mod): print('_proc_func', type(node)) +def _proc_class (node, mod): print('_proc_class', type(node)) +def _proc_patched(node, mod): print('_proc_patched', type(node)) +``` + +``` python +parent_node = copy.deepcopy(tree.body[7]) +patched_node = copy.deepcopy(tree.body[10]) +test_is(has_deco(patched_node, "patch"), True) +test_eq(str(patched_node.args.args[0].annotation), parent_node.name) + +_clean_patched_node(patched_node) +test_is(has_deco(patched_node, "patch"), False) +test_eq(patched_node.args.args[0].annotation, None) +``` + +``` python +empty_cls1, empty_cls2, empty_cls3 = ast.parse(''' +class A: + """An empty class.""" +class B: + pass +class C: + ... +''').body + +test_is(_is_empty_class(empty_cls1), True) +test_is(_is_empty_class(empty_cls2), True) +test_is(_is_empty_class(empty_cls3), True) + +non_empty_cls, empty_func = ast.parse(''' +class A: + a = 1 +def f(): + ... +''').body +test_is(_is_empty_class(non_empty_cls), False) +test_is(_is_empty_class(empty_func), False) +``` + +``` python +# we could have reused `parent_node` and `patched_node` from the previous cells. +# copying them here allows us to run this cell multiple times which makes it a little easier to write tests. + +parent_node = copy.deepcopy(tree.body[7]) +patched_node = copy.deepcopy(tree.body[11]) +test_eq(len(parent_node.body),1) +_add_patched_node_to_parent(patched_node, parent_node) +test_eq(len(parent_node.body),2) +test_eq(parent_node.body[-1], patched_node) + +# patched node replaces an existing class method (A.h) +patched_h_node = ast.parse(""" +@patch +def h(self: A, *args, **kwargs): + ... +""", mode='single').body[0] + +_add_patched_node_to_parent(patched_h_node, parent_node) +test_eq(len(parent_node.body), 2) +test_eq(parent_node.body[0], patched_h_node) + +# patched node is added to an empty class +empty_cls, patched_node = ast.parse(''' +class Z: + """An empty class.""" + +@patch +def a(self: Z, *args, **kwargs): + ... +''').body + +test_eq(len(empty_cls.body), 1) +test_ne(empty_cls.body[0], patched_node) +_add_patched_node_to_parent(patched_node, empty_cls) +test_eq(len(empty_cls.body), 1) +test_eq(empty_cls.body[0], patched_node) +``` + +``` python +raw_tree = _get_tree(mod) +processed_tree = _proc_mod(mod) +n_raw_tree_nodes = len(raw_tree.body) +# mod contains 3 patch methods so our processed_tree should have 3 less nodes +test_eq(len(processed_tree.body), n_raw_tree_nodes-3) +``` + + _proc_class + _proc_body + _proc_func + _proc_body + _proc_class + _proc_class + _proc_patched + _proc_patched + _proc_body + +``` python +_proc_mod(mod); +``` + + _proc_class + _proc_body + _proc_func + _proc_body + _proc_class + _proc_class + _proc_patched + _proc_patched + _proc_body + +``` python +node.name +``` + + 'g' + +``` python +sym = getattr(mod, node.name) +sym +``` + + str> + +``` python +sig = signature(sym) +print(sig) +``` + + (c, d: test_py2pyi.X, *, b: str = 'a') -> str + +------------------------------------------------------------------------ + +source + +### sig2str + +> sig2str (sig) + +------------------------------------------------------------------------ + +source + +### ast_args + +> ast_args (func) + +``` python +newargs = ast_args(sym) +newargs +``` + +``` python +c, d: test_py2pyi.X, *, b: str='a' +``` + +``` python +node.args +``` + +``` python +c, d: X, **kwargs +``` + +``` python +node.args = newargs +node +``` + +``` python +@delegates(f) +def g(c, d: test_py2pyi.X, *, b: str='a') -> str: + """I am g""" + return 2 +``` + +``` python +_body_ellip(node) +node +``` + +``` python +@delegates(f) +def g(c, d: test_py2pyi.X, *, b: str='a') -> str: + """I am g""" + ... +``` + +``` python +tree = _get_tree(mod) +node = tree.body[5] +node +``` + +``` python +@delegates(f) +def g(c, d: X, **kwargs) -> str: + """I am g""" + return 2 +``` + +``` python +_update_func(node, sym) +node +``` + +``` python +def g(c, d: X, *, b: str='a') -> str: + """I am g""" + ... +``` + +``` python +tree = _proc_mod(mod) +tree.body[5] +``` + + _proc_class + _proc_class + _proc_class + _proc_patched + _proc_patched + +``` python +def g(c, d: X, *, b: str='a') -> str: + """I am g""" + ... +``` + +## Patch + +``` python +tree = _get_tree(mod) +node = tree.body[9] +node +``` + +``` python +@patch +@delegates(j) +def k(self: (A, B), b: bool=False, **kwargs): + return 1 +``` + +``` python +ann = node.args.args[0].annotation +``` + +``` python +if hasattr(ann, 'elts'): ann = ann.elts[0] +``` + +``` python +nm = ann.id +nm +``` + + 'A' + +``` python +cls = getattr(mod, nm) +sym = getattr(cls, node.name) +``` + +``` python +sig2str(signature(sym)) +``` + + "(self: (test_py2pyi.A, test_py2pyi.B), b: bool = False, *, d: str = 'a')" + +``` python +_update_func(node, sym) +``` + +``` python +node +``` + +``` python +@patch +def k(self: (A, B), b: bool=False, *, d: str='a'): + ... +``` + +``` python +tree = _get_tree(mod) +tree.body[9] +``` + +``` python +@patch +@delegates(j) +def k(self: (A, B), b: bool=False, **kwargs): + return 1 +``` + +## Class and file + +``` python +tree = _get_tree(mod) +node = tree.body[7] +node +``` + +``` python +class A: + + @delegates(j) + def h(self, b: bool=False, **kwargs): + a = 1 + return a +``` + +``` python +node.body +``` + + [@delegates(j) + def h(self, b: bool=False, **kwargs): + a = 1 + return a] + +``` python +tree = _proc_mod(mod) +tree.body[7] +``` + +``` python +class A: + + def h(self, b: bool=False, *, d: str='a'): + ... + + def k(self, b: bool=False, *, d: str='a'): + ... + + def m(self, b: bool=False, *, d: str='a'): + ... + + def n(self, b: bool=False, **kwargs): + """No delegates here mmm'k?""" + ... +``` + +------------------------------------------------------------------------ + +source + +### create_pyi + +> create_pyi (fn, package=None) + +*Convert `fname.py` to `fname.pyi` by removing function bodies and +expanding [`delegates`](https://fastcore.fast.ai/meta.html#delegates) +kwargs* + +``` python +create_pyi(fn) +``` + +``` python +# fn = Path('/Users/jhoward/git/fastcore/fastcore/docments.py') +# create_pyi(fn, 'fastcore') +``` + +## Script + +------------------------------------------------------------------------ + +source + +### py2pyi + +> py2pyi (fname:str, package:str=None) + +*Convert `fname.py` to `fname.pyi` by removing function bodies and +expanding [`delegates`](https://fastcore.fast.ai/meta.html#delegates) +kwargs* + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
fnamestrThe file name to convert
packagestrNoneThe parent package
+ +------------------------------------------------------------------------ + +source + +### replace_wildcards + +> replace_wildcards (path:str) + +*Expand wildcard imports in the specified Python file.* + + + + + + + + + + + + + + + + +
TypeDetails
pathstrPath to the Python file to process
diff --git a/robots.txt b/robots.txt new file mode 100644 index 00000000..7b04fdf9 --- /dev/null +++ b/robots.txt @@ -0,0 +1 @@ +Sitemap: https://fastcore.fast.ai/sitemap.xml diff --git a/script.html b/script.html new file mode 100644 index 00000000..301d19d7 --- /dev/null +++ b/script.html @@ -0,0 +1,1016 @@ + + + + + + + + + + +Script - CLI – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Script - CLI

+
+ +
+
+ A fast way to turn your python function into a script. +
+
+ + +
+ + + + +
+ + + +
+ + + +

Part of fast.ai’s toolkit for delightful developer experiences.

+
+

Overview

+

Sometimes, you want to create a quick script, either for yourself, or for others. But in Python, that involves a whole lot of boilerplate and ceremony, especially if you want to support command line arguments, provide help, and other niceties. You can use argparse for this purpose, which comes with Python, but it’s complex and verbose.

+

fastcore.script makes life easier. There are much fancier modules to help you write scripts (we recommend Python Fire, and Click is also popular), but fastcore.script is very fast and very simple. In fact, it’s <50 lines of code! Basically, it’s just a little wrapper around argparse that uses modern Python features and some thoughtful defaults to get rid of the boilerplate.

+

For full details, see the docs for core.

+
+
+

Example

+

Here’s a complete example (available in examples/test_fastcore.py):

+
from fastcore.script import *
+@call_parse
+def main(msg:str,     # The message
+         upper:bool): # Convert to uppercase?
+    "Print `msg`, optionally converting to uppercase"
+    print(msg.upper() if upper else msg)
+

If you copy that info a file and run it, you’ll see:

+
$ examples/test_fastcore.py --help
+usage: test_fastcore.py [-h] [--upper] msg
+
+Print `msg`, optionally converting to uppercase
+
+positional arguments:
+  msg          The message
+
+optional arguments:
+  -h, --help   show this help message and exit
+  --upper      Convert to uppercase? (default: False)
+

As you see, we didn’t need any if __name__ == "__main__", we didn’t have to parse arguments, we just wrote a function, added a decorator to it, and added some annotations to our function’s parameters. As a bonus, we can also use this function directly from a REPL such as Jupyter Notebook - it’s not just for command line scripts!

+

You should provide a default (after the =) for any optional parameters. If you don’t provide a default for a parameter, then it will be a positional parameter.

+
+
+

Param annotations

+

If you want to use the full power of argparse, you can do so by using Param annotations instead of type annotations and docments, like so:

+
from fastcore.script import *
+@call_parse
+def main(msg:Param("The message", str),
+         upper:Param("Convert to uppercase?", store_true)):
+    "Print `msg`, optionally converting to uppercase"
+    print(msg.upper() if upper else msg)
+

If you use this approach, then each parameter in your function should have an annotation Param(...) (as in the example above). You can pass the following when calling Param: help,type,opt,action,nargs,const,choices,required . Except for opt, all of these are just passed directly to argparse, so you have all the power of that module at your disposal. Generally you’ll want to pass at least help (since this is provided as the help string for that parameter) and type (to ensure that you get the type of data you expect). opt is a bool that defines whether a param is optional or required (positional) - but you’ll generally not need to set this manually, because fastcore.script will set it for you automatically based on default values.

+
+
+

setuptools scripts

+

There’s a really nice feature of pip/setuptools that lets you create commandline scripts directly from functions, makes them available in the PATH, and even makes your scripts cross-platform (e.g. in Windows it creates an exe). fastcore.script supports this feature too. The trick to making a function available as a script is to add a console_scripts section to your setup file, of the form: script_name=module:function_name. E.g. in this case we use: test_fastcore.script=fastcore.script.test_cli:main. With this, you can then just type test_fastcore.script at any time, from any directory, and your script will be called (once it’s installed using one of the methods below).

+

You don’t actually have to write a setup.py yourself. Instead, just use nbdev. Then modify settings.ini as appropriate for your module/script. To install your script directly, you can type pip install -e .. Your script, when installed this way (it’s called an editable install), will automatically be up to date even if you edit it - there’s no need to reinstall it after editing. With nbdev you can even make your module and script available for installation directly from pip and conda by running make release.

+
+
+

API details

+
+

source

+
+

store_true

+
+
 store_true ()
+
+

Placeholder to pass to Param for store_true action

+
+

source

+
+
+

store_false

+
+
 store_false ()
+
+

Placeholder to pass to Param for store_false action

+
+

source

+
+
+

bool_arg

+
+
 bool_arg (v)
+
+

Use as type for Param to get bool behavior

+
+

source

+
+
+

clean_type_str

+
+
 clean_type_str (x:str)
+
+
+
class Test: pass
+
+test_eq(clean_type_str(argparse.ArgumentParser), 'argparse.ArgumentParser')
+test_eq(clean_type_str(Test), 'Test')
+test_eq(clean_type_str(int), 'int')
+test_eq(clean_type_str(float), 'float')
+test_eq(clean_type_str(store_false), 'store_false')
+
+
+

source

+
+
+

Param

+
+
 Param (help='', type=None, opt=True, action=None, nargs=None, const=None,
+        choices=None, required=None, default=None)
+
+

A parameter in a function used in anno_parser or call_parse

+
+
test_eq(repr(Param("Help goes here")), '<Help goes here>')
+test_eq(repr(Param("Help", int)), 'int <Help>')
+test_eq(repr(Param(help=None, type=int)), 'int')
+test_eq(repr(Param(help=None, type=None)), '')
+
+

Each parameter in your function should have an annotation Param(...). You can pass the following when calling Param: help,type,opt,action,nargs,const,choices,required (i.e. it takes the same parameters as argparse.ArgumentParser.add_argument, plus opt). Except for opt, all of these are just passed directly to argparse, so you have all the power of that module at your disposal. Generally you’ll want to pass at least help (since this is provided as the help string for that parameter) and type (to ensure that you get the type of data you expect).

+

opt is a bool that defines whether a param is optional or required (positional) - but you’ll generally not need to set this manually, because fastcore.script will set it for you automatically based on default values. You should provide a default (after the =) for any optional parameters. If you don’t provide a default for a parameter, then it will be a positional parameter.

+

Param’s __repr__ also allows for more informative function annotation when looking up the function’s doc using shift+tab. You see the type annotation (if there is one) and the accompanying help documentation with it.

+
+
def f(required:Param("Required param", int),
+      a:Param("param 1", bool_arg),
+      b:Param("param 2", str)="test"):
+    "my docs"
+    ...
+
+
+
help(f)
+
+
Help on function f in module __main__:
+
+f(required: int <Required param>, a: bool_arg <param 1>, b: str <param 2> = 'test')
+    my docs
+
+
+
+
+
p = Param(help="help", type=int)
+p.set_default(1)
+test_eq(p.kwargs, {'help': 'help (default: 1)', 'type': int, 'default': 1})
+
+
+

source

+
+
+

anno_parser

+
+
 anno_parser (func, prog:str=None)
+
+

Look at params (annotated with Param) in func and return an ArgumentParser

+ + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
funcFunction to get arguments from
progstrNoneThe name of the program
+

This converts a function with parameter annotations of type Param into an argparse.ArgumentParser object. Function arguments with a default provided are optional, and other arguments are positional.

+
+
_en = str_enum('_en', 'aa','bb','cc')
+def f(required:Param("Required param", int),
+      a:Param("param 1", bool_arg),
+      b:Param("param 2", str)="test",
+      c:Param("param 3", _en)=_en.aa):
+    "my docs"
+    ...
+
+p = anno_parser(f, 'progname')
+p.print_help()
+
+
usage: progname [-h] [--b B] [--c {aa,bb,cc}] required a
+
+my docs
+
+positional arguments:
+  required        Required param
+  a               param 1
+
+optional arguments:
+  -h, --help      show this help message and exit
+  --b B           param 2 (default: test)
+  --c {aa,bb,cc}  param 3 (default: aa)
+
+
+

It also works with type annotations and docments:

+
+
def g(required:int,  # Required param
+      a:bool_arg,    # param 1
+      b="test",      # param 2
+      c:_en=_en.aa): # param 3
+    "my docs"
+    ...
+
+p = anno_parser(g, 'progname')
+p.print_help()
+
+
usage: progname [-h] [--b B] [--c {aa,bb,cc}] required a
+
+my docs
+
+positional arguments:
+  required        Required param
+  a               param 1
+
+optional arguments:
+  -h, --help      show this help message and exit
+  --b B           param 2 (default: test)
+  --c {aa,bb,cc}  param 3 (default: aa)
+
+
+
+

source

+
+
+

args_from_prog

+
+
 args_from_prog (func, prog)
+
+

Extract args from prog

+

Sometimes it’s convenient to extract arguments from the actual name of the called program. args_from_prog will do this, assuming that names and values of the params are separated by a #. Optionally there can also be a prefix separated by ## (double underscore).

+
+
exp = {'a': False, 'b': 'baa'}
+test_eq(args_from_prog(f, 'foo##a#0#b#baa'), exp)
+test_eq(args_from_prog(f, 'a#0#b#baa'), exp)
+
+
+

source

+
+
+

call_parse

+
+
 call_parse (func=None, nested=False)
+
+

Decorator to create a simple CLI from func using anno_parser

+
+
@call_parse
+def test_add(
+    a:int=0,  # param a
+    b:int=0  # param 1
+):
+    "Add up `a` and `b`"
+    return a + b
+
+

call_parse decorated functions work as regular functions and also as command-line interface functions.

+
+
test_eq(test_add(1,2), 3)
+
+

This is the main way to use fastcore.script; decorate your function with call_parse, add Param annotations (as shown above) or type annotations and docments, and it can then be used as a script.

+

Use the nested keyword argument to create nested parsers, where earlier parsers consume only their known args from sys.argv before later parsers are used. This is useful to create one command line application that executes another. For example:

+
myrunner --keyword 1 script.py -- <script.py args>
+

A separating -- after the first application’s args is recommended though not always required, otherwise args may be parsed in unexpected ways. For example:

+
myrunner script.py -h
+

would display myrunner’s help and not script.py’s.

+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/script.html.md b/script.html.md new file mode 100644 index 00000000..85fa87ee --- /dev/null +++ b/script.html.md @@ -0,0 +1,431 @@ +# Script - CLI + + + + +Part of [fast.ai](https://www.fast.ai)’s toolkit for delightful +developer experiences. + +## Overview + +Sometimes, you want to create a quick script, either for yourself, or +for others. But in Python, that involves a whole lot of boilerplate and +ceremony, especially if you want to support command line arguments, +provide help, and other niceties. You can use +[argparse](https://docs.python.org/3/library/argparse.html) for this +purpose, which comes with Python, but it’s complex and verbose. + +`fastcore.script` makes life easier. There are much fancier modules to +help you write scripts (we recommend [Python +Fire](https://github.com/google/python-fire), and +[Click](https://click.palletsprojects.com/en/7.x/) is also popular), but +fastcore.script is very fast and very simple. In fact, it’s \<50 lines +of code! Basically, it’s just a little wrapper around `argparse` that +uses modern Python features and some thoughtful defaults to get rid of +the boilerplate. + +For full details, see the [docs](https://fastcore.script.fast.ai) for +`core`. + +## Example + +Here’s a complete example (available in `examples/test_fastcore.py`): + +``` python +from fastcore.script import * +@call_parse +def main(msg:str, # The message + upper:bool): # Convert to uppercase? + "Print `msg`, optionally converting to uppercase" + print(msg.upper() if upper else msg) +``` + +If you copy that info a file and run it, you’ll see: + + $ examples/test_fastcore.py --help + usage: test_fastcore.py [-h] [--upper] msg + + Print `msg`, optionally converting to uppercase + + positional arguments: + msg The message + + optional arguments: + -h, --help show this help message and exit + --upper Convert to uppercase? (default: False) + +As you see, we didn’t need any `if __name__ == "__main__"`, we didn’t +have to parse arguments, we just wrote a function, added a decorator to +it, and added some annotations to our function’s parameters. As a bonus, +we can also use this function directly from a REPL such as Jupyter +Notebook - it’s not just for command line scripts! + +You should provide a default (after the `=`) for any *optional* +parameters. If you don’t provide a default for a parameter, then it will +be a *positional* parameter. + +## Param annotations + +If you want to use the full power of `argparse`, you can do so by using +[`Param`](https://fastcore.fast.ai/script.html#param) annotations +instead of type annotations and +[docments](https://fastcore.fast.ai/docments.html), like so: + +``` python +from fastcore.script import * +@call_parse +def main(msg:Param("The message", str), + upper:Param("Convert to uppercase?", store_true)): + "Print `msg`, optionally converting to uppercase" + print(msg.upper() if upper else msg) +``` + +If you use this approach, then each parameter in your function should +have an annotation `Param(...)` (as in the example above). You can pass +the following when calling +[`Param`](https://fastcore.fast.ai/script.html#param): +`help`,`type`,`opt`,`action`,`nargs`,`const`,`choices`,`required` . +Except for `opt`, all of these are just passed directly to `argparse`, +so you have all the power of that module at your disposal. Generally +you’ll want to pass at least `help` (since this is provided as the help +string for that parameter) and `type` (to ensure that you get the type +of data you expect). `opt` is a bool that defines whether a param is +optional or required (positional) - but you’ll generally not need to set +this manually, because fastcore.script will set it for you automatically +based on *default* values. + +## setuptools scripts + +There’s a really nice feature of pip/setuptools that lets you create +commandline scripts directly from functions, makes them available in the +`PATH`, and even makes your scripts cross-platform (e.g. in Windows it +creates an exe). fastcore.script supports this feature too. The trick to +making a function available as a script is to add a `console_scripts` +section to your setup file, of the form: +`script_name=module:function_name`. E.g. in this case we use: +`test_fastcore.script=fastcore.script.test_cli:main`. With this, you can +then just type `test_fastcore.script` at any time, from any directory, +and your script will be called (once it’s installed using one of the +methods below). + +You don’t actually have to write a `setup.py` yourself. Instead, just +use [nbdev](https://nbdev.fast.ai). Then modify `settings.ini` as +appropriate for your module/script. To install your script directly, you +can type `pip install -e .`. Your script, when installed this way (it’s +called an [editable +install](http://codumentary.blogspot.com/2014/11/python-tip-of-year-pip-install-editable.html)), +will automatically be up to date even if you edit it - there’s no need +to reinstall it after editing. With nbdev you can even make your module +and script available for installation directly from pip and conda by +running `make release`. + +## API details + +------------------------------------------------------------------------ + +source + +### store_true + +> store_true () + +*Placeholder to pass to +[`Param`](https://fastcore.fast.ai/script.html#param) for +[`store_true`](https://fastcore.fast.ai/script.html#store_true) action* + +------------------------------------------------------------------------ + +source + +### store_false + +> store_false () + +*Placeholder to pass to +[`Param`](https://fastcore.fast.ai/script.html#param) for +[`store_false`](https://fastcore.fast.ai/script.html#store_false) +action* + +------------------------------------------------------------------------ + +source + +### bool_arg + +> bool_arg (v) + +*Use as `type` for [`Param`](https://fastcore.fast.ai/script.html#param) +to get `bool` behavior* + +------------------------------------------------------------------------ + +source + +### clean_type_str + +> clean_type_str (x:str) + +``` python +class Test: pass + +test_eq(clean_type_str(argparse.ArgumentParser), 'argparse.ArgumentParser') +test_eq(clean_type_str(Test), 'Test') +test_eq(clean_type_str(int), 'int') +test_eq(clean_type_str(float), 'float') +test_eq(clean_type_str(store_false), 'store_false') +``` + +------------------------------------------------------------------------ + +source + +### Param + +> Param (help='', type=None, opt=True, action=None, nargs=None, const=None, +> choices=None, required=None, default=None) + +*A parameter in a function used in +[`anno_parser`](https://fastcore.fast.ai/script.html#anno_parser) or +[`call_parse`](https://fastcore.fast.ai/script.html#call_parse)* + +``` python +test_eq(repr(Param("Help goes here")), '') +test_eq(repr(Param("Help", int)), 'int ') +test_eq(repr(Param(help=None, type=int)), 'int') +test_eq(repr(Param(help=None, type=None)), '') +``` + +Each parameter in your function should have an annotation `Param(...)`. +You can pass the following when calling +[`Param`](https://fastcore.fast.ai/script.html#param): +`help`,`type`,`opt`,`action`,`nargs`,`const`,`choices`,`required` +(i.e. it takes the same parameters as +`argparse.ArgumentParser.add_argument`, plus `opt`). Except for `opt`, +all of these are just passed directly to `argparse`, so you have all the +power of that module at your disposal. Generally you’ll want to pass at +least `help` (since this is provided as the help string for that +parameter) and `type` (to ensure that you get the type of data you +expect). + +`opt` is a bool that defines whether a param is optional or required +(positional) - but you’ll generally not need to set this manually, +because fastcore.script will set it for you automatically based on +*default* values. You should provide a default (after the `=`) for any +*optional* parameters. If you don’t provide a default for a parameter, +then it will be a *positional* parameter. + +Param’s `__repr__` also allows for more informative function annotation +when looking up the function’s doc using shift+tab. You see the type +annotation (if there is one) and the accompanying help documentation +with it. + +``` python +def f(required:Param("Required param", int), + a:Param("param 1", bool_arg), + b:Param("param 2", str)="test"): + "my docs" + ... +``` + +``` python +help(f) +``` + + Help on function f in module __main__: + + f(required: int , a: bool_arg , b: str = 'test') + my docs + +``` python +p = Param(help="help", type=int) +p.set_default(1) +test_eq(p.kwargs, {'help': 'help (default: 1)', 'type': int, 'default': 1}) +``` + +------------------------------------------------------------------------ + +source + +### anno_parser + +> anno_parser (func, prog:str=None) + +*Look at params (annotated with +[`Param`](https://fastcore.fast.ai/script.html#param)) in func and +return an `ArgumentParser`* + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
funcFunction to get arguments from
progstrNoneThe name of the program
+ +This converts a function with parameter annotations of type +[`Param`](https://fastcore.fast.ai/script.html#param) into an +`argparse.ArgumentParser` object. Function arguments with a default +provided are optional, and other arguments are positional. + +``` python +_en = str_enum('_en', 'aa','bb','cc') +def f(required:Param("Required param", int), + a:Param("param 1", bool_arg), + b:Param("param 2", str)="test", + c:Param("param 3", _en)=_en.aa): + "my docs" + ... + +p = anno_parser(f, 'progname') +p.print_help() +``` + + usage: progname [-h] [--b B] [--c {aa,bb,cc}] required a + + my docs + + positional arguments: + required Required param + a param 1 + + optional arguments: + -h, --help show this help message and exit + --b B param 2 (default: test) + --c {aa,bb,cc} param 3 (default: aa) + +It also works with type annotations and docments: + +``` python +def g(required:int, # Required param + a:bool_arg, # param 1 + b="test", # param 2 + c:_en=_en.aa): # param 3 + "my docs" + ... + +p = anno_parser(g, 'progname') +p.print_help() +``` + + usage: progname [-h] [--b B] [--c {aa,bb,cc}] required a + + my docs + + positional arguments: + required Required param + a param 1 + + optional arguments: + -h, --help show this help message and exit + --b B param 2 (default: test) + --c {aa,bb,cc} param 3 (default: aa) + +------------------------------------------------------------------------ + +source + +### args_from_prog + +> args_from_prog (func, prog) + +*Extract args from `prog`* + +Sometimes it’s convenient to extract arguments from the actual name of +the called program. +[`args_from_prog`](https://fastcore.fast.ai/script.html#args_from_prog) +will do this, assuming that names and values of the params are separated +by a `#`. Optionally there can also be a prefix separated by `##` +(double underscore). + +``` python +exp = {'a': False, 'b': 'baa'} +test_eq(args_from_prog(f, 'foo##a#0#b#baa'), exp) +test_eq(args_from_prog(f, 'a#0#b#baa'), exp) +``` + +------------------------------------------------------------------------ + +source + +### call_parse + +> call_parse (func=None, nested=False) + +*Decorator to create a simple CLI from `func` using +[`anno_parser`](https://fastcore.fast.ai/script.html#anno_parser)* + +``` python +@call_parse +def test_add( + a:int=0, # param a + b:int=0 # param 1 +): + "Add up `a` and `b`" + return a + b +``` + +[`call_parse`](https://fastcore.fast.ai/script.html#call_parse) +decorated functions work as regular functions and also as command-line +interface functions. + +``` python +test_eq(test_add(1,2), 3) +``` + +This is the main way to use `fastcore.script`; decorate your function +with [`call_parse`](https://fastcore.fast.ai/script.html#call_parse), +add [`Param`](https://fastcore.fast.ai/script.html#param) annotations +(as shown above) or type annotations and docments, and it can then be +used as a script. + +Use the `nested` keyword argument to create nested parsers, where +earlier parsers consume only their known args from `sys.argv` before +later parsers are used. This is useful to create one command line +application that executes another. For example: + +``` sh +myrunner --keyword 1 script.py -- +``` + +A separating `--` after the first application’s args is recommended +though not always required, otherwise args may be parsed in unexpected +ways. For example: + +``` sh +myrunner script.py -h +``` + +would display `myrunner`’s help and not `script.py`’s. diff --git a/search.json b/search.json new file mode 100644 index 00000000..5983eeff --- /dev/null +++ b/search.json @@ -0,0 +1,672 @@ +[ + { + "objectID": "style.html", + "href": "style.html", + "title": "Style", + "section": "", + "text": "Note\n\n\n\nStyled outputs don’t show in Quarto documentation. Please use a notebook editor to correctly view this page.\n\n\n\nsource\n\nStyleCode\n\n StyleCode (name, code, typ)\n\nAn escape sequence for styling terminal text.\nThe primary building block of the S API.\n\nprint(str(StyleCode('blue', 34, 'fg')) + 'hello' + str(StyleCode('default', 39, 'fg')) + ' world')\n\nhello world\n\n\n\nsource\n\n\nStyle\n\n Style (codes=None)\n\nA minimal terminal text styler.\nThe main way to use it is via the exported S object.\n\n\nExported source\nS = Style()\n\n\nWe start with an empty style:\n\nS\n\n<Style: none>\n\n\nDefine a new style by chaining attributes:\n\ns = S.blue.bold.underline\ns\n\n<Style: blue bold underline>\n\n\nYou can see a full list of available styles with auto-complete by typing S . Tab.\nApply a style by calling it with a string:\n\ns('hello world')\n\n'\\x1b[34m\\x1b[1m\\x1b[4mhello world\\x1b[22m\\x1b[24m\\x1b[39m'\n\n\nThat’s a raw string with the underlying escape sequences that tell the terminal how to format text. To see the styled version we have to print it:\n\nprint(s('hello world'))\n\nhello world\n\n\nYou can also nest styles:\n\nprint(S.bold(S.blue('key') + ' = value ') + S.light_gray(' ' + S.underline('# With a comment')) + ' and unstyled text')\n\nkey = value # With a comment and unstyled text\n\n\n\nprint(S.blue('this '+S.bold('is')+' a test'))\n\nthis is a test\n\n\n\nsource\n\n\ndemo\n\n demo ()\n\nDemonstrate all available styles and their codes.\n\ndemo()\n\n 30 black \n 31 red \n 32 green \n 33 yellow \n 34 blue \n 35 magenta \n 36 cyan \n 37 light_gray \n 39 default \n 90 dark_gray \n 91 light_red \n 92 light_green \n 93 light_yellow \n 94 light_blue \n 95 light_magenta \n 96 light_cyan \n 97 white \n 40 black_bg \n 41 red_bg \n 42 green_bg \n 43 yellow_bg \n 44 blue_bg \n 45 magenta_bg \n 46 cyan_bg \n 47 light_gray_bg \n 49 default_bg \n100 dark_gray_bg \n101 light_red_bg \n102 light_green_bg \n103 light_yellow_bg \n104 light_blue_bg \n105 light_magenta_bg\n106 light_cyan_bg \n107 white_bg \n 1 bold \n 2 dim \n 3 italic \n 4 underline \n 5 blink \n 7 invert \n 8 hidden \n 9 strikethrough \n 22 reset_bold \n 22 reset_dim \n 23 reset_italic \n 24 reset_underline \n 25 reset_blink \n 27 reset_invert \n 28 reset_hidden \n 29 reset_strikethrough\n 0 reset", + "crumbs": [ + "Style" + ] + }, + { + "objectID": "xtras.html", + "href": "xtras.html", + "title": "Utility functions", + "section": "", + "text": "Utilities (other than extensions to Pathlib.Path) for dealing with IO.\n\nsource\n\n\n\n walk (path:pathlib.Path|str, symlinks:bool=True, keep_file:<built-\n infunctioncallable>=<function ret_true>, keep_folder:<built-\n infunctioncallable>=<function ret_true>, skip_folder:<built-\n infunctioncallable>=<function ret_false>, func:<built-\n infunctioncallable>=<function join>, ret_folders:bool=False)\n\nGenerator version of os.walk, using functions to filter files and folders\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\npath\npathlib.Path | str\n\npath to start searching\n\n\nsymlinks\nbool\nTrue\nfollow symlinks?\n\n\nkeep_file\ncallable\nret_true\nfunction that returns True for wanted files\n\n\nkeep_folder\ncallable\nret_true\nfunction that returns True for folders to enter\n\n\nskip_folder\ncallable\nret_false\nfunction that returns True for folders to skip\n\n\nfunc\ncallable\njoin\nfunction to apply to each matched file\n\n\nret_folders\nbool\nFalse\nreturn folders, not just files\n\n\n\n\nsource\n\n\n\n\n globtastic (path:pathlib.Path|str, recursive:bool=True,\n symlinks:bool=True, file_glob:str=None, file_re:str=None,\n folder_re:str=None, skip_file_glob:str=None,\n skip_file_re:str=None, skip_folder_re:str=None, func:<built-\n infunctioncallable>=<function join>, ret_folders:bool=False)\n\nA more powerful glob, including regex matches, symlink handling, and skip parameters\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\npath\npathlib.Path | str\n\npath to start searching\n\n\nrecursive\nbool\nTrue\nsearch subfolders\n\n\nsymlinks\nbool\nTrue\nfollow symlinks?\n\n\nfile_glob\nstr\nNone\nOnly include files matching glob\n\n\nfile_re\nstr\nNone\nOnly include files matching regex\n\n\nfolder_re\nstr\nNone\nOnly enter folders matching regex\n\n\nskip_file_glob\nstr\nNone\nSkip files matching glob\n\n\nskip_file_re\nstr\nNone\nSkip files matching regex\n\n\nskip_folder_re\nstr\nNone\nSkip folders matching regex,\n\n\nfunc\ncallable\njoin\nfunction to apply to each matched file\n\n\nret_folders\nbool\nFalse\nreturn folders, not just files\n\n\nReturns\nL\n\nPaths to matched files\n\n\n\n\nglobtastic('.', skip_folder_re='^[_.]', folder_re='core', file_glob='*.*py*', file_re='c')\n\n(#5) ['./fastcore/docments.py','./fastcore/dispatch.py','./fastcore/basics.py','./fastcore/docscrape.py','./fastcore/script.py']\n\n\n\nsource\n\n\n\n\n maybe_open (f, mode='r', **kwargs)\n\nContext manager: open f if it is a path (and close on exit)\nThis is useful for functions where you want to accept a path or file. maybe_open will not close your file handle if you pass one in.\n\ndef _f(fn):\n with maybe_open(fn) as f: return f.encoding\n\nfname = '00_test.ipynb'\nsys_encoding = 'cp1252' if sys.platform == 'win32' else 'UTF-8'\ntest_eq(_f(fname), sys_encoding)\nwith open(fname) as fh: test_eq(_f(fh), sys_encoding)\n\nFor example, we can use this to reimplement imghdr.what from the Python standard library, which is written in Python 3.9 as:\n\nfrom fastcore import imghdr\n\n\ndef what(file, h=None):\n f = None\n try:\n if h is None:\n if isinstance(file, (str,os.PathLike)):\n f = open(file, 'rb')\n h = f.read(32)\n else:\n location = file.tell()\n h = file.read(32)\n file.seek(location)\n for tf in imghdr.tests:\n res = tf(h, f)\n if res: return res\n finally:\n if f: f.close()\n return None\n\nHere’s an example of the use of this function:\n\nfname = 'images/puppy.jpg'\nwhat(fname)\n\n'jpeg'\n\n\nWith maybe_open, Self, and L.map_first, we can rewrite this in a much more concise and (in our opinion) clear way:\n\ndef what(file, h=None):\n if h is None:\n with maybe_open(file, 'rb') as f: h = f.peek(32)\n return L(imghdr.tests).map_first(Self(h,file))\n\n…and we can check that it still works:\n\ntest_eq(what(fname), 'jpeg')\n\n…along with the version passing a file handle:\n\nwith open(fname,'rb') as f: test_eq(what(f), 'jpeg')\n\n…along with the h parameter version:\n\nwith open(fname,'rb') as f: test_eq(what(None, h=f.read(32)), 'jpeg')\n\n\nsource\n\n\n\n\n mkdir (path, exist_ok=False, parents=False, overwrite=False, **kwargs)\n\nCreates and returns a directory defined by path, optionally removing previous existing directory if overwrite is True\n\nwith tempfile.TemporaryDirectory() as d:\n path = Path(os.path.join(d, 'new_dir'))\n new_dir = mkdir(path)\n assert new_dir.exists()\n test_eq(new_dir, path)\n \n # test overwrite\n with open(new_dir/'test.txt', 'w') as f: f.writelines('test')\n test_eq(len(list(walk(new_dir))), 1) # assert file is present\n new_dir = mkdir(new_dir, overwrite=True)\n test_eq(len(list(walk(new_dir))), 0) # assert file was deleted\n\n\nsource\n\n\n\n\n image_size (fn)\n\nTuple of (w,h) for png, gif, or jpg; None otherwise\n\ntest_eq(image_size(fname), (1200,803))\n\n\nsource\n\n\n\n\n bunzip (fn)\n\nbunzip fn, raising exception if output already exists\n\nf = Path('files/test.txt')\nif f.exists(): f.unlink()\nbunzip('files/test.txt.bz2')\nt = f.open().readlines()\ntest_eq(len(t),1)\ntest_eq(t[0], 'test\\n')\nf.unlink()\n\n\nsource\n\n\n\n\n loads (s, **kw)\n\nSame as json.loads, but handles None\n\nsource\n\n\n\n\n loads_multi (s:str)\n\nGenerator of >=0 decoded json dicts, possibly with non-json ignored text at start and end\n\ntst = \"\"\"\n# ignored\n{ \"a\":1 }\nhello\n{\n\"b\":2\n}\n\"\"\"\n\ntest_eq(list(loads_multi(tst)), [{'a': 1}, {'b': 2}])\n\n\nsource\n\n\n\n\n dumps (obj, **kw)\n\nSame as json.dumps, but uses ujson if available\n\nsource\n\n\n\n\n untar_dir (fname, dest, rename=False, overwrite=False)\n\nuntar file into dest, creating a directory if the root contains more than one item\n\ndef test_untar(foldername, rename=False, **kwargs):\n with tempfile.TemporaryDirectory() as d:\n nm = os.path.join(d, 'a')\n shutil.make_archive(nm, 'gztar', **kwargs)\n with tempfile.TemporaryDirectory() as d2:\n d2 = Path(d2)\n untar_dir(nm+'.tar.gz', d2, rename=rename)\n test_eq(d2.ls(), [d2/foldername])\n\nIf the contents of fname contain just one file or directory, it is placed directly in dest:\n\n# using `base_dir` in `make_archive` results in `images` directory included in file names\ntest_untar('images', base_dir='images')\n\nIf rename then the directory created is named based on the archive, without extension:\n\ntest_untar('a', base_dir='images', rename=True)\n\nIf the contents of fname contain multiple files and directories, a new folder in dest is created with the same name as fname (but without extension):\n\n# using `root_dir` in `make_archive` results in `images` directory *not* included in file names\ntest_untar('a', root_dir='images')\n\n\nsource\n\n\n\n\n repo_details (url)\n\nTuple of owner,name from ssh or https git repo url\n\ntest_eq(repo_details('https://github.com/fastai/fastai.git'), ['fastai', 'fastai'])\ntest_eq(repo_details('git@github.com:fastai/nbdev.git\\n'), ['fastai', 'nbdev'])\n\n\nsource\n\n\n\n\n run (cmd, *rest, same_in_win=False, ignore_ex=False, as_bytes=False,\n stderr=False)\n\nPass cmd (splitting with shlex if string) to subprocess.run; return stdout; raise IOError if fails\nYou can pass a string (which will be split based on standard shell rules), a list, or pass args directly:\n\nrun('echo', same_in_win=True)\nrun('pip', '--version', same_in_win=True)\nrun(['pip', '--version'], same_in_win=True)\n\n'pip 23.3.1 from /Users/jhoward/miniconda3/lib/python3.11/site-packages/pip (python 3.11)'\n\n\n\nif sys.platform == 'win32':\n assert 'ipynb' in run('cmd /c dir /p')\n assert 'ipynb' in run(['cmd', '/c', 'dir', '/p'])\n assert 'ipynb' in run('cmd', '/c', 'dir', '/p')\nelse:\n assert 'ipynb' in run('ls -ls')\n assert 'ipynb' in run(['ls', '-l'])\n assert 'ipynb' in run('ls', '-l')\n\nSome commands fail in non-error situations, like grep. Use ignore_ex in those cases, which will return a tuple of stdout and returncode:\n\nif sys.platform == 'win32':\n test_eq(run('cmd /c findstr asdfds 00_test.ipynb', ignore_ex=True)[0], 1)\nelse:\n test_eq(run('grep asdfds 00_test.ipynb', ignore_ex=True)[0], 1)\n\nrun automatically decodes returned bytes to a str. Use as_bytes to skip that:\n\nif sys.platform == 'win32':\n test_eq(run('cmd /c echo hi'), 'hi')\nelse:\n test_eq(run('echo hi', as_bytes=True), b'hi\\n')\n\n\nsource\n\n\n\n\n open_file (fn, mode='r', **kwargs)\n\nOpen a file, with optional compression if gz or bz2 suffix\n\nsource\n\n\n\n\n save_pickle (fn, o)\n\nSave a pickle file, to a file name or opened file\n\nsource\n\n\n\n\n load_pickle (fn)\n\nLoad a pickle file from a file name or opened file\n\nfor suf in '.pkl','.bz2','.gz':\n # delete=False is added for Windows\n # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file\n with tempfile.NamedTemporaryFile(suffix=suf, delete=False) as f:\n fn = Path(f.name)\n save_pickle(fn, 't')\n t = load_pickle(fn)\n f.close()\n test_eq(t,'t')\n\n\nsource\n\n\n\n\n parse_env (s:str=None, fn:Union[str,pathlib.Path]=None)\n\nParse a shell-style environment string or file\n\ntestf = \"\"\"# comment\n # another comment\n export FOO=\"bar#baz\"\nBAR=thing # comment \"ok\"\n baz='thong'\nQUX=quux\nexport ZAP = \"zip\" # more comments\n FOOBAR = 42 # trailing space and comment\"\"\"\n\nexp = dict(FOO='bar#baz', BAR='thing', baz='thong', QUX='quux', ZAP='zip', FOOBAR='42')\n\ntest_eq(parse_env(testf), exp)\n\n\nsource\n\n\n\n\n expand_wildcards (code)\n\nExpand all wildcard imports in the given code string.\n\ninp = \"\"\"from math import *\nfrom os import *\nfrom random import *\ndef func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)\"\"\"\n\nexp = \"\"\"from math import pi, sin\nfrom os import path\nfrom random import randint\ndef func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)\"\"\"\n\ntest_eq(expand_wildcards(inp), exp)\n\ninp = \"\"\"from itertools import *\ndef func(): pass\"\"\"\ntest_eq(expand_wildcards(inp), inp)\n\ninp = \"\"\"def outer():\n from math import *\n def inner():\n from os import *\n return sin(pi) + path.join('a', 'b')\"\"\"\n\nexp = \"\"\"def outer():\n from math import pi, sin\n def inner():\n from os import path\n return sin(pi) + path.join('a', 'b')\"\"\"\n\ntest_eq(expand_wildcards(inp), exp)", + "crumbs": [ + "Utility functions" + ] + }, + { + "objectID": "xtras.html#file-functions", + "href": "xtras.html#file-functions", + "title": "Utility functions", + "section": "", + "text": "Utilities (other than extensions to Pathlib.Path) for dealing with IO.\n\nsource\n\n\n\n walk (path:pathlib.Path|str, symlinks:bool=True, keep_file:<built-\n infunctioncallable>=<function ret_true>, keep_folder:<built-\n infunctioncallable>=<function ret_true>, skip_folder:<built-\n infunctioncallable>=<function ret_false>, func:<built-\n infunctioncallable>=<function join>, ret_folders:bool=False)\n\nGenerator version of os.walk, using functions to filter files and folders\n\n\n\n\n\n\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\npath\npathlib.Path | str\n\npath to start searching\n\n\nsymlinks\nbool\nTrue\nfollow symlinks?\n\n\nkeep_file\ncallable\nret_true\nfunction that returns True for wanted files\n\n\nkeep_folder\ncallable\nret_true\nfunction that returns True for folders to enter\n\n\nskip_folder\ncallable\nret_false\nfunction that returns True for folders to skip\n\n\nfunc\ncallable\njoin\nfunction to apply to each matched file\n\n\nret_folders\nbool\nFalse\nreturn folders, not just files\n\n\n\n\nsource\n\n\n\n\n globtastic (path:pathlib.Path|str, recursive:bool=True,\n symlinks:bool=True, file_glob:str=None, file_re:str=None,\n folder_re:str=None, skip_file_glob:str=None,\n skip_file_re:str=None, skip_folder_re:str=None, func:<built-\n infunctioncallable>=<function join>, ret_folders:bool=False)\n\nA more powerful glob, including regex matches, symlink handling, and skip parameters\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\npath\npathlib.Path | str\n\npath to start searching\n\n\nrecursive\nbool\nTrue\nsearch subfolders\n\n\nsymlinks\nbool\nTrue\nfollow symlinks?\n\n\nfile_glob\nstr\nNone\nOnly include files matching glob\n\n\nfile_re\nstr\nNone\nOnly include files matching regex\n\n\nfolder_re\nstr\nNone\nOnly enter folders matching regex\n\n\nskip_file_glob\nstr\nNone\nSkip files matching glob\n\n\nskip_file_re\nstr\nNone\nSkip files matching regex\n\n\nskip_folder_re\nstr\nNone\nSkip folders matching regex,\n\n\nfunc\ncallable\njoin\nfunction to apply to each matched file\n\n\nret_folders\nbool\nFalse\nreturn folders, not just files\n\n\nReturns\nL\n\nPaths to matched files\n\n\n\n\nglobtastic('.', skip_folder_re='^[_.]', folder_re='core', file_glob='*.*py*', file_re='c')\n\n(#5) ['./fastcore/docments.py','./fastcore/dispatch.py','./fastcore/basics.py','./fastcore/docscrape.py','./fastcore/script.py']\n\n\n\nsource\n\n\n\n\n maybe_open (f, mode='r', **kwargs)\n\nContext manager: open f if it is a path (and close on exit)\nThis is useful for functions where you want to accept a path or file. maybe_open will not close your file handle if you pass one in.\n\ndef _f(fn):\n with maybe_open(fn) as f: return f.encoding\n\nfname = '00_test.ipynb'\nsys_encoding = 'cp1252' if sys.platform == 'win32' else 'UTF-8'\ntest_eq(_f(fname), sys_encoding)\nwith open(fname) as fh: test_eq(_f(fh), sys_encoding)\n\nFor example, we can use this to reimplement imghdr.what from the Python standard library, which is written in Python 3.9 as:\n\nfrom fastcore import imghdr\n\n\ndef what(file, h=None):\n f = None\n try:\n if h is None:\n if isinstance(file, (str,os.PathLike)):\n f = open(file, 'rb')\n h = f.read(32)\n else:\n location = file.tell()\n h = file.read(32)\n file.seek(location)\n for tf in imghdr.tests:\n res = tf(h, f)\n if res: return res\n finally:\n if f: f.close()\n return None\n\nHere’s an example of the use of this function:\n\nfname = 'images/puppy.jpg'\nwhat(fname)\n\n'jpeg'\n\n\nWith maybe_open, Self, and L.map_first, we can rewrite this in a much more concise and (in our opinion) clear way:\n\ndef what(file, h=None):\n if h is None:\n with maybe_open(file, 'rb') as f: h = f.peek(32)\n return L(imghdr.tests).map_first(Self(h,file))\n\n…and we can check that it still works:\n\ntest_eq(what(fname), 'jpeg')\n\n…along with the version passing a file handle:\n\nwith open(fname,'rb') as f: test_eq(what(f), 'jpeg')\n\n…along with the h parameter version:\n\nwith open(fname,'rb') as f: test_eq(what(None, h=f.read(32)), 'jpeg')\n\n\nsource\n\n\n\n\n mkdir (path, exist_ok=False, parents=False, overwrite=False, **kwargs)\n\nCreates and returns a directory defined by path, optionally removing previous existing directory if overwrite is True\n\nwith tempfile.TemporaryDirectory() as d:\n path = Path(os.path.join(d, 'new_dir'))\n new_dir = mkdir(path)\n assert new_dir.exists()\n test_eq(new_dir, path)\n \n # test overwrite\n with open(new_dir/'test.txt', 'w') as f: f.writelines('test')\n test_eq(len(list(walk(new_dir))), 1) # assert file is present\n new_dir = mkdir(new_dir, overwrite=True)\n test_eq(len(list(walk(new_dir))), 0) # assert file was deleted\n\n\nsource\n\n\n\n\n image_size (fn)\n\nTuple of (w,h) for png, gif, or jpg; None otherwise\n\ntest_eq(image_size(fname), (1200,803))\n\n\nsource\n\n\n\n\n bunzip (fn)\n\nbunzip fn, raising exception if output already exists\n\nf = Path('files/test.txt')\nif f.exists(): f.unlink()\nbunzip('files/test.txt.bz2')\nt = f.open().readlines()\ntest_eq(len(t),1)\ntest_eq(t[0], 'test\\n')\nf.unlink()\n\n\nsource\n\n\n\n\n loads (s, **kw)\n\nSame as json.loads, but handles None\n\nsource\n\n\n\n\n loads_multi (s:str)\n\nGenerator of >=0 decoded json dicts, possibly with non-json ignored text at start and end\n\ntst = \"\"\"\n# ignored\n{ \"a\":1 }\nhello\n{\n\"b\":2\n}\n\"\"\"\n\ntest_eq(list(loads_multi(tst)), [{'a': 1}, {'b': 2}])\n\n\nsource\n\n\n\n\n dumps (obj, **kw)\n\nSame as json.dumps, but uses ujson if available\n\nsource\n\n\n\n\n untar_dir (fname, dest, rename=False, overwrite=False)\n\nuntar file into dest, creating a directory if the root contains more than one item\n\ndef test_untar(foldername, rename=False, **kwargs):\n with tempfile.TemporaryDirectory() as d:\n nm = os.path.join(d, 'a')\n shutil.make_archive(nm, 'gztar', **kwargs)\n with tempfile.TemporaryDirectory() as d2:\n d2 = Path(d2)\n untar_dir(nm+'.tar.gz', d2, rename=rename)\n test_eq(d2.ls(), [d2/foldername])\n\nIf the contents of fname contain just one file or directory, it is placed directly in dest:\n\n# using `base_dir` in `make_archive` results in `images` directory included in file names\ntest_untar('images', base_dir='images')\n\nIf rename then the directory created is named based on the archive, without extension:\n\ntest_untar('a', base_dir='images', rename=True)\n\nIf the contents of fname contain multiple files and directories, a new folder in dest is created with the same name as fname (but without extension):\n\n# using `root_dir` in `make_archive` results in `images` directory *not* included in file names\ntest_untar('a', root_dir='images')\n\n\nsource\n\n\n\n\n repo_details (url)\n\nTuple of owner,name from ssh or https git repo url\n\ntest_eq(repo_details('https://github.com/fastai/fastai.git'), ['fastai', 'fastai'])\ntest_eq(repo_details('git@github.com:fastai/nbdev.git\\n'), ['fastai', 'nbdev'])\n\n\nsource\n\n\n\n\n run (cmd, *rest, same_in_win=False, ignore_ex=False, as_bytes=False,\n stderr=False)\n\nPass cmd (splitting with shlex if string) to subprocess.run; return stdout; raise IOError if fails\nYou can pass a string (which will be split based on standard shell rules), a list, or pass args directly:\n\nrun('echo', same_in_win=True)\nrun('pip', '--version', same_in_win=True)\nrun(['pip', '--version'], same_in_win=True)\n\n'pip 23.3.1 from /Users/jhoward/miniconda3/lib/python3.11/site-packages/pip (python 3.11)'\n\n\n\nif sys.platform == 'win32':\n assert 'ipynb' in run('cmd /c dir /p')\n assert 'ipynb' in run(['cmd', '/c', 'dir', '/p'])\n assert 'ipynb' in run('cmd', '/c', 'dir', '/p')\nelse:\n assert 'ipynb' in run('ls -ls')\n assert 'ipynb' in run(['ls', '-l'])\n assert 'ipynb' in run('ls', '-l')\n\nSome commands fail in non-error situations, like grep. Use ignore_ex in those cases, which will return a tuple of stdout and returncode:\n\nif sys.platform == 'win32':\n test_eq(run('cmd /c findstr asdfds 00_test.ipynb', ignore_ex=True)[0], 1)\nelse:\n test_eq(run('grep asdfds 00_test.ipynb', ignore_ex=True)[0], 1)\n\nrun automatically decodes returned bytes to a str. Use as_bytes to skip that:\n\nif sys.platform == 'win32':\n test_eq(run('cmd /c echo hi'), 'hi')\nelse:\n test_eq(run('echo hi', as_bytes=True), b'hi\\n')\n\n\nsource\n\n\n\n\n open_file (fn, mode='r', **kwargs)\n\nOpen a file, with optional compression if gz or bz2 suffix\n\nsource\n\n\n\n\n save_pickle (fn, o)\n\nSave a pickle file, to a file name or opened file\n\nsource\n\n\n\n\n load_pickle (fn)\n\nLoad a pickle file from a file name or opened file\n\nfor suf in '.pkl','.bz2','.gz':\n # delete=False is added for Windows\n # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file\n with tempfile.NamedTemporaryFile(suffix=suf, delete=False) as f:\n fn = Path(f.name)\n save_pickle(fn, 't')\n t = load_pickle(fn)\n f.close()\n test_eq(t,'t')\n\n\nsource\n\n\n\n\n parse_env (s:str=None, fn:Union[str,pathlib.Path]=None)\n\nParse a shell-style environment string or file\n\ntestf = \"\"\"# comment\n # another comment\n export FOO=\"bar#baz\"\nBAR=thing # comment \"ok\"\n baz='thong'\nQUX=quux\nexport ZAP = \"zip\" # more comments\n FOOBAR = 42 # trailing space and comment\"\"\"\n\nexp = dict(FOO='bar#baz', BAR='thing', baz='thong', QUX='quux', ZAP='zip', FOOBAR='42')\n\ntest_eq(parse_env(testf), exp)\n\n\nsource\n\n\n\n\n expand_wildcards (code)\n\nExpand all wildcard imports in the given code string.\n\ninp = \"\"\"from math import *\nfrom os import *\nfrom random import *\ndef func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)\"\"\"\n\nexp = \"\"\"from math import pi, sin\nfrom os import path\nfrom random import randint\ndef func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)\"\"\"\n\ntest_eq(expand_wildcards(inp), exp)\n\ninp = \"\"\"from itertools import *\ndef func(): pass\"\"\"\ntest_eq(expand_wildcards(inp), inp)\n\ninp = \"\"\"def outer():\n from math import *\n def inner():\n from os import *\n return sin(pi) + path.join('a', 'b')\"\"\"\n\nexp = \"\"\"def outer():\n from math import pi, sin\n def inner():\n from os import path\n return sin(pi) + path.join('a', 'b')\"\"\"\n\ntest_eq(expand_wildcards(inp), exp)", + "crumbs": [ + "Utility functions" + ] + }, + { + "objectID": "xtras.html#collections", + "href": "xtras.html#collections", + "title": "Utility functions", + "section": "Collections", + "text": "Collections\n\nsource\n\ndict2obj\n\n dict2obj (d, list_func=<class 'fastcore.foundation.L'>, dict_func=<class\n 'fastcore.basics.AttrDict'>)\n\nConvert (possibly nested) dicts (or lists of dicts) to AttrDict\nThis is a convenience to give you “dotted” access to (possibly nested) dictionaries, e.g:\n\nd1 = dict(a=1, b=dict(c=2,d=3))\nd2 = dict2obj(d1)\ntest_eq(d2.b.c, 2)\ntest_eq(d2.b['c'], 2)\n\nIt can also be used on lists of dicts.\n\n_list_of_dicts = [d1, d1]\nds = dict2obj(_list_of_dicts)\ntest_eq(ds[0].b.c, 2)\n\n\nsource\n\n\nobj2dict\n\n obj2dict (d)\n\nConvert (possibly nested) AttrDicts (or lists of AttrDicts) to dict\nobj2dict can be used to reverse what is done by dict2obj:\n\ntest_eq(obj2dict(d2), d1)\ntest_eq(obj2dict(ds), _list_of_dicts)\n\n\nsource\n\n\nrepr_dict\n\n repr_dict (d)\n\nPrint nested dicts and lists, such as returned by dict2obj\n\nprint(repr_dict(d2))\n\n- a: 1\n- b: \n - c: 2\n - d: 3\n\n\n\nsource\n\n\nis_listy\n\n is_listy (x)\n\nisinstance(x, (tuple,list,L,slice,Generator))\n\nassert is_listy((1,))\nassert is_listy([1])\nassert is_listy(L([1]))\nassert is_listy(slice(2))\nassert not is_listy(array([1]))\n\n\nsource\n\n\nmapped\n\n mapped (f, it)\n\nmap f over it, unless it’s not listy, in which case return f(it)\n\ndef _f(x,a=1): return x-a\n\ntest_eq(mapped(_f,1),0)\ntest_eq(mapped(_f,[1,2]),[0,1])\ntest_eq(mapped(_f,(1,)),(0,))", + "crumbs": [ + "Utility functions" + ] + }, + { + "objectID": "xtras.html#extensions-to-pathlib.path", + "href": "xtras.html#extensions-to-pathlib.path", + "title": "Utility functions", + "section": "Extensions to Pathlib.Path", + "text": "Extensions to Pathlib.Path\nThe following methods are added to the standard python libary Pathlib.Path.\n\nsource\n\nPath.readlines\n\n Path.readlines (hint=-1, encoding='utf8')\n\nRead the content of self\n\nsource\n\n\nPath.read_json\n\n Path.read_json (encoding=None, errors=None)\n\nSame as read_text followed by loads\n\nsource\n\n\nPath.mk_write\n\n Path.mk_write (data, encoding=None, errors=None, mode=511)\n\nMake all parent dirs of self, and write data\n\nsource\n\n\nPath.relpath\n\n Path.relpath (start=None)\n\nSame as os.path.relpath, but returns a Path, and resolves symlinks\n\np = Path('../fastcore/').resolve()\np\n\nPath('/Users/daniel.roy.greenfeld/fh/fastcore/fastcore')\n\n\n\np.relpath(Path.cwd())\n\nPath('../fastcore')\n\n\n\nsource\n\n\nPath.ls\n\n Path.ls (n_max=None, file_type=None, file_exts=None)\n\nContents of path as a list\nWe add an ls() method to pathlib.Path which is simply defined as list(Path.iterdir()), mainly for convenience in REPL environments such as notebooks.\n\npath = Path()\nt = path.ls()\nassert len(t)>0\nt1 = path.ls(10)\ntest_eq(len(t1), 10)\nt2 = path.ls(file_exts='.ipynb')\nassert len(t)>len(t2)\nt[0]\n\nPath('000_tour.ipynb')\n\n\nYou can also pass an optional file_type MIME prefix and/or a list of file extensions.\n\nlib_path = (path/'../fastcore')\ntxt_files=lib_path.ls(file_type='text')\nassert len(txt_files) > 0 and txt_files[0].suffix=='.py'\nipy_files=path.ls(file_exts=['.ipynb'])\nassert len(ipy_files) > 0 and ipy_files[0].suffix=='.ipynb'\ntxt_files[0],ipy_files[0]\n\n(Path('../fastcore/shutil.py'), Path('000_tour.ipynb'))\n\n\n\nsource\n\n\nPath.__repr__\n\n Path.__repr__ ()\n\nReturn repr(self).\nfastai also updates the repr of Path such that, if Path.BASE_PATH is defined, all paths are printed relative to that path (as long as they are contained in Path.BASE_PATH:\n\nt = ipy_files[0].absolute()\ntry:\n Path.BASE_PATH = t.parent.parent\n test_eq(repr(t), f\"Path('nbs/{t.name}')\")\nfinally: Path.BASE_PATH = None\n\n\nsource\n\n\nPath.delete\n\n Path.delete ()\n\nDelete a file, symlink, or directory tree", + "crumbs": [ + "Utility functions" + ] + }, + { + "objectID": "xtras.html#reindexing-collections", + "href": "xtras.html#reindexing-collections", + "title": "Utility functions", + "section": "Reindexing Collections", + "text": "Reindexing Collections\n\nsource\n\nReindexCollection\n\n ReindexCollection (coll, idxs=None, cache=None, tfm=<function noop>)\n\nReindexes collection coll with indices idxs and optional LRU cache of size cache\nThis is useful when constructing batches or organizing data in a particular manner (i.e. for deep learning). This class is primarly used in organizing data for language models in fastai.\nYou can supply a custom index upon instantiation with the idxs argument, or you can call the reindex method to supply a new index for your collection.\nHere is how you can reindex a list such that the elements are reversed:\n\nrc=ReindexCollection(['a', 'b', 'c', 'd', 'e'], idxs=[4,3,2,1,0])\nlist(rc)\n\n['e', 'd', 'c', 'b', 'a']\n\n\nAlternatively, you can use the reindex method:\n\nsource\n\nReindexCollection.reindex\n\n ReindexCollection.reindex (idxs)\n\nReplace self.idxs with idxs\n\nrc=ReindexCollection(['a', 'b', 'c', 'd', 'e'])\nrc.reindex([4,3,2,1,0])\nlist(rc)\n\n['e', 'd', 'c', 'b', 'a']\n\n\nYou can optionally specify a LRU cache, which uses functools.lru_cache upon instantiation:\n\nsz = 50\nt = ReindexCollection(L.range(sz), cache=2)\n\n#trigger a cache hit by indexing into the same element multiple times\nt[0], t[0]\nt._get.cache_info()\n\nCacheInfo(hits=1, misses=1, maxsize=2, currsize=1)\n\n\nYou can optionally clear the LRU cache by calling the cache_clear method:\n\nsource\n\n\nReindexCollection.cache_clear\n\n ReindexCollection.cache_clear ()\n\nClear LRU cache\n\nsz = 50\nt = ReindexCollection(L.range(sz), cache=2)\n\n#trigger a cache hit by indexing into the same element multiple times\nt[0], t[0]\nt.cache_clear()\nt._get.cache_info()\n\nCacheInfo(hits=0, misses=0, maxsize=2, currsize=0)\n\n\n\nsource\n\n\nReindexCollection.shuffle\n\n ReindexCollection.shuffle ()\n\nRandomly shuffle indices\nNote that an ordered index is automatically constructed for the data structure even if one is not supplied.\n\nrc=ReindexCollection(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])\nrc.shuffle()\nlist(rc)\n\n['a', 'd', 'h', 'c', 'e', 'b', 'f', 'g']\n\n\n\nsz = 50\nt = ReindexCollection(L.range(sz), cache=2)\ntest_eq(list(t), range(sz))\ntest_eq(t[sz-1], sz-1)\ntest_eq(t._get.cache_info().hits, 1)\nt.shuffle()\ntest_eq(t._get.cache_info().hits, 1)\ntest_ne(list(t), range(sz))\ntest_eq(set(t), set(range(sz)))\nt.cache_clear()\ntest_eq(t._get.cache_info().hits, 0)\ntest_eq(t.count(0), 1)", + "crumbs": [ + "Utility functions" + ] + }, + { + "objectID": "xtras.html#other-helpers", + "href": "xtras.html#other-helpers", + "title": "Utility functions", + "section": "Other Helpers", + "text": "Other Helpers\n\nsource\n\nget_source_link\n\n get_source_link (func)\n\nReturn link to func in source code\nget_source_link allows you get a link to source code related to an object. For nbdev related projects such as fastcore, we can get the full link to a GitHub repo. For nbdev projects, be sure to properly set the git_url in settings.ini (derived from lib_name and branch on top of the prefix you will need to adapt) so that those links are correct.\nFor example, below we get the link to fastcore.test.test_eq:\n\nfrom fastcore.test import test_eq\n\n\nassert 'fastcore/test.py' in get_source_link(test_eq)\nassert get_source_link(test_eq).startswith('https://github.com/fastai/fastcore')\nget_source_link(test_eq)\n\n'https://github.com/fastai/fastcore/tree/master/fastcore/test.py#L35'\n\n\n\nsource\n\n\ntruncstr\n\n truncstr (s:str, maxlen:int, suf:str='…', space='')\n\nTruncate s to length maxlen, adding suffix suf if truncated\n\nw = 'abacadabra'\ntest_eq(truncstr(w, 10), w)\ntest_eq(truncstr(w, 5), 'abac…')\ntest_eq(truncstr(w, 5, suf=''), 'abaca')\ntest_eq(truncstr(w, 11, space='_'), w+\"_\")\ntest_eq(truncstr(w, 10, space='_'), w[:-1]+'…')\ntest_eq(truncstr(w, 5, suf='!!'), 'aba!!')\n\n\nsource\n\n\nsparkline\n\n sparkline (data, mn=None, mx=None, empty_zero=False)\n\nSparkline for data, with Nones (and zero, if empty_zero) shown as empty column\n\ndata = [9,6,None,1,4,0,8,15,10]\nprint(f'without \"empty_zero\": {sparkline(data, empty_zero=False)}')\nprint(f' with \"empty_zero\": {sparkline(data, empty_zero=True )}')\n\nwithout \"empty_zero\": ▅▂ ▁▂▁▃▇▅\n with \"empty_zero\": ▅▂ ▁▂ ▃▇▅\n\n\nYou can set a maximum and minimum for the y-axis of the sparkline with the arguments mn and mx respectively:\n\nsparkline([1,2,3,400], mn=0, mx=3)\n\n'▂▅▇▇'\n\n\n\nsource\n\n\nmodify_exception\n\n modify_exception (e:Exception, msg:str=None, replace:bool=False)\n\nModifies e with a custom message attached\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\ne\nException\n\nAn exception\n\n\nmsg\nstr\nNone\nA custom message\n\n\nreplace\nbool\nFalse\nWhether to replace e.args with [msg]\n\n\nReturns\nException\n\n\n\n\n\n\nmsg = \"This is my custom message!\"\n\ntest_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), None)), contains='')\ntest_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), msg)), contains=msg)\ntest_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(\"The first message\"), msg)), contains=\"The first message This is my custom message!\")\ntest_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(\"The first message\"), msg, True)), contains=\"This is my custom message!\")\n\n\nsource\n\n\nround_multiple\n\n round_multiple (x, mult, round_down=False)\n\nRound x to nearest multiple of mult\n\ntest_eq(round_multiple(63,32), 64)\ntest_eq(round_multiple(50,32), 64)\ntest_eq(round_multiple(40,32), 32)\ntest_eq(round_multiple( 0,32), 0)\ntest_eq(round_multiple(63,32, round_down=True), 32)\ntest_eq(round_multiple((63,40),32), (64,32))\n\n\nsource\n\n\nset_num_threads\n\n set_num_threads (nt)\n\nGet numpy (and others) to use nt threads\nThis sets the number of threads consistently for many tools, by:\n\nSet the following environment variables equal to nt: OPENBLAS_NUM_THREADS,NUMEXPR_NUM_THREADS,OMP_NUM_THREADS,MKL_NUM_THREADS\nSets nt threads for numpy and pytorch.\n\n\nsource\n\n\njoin_path_file\n\n join_path_file (file, path, ext='')\n\nReturn path/file if file is a string or a Path, file otherwise\n\npath = Path.cwd()/'_tmp'/'tst'\nf = join_path_file('tst.txt', path)\nassert path.exists()\ntest_eq(f, path/'tst.txt')\nwith open(f, 'w') as f_: assert join_path_file(f_, path) == f_\nshutil.rmtree(Path.cwd()/'_tmp')\n\n\nsource\n\n\nautostart\n\n autostart (g)\n\nDecorator that automatically starts a generator\n\nsource\n\nEventTimer\n\n EventTimer (store=5, span=60)\n\nAn event timer with history of store items of time span\nAdd events with add, and get number of events and their frequency (freq).\n\n# Random wait function for testing\ndef _randwait(): yield from (sleep(random.random()/200) for _ in range(100))\n\nc = EventTimer(store=5, span=0.03)\nfor o in _randwait(): c.add(1)\nprint(f'Num Events: {c.events}, Freq/sec: {c.freq:.01f}')\nprint('Most recent: ', sparkline(c.hist), *L(c.hist).map('{:.01f}'))\n\nNum Events: 3, Freq/sec: 205.6\nMost recent: ▁▁▃▁▇ 254.1 263.2 284.5 259.9 315.7\n\n\n\nsource\n\n\n\nstringfmt_names\n\n stringfmt_names (s:str)\n\nUnique brace-delimited names in s\n\ns = '/pulls/{pull_number}/reviews/{review_id}'\ntest_eq(stringfmt_names(s), ['pull_number','review_id'])\n\n\nsource\n\nPartialFormatter\n\n PartialFormatter ()\n\nA string.Formatter that doesn’t error on missing fields, and tracks missing fields and unused args\n\nsource\n\n\n\npartial_format\n\n partial_format (s:str, **kwargs)\n\nstring format s, ignoring missing field errors, returning missing and extra fields\nThe result is a tuple of (formatted_string,missing_fields,extra_fields), e.g:\n\nres,missing,xtra = partial_format(s, pull_number=1, foo=2)\ntest_eq(res, '/pulls/1/reviews/{review_id}')\ntest_eq(missing, ['review_id'])\ntest_eq(xtra, {'foo':2})\n\n\nsource\n\n\nutc2local\n\n utc2local (dt:datetime.datetime)\n\nConvert dt from UTC to local time\n\ndt = datetime(2000,1,1,12)\nprint(f'{dt} UTC is {utc2local(dt)} local time')\n\n2000-01-01 12:00:00 UTC is 2000-01-01 22:00:00+10:00 local time\n\n\n\nsource\n\n\nlocal2utc\n\n local2utc (dt:datetime.datetime)\n\nConvert dt from local to UTC time\n\nprint(f'{dt} local is {local2utc(dt)} UTC time')\n\n2000-01-01 12:00:00 local is 2000-01-01 02:00:00+00:00 UTC time\n\n\n\nsource\n\n\ntrace\n\n trace (f)\n\nAdd set_trace to an existing function f\nYou can add a breakpoint to an existing function, e.g:\nPath.cwd = trace(Path.cwd)\nPath.cwd()\nNow, when the function is called it will drop you into the debugger. Note, you must issue the s command when you begin to step into the function that is being traced.\n\nsource\n\n\nmodified_env\n\n modified_env (*delete, **replace)\n\nContext manager temporarily modifying os.environ by deleting delete and replacing replace\n\n# USER isn't in Cloud Linux Environments\nenv_test = 'USERNAME' if sys.platform == \"win32\" else 'SHELL'\noldusr = os.environ[env_test]\n\nreplace_param = {env_test: 'a'}\nwith modified_env('PATH', **replace_param):\n test_eq(os.environ[env_test], 'a')\n assert 'PATH' not in os.environ\n\nassert 'PATH' in os.environ\ntest_eq(os.environ[env_test], oldusr)\n\n\nsource\n\nContextManagers\n\n ContextManagers (mgrs)\n\nWrapper for contextlib.ExitStack which enters a collection of context managers\n\nsource\n\n\n\nshufflish\n\n shufflish (x, pct=0.04)\n\nRandomly relocate items of x up to pct of len(x) from their starting location\n\nsource\n\n\nconsole_help\n\n console_help (libname:str)\n\nShow help for all console scripts from libname\n\n\n\n\nType\nDetails\n\n\n\n\nlibname\nstr\nname of library for console script listing\n\n\n\n\nsource\n\n\nhl_md\n\n hl_md (s, lang='xml', show=True)\n\nSyntax highlight s using lang.\nWhen we display code in a notebook, it’s nice to highlight it, so we create a function to simplify that:\n\nhl_md('<test><xml foo=\"bar\">a child</xml></test>')\n\n<test><xml foo=\"bar\">a child</xml></test>\n\n\n\nsource\n\n\ntype2str\n\n type2str (typ:type)\n\nStringify typ\n\ntest_eq(type2str(Optional[float]), 'Union[float, None]')\n\n\nsource\n\n\ndataclass_src\n\n dataclass_src (cls)\n\n\nDC = make_dataclass('DC', [('x', int), ('y', Optional[float], None), ('z', float, None)])\nprint(dataclass_src(DC))\n\n@dataclass\nclass DC:\n x: int\n y: Union[float, None] = None\n z: float = None\n\n\n\n\nsource\n\n\nUnset\n\n Unset (value, names=None, module=None, qualname=None, type=None, start=1)\n\nAn enumeration.\n\nsource\n\n\nnullable_dc\n\n nullable_dc (cls)\n\nLike dataclass, but default of UNSET added to fields without defaults\n\n@nullable_dc\nclass Person: name: str; age: int; city: str = \"Unknown\"\nPerson(name=\"Bob\")\n\nPerson(name='Bob', age=UNSET, city='Unknown')\n\n\n\nsource\n\n\nmake_nullable\n\n make_nullable (clas)\n\n\n@dataclass\nclass Person: name: str; age: int; city: str = \"Unknown\"\n\nmake_nullable(Person)\nPerson(\"Bob\", city='NY')\n\nPerson(name='Bob', age=UNSET, city='NY')\n\n\n\nPerson(name=\"Bob\")\n\nPerson(name='Bob', age=UNSET, city='Unknown')\n\n\n\nPerson(\"Bob\", 34)\n\nPerson(name='Bob', age=34, city='Unknown')\n\n\n\nsource\n\n\nflexiclass\n\n flexiclass (cls)\n\nConvert cls into a dataclass like make_nullable. Converts in place and also returns the result.\n\n\n\n\nType\nDetails\n\n\n\n\ncls\n\nThe class to convert\n\n\nReturns\ndataclass\n\n\n\n\nThis can be used as a decorator…\n\n@flexiclass\nclass Person: name: str; age: int; city: str = \"Unknown\"\n\nbob = Person(name=\"Bob\")\nbob\n\nPerson(name='Bob', age=UNSET, city='Unknown')\n\n\n…or can update the behavior of an existing class (or dataclass):\n\nclass Person: name: str; age: int; city: str = \"Unknown\"\n\nflexiclass(Person)\nbob = Person(name=\"Bob\")\nbob\n\nPerson(name='Bob', age=UNSET, city='Unknown')\n\n\nAction occurs in-place:\n\nclass Person: name: str; age: int; city: str = \"Unknown\"\n\nflexiclass(Person)\nis_dataclass(Person)\n\nTrue\n\n\n\nsource\n\n\nasdict\n\n asdict (o)\n\nConvert o to a dict, supporting dataclasses, namedtuples, iterables, and __dict__ attrs.\nAny UNSET values are not included.\n\nasdict(bob)\n\n{'name': 'Bob', 'city': 'Unknown'}\n\n\nTo customise dict conversion behavior for a class, implement the _asdict method (this is used in the Python stdlib for named tuples).\n\nsource\n\n\nis_typeddict\n\n is_typeddict (cls:type)\n\nCheck if cls is a TypedDict\n\nclass MyDict(TypedDict): name:str\n\nassert is_typeddict(MyDict)\nassert not is_typeddict({'a':1})\n\n\nsource\n\n\nis_namedtuple\n\n is_namedtuple (cls)\n\nTrue if cls is a namedtuple type\n\nassert is_namedtuple(namedtuple('tst', ['a']))\nassert not is_namedtuple(tuple)\n\n\nsource\n\n\nflexicache\n\n flexicache (*funcs, maxsize=128)\n\nLike lru_cache, but customisable with policy funcs\nThis is a flexible lru cache function that you can pass a list of functions to. Those functions define the cache eviction policy. For instance, time_policy is provided for time-based cache eviction, and mtime_policy evicts based on a file’s modified-time changing. The policy functions are passed the last value that function returned was (initially None), and return a new value to indicate the cache has expired. When the cache expires, all functions are called with None to force getting new values.\n\nsource\n\n\ntime_policy\n\n time_policy (seconds)\n\nA flexicache policy that expires cached items after seconds have passed\n\nsource\n\n\nmtime_policy\n\n mtime_policy (filepath)\n\nA flexicache policy that expires cached items after filepath modified-time changes\n\n@flexicache(time_policy(10), mtime_policy('000_tour.ipynb'))\ndef cached_func(x, y): return x+y\n\ncached_func(1,2)\n\n3\n\n\n\n@flexicache(time_policy(10), mtime_policy('000_tour.ipynb'))\nasync def cached_func(x, y): return x+y\n\nawait cached_func(1,2)\nawait cached_func(1,2)\n\n3\n\n\n\nsource\n\n\ntimed_cache\n\n timed_cache (seconds=60, maxsize=128)\n\nLike lru_cache, but also with time-based eviction\nThis function is a small convenience wrapper for using flexicache with time_policy.\n\n@timed_cache(seconds=0.05, maxsize=2)\ndef cached_func(x): return x * 2, time()\n\n# basic caching\nresult1, time1 = cached_func(2)\ntest_eq(result1, 4)\nsleep(0.001)\nresult2, time2 = cached_func(2)\ntest_eq(result2, 4)\ntest_eq(time1, time2)\n\n# caching different values\nresult3, _ = cached_func(3)\ntest_eq(result3, 6)\n\n# maxsize\n_, time4 = cached_func(4)\n_, time2_new = cached_func(2)\ntest_close(time2, time2_new, eps=0.1)\n_, time3_new = cached_func(3)\ntest_ne(time3_new, time())\n\n# time expiration\nsleep(0.05)\n_, time4_new = cached_func(4)\ntest_ne(time4_new, time())", + "crumbs": [ + "Utility functions" + ] + }, + { + "objectID": "foundation.html", + "href": "foundation.html", + "title": "Foundation", + "section": "", + "text": "source\n\n\n\n working_directory (path)\n\nChange working directory to path and return to previous on exit.\n\nsource\n\n\n\n\n add_docs (cls, cls_doc=None, **docs)\n\nCopy values from docs to cls docstrings, and confirm all public methods are documented\nadd_docs allows you to add docstrings to a class and its associated methods. This function allows you to group docstrings together seperate from your code, which enables you to define one-line functions as well as organize your code more succintly. We believe this confers a number of benefits which we discuss in our style guide.\nSuppose you have the following undocumented class:\n\nclass T:\n def foo(self): pass\n def bar(self): pass\n\nYou can add documentation to this class like so:\n\nadd_docs(T, cls_doc=\"A docstring for the class.\",\n foo=\"The foo method.\",\n bar=\"The bar method.\")\n\nNow, docstrings will appear as expected:\n\ntest_eq(T.__doc__, \"A docstring for the class.\")\ntest_eq(T.foo.__doc__, \"The foo method.\")\ntest_eq(T.bar.__doc__, \"The bar method.\")\n\nadd_docs also validates that all of your public methods contain a docstring. If one of your methods is not documented, it will raise an error:\n\nclass T:\n def foo(self): pass\n def bar(self): pass\n\nf=lambda: add_docs(T, \"A docstring for the class.\", foo=\"The foo method.\")\ntest_fail(f, contains=\"Missing docs\")\n\n\nsource\n\n\n\n\n docs (cls)\n\nDecorator version of add_docs, using _docs dict\nInstead of using add_docs, you can use the decorator docs as shown below. Note that the docstring for the class can be set with the argument cls_doc:\n\n@docs\nclass _T:\n def f(self): pass\n def g(cls): pass\n \n _docs = dict(cls_doc=\"The class docstring\", \n f=\"The docstring for method f.\",\n g=\"A different docstring for method g.\")\n\n \ntest_eq(_T.__doc__, \"The class docstring\")\ntest_eq(_T.f.__doc__, \"The docstring for method f.\")\ntest_eq(_T.g.__doc__, \"A different docstring for method g.\")\n\nFor either the docs decorator or the add_docs function, you can still define your docstrings in the normal way. Below we set the docstring for the class as usual, but define the method docstrings through the _docs attribute:\n\n@docs\nclass _T:\n \"The class docstring\"\n def f(self): pass\n _docs = dict(f=\"The docstring for method f.\")\n\n \ntest_eq(_T.__doc__, \"The class docstring\")\ntest_eq(_T.f.__doc__, \"The docstring for method f.\")\n\n\n\n\n\n\n is_iter (o)\n\nTest whether o can be used in a for loop\n\nassert is_iter([1])\nassert not is_iter(array(1))\nassert is_iter(array([1,2]))\nassert (o for o in range(3))\n\n\nsource\n\n\n\n\n coll_repr (c, max_n=10)\n\nString repr of up to max_n items of (possibly lazy) collection c\ncoll_repr is used to provide a more informative __repr__ about list-like objects. coll_repr and is used by L to build a __repr__ that displays the length of a list in addition to a preview of a list.\nBelow is an example of the __repr__ string created for a list of 1000 elements:\n\ntest_eq(coll_repr(range(1000)), '(#1000) [0,1,2,3,4,5,6,7,8,9...]')\ntest_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]')\ntest_eq(coll_repr(range(10), 5), '(#10) [0,1,2,3,4...]')\ntest_eq(coll_repr(range(5), 5), '(#5) [0,1,2,3,4]')\n\nWe can set the option max_n to optionally preview a specified number of items instead of the default:\n\ntest_eq(coll_repr(range(1000), max_n=5), '(#1000) [0,1,2,3,4...]')\n\n\nsource\n\n\n\n\n is_bool (x)\n\nCheck whether x is a bool or None\n\nsource\n\n\n\n\n mask2idxs (mask)\n\nConvert bool mask or index list to index L\n\ntest_eq(mask2idxs([False,True,False,True]), [1,3])\ntest_eq(mask2idxs(array([False,True,False,True])), [1,3])\ntest_eq(mask2idxs(array([1,2,3])), [1,2,3])\n\n\nsource\n\n\n\n\n cycle (o)\n\nLike itertools.cycle except creates list of Nones if o is empty\n\ntest_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2])\ntest_eq(itertools.islice(cycle([]),3), [None]*3)\ntest_eq(itertools.islice(cycle(None),3), [None]*3)\ntest_eq(itertools.islice(cycle(1),3), [1,1,1])\n\n\nsource\n\n\n\n\n zip_cycle (x, *args)\n\nLike itertools.zip_longest but cycles through elements of all but first argument\n\ntest_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])\n\n\nsource\n\n\n\n\n is_indexer (idx)\n\nTest whether idx will index a single item in a list\nYou can, for example index a single item in a list with an integer or a 0-dimensional numpy array:\n\nassert is_indexer(1)\nassert is_indexer(np.array(1))\n\nHowever, you cannot index into single item in a list with another list or a numpy array with ndim > 0.\n\nassert not is_indexer([1, 2])\nassert not is_indexer(np.array([[1, 2], [3, 4]]))", + "crumbs": [ + "Foundation" + ] + }, + { + "objectID": "foundation.html#foundational-functions", + "href": "foundation.html#foundational-functions", + "title": "Foundation", + "section": "", + "text": "source\n\n\n\n working_directory (path)\n\nChange working directory to path and return to previous on exit.\n\nsource\n\n\n\n\n add_docs (cls, cls_doc=None, **docs)\n\nCopy values from docs to cls docstrings, and confirm all public methods are documented\nadd_docs allows you to add docstrings to a class and its associated methods. This function allows you to group docstrings together seperate from your code, which enables you to define one-line functions as well as organize your code more succintly. We believe this confers a number of benefits which we discuss in our style guide.\nSuppose you have the following undocumented class:\n\nclass T:\n def foo(self): pass\n def bar(self): pass\n\nYou can add documentation to this class like so:\n\nadd_docs(T, cls_doc=\"A docstring for the class.\",\n foo=\"The foo method.\",\n bar=\"The bar method.\")\n\nNow, docstrings will appear as expected:\n\ntest_eq(T.__doc__, \"A docstring for the class.\")\ntest_eq(T.foo.__doc__, \"The foo method.\")\ntest_eq(T.bar.__doc__, \"The bar method.\")\n\nadd_docs also validates that all of your public methods contain a docstring. If one of your methods is not documented, it will raise an error:\n\nclass T:\n def foo(self): pass\n def bar(self): pass\n\nf=lambda: add_docs(T, \"A docstring for the class.\", foo=\"The foo method.\")\ntest_fail(f, contains=\"Missing docs\")\n\n\nsource\n\n\n\n\n docs (cls)\n\nDecorator version of add_docs, using _docs dict\nInstead of using add_docs, you can use the decorator docs as shown below. Note that the docstring for the class can be set with the argument cls_doc:\n\n@docs\nclass _T:\n def f(self): pass\n def g(cls): pass\n \n _docs = dict(cls_doc=\"The class docstring\", \n f=\"The docstring for method f.\",\n g=\"A different docstring for method g.\")\n\n \ntest_eq(_T.__doc__, \"The class docstring\")\ntest_eq(_T.f.__doc__, \"The docstring for method f.\")\ntest_eq(_T.g.__doc__, \"A different docstring for method g.\")\n\nFor either the docs decorator or the add_docs function, you can still define your docstrings in the normal way. Below we set the docstring for the class as usual, but define the method docstrings through the _docs attribute:\n\n@docs\nclass _T:\n \"The class docstring\"\n def f(self): pass\n _docs = dict(f=\"The docstring for method f.\")\n\n \ntest_eq(_T.__doc__, \"The class docstring\")\ntest_eq(_T.f.__doc__, \"The docstring for method f.\")\n\n\n\n\n\n\n is_iter (o)\n\nTest whether o can be used in a for loop\n\nassert is_iter([1])\nassert not is_iter(array(1))\nassert is_iter(array([1,2]))\nassert (o for o in range(3))\n\n\nsource\n\n\n\n\n coll_repr (c, max_n=10)\n\nString repr of up to max_n items of (possibly lazy) collection c\ncoll_repr is used to provide a more informative __repr__ about list-like objects. coll_repr and is used by L to build a __repr__ that displays the length of a list in addition to a preview of a list.\nBelow is an example of the __repr__ string created for a list of 1000 elements:\n\ntest_eq(coll_repr(range(1000)), '(#1000) [0,1,2,3,4,5,6,7,8,9...]')\ntest_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]')\ntest_eq(coll_repr(range(10), 5), '(#10) [0,1,2,3,4...]')\ntest_eq(coll_repr(range(5), 5), '(#5) [0,1,2,3,4]')\n\nWe can set the option max_n to optionally preview a specified number of items instead of the default:\n\ntest_eq(coll_repr(range(1000), max_n=5), '(#1000) [0,1,2,3,4...]')\n\n\nsource\n\n\n\n\n is_bool (x)\n\nCheck whether x is a bool or None\n\nsource\n\n\n\n\n mask2idxs (mask)\n\nConvert bool mask or index list to index L\n\ntest_eq(mask2idxs([False,True,False,True]), [1,3])\ntest_eq(mask2idxs(array([False,True,False,True])), [1,3])\ntest_eq(mask2idxs(array([1,2,3])), [1,2,3])\n\n\nsource\n\n\n\n\n cycle (o)\n\nLike itertools.cycle except creates list of Nones if o is empty\n\ntest_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2])\ntest_eq(itertools.islice(cycle([]),3), [None]*3)\ntest_eq(itertools.islice(cycle(None),3), [None]*3)\ntest_eq(itertools.islice(cycle(1),3), [1,1,1])\n\n\nsource\n\n\n\n\n zip_cycle (x, *args)\n\nLike itertools.zip_longest but cycles through elements of all but first argument\n\ntest_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])\n\n\nsource\n\n\n\n\n is_indexer (idx)\n\nTest whether idx will index a single item in a list\nYou can, for example index a single item in a list with an integer or a 0-dimensional numpy array:\n\nassert is_indexer(1)\nassert is_indexer(np.array(1))\n\nHowever, you cannot index into single item in a list with another list or a numpy array with ndim > 0.\n\nassert not is_indexer([1, 2])\nassert not is_indexer(np.array([[1, 2], [3, 4]]))", + "crumbs": [ + "Foundation" + ] + }, + { + "objectID": "foundation.html#l-helpers", + "href": "foundation.html#l-helpers", + "title": "Foundation", + "section": "L helpers", + "text": "L helpers\n\nsource\n\nCollBase\n\n CollBase (items)\n\nBase class for composing a list of items\nColBase is a base class that emulates the functionality of a python list:\n\nclass _T(CollBase): pass\nl = _T([1,2,3,4,5])\n\ntest_eq(len(l), 5) # __len__\ntest_eq(l[-1], 5); test_eq(l[0], 1) #__getitem__\nl[2] = 100; test_eq(l[2], 100) # __set_item__\ndel l[0]; test_eq(len(l), 4) # __delitem__\ntest_eq(str(l), '[2, 100, 4, 5]') # __repr__\n\n\nsource\n\n\nL\n\n L (x=None, *args, **kwargs)\n\nBehaves like a list of items but can also index with list of indices or masks\nL is a drop in replacement for a python list. Inspired by NumPy, L, supports advanced indexing and has additional methods (outlined below) that provide additional functionality and encourage simple expressive code. For example, the code below takes a list of pairs, selects the second item of each pair, takes its absolute value, filters items greater than 4, and adds them up:\n\nfrom fastcore.utils import gt\n\n\nd = dict(a=1,b=-5,d=6,e=9).items()\ntest_eq(L(d).itemgot(1).map(abs).filter(gt(4)).sum(), 20) # abs(-5) + abs(6) + abs(9) = 20; 1 was filtered out.\n\nRead this overview section for a quick tutorial of L, as well as background on the name.\nYou can create an L from an existing iterable (e.g. a list, range, etc) and access or modify it with an int list/tuple index, mask, int, or slice. All list methods can also be used with L.\n\nt = L(range(12))\ntest_eq(t, list(range(12)))\ntest_ne(t, list(range(11)))\nt.reverse()\ntest_eq(t[0], 11)\nt[3] = \"h\"\ntest_eq(t[3], \"h\")\nt[3,5] = (\"j\",\"k\")\ntest_eq(t[3,5], [\"j\",\"k\"])\ntest_eq(t, L(t))\ntest_eq(L(L(1,2),[3,4]), ([1,2],[3,4]))\nt\n\n(#12) [11,10,9,'j',7,'k',5,4,3,2...]\n\n\nAny L is a Sequence so you can use it with methods like random.sample:\n\nassert isinstance(t, Sequence)\n\n\nimport random\n\n\nrandom.seed(0)\nrandom.sample(t, 3)\n\n[5, 0, 11]\n\n\nThere are optimized indexers for arrays, tensors, and DataFrames.\n\nimport pandas as pd\n\n\narr = np.arange(9).reshape(3,3)\nt = L(arr, use_list=None)\ntest_eq(t[1,2], arr[[1,2]])\n\ndf = pd.DataFrame({'a':[1,2,3]})\nt = L(df, use_list=None)\ntest_eq(t[1,2], L(pd.DataFrame({'a':[2,3]}, index=[1,2]), use_list=None))\n\nYou can also modify an L with append, +, and *.\n\nt = L()\ntest_eq(t, [])\nt.append(1)\ntest_eq(t, [1])\nt += [3,2]\ntest_eq(t, [1,3,2])\nt = t + [4]\ntest_eq(t, [1,3,2,4])\nt = 5 + t\ntest_eq(t, [5,1,3,2,4])\ntest_eq(L(1,2,3), [1,2,3])\ntest_eq(L(1,2,3), L(1,2,3))\nt = L(1)*5\nt = t.map(operator.neg)\ntest_eq(t,[-1]*5)\ntest_eq(~L([True,False,False]), L([False,True,True]))\nt = L(range(4))\ntest_eq(zip(t, L(1).cycle()), zip(range(4),(1,1,1,1)))\nt = L.range(100)\ntest_shuffled(t,t.shuffle())\n\n\ntest_eq(L([]).sum(), 0)\ntest_eq(L([]).product(), 1)\n\n\ndef _f(x,a=0): return x+a\nt = L(1)*5\ntest_eq(t.map(_f), t)\ntest_eq(t.map(_f,1), [2]*5)\ntest_eq(t.map(_f,a=2), [3]*5)\n\nAn L can be constructed from anything iterable, although tensors and arrays will not be iterated over on construction, unless you pass use_list to the constructor.\n\ntest_eq(L([1,2,3]),[1,2,3])\ntest_eq(L(L([1,2,3])),[1,2,3])\ntest_ne(L([1,2,3]),[1,2,])\ntest_eq(L('abc'),['abc'])\ntest_eq(L(range(0,3)),[0,1,2])\ntest_eq(L(o for o in range(0,3)),[0,1,2])\ntest_eq(L(array(0)),[array(0)])\ntest_eq(L([array(0),array(1)]),[array(0),array(1)])\ntest_eq(L(array([0.,1.1]))[0],array([0.,1.1]))\ntest_eq(L(array([0.,1.1]), use_list=True), [array(0.),array(1.1)]) # `use_list=True` to unwrap arrays/arrays\n\nIf match is not None then the created list is same len as match, either by:\n\nIf len(items)==1 then items is replicated,\nOtherwise an error is raised if match and items are not already the same size.\n\n\ntest_eq(L(1,match=[1,2,3]),[1,1,1])\ntest_eq(L([1,2],match=[2,3]),[1,2])\ntest_fail(lambda: L([1,2],match=[1,2,3]))\n\nIf you create an L from an existing L then you’ll get back the original object (since L uses the NewChkMeta metaclass).\n\ntest_is(L(t), t)\n\nAn L is considred equal to a list if they have the same elements. It’s never considered equal to a str a set or a dict even if they have the same elements/keys.\n\ntest_eq(L(['a', 'b']), ['a', 'b'])\ntest_ne(L(['a', 'b']), 'ab')\ntest_ne(L(['a', 'b']), {'a':1, 'b':2})\n\n\n\nL Methods\n\nsource\n\n\nL.__getitem__\n\n L.__getitem__ (idx)\n\nRetrieve idx (can be list of indices, or mask, or int) items\n\nt = L(range(12))\ntest_eq(t[1,2], [1,2]) # implicit tuple\ntest_eq(t[[1,2]], [1,2]) # list\ntest_eq(t[:3], [0,1,2]) # slice\ntest_eq(t[[False]*11 + [True]], [11]) # mask\ntest_eq(t[array(3)], 3)\n\n\nsource\n\n\nL.__setitem__\n\n L.__setitem__ (idx, o)\n\nSet idx (can be list of indices, or mask, or int) items to o (which is broadcast if not iterable)\n\nt[4,6] = 0\ntest_eq(t[4,6], [0,0])\nt[4,6] = [1,2]\ntest_eq(t[4,6], [1,2])\n\n\nsource\n\n\nL.unique\n\n L.unique (sort=False, bidir=False, start=None)\n\nUnique items, in stable order\n\ntest_eq(L(4,1,2,3,4,4).unique(), [4,1,2,3])\n\n\nsource\n\n\nL.val2idx\n\n L.val2idx ()\n\nDict from value to index\n\ntest_eq(L(1,2,3).val2idx(), {3:2,1:0,2:1})\n\n\nsource\n\n\nL.filter\n\n L.filter (f=<function noop>, negate=False, **kwargs)\n\nCreate new L filtered by predicate f, passing args and kwargs to f\n\nlist(t)\n\n[0, 1, 2, 3, 1, 5, 2, 7, 8, 9, 10, 11]\n\n\n\ntest_eq(t.filter(lambda o:o<5), [0,1,2,3,1,2])\ntest_eq(t.filter(lambda o:o<5, negate=True), [5,7,8,9,10,11])\n\n\nsource\n\n\nL.argwhere\n\n L.argwhere (f, negate=False, **kwargs)\n\nLike filter, but return indices for matching items\n\ntest_eq(t.argwhere(lambda o:o<5), [0,1,2,3,4,6])\n\n\nsource\n\n\nL.argfirst\n\n L.argfirst (f, negate=False)\n\nReturn index of first matching item\n\ntest_eq(t.argfirst(lambda o:o>4), 5)\ntest_eq(t.argfirst(lambda o:o>4,negate=True),0)\n\n\nsource\n\n\nL.map\n\n L.map (f, *args, **kwargs)\n\nCreate new L with f applied to all items, passing args and kwargs to f\n\ntest_eq(L.range(4).map(operator.neg), [0,-1,-2,-3])\n\nIf f is a string then it is treated as a format string to create the mapping:\n\ntest_eq(L.range(4).map('#{}#'), ['#0#','#1#','#2#','#3#'])\n\nIf f is a dictionary (or anything supporting __getitem__) then it is indexed to create the mapping:\n\ntest_eq(L.range(4).map(list('abcd')), list('abcd'))\n\nYou can also pass the same arg params that bind accepts:\n\ndef f(a=None,b=None): return b\ntest_eq(L.range(4).map(f, b=arg0), range(4))\n\n\nsource\n\n\nL.map_dict\n\n L.map_dict (f=<function noop>, *args, **kwargs)\n\nLike map, but creates a dict from items to function results\n\ntest_eq(L(range(1,5)).map_dict(), {1:1, 2:2, 3:3, 4:4})\ntest_eq(L(range(1,5)).map_dict(operator.neg), {1:-1, 2:-2, 3:-3, 4:-4})\n\n\nsource\n\n\nL.zip\n\n L.zip (cycled=False)\n\nCreate new L with zip(*items)\n\nt = L([[1,2,3],'abc'])\ntest_eq(t.zip(), [(1, 'a'),(2, 'b'),(3, 'c')])\n\n\nt = L([[1,2,3,4],['a','b','c']])\ntest_eq(t.zip(cycled=True ), [(1, 'a'),(2, 'b'),(3, 'c'),(4, 'a')])\ntest_eq(t.zip(cycled=False), [(1, 'a'),(2, 'b'),(3, 'c')])\n\n\nsource\n\n\nL.map_zip\n\n L.map_zip (f, *args, cycled=False, **kwargs)\n\nCombine zip and starmap\n\nt = L([1,2,3],[2,3,4])\ntest_eq(t.map_zip(operator.mul), [2,6,12])\n\n\nsource\n\n\nL.zipwith\n\n L.zipwith (*rest, cycled=False)\n\nCreate new L with self zip with each of *rest\n\nb = [[0],[1],[2,2]]\nt = L([1,2,3]).zipwith(b)\ntest_eq(t, [(1,[0]), (2,[1]), (3,[2,2])])\n\n\nsource\n\n\nL.map_zipwith\n\n L.map_zipwith (f, *rest, cycled=False, **kwargs)\n\nCombine zipwith and starmap\n\ntest_eq(L(1,2,3).map_zipwith(operator.mul, [2,3,4]), [2,6,12])\n\n\nsource\n\n\nL.itemgot\n\n L.itemgot (*idxs)\n\nCreate new L with item idx of all items\n\ntest_eq(t.itemgot(1), b)\n\n\nsource\n\n\nL.attrgot\n\n L.attrgot (k, default=None)\n\nCreate new L with attr k (or value k for dicts) of all items.\n\n# Example when items are not a dict\na = [SimpleNamespace(a=3,b=4),SimpleNamespace(a=1,b=2)]\ntest_eq(L(a).attrgot('b'), [4,2])\n\n#Example of when items are a dict\nb =[{'id': 15, 'name': 'nbdev'}, {'id': 17, 'name': 'fastcore'}]\ntest_eq(L(b).attrgot('id'), [15, 17])\n\n\nsource\n\n\nL.sorted\n\n L.sorted (key=None, reverse=False)\n\nNew L sorted by key. If key is str use attrgetter; if int use itemgetter\n\ntest_eq(L(a).sorted('a').attrgot('b'), [2,4])\n\n\nsource\n\n\nL.split\n\n L.split (s, sep=None, maxsplit=-1)\n\nClass Method: Same as str.split, but returns an L\n\ntest_eq(L.split('a b c'), list('abc'))\n\n\nsource\n\n\nL.range\n\n L.range (a, b=None, step=None)\n\nClass Method: Same as range, but returns L. Can pass collection for a, to use len(a)\n\ntest_eq_type(L.range([1,1,1]), L(range(3)))\ntest_eq_type(L.range(5,2,2), L(range(5,2,2)))\n\n\nsource\n\n\nL.concat\n\n L.concat ()\n\nConcatenate all elements of list\n\ntest_eq(L([0,1,2,3],4,L(5,6)).concat(), range(7))\n\n\nsource\n\n\nL.copy\n\n L.copy ()\n\nSame as list.copy, but returns an L\n\nt = L([0,1,2,3],4,L(5,6)).copy()\ntest_eq(t.concat(), range(7))\n\n\nsource\n\n\nL.map_first\n\n L.map_first (f=<function noop>, g=<function noop>, *args, **kwargs)\n\nFirst element of map_filter\n\nt = L(0,1,2,3)\ntest_eq(t.map_first(lambda o:o*2 if o>2 else None), 6)\n\n\nsource\n\n\nL.setattrs\n\n L.setattrs (attr, val)\n\nCall setattr on all items\n\nt = L(SimpleNamespace(),SimpleNamespace())\nt.setattrs('foo', 'bar')\ntest_eq(t.attrgot('foo'), ['bar','bar'])", + "crumbs": [ + "Foundation" + ] + }, + { + "objectID": "foundation.html#config", + "href": "foundation.html#config", + "title": "Foundation", + "section": "Config", + "text": "Config\n\nsource\n\nsave_config_file\n\n save_config_file (file, d, **kwargs)\n\nWrite settings dict to a new config file, or overwrite the existing one.\n\nsource\n\n\nread_config_file\n\n read_config_file (file, **kwargs)\n\nConfig files are saved and read using Python’s configparser.ConfigParser, inside the DEFAULT section.\n\n_d = dict(user='fastai', lib_name='fastcore', some_path='test', some_bool=True, some_num=3)\ntry:\n save_config_file('tmp.ini', _d)\n res = read_config_file('tmp.ini')\nfinally: os.unlink('tmp.ini')\ndict(res)\n\n{'user': 'fastai',\n 'lib_name': 'fastcore',\n 'some_path': 'test',\n 'some_bool': 'True',\n 'some_num': '3'}\n\n\n\nsource\n\n\nConfig\n\n Config (cfg_path, cfg_name, create=None, save=True, extra_files=None,\n types=None)\n\nReading and writing ConfigParser ini files\nConfig is a convenient wrapper around ConfigParser ini files with a single section (DEFAULT).\nInstantiate a Config from an ini file at cfg_path/cfg_name:\n\nsave_config_file('../tmp.ini', _d)\ntry: cfg = Config('..', 'tmp.ini')\nfinally: os.unlink('../tmp.ini')\ncfg\n\n{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'}\n\n\nYou can create a new file if one doesn’t exist by providing a create dict:\n\ntry: cfg = Config('..', 'tmp.ini', create=_d)\nfinally: os.unlink('../tmp.ini')\ncfg\n\n{'user': 'fastai', 'lib_name': 'fastcore', 'some_path': 'test', 'some_bool': 'True', 'some_num': '3'}\n\n\nIf you additionally pass save=False, the Config will contain the items from create without writing a new file:\n\ncfg = Config('..', 'tmp.ini', create=_d, save=False)\ntest_eq(cfg.user,'fastai')\nassert not Path('../tmp.ini').exists()\n\n\nsource\n\n\nConfig.get\n\n Config.get (k, default=None)\n\nKeys can be accessed as attributes, items, or with get and an optional default:\n\ntest_eq(cfg.user,'fastai')\ntest_eq(cfg['some_path'], 'test')\ntest_eq(cfg.get('foo','bar'),'bar')\n\nExtra files can be read before cfg_path/cfg_name using extra_files, in the order they appear:\n\nwith tempfile.TemporaryDirectory() as d:\n a = Config(d, 'a.ini', {'a':0,'b':0})\n b = Config(d, 'b.ini', {'a':1,'c':0})\n c = Config(d, 'c.ini', {'a':2,'d':0}, extra_files=[a.config_file,b.config_file])\n test_eq(c.d, {'a':'2','b':'0','c':'0','d':'0'})\n\nIf you pass a dict types, then the values of that dict will be used as types to instantiate all values returned. Path is a special case – in that case, the path returned will be relative to the path containing the config file (assuming the value is relative). bool types use str2bool to convert to boolean.\n\n_types = dict(some_path=Path, some_bool=bool, some_num=int)\ncfg = Config('..', 'tmp.ini', create=_d, save=False, types=_types)\n\ntest_eq(cfg.user,'fastai')\ntest_eq(cfg['some_path'].resolve(), (Path('..')/'test').resolve())\ntest_eq(cfg.get('some_num'), 3)\n\n\nsource\n\n\nConfig.find\n\n Config.find (cfg_name, cfg_path=None, **kwargs)\n\nSearch cfg_path and its parents to find cfg_name\nYou can use Config.find to search subdirectories for a config file, starting in the current path if no path is specified:\n\nConfig.find('settings.ini').repo\n\n'fastcore'", + "crumbs": [ + "Foundation" + ] + }, + { + "objectID": "tour.html", + "href": "tour.html", + "title": "A tour of fastcore", + "section": "", + "text": "Here’s a (somewhat) quick tour of a few higlights from fastcore.\n\nDocumentation\nAll fast.ai projects, including this one, are built with nbdev, which is a full literate programming environment built on Jupyter Notebooks. That means that every piece of documentation, including the page you’re reading now, can be accessed as interactive Jupyter notebooks. In fact, you can even grab a link directly to a notebook running interactively on Google Colab - if you want to follow along with this tour, click the link below:\n\ncolab_link('index')\n\nOpen index in Colab\n\n\nThe full docs are available at fastcore.fast.ai. The code in the examples and in all fast.ai libraries follow the fast.ai style guide. In order to support interactive programming, all fast.ai libraries are designed to allow for import * to be used safely, particular by ensuring that __all__ is defined in all packages. In order to see where a function is from, just type it:\n\ncoll_repr\n\n<function fastcore.foundation.coll_repr(c, max_n=10)>\n\n\nFor more details, including a link to the full documentation and source code, use doc, which pops up a window with this information:\ndoc(coll_repr)\n\nThe documentation also contains links to any related functions or classes, which appear like this: coll_repr (in the notebook itself you will just see a word with back-ticks around it; the links are auto-generated in the documentation site). The documentation will generally show one or more examples of use, along with any background context necessary to understand them. As you’ll see, the examples for each function and method are shown as tests, rather than example outputs, so let’s start by explaining that.\n\n\nTesting\nfastcore’s testing module is designed to work well with nbdev, which is a full literate programming environment built on Jupyter Notebooks. That means that your tests, docs, and code all live together in the same notebook. fastcore and nbdev’s approach to testing starts with the premise that all your tests should pass. If one fails, no more tests in a notebook are run.\nTests look like this:\n\ntest_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]')\n\nThat’s an example from the docs for coll_repr. As you see, it’s not showing you the output directly. Here’s what that would look like:\n\ncoll_repr(range(1000), 5)\n\n'(#1000) [0,1,2,3,4...]'\n\n\nSo, the test is actually showing you what the output looks like, because if the function call didn’t return '(#1000) [0,1,2,3,4...]', then the test would have failed.\nSo every test shown in the docs is also showing you the behavior of the library — and vice versa!\nTest functions always start with test_, and then follow with the operation being tested. So test_eq tests for equality (as you saw in the example above). This includes tests for equality of arrays and tensors, lists and generators, and many more:\n\ntest_eq([0,1,2,3], np.arange(4))\n\nWhen a test fails, it prints out information about what was expected:\ntest_eq([0,1,2,3], np.arange(3))\n----\n AssertionError: ==:\n [0, 1, 2, 3]\n [0 1 2]\nIf you want to check that objects are the same type, rather than the just contain the same collection, use test_eq_type.\nYou can test with any comparison function using test, e.g test whether an object is less than:\n\ntest(2, 3, operator.lt)\n\nYou can even test that exceptions are raised:\n\ndef divide_zero(): return 1/0\ntest_fail(divide_zero)\n\n…and test that things are printed to stdout:\n\ntest_stdout(lambda: print('hi'), 'hi')\n\n\n\nFoundations\nfast.ai is unusual in that we often use mixins in our code. Mixins are widely used in many programming languages, such as Ruby, but not so much in Python. We use mixins to attach new behavior to existing libraries, or to allow modules to add new behavior to our own classes, such as in extension modules. One useful example of a mixin we define is Path.ls, which lists a directory and returns an L (an extended list class which we’ll discuss shortly):\n\np = Path('images')\np.ls()\n\n(#6) [Path('images/mnist3.png'),Path('images/att_00000.png'),Path('images/puppy.jpg'),Path('images/att_00005.png'),Path('images/att_00007.png'),Path('images/att_00006.png')]\n\n\nYou can easily add you own mixins with the patch decorator, which takes advantage of Python 3 function annotations to say what class to patch:\n\n@patch\ndef num_items(self:Path): return len(self.ls())\n\np.num_items()\n\n6\n\n\nWe also use **kwargs frequently. In python **kwargs in a parameter like means “put any additional keyword arguments into a dict called kwargs”. Normally, using kwargs makes an API quite difficult to work with, because it breaks things like tab-completion and popup lists of signatures. utils provides use_kwargs and delegates to avoid this problem. See our detailed article on delegation on this topic.\nGetAttr solves a similar problem (and is also discussed in the article linked above): it’s allows you to use Python’s exceptionally useful __getattr__ magic method, but avoids the problem that normally in Python tab-completion and docs break when using this. For instance, you can see here that Python’s dir function, which is used to find the attributes of a python object, finds everything inside the self.default attribute here:\n\nclass Author:\n def __init__(self, name): self.name = name\n\nclass ProductPage(GetAttr):\n _default = 'author'\n def __init__(self,author,price,cost): self.author,self.price,self.cost = author,price,cost\n\np = ProductPage(Author(\"Jeremy\"), 1.50, 0.50)\n[o for o in dir(p) if not o.startswith('_')]\n\n['author', 'cost', 'name', 'price']\n\n\nLooking at that ProductPage example, it’s rather verbose and duplicates a lot of attribute names, which can lead to bugs later if you change them only in one place. fastcore provides store_attr to simplify this common pattern. It also provides basic_repr to give simple objects a useful repr:\n\nclass ProductPage:\n def __init__(self,author,price,cost): store_attr()\n __repr__ = basic_repr('author,price,cost')\n\nProductPage(\"Jeremy\", 1.50, 0.50)\n\n__main__.ProductPage(author='Jeremy', price=1.5, cost=0.5)\n\n\nOne of the most interesting fastcore functions is the funcs_kwargs decorator. This allows class behavior to be modified without sub-classing. This can allow folks that aren’t familiar with object-oriented programming to customize your class more easily. Here’s an example of a class that uses funcs_kwargs:\n\n@funcs_kwargs\nclass T:\n _methods=['some_method']\n def __init__(self, **kwargs): assert not kwargs, f'Passed unknown args: {kwargs}'\n\np = T(some_method = print)\np.some_method(\"hello\")\n\nhello\n\n\nThe assert not kwargs above is used to ensure that the user doesn’t pass an unknown parameter (i.e one that’s not in _methods). fastai uses funcs_kwargs in many places, for instance, you can customize any part of a DataLoader by passing your own methods.\nfastcore also provides many utility functions that make a Python programmer’s life easier, in fastcore.utils. We won’t look at many here, since you can easily look at the docs yourself. To get you started, have a look at the docs for chunked (remember, if you’re in a notebook, type doc(chunked)), which is a handy function for creating lazily generated batches from a collection.\nPython’s ProcessPoolExecutor is extended to allow max_workers to be set to 0, to easily turn off parallel processing. This makes it easy to debug your code in serial, then run it in parallel. It also allows you to pass arguments to your parallel function, and to ensure there’s a pause between calls, in case the process you are running has race conditions. parallel makes parallel processing even easier to use, and even adds an optional progress bar.\n\n\nL\nLike most languages, Python allows for very concise syntax for some very common types, such as list, which can be constructed with [1,2,3]. Perl’s designer Larry Wall explained the reasoning for this kind of syntax:\n\nIn metaphorical honor of Huffman’s compression code that assigns smaller numbers of bits to more common bytes. In terms of syntax, it simply means that commonly used things should be shorter, but you shouldn’t waste short sequences on less common constructs.\n\nOn this basis, fastcore has just one type that has a single letter name: L. The reason for this is that it is designed to be a replacement for list, so we want it to be just as easy to use as [1,2,3]. Here’s how to create that as an L:\n\nL(1,2,3)\n\n(#3) [1,2,3]\n\n\nThe first thing to notice is that an L object includes in its representation its number of elements; that’s the (#3) in the output above. If there’s more than 10 elements, it will automatically truncate the list:\n\np = L.range(20).shuffle()\np\n\n(#20) [5,1,9,10,18,13,6,17,3,16...]\n\n\nL contains many of the same indexing ideas that NumPy’s array does, including indexing with a list of indexes, or a boolean mask list:\n\np[2,4,6]\n\n(#3) [9,18,6]\n\n\nIt also contains other methods used in array, such as L.argwhere:\n\np.argwhere(ge(15))\n\n(#5) [4,7,9,18,19]\n\n\nAs you can see from this example, fastcore also includes a number of features that make a functional style of programming easier, such as a full range of boolean functions (e.g ge, gt, etc) which give the same answer as the functions from Python’s operator module if given two parameters, but return a curried function if given one parameter.\nThere’s too much functionality to show it all here, so be sure to check the docs. Many little things are added that we thought should have been in list in the first place, such as making this do what you’d expect (which is an error with list, but works fine with L):\n\n1 + L(2,3,4)\n\n(#4) [1,2,3,4]\n\n\n\n\nTransforms\nA Transform is the main building block of the fastai data pipelines. In the most general terms a transform can be any function you want to apply to your data, however the Transform class provides several mechanisms that make the process of building them easy and flexible (see the docs for information about each of these):\n\nType dispatch\nDispatch over tuples\nReversability\nType propagation\nPreprocessing\nFiltering based on the dataset type\nOrdering\nAppending new behavior with decorators\n\nTransform looks for three special methods, encodes, decodes, and setups, which provide the implementation for __call__, decode, and setup respectively. For instance:\n\nclass A(Transform):\n def encodes(self, x): return x+1\n\nA()(1)\n\n2\n\n\nFor simple transforms like this, you can also use Transform as a decorator:\n\n@Transform\ndef f(x): return x+1\n\nf(1)\n\n2\n\n\nTransforms can be composed into a Pipeline:\n\n@Transform\ndef g(x): return x/2\n\npipe = Pipeline([f,g])\npipe(3)\n\n2.0\n\n\nThe power of Transform and Pipeline is best understood by seeing how they’re used to create a complete data processing pipeline. This is explained in chapter 11 of the fastai book, which is available for free in Jupyter Notebook format.", + "crumbs": [ + "A tour of fastcore" + ] + }, + { + "objectID": "net.html", + "href": "net.html", + "title": "Network functionality", + "section": "", + "text": "from fastcore.test import *\nfrom nbdev.showdoc import *\nfrom fastcore.nb_imports import *", + "crumbs": [ + "Network functionality" + ] + }, + { + "objectID": "net.html#urls", + "href": "net.html#urls", + "title": "Network functionality", + "section": "URLs", + "text": "URLs\n\nsource\n\nurlquote\n\n urlquote (url)\n\nUpdate url’s path with urllib.parse.quote\n\nurlquote(\"https://github.com/fastai/fastai/compare/master@{1.day.ago}…master\")\n\n'https://github.com/fastai/fastai/compare/master@%7B1.day.ago%7D%E2%80%A6master'\n\n\n\nurlquote(\"https://www.google.com/search?q=你好\")\n\n'https://www.google.com/search?q=%E4%BD%A0%E5%A5%BD'\n\n\n\nsource\n\n\nurlwrap\n\n urlwrap (url, data=None, headers=None)\n\nWrap url in a urllib Request with urlquote\n\nsource\n\nHTTP4xxClientError\n\n HTTP4xxClientError (url, code, msg, hdrs, fp)\n\nBase class for client exceptions (code 4xx) from url* functions\n\nsource\n\n\nHTTP5xxServerError\n\n HTTP5xxServerError (url, code, msg, hdrs, fp)\n\nBase class for server exceptions (code 5xx) from url* functions\n\nsource\n\n\n\nurlopener\n\n urlopener ()\n\n\nsource\n\n\nurlopen\n\n urlopen (url, data=None, headers=None, timeout=None, **kwargs)\n\nLike urllib.request.urlopen, but first urlwrap the url, and encode data\nWith urlopen, the body of the response will also be returned in addition to the message if there is an error:\n\ntry: urlopen('https://api.github.com/v3')\nexcept HTTPError as e: \n print(e.code, e.msg)\n assert 'documentation_url' in e.msg\n\n404 Not Found\n====Error Body====\n{\n \"message\": \"Not Found\",\n \"documentation_url\": \"https://docs.github.com/rest\"\n}\n\n\n\n\nsource\n\n\nurlread\n\n urlread (url, data=None, headers=None, decode=True, return_json=False,\n return_headers=False, timeout=None, **kwargs)\n\nRetrieve url, using data dict or kwargs to POST if present\n\nsource\n\n\nurljson\n\n urljson (url, data=None, timeout=None)\n\nRetrieve url and decode json\n\ntest_eq(urljson('https://httpbin.org/get')['headers']['User-Agent'], url_default_headers['User-Agent'])\n\n\nsource\n\n\nurlcheck\n\n urlcheck (url, headers=None, timeout=10)\n\n\nsource\n\n\nurlclean\n\n urlclean (url)\n\nRemove fragment, params, and querystring from url if present\n\ntest_eq(urlclean('http://a.com/b?c=1#d'), 'http://a.com/b')\n\n\nsource\n\n\nurlretrieve\n\n urlretrieve (url, filename=None, reporthook=None, data=None,\n headers=None, timeout=None)\n\nSame as urllib.request.urlretrieve but also works with Request objects\n\nsource\n\n\nurldest\n\n urldest (url, dest=None)\n\n\nsource\n\n\nurlsave\n\n urlsave (url, dest=None, reporthook=None, headers=None, timeout=None)\n\nRetrieve url and save based on its name\n\n#skip\nwith tempfile.TemporaryDirectory() as d: urlsave('http://www.google.com/index.html', d)\n\n\nsource\n\n\nurlvalid\n\n urlvalid (x)\n\nTest if x is a valid URL\n\nassert urlvalid('http://www.google.com/')\nassert not urlvalid('www.google.com/')\nassert not urlvalid(1)\n\n\nsource\n\n\nurlrequest\n\n urlrequest (url, verb, headers=None, route=None, query=None, data=None,\n json_data=True)\n\nRequest for url with optional route params replaced by route, plus query string, and post data\n\nhdr = {'Hdr1':'1', 'Hdr2':'2'}\nreq = urlrequest('http://example.com/{foo}/1', 'POST',\n headers=hdr, route={'foo':'3'}, query={'q':'4'}, data={'d':'5'})\n\ntest_eq(req.headers, hdr)\ntest_eq(req.full_url, 'http://example.com/3/1?q=4')\ntest_eq(req.method, 'POST')\ntest_eq(req.data, b'{\"d\": \"5\"}')\n\n\nreq = urlrequest('http://example.com/{foo}/1', 'POST', data={'d':'5','e':'6'}, headers=hdr, json_data=False)\ntest_eq(req.data, b'd=5&e=6')\n\n\nsource\n\n\nRequest.summary\n\n Request.summary (skip=None)\n\nSummary containing full_url, headers, method, and data, removing skip from headers\n\nreq.summary(skip='Hdr1')\n\n{'full_url': 'http://example.com/{foo}/1',\n 'method': 'POST',\n 'data': b'd=5&e=6',\n 'headers': {'Hdr2': '2'}}\n\n\n\nsource\n\n\nurlsend\n\n urlsend (url, verb, headers=None, decode=True, route=None, query=None,\n data=None, json_data=True, return_json=True,\n return_headers=False, debug=None, timeout=None)\n\nSend request with urlrequest, converting result to json if return_json\n\nsource\n\n\ndo_request\n\n do_request (url, post=False, headers=None, **data)\n\nCall GET or json-encoded POST on url, depending on post", + "crumbs": [ + "Network functionality" + ] + }, + { + "objectID": "net.html#basic-clientserver", + "href": "net.html#basic-clientserver", + "title": "Network functionality", + "section": "Basic client/server", + "text": "Basic client/server\n\nsource\n\nstart_server\n\n start_server (port, host=None, dgram=False, reuse_addr=True,\n n_queue=None)\n\nCreate a socket server on port, with optional host, of type dgram\nYou can create a TCP client and server pass an int as port and optional host. host defaults to your main network interface if not provided. You can create a Unix socket client and server by passing a string to port. A SOCK_STREAM socket is created by default, unless you pass dgram=True, in which case a SOCK_DGRAM socket is created. n_queue sets the listening queue size.\n\nsource\n\n\nstart_client\n\n start_client (port, host=None, dgram=False)\n\nCreate a socket client on port, with optional host, of type dgram\n\nsource\n\n\ntobytes\n\n tobytes (s:str)\n\nConvert s into HTTP-ready bytes format\n\ntest_eq(tobytes('foo\\nbar'), b'foo\\r\\nbar')\n\n\nsource\n\n\nhttp_response\n\n http_response (body=None, status=200, hdrs=None, **kwargs)\n\nCreate an HTTP-ready response, adding kwargs to hdrs\n\nexp = b'HTTP/1.1 200 OK\\r\\nUser-Agent: me\\r\\nContent-Length: 4\\r\\n\\r\\nbody'\ntest_eq(http_response('body', 200, User_Agent='me'), exp)\n\n\nsource\n\n\nrecv_once\n\n recv_once (host:str='localhost', port:int=8000)\n\nSpawn a thread to receive a single HTTP request and store in d['r']", + "crumbs": [ + "Network functionality" + ] + }, + { + "objectID": "index.html", + "href": "index.html", + "title": "Welcome to fastcore", + "section": "", + "text": "Python is a powerful, dynamic language. Rather than bake everything into the language, it lets the programmer customize it to make it work for them. fastcore uses this flexibility to add to Python features inspired by other languages we’ve loved, like multiple dispatch from Julia, mixins from Ruby, and currying, binding, and more from Haskell. It also adds some “missing features” and clean up some rough edges in the Python standard library, such as simplifying parallel processing, and bringing ideas from NumPy over to Python’s list type.", + "crumbs": [ + "Welcome to fastcore" + ] + }, + { + "objectID": "index.html#getting-started", + "href": "index.html#getting-started", + "title": "Welcome to fastcore", + "section": "Getting started", + "text": "Getting started\nTo install fastcore run: conda install fastcore -c fastai (if you use Anaconda, which we recommend) or pip install fastcore. For an editable install, clone this repo and run: pip install -e \".[dev]\". fastcore is tested to work on Ubuntu, macOS and Windows (versions tested are those shown with the -latest suffix here).\nfastcore contains many features, including:\n\nfastcore.test: Simple testing functions\nfastcore.foundation: Mixins, delegation, composition, and more\nfastcore.xtras: Utility functions to help with functional-style programming, parallel processing, and more\nfastcore.dispatch: Multiple dispatch methods\nfastcore.transform: Pipelines of composed partially reversible transformations\n\nTo get started, we recommend you read through the fastcore tour.", + "crumbs": [ + "Welcome to fastcore" + ] + }, + { + "objectID": "index.html#contributing", + "href": "index.html#contributing", + "title": "Welcome to fastcore", + "section": "Contributing", + "text": "Contributing\nAfter you clone this repository, please run nbdev_install_hooks in your terminal. This sets up git hooks, which clean up the notebooks to remove the extraneous stuff stored in the notebooks (e.g. which cells you ran) which causes unnecessary merge conflicts.\nTo run the tests in parallel, launch nbdev_test.\nBefore submitting a PR, check that the local library and notebooks match.\n\nIf you made a change to the notebooks in one of the exported cells, you can export it to the library with nbdev_prepare.\nIf you made a change to the library, you can export it back to the notebooks with nbdev_update.", + "crumbs": [ + "Welcome to fastcore" + ] + }, + { + "objectID": "basics.html", + "href": "basics.html", + "title": "Basic functionality", + "section": "", + "text": "source\n\n\n\n ifnone (a, b)\n\nb if a is None else a\nSince b if a is None else a is such a common pattern, we wrap it in a function. However, be careful, because python will evaluate both a and b when calling ifnone (which it doesn’t do if using the if version directly).\n\ntest_eq(ifnone(None,1), 1)\ntest_eq(ifnone(2 ,1), 2)\n\n\nsource\n\n\n\n\n maybe_attr (o, attr)\n\ngetattr(o,attr,o)\nReturn the attribute attr for object o. If the attribute doesn’t exist, then return the object o instead.\n\nclass myobj: myattr='foo'\n\ntest_eq(maybe_attr(myobj, 'myattr'), 'foo')\ntest_eq(maybe_attr(myobj, 'another_attr'), myobj)\n\n\nsource\n\n\n\n\n basic_repr (flds=None)\n\nMinimal __repr__\nIn types which provide rich display functionality in Jupyter, their __repr__ is also called in order to provide a fallback text representation. Unfortunately, this includes a memory address which changes on every invocation, making it non-deterministic. This causes diffs to get messy and creates conflicts in git. To fix this, put __repr__=basic_repr() inside your class.\n\nclass SomeClass: __repr__=basic_repr()\nrepr(SomeClass())\n\n'SomeClass()'\n\n\nIf you pass a list of attributes (flds) of an object, then this will generate a string with the name of each attribute and its corresponding value. The format of this string is key=value, where key is the name of the attribute, and value is the value of the attribute. For each value, attempt to use the __name__ attribute, otherwise fall back to using the value’s __repr__ when constructing the string.\n\nclass SomeClass:\n a=1\n b='foo'\n __repr__=basic_repr('a,b')\n __name__='some-class'\n\nrepr(SomeClass())\n\n\"SomeClass(a=1, b='foo')\"\n\n\nNested objects work too:\n\nclass AnotherClass:\n c=SomeClass()\n d='bar'\n __repr__=basic_repr(['c', 'd'])\n\nrepr(AnotherClass())\n\n\"AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')\"\n\n\nInstance variables (but not class variables) are shown if basic_repr is called with no arguments:\n\nclass SomeClass:\n def __init__(self, a=1, b='foo'): self.a,self.b = a,b\n __repr__=basic_repr()\n\nrepr(SomeClass())\n\n\"SomeClass(a=1, b='foo')\"\n\n\n\nsource\n\n\n\n\n BasicRepr ()\n\nBase class for objects needing a basic __repr__\nAs a shortcut for creating a __repr__ for instance variables, you can inherit from BasicRepr:\n\nclass SomeClass(BasicRepr):\n def __init__(self, a=1, b='foo'): self.a,self.b = a,b\n\nrepr(SomeClass())\n\n\"SomeClass(a=1, b='foo')\"\n\n\n\nsource\n\n\n\n\n is_array (x)\n\nTrue if x supports __array__ or iloc\n\nis_array(np.array(1)),is_array([1])\n\n(True, False)\n\n\n\nsource\n\n\n\n\n listify (o=None, *rest, use_list=False, match=None)\n\nConvert o to a list\nConversion is designed to “do what you mean”, e.g:\n\ntest_eq(listify('hi'), ['hi'])\ntest_eq(listify(b'hi'), [b'hi'])\ntest_eq(listify(array(1)), [array(1)])\ntest_eq(listify(1), [1])\ntest_eq(listify([1,2]), [1,2])\ntest_eq(listify(range(3)), [0,1,2])\ntest_eq(listify(None), [])\ntest_eq(listify(1,2), [1,2])\n\n\narr = np.arange(9).reshape(3,3)\nlistify(arr)\n\n[array([[0, 1, 2],\n [3, 4, 5],\n [6, 7, 8]])]\n\n\n\nlistify(array([1,2]))\n\n[array([1, 2])]\n\n\nGenerators are turned into lists too:\n\ngen = (o for o in range(3))\ntest_eq(listify(gen), [0,1,2])\n\nUse match to provide a length to match:\n\ntest_eq(listify(1,match=3), [1,1,1])\n\nIf match is a sequence, it’s length is used:\n\ntest_eq(listify(1,match=range(3)), [1,1,1])\n\nIf the listified item is not of length 1, it must be the same length as match:\n\ntest_eq(listify([1,1,1],match=3), [1,1,1])\ntest_fail(lambda: listify([1,1],match=3))\n\n\nsource\n\n\n\n\n tuplify (o, use_list=False, match=None)\n\nMake o a tuple\n\ntest_eq(tuplify(None),())\ntest_eq(tuplify([1,2,3]),(1,2,3))\ntest_eq(tuplify(1,match=[1,2,3]),(1,1,1))\n\n\nsource\n\n\n\n\n true (x)\n\nTest whether x is truthy; collections with >0 elements are considered True\n\n[(o,true(o)) for o in\n (array(0),array(1),array([0]),array([0,1]),1,0,'',None)]\n\n[(array(0), False),\n (array(1), True),\n (array([0]), True),\n (array([0, 1]), True),\n (1, True),\n (0, False),\n ('', False),\n (None, False)]\n\n\n\nsource\n\n\n\n\n NullType ()\n\nAn object that is False and can be called, chained, and indexed\n\nbool(null.hi().there[3])\n\nFalse\n\n\n\nsource\n\n\n\n\n tonull (x)\n\nConvert None to null\n\nbool(tonull(None).hi().there[3])\n\nFalse\n\n\n\nsource\n\n\n\n\n get_class (nm, *fld_names, sup=None, doc=None, funcs=None, anno=None,\n **flds)\n\nDynamically create a class, optionally inheriting from sup, containing fld_names\n\n_t = get_class('_t', 'a', b=2, anno={'b':int})\nt = _t()\ntest_eq(t.a, None)\ntest_eq(t.b, 2)\nt = _t(1, b=3)\ntest_eq(t.a, 1)\ntest_eq(t.b, 3)\nt = _t(1, 3)\ntest_eq(t.a, 1)\ntest_eq(t.b, 3)\ntest_eq(t, pickle.loads(pickle.dumps(t)))\ntest_eq(_t.__annotations__, {'b':int, 'a':typing.Any})\nrepr(t)\n\n'__main__._t(a=1, b=3)'\n\n\nMost often you’ll want to call mk_class, since it adds the class to your module. See mk_class for more details and examples of use (which also apply to get_class).\n\nsource\n\n\n\n\n mk_class (nm, *fld_names, sup=None, doc=None, funcs=None, mod=None,\n anno=None, **flds)\n\nCreate a class using get_class and add to the caller’s module\nAny kwargs will be added as class attributes, and sup is an optional (tuple of) base classes.\n\nmk_class('_t', a=1, sup=dict)\nt = _t()\ntest_eq(t.a, 1)\nassert(isinstance(t,dict))\n\nA __init__ is provided that sets attrs for any kwargs, and for any args (matching by position to fields), along with a __repr__ which prints all attrs. The docstring is set to doc. You can pass funcs which will be added as attrs with the function names.\n\ndef foo(self): return 1\nmk_class('_t', 'a', sup=dict, doc='test doc', funcs=foo)\n\nt = _t(3, b=2)\ntest_eq(t.a, 3)\ntest_eq(t.b, 2)\ntest_eq(t.foo(), 1)\ntest_eq(t.__doc__, 'test doc')\nt\n\n{}\n\n\n\nsource\n\n\n\n\n wrap_class (nm, *fld_names, sup=None, doc=None, funcs=None, **flds)\n\nDecorator: makes function a method of a new class nm passing parameters to mk_class\n\n@wrap_class('_t', a=2)\ndef bar(self,x): return x+1\n\nt = _t()\ntest_eq(t.a, 2)\ntest_eq(t.bar(3), 4)\n\n\nsource\n\n\n\n ignore_exceptions ()\n\nContext manager to ignore exceptions\n\nwith ignore_exceptions(): \n # Exception will be ignored\n raise Exception\n\n\nsource\n\n\n\n\n\n exec_local (code, var_name)\n\nCall exec on code and return the var var_name\n\ntest_eq(exec_local(\"a=1\", \"a\"), 1)\n\n\nsource\n\n\n\n\n risinstance (types, obj=None)\n\nCurried isinstance but with args reversed\n\nassert risinstance(int, 1)\nassert not risinstance(str, 0)\nassert risinstance(int)(1)\nassert not risinstance(int)(None)\n\ntypes can also be strings:\n\nassert risinstance(('str','int'), 'a')\nassert risinstance('str', 'a')\nassert not risinstance('int', 'a')\n\n\nsource\n\n\n\n\n ver2tuple (v:str)\n\n\ntest_eq(ver2tuple('3.8.1'), (3,8,1))\ntest_eq(ver2tuple('3.1'), (3,1,0))\ntest_eq(ver2tuple('3.'), (3,0,0))\ntest_eq(ver2tuple('3'), (3,0,0))", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#basics", + "href": "basics.html#basics", + "title": "Basic functionality", + "section": "", + "text": "source\n\n\n\n ifnone (a, b)\n\nb if a is None else a\nSince b if a is None else a is such a common pattern, we wrap it in a function. However, be careful, because python will evaluate both a and b when calling ifnone (which it doesn’t do if using the if version directly).\n\ntest_eq(ifnone(None,1), 1)\ntest_eq(ifnone(2 ,1), 2)\n\n\nsource\n\n\n\n\n maybe_attr (o, attr)\n\ngetattr(o,attr,o)\nReturn the attribute attr for object o. If the attribute doesn’t exist, then return the object o instead.\n\nclass myobj: myattr='foo'\n\ntest_eq(maybe_attr(myobj, 'myattr'), 'foo')\ntest_eq(maybe_attr(myobj, 'another_attr'), myobj)\n\n\nsource\n\n\n\n\n basic_repr (flds=None)\n\nMinimal __repr__\nIn types which provide rich display functionality in Jupyter, their __repr__ is also called in order to provide a fallback text representation. Unfortunately, this includes a memory address which changes on every invocation, making it non-deterministic. This causes diffs to get messy and creates conflicts in git. To fix this, put __repr__=basic_repr() inside your class.\n\nclass SomeClass: __repr__=basic_repr()\nrepr(SomeClass())\n\n'SomeClass()'\n\n\nIf you pass a list of attributes (flds) of an object, then this will generate a string with the name of each attribute and its corresponding value. The format of this string is key=value, where key is the name of the attribute, and value is the value of the attribute. For each value, attempt to use the __name__ attribute, otherwise fall back to using the value’s __repr__ when constructing the string.\n\nclass SomeClass:\n a=1\n b='foo'\n __repr__=basic_repr('a,b')\n __name__='some-class'\n\nrepr(SomeClass())\n\n\"SomeClass(a=1, b='foo')\"\n\n\nNested objects work too:\n\nclass AnotherClass:\n c=SomeClass()\n d='bar'\n __repr__=basic_repr(['c', 'd'])\n\nrepr(AnotherClass())\n\n\"AnotherClass(c=SomeClass(a=1, b='foo'), d='bar')\"\n\n\nInstance variables (but not class variables) are shown if basic_repr is called with no arguments:\n\nclass SomeClass:\n def __init__(self, a=1, b='foo'): self.a,self.b = a,b\n __repr__=basic_repr()\n\nrepr(SomeClass())\n\n\"SomeClass(a=1, b='foo')\"\n\n\n\nsource\n\n\n\n\n BasicRepr ()\n\nBase class for objects needing a basic __repr__\nAs a shortcut for creating a __repr__ for instance variables, you can inherit from BasicRepr:\n\nclass SomeClass(BasicRepr):\n def __init__(self, a=1, b='foo'): self.a,self.b = a,b\n\nrepr(SomeClass())\n\n\"SomeClass(a=1, b='foo')\"\n\n\n\nsource\n\n\n\n\n is_array (x)\n\nTrue if x supports __array__ or iloc\n\nis_array(np.array(1)),is_array([1])\n\n(True, False)\n\n\n\nsource\n\n\n\n\n listify (o=None, *rest, use_list=False, match=None)\n\nConvert o to a list\nConversion is designed to “do what you mean”, e.g:\n\ntest_eq(listify('hi'), ['hi'])\ntest_eq(listify(b'hi'), [b'hi'])\ntest_eq(listify(array(1)), [array(1)])\ntest_eq(listify(1), [1])\ntest_eq(listify([1,2]), [1,2])\ntest_eq(listify(range(3)), [0,1,2])\ntest_eq(listify(None), [])\ntest_eq(listify(1,2), [1,2])\n\n\narr = np.arange(9).reshape(3,3)\nlistify(arr)\n\n[array([[0, 1, 2],\n [3, 4, 5],\n [6, 7, 8]])]\n\n\n\nlistify(array([1,2]))\n\n[array([1, 2])]\n\n\nGenerators are turned into lists too:\n\ngen = (o for o in range(3))\ntest_eq(listify(gen), [0,1,2])\n\nUse match to provide a length to match:\n\ntest_eq(listify(1,match=3), [1,1,1])\n\nIf match is a sequence, it’s length is used:\n\ntest_eq(listify(1,match=range(3)), [1,1,1])\n\nIf the listified item is not of length 1, it must be the same length as match:\n\ntest_eq(listify([1,1,1],match=3), [1,1,1])\ntest_fail(lambda: listify([1,1],match=3))\n\n\nsource\n\n\n\n\n tuplify (o, use_list=False, match=None)\n\nMake o a tuple\n\ntest_eq(tuplify(None),())\ntest_eq(tuplify([1,2,3]),(1,2,3))\ntest_eq(tuplify(1,match=[1,2,3]),(1,1,1))\n\n\nsource\n\n\n\n\n true (x)\n\nTest whether x is truthy; collections with >0 elements are considered True\n\n[(o,true(o)) for o in\n (array(0),array(1),array([0]),array([0,1]),1,0,'',None)]\n\n[(array(0), False),\n (array(1), True),\n (array([0]), True),\n (array([0, 1]), True),\n (1, True),\n (0, False),\n ('', False),\n (None, False)]\n\n\n\nsource\n\n\n\n\n NullType ()\n\nAn object that is False and can be called, chained, and indexed\n\nbool(null.hi().there[3])\n\nFalse\n\n\n\nsource\n\n\n\n\n tonull (x)\n\nConvert None to null\n\nbool(tonull(None).hi().there[3])\n\nFalse\n\n\n\nsource\n\n\n\n\n get_class (nm, *fld_names, sup=None, doc=None, funcs=None, anno=None,\n **flds)\n\nDynamically create a class, optionally inheriting from sup, containing fld_names\n\n_t = get_class('_t', 'a', b=2, anno={'b':int})\nt = _t()\ntest_eq(t.a, None)\ntest_eq(t.b, 2)\nt = _t(1, b=3)\ntest_eq(t.a, 1)\ntest_eq(t.b, 3)\nt = _t(1, 3)\ntest_eq(t.a, 1)\ntest_eq(t.b, 3)\ntest_eq(t, pickle.loads(pickle.dumps(t)))\ntest_eq(_t.__annotations__, {'b':int, 'a':typing.Any})\nrepr(t)\n\n'__main__._t(a=1, b=3)'\n\n\nMost often you’ll want to call mk_class, since it adds the class to your module. See mk_class for more details and examples of use (which also apply to get_class).\n\nsource\n\n\n\n\n mk_class (nm, *fld_names, sup=None, doc=None, funcs=None, mod=None,\n anno=None, **flds)\n\nCreate a class using get_class and add to the caller’s module\nAny kwargs will be added as class attributes, and sup is an optional (tuple of) base classes.\n\nmk_class('_t', a=1, sup=dict)\nt = _t()\ntest_eq(t.a, 1)\nassert(isinstance(t,dict))\n\nA __init__ is provided that sets attrs for any kwargs, and for any args (matching by position to fields), along with a __repr__ which prints all attrs. The docstring is set to doc. You can pass funcs which will be added as attrs with the function names.\n\ndef foo(self): return 1\nmk_class('_t', 'a', sup=dict, doc='test doc', funcs=foo)\n\nt = _t(3, b=2)\ntest_eq(t.a, 3)\ntest_eq(t.b, 2)\ntest_eq(t.foo(), 1)\ntest_eq(t.__doc__, 'test doc')\nt\n\n{}\n\n\n\nsource\n\n\n\n\n wrap_class (nm, *fld_names, sup=None, doc=None, funcs=None, **flds)\n\nDecorator: makes function a method of a new class nm passing parameters to mk_class\n\n@wrap_class('_t', a=2)\ndef bar(self,x): return x+1\n\nt = _t()\ntest_eq(t.a, 2)\ntest_eq(t.bar(3), 4)\n\n\nsource\n\n\n\n ignore_exceptions ()\n\nContext manager to ignore exceptions\n\nwith ignore_exceptions(): \n # Exception will be ignored\n raise Exception\n\n\nsource\n\n\n\n\n\n exec_local (code, var_name)\n\nCall exec on code and return the var var_name\n\ntest_eq(exec_local(\"a=1\", \"a\"), 1)\n\n\nsource\n\n\n\n\n risinstance (types, obj=None)\n\nCurried isinstance but with args reversed\n\nassert risinstance(int, 1)\nassert not risinstance(str, 0)\nassert risinstance(int)(1)\nassert not risinstance(int)(None)\n\ntypes can also be strings:\n\nassert risinstance(('str','int'), 'a')\nassert risinstance('str', 'a')\nassert not risinstance('int', 'a')\n\n\nsource\n\n\n\n\n ver2tuple (v:str)\n\n\ntest_eq(ver2tuple('3.8.1'), (3,8,1))\ntest_eq(ver2tuple('3.1'), (3,1,0))\ntest_eq(ver2tuple('3.'), (3,0,0))\ntest_eq(ver2tuple('3'), (3,0,0))", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#noop", + "href": "basics.html#noop", + "title": "Basic functionality", + "section": "NoOp", + "text": "NoOp\nThese are used when you need a pass-through function.\n\n\nnoop\n\n noop (x=None, *args, **kwargs)\n\nDo nothing\n\nnoop()\ntest_eq(noop(1),1)\n\n\n\n\nnoops\n\n noops (x=None, *args, **kwargs)\n\nDo nothing (method)\n\nclass _t: foo=noops\ntest_eq(_t().foo(1),1)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#infinite-lists", + "href": "basics.html#infinite-lists", + "title": "Basic functionality", + "section": "Infinite Lists", + "text": "Infinite Lists\nThese lists are useful for things like padding an array or adding index column(s) to arrays.\nInf defines the following properties:\n\ncount: itertools.count()\nzeros: itertools.cycle([0])\nones : itertools.cycle([1])\nnones: itertools.cycle([None])\n\n\ntest_eq([o for i,o in zip(range(5), Inf.count)],\n [0, 1, 2, 3, 4])\n\ntest_eq([o for i,o in zip(range(5), Inf.zeros)],\n [0]*5)\n\ntest_eq([o for i,o in zip(range(5), Inf.ones)],\n [1]*5)\n\ntest_eq([o for i,o in zip(range(5), Inf.nones)],\n [None]*5)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#operator-functions", + "href": "basics.html#operator-functions", + "title": "Basic functionality", + "section": "Operator Functions", + "text": "Operator Functions\n\nsource\n\nin_\n\n in_ (x, a)\n\nTrue if x in a\n\n# test if element is in another\nassert in_('c', ('b', 'c', 'a'))\nassert in_(4, [2,3,4,5])\nassert in_('t', 'fastai')\ntest_fail(in_('h', 'fastai'))\n\n# use in_ as a partial\nassert in_('fastai')('t')\nassert in_([2,3,4,5])(4)\ntest_fail(in_('fastai')('h'))\n\nIn addition to in_, the following functions are provided matching the behavior of the equivalent versions in operator: lt gt le ge eq ne add sub mul truediv is_ is_not mod.\n\nlt(3,5),gt(3,5),is_(None,None),in_(0,[1,2]),mod(3,2)\n\n(True, False, True, False, 1)\n\n\nSimilarly to _in, they also have additional functionality: if you only pass one param, they return a partial function that passes that param as the second positional parameter.\n\nlt(5)(3),gt(5)(3),is_(None)(None),in_([1,2])(0),mod(2)(3)\n\n(True, False, True, False, 1)\n\n\n\nsource\n\n\nret_true\n\n ret_true (*args, **kwargs)\n\nPredicate: always True\n\nassert ret_true(1,2,3)\nassert ret_true(False)\n\n\nsource\n\n\nret_false\n\n ret_false (*args, **kwargs)\n\nPredicate: always False\n\nsource\n\n\nstop\n\n stop (e=<class 'StopIteration'>)\n\nRaises exception e (by default StopIteration)\n\nsource\n\n\ngen\n\n gen (func, seq, cond=<function ret_true>)\n\nLike (func(o) for o in seq if cond(func(o))) but handles StopIteration\n\ntest_eq(gen(noop, Inf.count, lt(5)),\n range(5))\ntest_eq(gen(operator.neg, Inf.count, gt(-5)),\n [0,-1,-2,-3,-4])\ntest_eq(gen(lambda o:o if o<5 else stop(), Inf.count),\n range(5))\n\n\nsource\n\n\nchunked\n\n chunked (it, chunk_sz=None, drop_last=False, n_chunks=None)\n\nReturn batches from iterator it of size chunk_sz (or return n_chunks total)\nNote that you must pass either chunk_sz, or n_chunks, but not both.\n\nt = list(range(10))\ntest_eq(chunked(t,3), [[0,1,2], [3,4,5], [6,7,8], [9]])\ntest_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ])\n\nt = map(lambda o:stop() if o==6 else o, Inf.count)\ntest_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5]])\nt = map(lambda o:stop() if o==7 else o, Inf.count)\ntest_eq(chunked(t,3), [[0, 1, 2], [3, 4, 5], [6]])\n\nt = np.arange(10)\ntest_eq(chunked(t,3), [[0,1,2], [3,4,5], [6,7,8], [9]])\ntest_eq(chunked(t,3,True), [[0,1,2], [3,4,5], [6,7,8], ])\n\ntest_eq(chunked([], 3), [])\ntest_eq(chunked([], n_chunks=3), [])\n\n\nsource\n\n\notherwise\n\n otherwise (x, tst, y)\n\ny if tst(x) else x\n\ntest_eq(otherwise(2+1, gt(3), 4), 3)\ntest_eq(otherwise(2+1, gt(2), 4), 4)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#attribute-helpers", + "href": "basics.html#attribute-helpers", + "title": "Basic functionality", + "section": "Attribute Helpers", + "text": "Attribute Helpers\nThese functions reduce boilerplate when setting or manipulating attributes or properties of objects.\n\nsource\n\ncustom_dir\n\n custom_dir (c, add)\n\nImplement custom __dir__, adding add to cls\ncustom_dir allows you extract the __dict__ property of a class and appends the list add to it.\n\nclass _T: \n def f(): pass\n\ns = custom_dir(_T(), add=['foo', 'bar'])\nassert {'foo', 'bar', 'f'}.issubset(s)\n\n\nsource\n\n\nAttrDict\ndict subclass that also provides access to keys as attrs\n\nd = AttrDict(a=1,b=\"two\")\ntest_eq(d.a, 1)\ntest_eq(d['b'], 'two')\ntest_eq(d.get('c','nope'), 'nope')\nd.b = 2\ntest_eq(d.b, 2)\ntest_eq(d['b'], 2)\nd['b'] = 3\ntest_eq(d['b'], 3)\ntest_eq(d.b, 3)\nassert 'a' in dir(d)\n\nAttrDict will pretty print in Jupyter Notebooks:\n\n_test_dict = {'a':1, 'b': {'c':1, 'd':2}, 'c': {'c':1, 'd':2}, 'd': {'c':1, 'd':2},\n 'e': {'c':1, 'd':2}, 'f': {'c':1, 'd':2, 'e': 4, 'f':[1,2,3,4,5]}}\nAttrDict(_test_dict)\n\n{ 'a': 1,\n 'b': {'c': 1, 'd': 2},\n 'c': {'c': 1, 'd': 2},\n 'd': {'c': 1, 'd': 2},\n 'e': {'c': 1, 'd': 2},\n 'f': {'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]}}\n\n\n\nsource\n\n\nAttrDictDefault\n\n AttrDictDefault (*args, default_=None, **kwargs)\n\nAttrDict subclass that returns None for missing attrs\n\nd = AttrDictDefault(a=1,b=\"two\", default_='nope')\ntest_eq(d.a, 1)\ntest_eq(d['b'], 'two')\ntest_eq(d.c, 'nope')\n\n\nsource\n\n\nNS\nSimpleNamespace subclass that also adds iter and dict support\nThis is very similar to AttrDict, but since it starts with SimpleNamespace, it has some differences in behavior. You can use it just like SimpleNamespace:\n\nd = NS(**_test_dict)\nd\n\nnamespace(a=1,\n b={'c': 1, 'd': 2},\n c={'c': 1, 'd': 2},\n d={'c': 1, 'd': 2},\n e={'c': 1, 'd': 2},\n f={'c': 1, 'd': 2, 'e': 4, 'f': [1, 2, 3, 4, 5]})\n\n\n…but you can also index it to get/set:\n\nd['a']\n\n1\n\n\n…and iterate t:\n\nlist(d)\n\n['a', 'b', 'c', 'd', 'e', 'f']\n\n\n\nsource\n\n\nget_annotations_ex\n\n get_annotations_ex (obj, globals=None, locals=None)\n\nBackport of py3.10 get_annotations that returns globals/locals\nIn Python 3.10 inspect.get_annotations was added. However previous versions of Python are unable to evaluate type annotations correctly if from future import __annotations__ is used. Furthermore, all annotations are evaluated, even if only some subset are needed. get_annotations_ex provides the same functionality as inspect.get_annotations, but works on earlier versions of Python, and returns the globals and locals needed to evaluate types.\n\nsource\n\n\neval_type\n\n eval_type (t, glb, loc)\n\neval a type or collection of types, if needed, for annotations in py3.10+\nIn py3.10, or if from future import __annotations__ is used, a is a str:\n\nclass _T2a: pass\ndef func(a: _T2a): pass\nann,glb,loc = get_annotations_ex(func)\n\neval_type(ann['a'], glb, loc)\n\n__main__._T2a\n\n\n| is supported for defining Union types when using eval_type even for python versions prior to 3.9:\n\nclass _T2b: pass\ndef func(a: _T2a|_T2b): pass\nann,glb,loc = get_annotations_ex(func)\n\neval_type(ann['a'], glb, loc)\n\ntyping.Union[__main__._T2a, __main__._T2b]\n\n\n\nsource\n\n\ntype_hints\n\n type_hints (f)\n\nLike typing.get_type_hints but returns {} if not allowed type\nBelow is a list of allowed types for type hints in python:\n\nlist(typing._allowed_types)\n\n[function,\n builtin_function_or_method,\n method,\n module,\n wrapper_descriptor,\n method-wrapper,\n method_descriptor]\n\n\nFor example, type func is allowed so type_hints returns the same value as typing.get_hints:\n\ndef f(a:int)->bool: ... # a function with type hints (allowed)\nexp = {'a':int,'return':bool}\ntest_eq(type_hints(f), typing.get_type_hints(f))\ntest_eq(type_hints(f), exp)\n\nHowever, class is not an allowed type, so type_hints returns {}:\n\nclass _T:\n def __init__(self, a:int=0)->bool: ...\nassert not type_hints(_T)\n\n\nsource\n\n\nannotations\n\n annotations (o)\n\nAnnotations for o, or type(o)\nThis supports a wider range of situations than type_hints, by checking type() and __init__ for annotations too:\n\nfor o in _T,_T(),_T.__init__,f: test_eq(annotations(o), exp)\nassert not annotations(int)\nassert not annotations(print)\n\n\nsource\n\n\nanno_ret\n\n anno_ret (func)\n\nGet the return annotation of func\n\ndef f(x) -> float: return x\ntest_eq(anno_ret(f), float)\n\ndef f(x) -> typing.Tuple[float,float]: return x\nassert anno_ret(f)==typing.Tuple[float,float]\n\nIf your return annotation is None, anno_ret will return NoneType (and not None):\n\ndef f(x) -> None: return x\n\ntest_eq(anno_ret(f), NoneType)\nassert anno_ret(f) is not None # returns NoneType instead of None\n\nIf your function does not have a return type, or if you pass in None instead of a function, then anno_ret returns None:\n\ndef f(x): return x\n\ntest_eq(anno_ret(f), None)\ntest_eq(anno_ret(None), None) # instead of passing in a func, pass in None\n\n\nsource\n\n\nsignature_ex\n\n signature_ex (obj, eval_str:bool=False)\n\nBackport of inspect.signature(..., eval_str=True to <py310\n\nsource\n\n\nunion2tuple\n\n union2tuple (t)\n\n\ntest_eq(union2tuple(Union[int,str]), (int,str))\ntest_eq(union2tuple(int), int)\nassert union2tuple(Tuple[int,str])==Tuple[int,str]\ntest_eq(union2tuple((int,str)), (int,str))\nif UnionType: test_eq(union2tuple(int|str), (int,str))\n\n\nsource\n\n\nargnames\n\n argnames (f, frame=False)\n\nNames of arguments to function or frame f\n\ntest_eq(argnames(f), ['x'])\n\n\nsource\n\n\nwith_cast\n\n with_cast (f)\n\nDecorator which uses any parameter annotations as preprocessing functions\n\n@with_cast\ndef _f(a, b:Path, c:str='', d=0): return (a,b,c,d)\n\ntest_eq(_f(1, '.', 3), (1,Path('.'),'3',0))\ntest_eq(_f(1, '.'), (1,Path('.'),'',0))\n\n@with_cast\ndef _g(a:int=0)->str: return a\n\ntest_eq(_g(4.0), '4')\ntest_eq(_g(4.4), '4')\ntest_eq(_g(2), '2')\n\n\nsource\n\n\nstore_attr\n\n store_attr (names=None, but='', cast=False, store_args=None, **attrs)\n\nStore params named in comma-separated names from calling context into attrs in self\nIn it’s most basic form, you can use store_attr to shorten code like this:\n\nclass T:\n def __init__(self, a,b,c): self.a,self.b,self.c = a,b,c\n\n…to this:\n\nclass T:\n def __init__(self, a,b,c): store_attr('a,b,c', self)\n\nThis class behaves as if we’d used the first form:\n\nt = T(1,c=2,b=3)\nassert t.a==1 and t.b==3 and t.c==2\n\n\nclass T1:\n def __init__(self, a,b,c): store_attr()\n\nIn addition, it stores the attrs as a dict in __stored_args__, which you can use for display, logging, and so forth.\n\ntest_eq(t.__stored_args__, {'a':1, 'b':3, 'c':2})\n\nSince you normally want to use the first argument (often called self) for storing attributes, it’s optional:\n\nclass T:\n def __init__(self, a,b,c:str): store_attr('a,b,c')\n\nt = T(1,c=2,b=3)\nassert t.a==1 and t.b==3 and t.c==2\n\nWith cast=True any parameter annotations will be used as preprocessing functions for the corresponding arguments:\n\nclass T:\n def __init__(self, a:listify, b, c:str): store_attr('a,b,c', cast=True)\n\nt = T(1,c=2,b=3)\nassert t.a==[1] and t.b==3 and t.c=='2'\n\nYou can inherit from a class using store_attr, and just call it again to add in any new attributes added in the derived class:\n\nclass T2(T):\n def __init__(self, d, **kwargs):\n super().__init__(**kwargs)\n store_attr('d')\n\nt = T2(d=1,a=2,b=3,c=4)\nassert t.a==2 and t.b==3 and t.c==4 and t.d==1\n\nYou can skip passing a list of attrs to store. In this case, all arguments passed to the method are stored:\n\nclass T:\n def __init__(self, a,b,c): store_attr()\n\nt = T(1,c=2,b=3)\nassert t.a==1 and t.b==3 and t.c==2\n\n\nclass T4(T):\n def __init__(self, d, **kwargs):\n super().__init__(**kwargs)\n store_attr()\n\nt = T4(4, a=1,c=2,b=3)\nassert t.a==1 and t.b==3 and t.c==2 and t.d==4\n\n\nclass T4:\n def __init__(self, *, a: int, b: float = 1):\n store_attr()\n \nt = T4(a=3)\nassert t.a==3 and t.b==1\nt = T4(a=3, b=2)\nassert t.a==3 and t.b==2\n\nYou can skip some attrs by passing but:\n\nclass T:\n def __init__(self, a,b,c): store_attr(but='a')\n\nt = T(1,c=2,b=3)\nassert t.b==3 and t.c==2\nassert not hasattr(t,'a')\n\nYou can also pass keywords to store_attr, which is identical to setting the attrs directly, but also stores them in __stored_args__.\n\nclass T:\n def __init__(self): store_attr(a=1)\n\nt = T()\nassert t.a==1\n\nYou can also use store_attr inside functions.\n\ndef create_T(a, b):\n t = SimpleNamespace()\n store_attr(self=t)\n return t\n\nt = create_T(a=1, b=2)\nassert t.a==1 and t.b==2\n\n\nsource\n\n\nattrdict\n\n attrdict (o, *ks, default=None)\n\nDict from each k in ks to getattr(o,k)\n\nclass T:\n def __init__(self, a,b,c): store_attr()\n\nt = T(1,c=2,b=3)\ntest_eq(attrdict(t,'b','c'), {'b':3, 'c':2})\n\n\nsource\n\n\nproperties\n\n properties (cls, *ps)\n\nChange attrs in cls with names in ps to properties\n\nclass T:\n def a(self): return 1\n def b(self): return 2\nproperties(T,'a')\n\ntest_eq(T().a,1)\ntest_eq(T().b(),2)\n\n\nsource\n\n\ncamel2words\n\n camel2words (s, space=' ')\n\nConvert CamelCase to ‘spaced words’\n\ntest_eq(camel2words('ClassAreCamel'), 'Class Are Camel')\n\n\nsource\n\n\ncamel2snake\n\n camel2snake (name)\n\nConvert CamelCase to snake_case\n\ntest_eq(camel2snake('ClassAreCamel'), 'class_are_camel')\ntest_eq(camel2snake('Already_Snake'), 'already__snake')\n\n\nsource\n\n\nsnake2camel\n\n snake2camel (s)\n\nConvert snake_case to CamelCase\n\ntest_eq(snake2camel('a_b_cc'), 'ABCc')\n\n\nsource\n\n\nclass2attr\n\n class2attr (cls_name)\n\nReturn the snake-cased name of the class; strip ending cls_name if it exists.\n\nclass Parent:\n @property\n def name(self): return class2attr(self, 'Parent')\n\nclass ChildOfParent(Parent): pass\nclass ParentChildOf(Parent): pass\n\np = Parent()\ncp = ChildOfParent()\ncp2 = ParentChildOf()\n\ntest_eq(p.name, 'parent')\ntest_eq(cp.name, 'child_of')\ntest_eq(cp2.name, 'parent_child_of')\n\n\nsource\n\n\ngetcallable\n\n getcallable (o, attr)\n\nCalls getattr with a default of noop\n\nclass Math:\n def addition(self,a,b): return a+b\n\nm = Math()\n\ntest_eq(getcallable(m, \"addition\")(a=1,b=2), 3)\ntest_eq(getcallable(m, \"subtraction\")(a=1,b=2), None)\n\n\nsource\n\n\ngetattrs\n\n getattrs (o, *attrs, default=None)\n\nList of all attrs in o\n\nfrom fractions import Fraction\n\n\ngetattrs(Fraction(1,2), 'numerator', 'denominator')\n\n[1, 2]\n\n\n\nsource\n\n\nhasattrs\n\n hasattrs (o, attrs)\n\nTest whether o contains all attrs\n\nassert hasattrs(1,('imag','real'))\nassert not hasattrs(1,('imag','foo'))\n\n\nsource\n\n\nsetattrs\n\n setattrs (dest, flds, src)\n\n\nd = dict(a=1,bb=\"2\",ignore=3)\no = SimpleNamespace()\nsetattrs(o, \"a,bb\", d)\ntest_eq(o.a, 1)\ntest_eq(o.bb, \"2\")\n\n\nd = SimpleNamespace(a=1,bb=\"2\",ignore=3)\no = SimpleNamespace()\nsetattrs(o, \"a,bb\", d)\ntest_eq(o.a, 1)\ntest_eq(o.bb, \"2\")\n\n\nsource\n\n\ntry_attrs\n\n try_attrs (obj, *attrs)\n\nReturn first attr that exists in obj\n\ntest_eq(try_attrs(1, 'real'), 1)\ntest_eq(try_attrs(1, 'foobar', 'real'), 1)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#attribute-delegation", + "href": "basics.html#attribute-delegation", + "title": "Basic functionality", + "section": "Attribute Delegation", + "text": "Attribute Delegation\n\nsource\n\nGetAttrBase\n\n GetAttrBase ()\n\nBasic delegation of __getattr__ and __dir__\n\nsource\n\nGetAttr\n\n GetAttr ()\n\nInherit from this to have all attr accesses in self._xtra passed down to self.default\nInherit from GetAttr to have attr access passed down to an instance attribute. This makes it easy to create composites that don’t require callers to know about their components. For a more detailed discussion of how this works as well as relevant context, we suggest reading the delegated composition section of this blog article.\nYou can customise the behaviour of GetAttr in subclasses via; - _default - By default, this is set to 'default', so attr access is passed down to self.default - _default can be set to the name of any instance attribute that does not start with dunder __ - _xtra - By default, this is None, so all attr access is passed down - You can limit which attrs get passed down by setting _xtra to a list of attribute names\nTo illuminate the utility of GetAttr, suppose we have the following two classes, _WebPage which is a superclass of _ProductPage, which we wish to compose like so:\n\nclass _WebPage:\n def __init__(self, title, author=\"Jeremy\"):\n self.title,self.author = title,author\n\nclass _ProductPage:\n def __init__(self, page, price): self.page,self.price = page,price\n \npage = _WebPage('Soap', author=\"Sylvain\")\np = _ProductPage(page, 15.0)\n\nHow do we make it so we can just write p.author, instead of p.page.author to access the author attribute? We can use GetAttr, of course! First, we subclass GetAttr when defining _ProductPage. Next, we set self.default to the object whose attributes we want to be able to access directly, which in this case is the page argument passed on initialization:\n\nclass _ProductPage(GetAttr):\n def __init__(self, page, price): self.default,self.price = page,price #self.default allows you to access page directly.\n\np = _ProductPage(page, 15.0)\n\nNow, we can access the author attribute directly from the instance:\n\ntest_eq(p.author, 'Sylvain')\n\nIf you wish to store the object you are composing in an attribute other than self.default, you can set the class attribute _data as shown below. This is useful in the case where you might have a name collision with self.default:\n\nclass _C(GetAttr):\n _default = '_data' # use different component name; `self._data` rather than `self.default`\n def __init__(self,a): self._data = a\n def foo(self): noop\n\nt = _C('Hi')\ntest_eq(t._data, 'Hi') \ntest_fail(lambda: t.default) # we no longer have self.default\ntest_eq(t.lower(), 'hi')\ntest_eq(t.upper(), 'HI')\nassert 'lower' in dir(t)\nassert 'upper' in dir(t)\n\nBy default, all attributes and methods of the object you are composing are retained. In the below example, we compose a str object with the class _C. This allows us to directly call string methods on instances of class _C, such as str.lower() or str.upper():\n\nclass _C(GetAttr):\n # allow all attributes and methods to get passed to `self.default` (by leaving _xtra=None)\n def __init__(self,a): self.default = a\n def foo(self): noop\n\nt = _C('Hi')\ntest_eq(t.lower(), 'hi')\ntest_eq(t.upper(), 'HI')\nassert 'lower' in dir(t)\nassert 'upper' in dir(t)\n\nHowever, you can choose which attributes or methods to retain by defining a class attribute _xtra, which is a list of allowed attribute and method names to delegate. In the below example, we only delegate the lower method from the composed str object when defining class _C:\n\nclass _C(GetAttr):\n _xtra = ['lower'] # specify which attributes get passed to `self.default`\n def __init__(self,a): self.default = a\n def foo(self): noop\n\nt = _C('Hi')\ntest_eq(t.default, 'Hi')\ntest_eq(t.lower(), 'hi')\ntest_fail(lambda: t.upper()) # upper wasn't in _xtra, so it isn't available to be called\nassert 'lower' in dir(t)\nassert 'upper' not in dir(t)\n\nYou must be careful to properly set an instance attribute in __init__ that corresponds to the class attribute _default. The below example sets the class attribute _default to data, but erroneously fails to define self.data (and instead defines self.default).\nFailing to properly set instance attributes leads to errors when you try to access methods directly:\n\nclass _C(GetAttr):\n _default = 'data' # use a bad component name; i.e. self.data does not exist\n def __init__(self,a): self.default = a\n def foo(self): noop\n \n# TODO: should we raise an error when we create a new instance ...\nt = _C('Hi')\ntest_eq(t.default, 'Hi')\n# ... or is it enough for all GetAttr features to raise errors\ntest_fail(lambda: t.data)\ntest_fail(lambda: t.lower())\ntest_fail(lambda: t.upper())\ntest_fail(lambda: dir(t))\n\n\nsource\n\n\n\ndelegate_attr\n\n delegate_attr (k, to)\n\nUse in __getattr__ to delegate to attr to without inheriting from GetAttr\ndelegate_attr is a functional way to delegate attributes, and is an alternative to GetAttr. We recommend reading the documentation of GetAttr for more details around delegation.\nYou can use achieve delegation when you define __getattr__ by using delegate_attr:\n\nclass _C:\n def __init__(self, o): self.o = o # self.o corresponds to the `to` argument in delegate_attr.\n def __getattr__(self, k): return delegate_attr(self, k, to='o')\n \n\nt = _C('HELLO') # delegates to a string\ntest_eq(t.lower(), 'hello')\n\nt = _C(np.array([5,4,3])) # delegates to a numpy array\ntest_eq(t.sum(), 12)\n\nt = _C(pd.DataFrame({'a': [1,2], 'b': [3,4]})) # delegates to a pandas.DataFrame\ntest_eq(t.b.max(), 4)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#extensible-types", + "href": "basics.html#extensible-types", + "title": "Basic functionality", + "section": "Extensible Types", + "text": "Extensible Types\nShowPrint is a base class that defines a show method, which is used primarily for callbacks in fastai that expect this method to be defined.\nInt, Float, and Str extend int, float and str respectively by adding an additional show method by inheriting from ShowPrint.\nThe code for Int is shown below:\nExamples:\n\nInt(0).show()\nFloat(2.0).show()\nStr('Hello').show()\n\n0\n2.0\nHello", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#collection-functions", + "href": "basics.html#collection-functions", + "title": "Basic functionality", + "section": "Collection functions", + "text": "Collection functions\nFunctions that manipulate popular python collections.\n\nsource\n\npartition\n\n partition (coll, f)\n\nPartition a collection by a predicate\n\nts,fs = partition(range(10), mod(2))\ntest_eq(fs, [0,2,4,6,8])\ntest_eq(ts, [1,3,5,7,9])\n\n\nsource\n\n\nflatten\n\n flatten (o)\n\nConcatenate all collections and items as a generator\n\nsource\n\n\nconcat\n\n concat (colls)\n\nConcatenate all collections and items as a list\n\nconcat([(o for o in range(2)),[2,3,4], 5])\n\n[0, 1, 2, 3, 4, 5]\n\n\n\nconcat([[\"abc\", \"xyz\"], [\"foo\", \"bar\"]])\n\n['abc', 'xyz', 'foo', 'bar']\n\n\n\nsource\n\n\nstrcat\n\n strcat (its, sep:str='')\n\nConcatenate stringified items its\n\ntest_eq(strcat(['a',2]), 'a2')\ntest_eq(strcat(['a',2], ';'), 'a;2')\n\n\nsource\n\n\ndetuplify\n\n detuplify (x)\n\nIf x is a tuple with one thing, extract it\n\ntest_eq(detuplify(()),None)\ntest_eq(detuplify([1]),1)\ntest_eq(detuplify([1,2]), [1,2])\ntest_eq(detuplify(np.array([[1,2]])), np.array([[1,2]]))\n\n\nsource\n\n\nreplicate\n\n replicate (item, match)\n\nCreate tuple of item copied len(match) times\n\nt = [1,1]\ntest_eq(replicate([1,2], t),([1,2],[1,2]))\ntest_eq(replicate(1, t),(1,1))\n\n\nsource\n\n\nsetify\n\n setify (o)\n\nTurn any list like-object into a set.\n\n# test\ntest_eq(setify(None),set())\ntest_eq(setify('abc'),{'abc'})\ntest_eq(setify([1,2,2]),{1,2})\ntest_eq(setify(range(0,3)),{0,1,2})\ntest_eq(setify({1,2}),{1,2})\n\n\nsource\n\n\nmerge\n\n merge (*ds)\n\nMerge all dictionaries in ds\n\ntest_eq(merge(), {})\ntest_eq(merge(dict(a=1,b=2)), dict(a=1,b=2))\ntest_eq(merge(dict(a=1,b=2), dict(b=3,c=4), None), dict(a=1, b=3, c=4))\n\n\nsource\n\n\nrange_of\n\n range_of (x)\n\nAll indices of collection x (i.e. list(range(len(x))))\n\ntest_eq(range_of([1,1,1,1]), [0,1,2,3])\n\n\nsource\n\n\ngroupby\n\n groupby (x, key, val=<function noop>)\n\nLike itertools.groupby but doesn’t need to be sorted, and isn’t lazy, plus some extensions\n\ntest_eq(groupby('aa ab bb'.split(), itemgetter(0)), {'a':['aa','ab'], 'b':['bb']})\n\nHere’s an example of how to invert a grouping, using an int as key (which uses itemgetter; passing a str will use attrgetter), and using a val function:\n\nd = {0: [1, 3, 7], 2: [3], 3: [5], 4: [8], 5: [4], 7: [5]}\ngroupby(((o,k) for k,v in d.items() for o in v), 0, 1)\n\n{1: [0], 3: [0, 2], 7: [0], 5: [3, 7], 8: [4], 4: [5]}\n\n\n\nsource\n\n\nlast_index\n\n last_index (x, o)\n\nFinds the last index of occurence of x in o (returns -1 if no occurence)\n\ntest_eq(last_index(9, [1, 2, 9, 3, 4, 9, 10]), 5)\ntest_eq(last_index(6, [1, 2, 9, 3, 4, 9, 10]), -1)\n\n\nsource\n\n\nfilter_dict\n\n filter_dict (d, func)\n\nFilter a dict using func, applied to keys and values\n\nletters = {o:chr(o) for o in range(65,73)}\nletters\n\n{65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H'}\n\n\n\nfilter_dict(letters, lambda k,v: k<67 or v in 'FG')\n\n{65: 'A', 66: 'B', 70: 'F', 71: 'G'}\n\n\n\nsource\n\n\nfilter_keys\n\n filter_keys (d, func)\n\nFilter a dict using func, applied to keys\n\nfilter_keys(letters, lt(67))\n\n{65: 'A', 66: 'B'}\n\n\n\nsource\n\n\nfilter_values\n\n filter_values (d, func)\n\nFilter a dict using func, applied to values\n\nfilter_values(letters, in_('FG'))\n\n{70: 'F', 71: 'G'}\n\n\n\nsource\n\n\ncycle\n\n cycle (o)\n\nLike itertools.cycle except creates list of Nones if o is empty\n\ntest_eq(itertools.islice(cycle([1,2,3]),5), [1,2,3,1,2])\ntest_eq(itertools.islice(cycle([]),3), [None]*3)\ntest_eq(itertools.islice(cycle(None),3), [None]*3)\ntest_eq(itertools.islice(cycle(1),3), [1,1,1])\n\n\nsource\n\n\nzip_cycle\n\n zip_cycle (x, *args)\n\nLike itertools.zip_longest but cycles through elements of all but first argument\n\ntest_eq(zip_cycle([1,2,3,4],list('abc')), [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'a')])\n\n\nsource\n\n\nsorted_ex\n\n sorted_ex (iterable, key=None, reverse=False)\n\nLike sorted, but if key is str use attrgetter; if int use itemgetter\n\nsource\n\n\nnot_\n\n not_ (f)\n\nCreate new function that negates result of f\n\ndef f(a): return a>0\ntest_eq(f(1),True)\ntest_eq(not_(f)(1),False)\ntest_eq(not_(f)(a=-1),True)\n\n\nsource\n\n\nargwhere\n\n argwhere (iterable, f, negate=False, **kwargs)\n\nLike filter_ex, but return indices for matching items\n\nsource\n\n\nfilter_ex\n\n filter_ex (iterable, f=<function noop>, negate=False, gen=False,\n **kwargs)\n\nLike filter, but passing kwargs to f, defaulting f to noop, and adding negate and gen\n\nsource\n\n\nrange_of\n\n range_of (a, b=None, step=None)\n\nAll indices of collection a, if a is a collection, otherwise range\n\ntest_eq(range_of([1,1,1,1]), [0,1,2,3])\ntest_eq(range_of(4), [0,1,2,3])\n\n\nsource\n\n\nrenumerate\n\n renumerate (iterable, start=0)\n\nSame as enumerate, but returns index as 2nd element instead of 1st\n\ntest_eq(renumerate('abc'), (('a',0),('b',1),('c',2)))\n\n\nsource\n\n\nfirst\n\n first (x, f=None, negate=False, **kwargs)\n\nFirst element of x, optionally filtered by f, or None if missing\n\ntest_eq(first(['a', 'b', 'c', 'd', 'e']), 'a')\ntest_eq(first([False]), False)\ntest_eq(first([False], noop), None)\n\n\nsource\n\n\nonly\n\n only (o)\n\nReturn the only item of o, raise if o doesn’t have exactly one item\n\nsource\n\n\nnested_attr\n\n nested_attr (o, attr, default=None)\n\nSame as getattr, but if attr includes a ., then looks inside nested objects\n\nclass CustomIndexable:\n def __init__(self): self.data = {'a':1,'b':'v','c':{'d':5}}\n def __getitem__(self, key): return self.data[key]\n\ncustom_indexable = CustomIndexable()\ntest_eq(nested_attr(custom_indexable,'a'),1)\ntest_eq(nested_attr(custom_indexable,'c.d'),5)\ntest_eq(nested_attr(custom_indexable,'e'),None)\n\nclass TestObj: def init(self): self.nested = {‘key’: [1, 2, {‘inner’: ‘value’}]} test_obj = TestObj()\ntest_eq(nested_attr(test_obj, ‘nested.key.2.inner’),‘value’) test_eq(nested_attr([1, 2, 3], ‘1’),2)\n\nb = {'a':1,'b':'v','c':{'d':5}}\ntest_eq(nested_attr(b,'b'),'v')\ntest_eq(nested_attr(b,'c.d'),5)\n\n\na = SimpleNamespace(b=(SimpleNamespace(c=1)))\ntest_eq(nested_attr(a, 'b.c'), getattr(getattr(a, 'b'), 'c'))\ntest_eq(nested_attr(a, 'b.d'), None)\ntest_eq(nested_attr(b, 'a'), 1)\n\n\nsource\n\n\nnested_setdefault\n\n nested_setdefault (o, attr, default)\n\nSame as setdefault, but if attr includes a ., then looks inside nested objects\n\nsource\n\n\nnested_callable\n\n nested_callable (o, attr)\n\nSame as nested_attr but if not found will return noop\n\na = SimpleNamespace(b=(SimpleNamespace(c=1)))\ntest_eq(nested_callable(a, 'b.c'), getattr(getattr(a, 'b'), 'c'))\ntest_eq(nested_callable(a, 'b.d'), noop)\n\n\nsource\n\n\nnested_idx\n\n nested_idx (coll, *idxs)\n\nIndex into nested collections, dicts, etc, with idxs\n\na = {'b':[1,{'c':2}]}\ntest_eq(nested_idx(a, 'nope'), None)\ntest_eq(nested_idx(a, 'nope', 'nup'), None)\ntest_eq(nested_idx(a, 'b', 3), None)\ntest_eq(nested_idx(a), a)\ntest_eq(nested_idx(a, 'b'), [1,{'c':2}])\ntest_eq(nested_idx(a, 'b', 1), {'c':2})\ntest_eq(nested_idx(a, 'b', 1, 'c'), 2)\n\n\na = SimpleNamespace(b=[1,{'c':2}])\ntest_eq(nested_idx(a, 'nope'), None)\ntest_eq(nested_idx(a, 'nope', 'nup'), None)\ntest_eq(nested_idx(a, 'b', 3), None)\ntest_eq(nested_idx(a), a)\ntest_eq(nested_idx(a, 'b'), [1,{'c':2}])\ntest_eq(nested_idx(a, 'b', 1), {'c':2})\ntest_eq(nested_idx(a, 'b', 1, 'c'), 2)\n\n\nsource\n\n\nset_nested_idx\n\n set_nested_idx (coll, value, *idxs)\n\nSet value indexed like `nested_idx\n\nset_nested_idx(a, 3, 'b', 0)\ntest_eq(nested_idx(a, 'b', 0), 3)\n\n\nsource\n\n\nval2idx\n\n val2idx (x)\n\nDict from value to index\n\ntest_eq(val2idx([1,2,3]), {3:2,1:0,2:1})\n\n\nsource\n\n\nuniqueify\n\n uniqueify (x, sort=False, bidir=False, start=None)\n\nUnique elements in x, optional sort, optional return reverse correspondence, optional prepend with elements.\n\nt = [1,1,0,5,0,3]\ntest_eq(uniqueify(t),[1,0,5,3])\ntest_eq(uniqueify(t, sort=True),[0,1,3,5])\ntest_eq(uniqueify(t, start=[7,8,6]), [7,8,6,1,0,5,3])\nv,o = uniqueify(t, bidir=True)\ntest_eq(v,[1,0,5,3])\ntest_eq(o,{1:0, 0: 1, 5: 2, 3: 3})\nv,o = uniqueify(t, sort=True, bidir=True)\ntest_eq(v,[0,1,3,5])\ntest_eq(o,{0:0, 1: 1, 3: 2, 5: 3})\n\n\nsource\n\n\nloop_first_last\n\n loop_first_last (values)\n\nIterate and generate a tuple with a flag for first and last value.\n\ntest_eq(loop_first_last(range(3)), [(True,False,0), (False,False,1), (False,True,2)])\n\n\nsource\n\n\nloop_first\n\n loop_first (values)\n\nIterate and generate a tuple with a flag for first value.\n\ntest_eq(loop_first(range(3)), [(True,0), (False,1), (False,2)])\n\n\nsource\n\n\nloop_last\n\n loop_last (values)\n\nIterate and generate a tuple with a flag for last value.\n\ntest_eq(loop_last(range(3)), [(False,0), (False,1), (True,2)])\n\n\nsource\n\n\nfirst_match\n\n first_match (lst, f, default=None)\n\nFirst element of lst matching predicate f, or default if none\n\na = [0,2,4,5,6,7,10]\ntest_eq(first_match(a, lambda o:o%2), 3)\n\n\nsource\n\n\nlast_match\n\n last_match (lst, f, default=None)\n\nLast element of lst matching predicate f, or default if none\n\ntest_eq(last_match(a, lambda o:o%2), 5)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#fastuple", + "href": "basics.html#fastuple", + "title": "Basic functionality", + "section": "fastuple", + "text": "fastuple\nA tuple with extended functionality.\n\nsource\n\nfastuple\n\n fastuple (x=None, *rest)\n\nA tuple with elementwise ops and more friendly init behavior\n\n\nFriendly init behavior\nCommon failure modes when trying to initialize a tuple in python:\ntuple(3)\n> TypeError: 'int' object is not iterable\nor\ntuple(3, 4)\n> TypeError: tuple expected at most 1 arguments, got 2\nHowever, fastuple allows you to define tuples like this and in the usual way:\n\ntest_eq(fastuple(3), (3,))\ntest_eq(fastuple(3,4), (3, 4))\ntest_eq(fastuple((3,4)), (3, 4))\n\n\n\nElementwise operations\n\nsource\n\nfastuple.add\n\n fastuple.add (*args)\n\n+ is already defined in tuple for concat, so use add instead\n\ntest_eq(fastuple.add((1,1),(2,2)), (3,3))\ntest_eq_type(fastuple(1,1).add(2), fastuple(3,3))\ntest_eq(fastuple('1','2').add('2'), fastuple('12','22'))\n\n\nsource\n\n\nfastuple.mul\n\n fastuple.mul (*args)\n\n* is already defined in tuple for replicating, so use mul instead\n\ntest_eq_type(fastuple(1,1).mul(2), fastuple(2,2))\n\n\n\n\nOther Elementwise Operations\nAdditionally, the following elementwise operations are available: - le: less than or equal - eq: equal - gt: greater than - min: minimum of\n\ntest_eq(fastuple(3,1).le(1), (False, True))\ntest_eq(fastuple(3,1).eq(1), (False, True))\ntest_eq(fastuple(3,1).gt(1), (True, False))\ntest_eq(fastuple(3,1).min(2), (2,1))\n\nYou can also do other elementwise operations like negate a fastuple, or subtract two fastuples:\n\ntest_eq(-fastuple(1,2), (-1,-2))\ntest_eq(~fastuple(1,0,1), (False,True,False))\n\ntest_eq(fastuple(1,1)-fastuple(2,2), (-1,-1))\n\n\ntest_eq(type(fastuple(1)), fastuple)\ntest_eq_type(fastuple(1,2), fastuple(1,2))\ntest_ne(fastuple(1,2), fastuple(1,3))\ntest_eq(fastuple(), ())", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#functions-on-functions", + "href": "basics.html#functions-on-functions", + "title": "Basic functionality", + "section": "Functions on Functions", + "text": "Functions on Functions\nUtilities for functional programming or for defining, modifying, or debugging functions.\n\nsource\n\nbind\n\n bind (func, *pargs, **pkwargs)\n\nSame as partial, except you can use arg0 arg1 etc param placeholders\nbind is the same as partial, but also allows you to reorder positional arguments using variable name(s) arg{i} where i refers to the zero-indexed positional argument. bind as implemented currently only supports reordering of up to the first 5 positional arguments.\nConsider the function myfunc below, which has 3 positional arguments. These arguments can be referenced as arg0, arg1, and arg1, respectively.\n\ndef myfn(a,b,c,d=1,e=2): return(a,b,c,d,e)\n\nIn the below example we bind the positional arguments of myfn as follows:\n\nThe second input 14, referenced by arg1, is substituted for the first positional argument.\nWe supply a default value of 17 for the second positional argument.\nThe first input 19, referenced by arg0, is subsituted for the third positional argument.\n\n\ntest_eq(bind(myfn, arg1, 17, arg0, e=3)(19,14), (14,17,19,1,3))\n\nIn this next example:\n\nWe set the default value to 17 for the first positional argument.\nThe first input 19 refrenced by arg0, becomes the second positional argument.\nThe second input 14 becomes the third positional argument.\nWe override the default the value for named argument e to 3.\n\n\ntest_eq(bind(myfn, 17, arg0, e=3)(19,14), (17,19,14,1,3))\n\nThis is an example of using bind like partial and do not reorder any arguments:\n\ntest_eq(bind(myfn)(17,19,14), (17,19,14,1,2))\n\nbind can also be used to change default values. In the below example, we use the first input 3 to override the default value of the named argument e, and supply default values for the first three positional arguments:\n\ntest_eq(bind(myfn, 17,19,14,e=arg0)(3), (17,19,14,1,3))\n\n\nsource\n\n\nmapt\n\n mapt (func, *iterables)\n\nTuplified map\n\nt = [0,1,2,3]\ntest_eq(mapt(operator.neg, t), (0,-1,-2,-3))\n\n\nsource\n\n\nmap_ex\n\n map_ex (iterable, f, *args, gen=False, **kwargs)\n\nLike map, but use bind, and supports str and indexing\n\ntest_eq(map_ex(t,operator.neg), [0,-1,-2,-3])\n\nIf f is a string then it is treated as a format string to create the mapping:\n\ntest_eq(map_ex(t, '#{}#'), ['#0#','#1#','#2#','#3#'])\n\nIf f is a dictionary (or anything supporting __getitem__) then it is indexed to create the mapping:\n\ntest_eq(map_ex(t, list('abcd')), list('abcd'))\n\nYou can also pass the same arg params that bind accepts:\n\ndef f(a=None,b=None): return b\ntest_eq(map_ex(t, f, b=arg0), range(4))\n\n\nsource\n\n\ncompose\n\n compose (*funcs, order=None)\n\nCreate a function that composes all functions in funcs, passing along remaining *args and **kwargs to all\n\nf1 = lambda o,p=0: (o*2)+p\nf2 = lambda o,p=1: (o+1)/p\ntest_eq(f2(f1(3)), compose(f1,f2)(3))\ntest_eq(f2(f1(3,p=3),p=3), compose(f1,f2)(3,p=3))\ntest_eq(f2(f1(3, 3), 3), compose(f1,f2)(3, 3))\n\nf1.order = 1\ntest_eq(f1(f2(3)), compose(f1,f2, order=\"order\")(3))\n\n\nsource\n\n\nmaps\n\n maps (*args, retain=<function noop>)\n\nLike map, except funcs are composed first\n\ntest_eq(maps([1]), [1])\ntest_eq(maps(operator.neg, [1,2]), [-1,-2])\ntest_eq(maps(operator.neg, operator.neg, [1,2]), [1,2])\n\n\nsource\n\n\npartialler\n\n partialler (f, *args, order=None, **kwargs)\n\nLike functools.partial but also copies over docstring\n\ndef _f(x,a=1):\n \"test func\"\n return x-a\n_f.order=1\n\nf = partialler(_f, 2)\ntest_eq(f.order, 1)\ntest_eq(f(3), -1)\nf = partialler(_f, a=2, order=3)\ntest_eq(f.__doc__, \"test func\")\ntest_eq(f.order, 3)\ntest_eq(f(3), _f(3,2))\n\n\nclass partial0:\n \"Like `partialler`, but args passed to callable are inserted at started, instead of at end\"\n def __init__(self, f, *args, order=None, **kwargs):\n self.f,self.args,self.kwargs = f,args,kwargs\n self.order = ifnone(order, getattr(f,'order',None))\n self.__doc__ = f.__doc__\n\n def __call__(self, *args, **kwargs): return self.f(*args, *self.args, **kwargs, **self.kwargs)\n\n\nf = partial0(_f, 2)\ntest_eq(f.order, 1)\ntest_eq(f(3), 1) # NB: different to `partialler` example\n\n\nsource\n\n\ninstantiate\n\n instantiate (t)\n\nInstantiate t if it’s a type, otherwise do nothing\n\ntest_eq_type(instantiate(int), 0)\ntest_eq_type(instantiate(1), 1)\n\n\nsource\n\n\nusing_attr\n\n using_attr (f, attr)\n\nConstruct a function which applies f to the argument’s attribute attr\n\nt = Path('/a/b.txt')\nf = using_attr(str.upper, 'name')\ntest_eq(f(t), 'B.TXT')\n\n\n\nSelf (with an uppercase S)\nA Concise Way To Create Lambdas\nThis is a concise way to create lambdas that are calling methods on an object (note the capitalization!)\nSelf.sum(), for instance, is a shortcut for lambda o: o.sum().\n\nf = Self.sum()\nx = np.array([3.,1])\ntest_eq(f(x), 4.)\n\n# This is equivalent to above\nf = lambda o: o.sum()\nx = np.array([3.,1])\ntest_eq(f(x), 4.)\n\nf = Self.argmin()\narr = np.array([1,2,3,4,5])\ntest_eq(f(arr), arr.argmin())\n\nf = Self.sum().is_integer()\nx = np.array([3.,1])\ntest_eq(f(x), True)\n\nf = Self.sum().real.is_integer()\nx = np.array([3.,1])\ntest_eq(f(x), True)\n\nf = Self.imag()\ntest_eq(f(3), 0)\n\nf = Self[1]\ntest_eq(f(x), 1)\n\nSelf is also callable, which creates a function which calls any function passed to it, using the arguments passed to Self:\n\ndef f(a, b=3): return a+b+2\ndef g(a, b=3): return a*b\nfg = Self(1,b=2)\nlist(map(fg, [f,g]))\n\n[5, 2]", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#patching", + "href": "basics.html#patching", + "title": "Basic functionality", + "section": "Patching", + "text": "Patching\n\nsource\n\ncopy_func\n\n copy_func (f)\n\nCopy a non-builtin function (NB copy.copy does not work for this)\nSometimes it may be desirable to make a copy of a function that doesn’t point to the original object. When you use Python’s built in copy.copy or copy.deepcopy to copy a function, you get a reference to the original object:\n\nimport copy as cp\n\n\ndef foo(): pass\na = cp.copy(foo)\nb = cp.deepcopy(foo)\n\na.someattr = 'hello' # since a and b point at the same object, updating a will update b\ntest_eq(b.someattr, 'hello')\n\nassert a is foo and b is foo\n\nHowever, with copy_func, you can retrieve a copy of a function without a reference to the original object:\n\nc = copy_func(foo) # c is an indpendent object\nassert c is not foo\n\n\ndef g(x, *, y=3): return x+y\ntest_eq(copy_func(g)(4), 7)\n\n\nsource\n\n\npatch_to\n\n patch_to (cls, as_prop=False, cls_method=False)\n\nDecorator: add f to cls\nThe @patch_to decorator allows you to monkey patch a function into a class as a method:\n\nclass _T3(int): pass \n\n@patch_to(_T3)\ndef func1(self, a): return self+a\n\nt = _T3(1) # we initialized `t` to a type int = 1\ntest_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3\n\nYou can access instance properties in the usual way via self:\n\nclass _T4():\n def __init__(self, g): self.g = g\n \n@patch_to(_T4)\ndef greet(self, x): return self.g + x\n \nt = _T4('hello ') # this sets self.g = 'hello '\ntest_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello '\n\nYou can instead specify that the method should be a class method by setting cls_method=True:\n\nclass _T5(int): attr = 3 # attr is a class attribute we will access in a later method\n \n@patch_to(_T5, cls_method=True)\ndef func(cls, x): return cls.attr + x # you can access class attributes in the normal way\n\ntest_eq(_T5.func(4), 7)\n\nAdditionally you can specify that the function you want to patch should be a class attribute with as_prop=True:\n\n@patch_to(_T5, as_prop=True)\ndef add_ten(self): return self + 10\n\nt = _T5(4)\ntest_eq(t.add_ten, 14)\n\nInstead of passing one class to the @patch_to decorator, you can pass multiple classes in a tuple to simulteanously patch more than one class with the same method:\n\nclass _T6(int): pass\nclass _T7(int): pass\n\n@patch_to((_T6,_T7))\ndef func_mult(self, a): return self*a\n\nt = _T6(2)\ntest_eq(t.func_mult(4), 8)\nt = _T7(2)\ntest_eq(t.func_mult(4), 8)\n\n\nsource\n\n\npatch\n\n patch (f=None, as_prop=False, cls_method=False)\n\nDecorator: add f to the first parameter’s class (based on f’s type annotations)\n@patch is an alternative to @patch_to that allows you similarly monkey patch class(es) by using type annotations:\n\nclass _T8(int): pass \n\n@patch\ndef func(self:_T8, a): return self+a\n\nt = _T8(1) # we initilized `t` to a type int = 1\ntest_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4\ntest_eq(t.func.__qualname__, '_T8.func')\n\nSimilarly to patch_to, you can supply a union of classes instead of a single class in your type annotations to patch multiple classes:\n\nclass _T9(int): pass \n\n@patch\ndef func2(x:_T8|_T9, a): return x*a # will patch both _T8 and _T9\n\nt = _T8(2)\ntest_eq(t.func2(4), 8)\ntest_eq(t.func2.__qualname__, '_T8.func2')\n\nt = _T9(2)\ntest_eq(t.func2(4), 8)\ntest_eq(t.func2.__qualname__, '_T9.func2')\n\nJust like patch_to decorator you can use as_prop and cls_method parameters with patch decorator:\n\n@patch(as_prop=True)\ndef add_ten(self:_T5): return self + 10\n\nt = _T5(4)\ntest_eq(t.add_ten, 14)\n\n\nclass _T5(int): attr = 3 # attr is a class attribute we will access in a later method\n \n@patch(cls_method=True)\ndef func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way\n\ntest_eq(_T5.func(4), 7)\n\n\nsource\n\n\npatch_property\n\n patch_property (f)\n\nDeprecated; use patch(as_prop=True) instead\nPatching classmethod shouldn’t affect how python’s inheritance works\n\nclass FastParent: pass\n\n@patch(cls_method=True)\ndef type_cls(cls: FastParent): return cls\n\nclass FastChild(FastParent): pass\n\nparent = FastParent()\ntest_eq(parent.type_cls(), FastParent)\n\nchild = FastChild()\ntest_eq(child.type_cls(), FastChild)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#other-helpers", + "href": "basics.html#other-helpers", + "title": "Basic functionality", + "section": "Other Helpers", + "text": "Other Helpers\n\nsource\n\ncompile_re\n\n compile_re (pat)\n\nCompile pat if it’s not None\n\nassert compile_re(None) is None\nassert compile_re('a').match('ab')\n\n\nsource\n\nImportEnum\n\n ImportEnum (value, names=None, module=None, qualname=None, type=None,\n start=1)\n\nAn Enum that can have its values imported\n\n_T = ImportEnum('_T', {'foobar':1, 'goobar':2})\n_T.imports()\ntest_eq(foobar, _T.foobar)\ntest_eq(goobar, _T.goobar)\n\n\nsource\n\n\nStrEnum\n\n StrEnum (value, names=None, module=None, qualname=None, type=None,\n start=1)\n\nAn ImportEnum that behaves like a str\n\nsource\n\n\n\nstr_enum\n\n str_enum (name, *vals)\n\nSimplified creation of StrEnum types\n\nsource\n\nValEnum\n\n ValEnum (value, names=None, module=None, qualname=None, type=None,\n start=1)\n\nAn ImportEnum that stringifies using values\n\n_T = str_enum('_T', 'a', 'b')\ntest_eq(f'{_T.a}', 'a')\ntest_eq(_T.a, 'a')\ntest_eq(list(_T.__members__), ['a','b'])\nprint(_T.a, _T.a.upper())\n\na A\n\n\n\nsource\n\n\nStateful\n\n Stateful (*args, **kwargs)\n\nA base class/mixin for objects that should not serialize all their state\n\nclass _T(Stateful):\n def __init__(self):\n super().__init__()\n self.a=1\n self._state['test']=2\n\nt = _T()\nt2 = pickle.loads(pickle.dumps(t))\ntest_eq(t.a,1)\ntest_eq(t._state['test'],2)\ntest_eq(t2.a,1)\ntest_eq(t2._state,{})\n\nOverride _init_state to do any necessary setup steps that are required during __init__ or during deserialization (e.g. pickle.load). Here’s an example of how Stateful simplifies the official Python example for Handling Stateful Objects.\n\nclass TextReader(Stateful):\n \"\"\"Print and number lines in a text file.\"\"\"\n _stateattrs=('file',)\n def __init__(self, filename):\n self.filename,self.lineno = filename,0\n super().__init__()\n\n def readline(self):\n self.lineno += 1\n line = self.file.readline()\n if line: return f\"{self.lineno}: {line.strip()}\"\n\n def _init_state(self):\n self.file = open(self.filename)\n for _ in range(self.lineno): self.file.readline()\n\n\nreader = TextReader(\"00_test.ipynb\")\nprint(reader.readline())\nprint(reader.readline())\n\nnew_reader = pickle.loads(pickle.dumps(reader))\nprint(reader.readline())\n\n1: {\n2: \"cells\": [\n3: {\n\n\n\nsource\n\n\n\nNotStr\n\n NotStr (s)\n\nBehaves like a str, but isn’t an instance of one\n\ns = NotStr(\"hello\")\nassert not isinstance(s, str)\ntest_eq(s, 'hello')\ntest_eq(s*2, 'hellohello')\ntest_eq(len(s), 5)\n\n\nsource\n\nPrettyString\nLittle hack to get strings to show properly in Jupyter.\nAllow strings with special characters to render properly in Jupyter. Without calling print() strings with special characters are displayed like so:\n\nwith_special_chars='a string\\nwith\\nnew\\nlines and\\ttabs'\nwith_special_chars\n\n'a string\\nwith\\nnew\\nlines and\\ttabs'\n\n\nWe can correct this with PrettyString:\n\nPrettyString(with_special_chars)\n\na string\nwith\nnew\nlines and tabs\n\n\n\nsource\n\n\n\neven_mults\n\n even_mults (start, stop, n)\n\nBuild log-stepped array from start to stop in n steps.\n\ntest_eq(even_mults(2,8,3), [2,4,8])\ntest_eq(even_mults(2,32,5), [2,4,8,16,32])\ntest_eq(even_mults(2,8,1), 8)\n\n\nsource\n\n\nnum_cpus\n\n num_cpus ()\n\nGet number of cpus\n\nnum_cpus()\n\n10\n\n\n\nsource\n\n\nadd_props\n\n add_props (f, g=None, n=2)\n\nCreate properties passing each of range(n) to f\n\nclass _T(): a,b = add_props(lambda i,x:i*2)\n\nt = _T()\ntest_eq(t.a,0)\ntest_eq(t.b,2)\n\n\nclass _T(): \n def __init__(self, v): self.v=v\n def _set(i, self, v): self.v[i] = v\n a,b = add_props(lambda i,x: x.v[i], _set)\n\nt = _T([0,2])\ntest_eq(t.a,0)\ntest_eq(t.b,2)\nt.a = t.a+1\nt.b = 3\ntest_eq(t.a,1)\ntest_eq(t.b,3)\n\n\nsource\n\n\nstr2bool\n\n str2bool (s)\n\nCase-insensitive convert string s too a bool (y,yes,t,true,on,1->True)\nTrue values are ‘y’, ‘yes’, ‘t’, ‘true’, ‘on’, and ‘1’; false values are ‘n’, ‘no’, ‘f’, ‘false’, ‘off’, and ‘0’. Raises ValueError if ‘val’ is anything else.\n\nfor o in \"y YES t True on 1\".split(): assert str2bool(o)\nfor o in \"n no FALSE off 0\".split(): assert not str2bool(o)\nfor o in 0,None,'',False: assert not str2bool(o)\nfor o in 1,True: assert str2bool(o)\n\n\nsource\n\n\nstr2int\n\n str2int (s)\n\nConvert s to an int\n\nsource\n\n\nstr2float\n\n str2float (s:str)\n\nConvert s to a float\n\nsource\n\n\nstr2list\n\n str2list (s:str)\n\nConvert s to a list\n\nsource\n\n\nstr2date\n\n str2date (s:str)\n\ndate.fromisoformat with empty string handling\n\nsource\n\n\nto_date\n\n to_date (arg)\n\n\nsource\n\n\nto_list\n\n to_list (arg)\n\n\nsource\n\n\nto_float\n\n to_float (arg)\n\n\nsource\n\n\nto_int\n\n to_int (arg)\n\n\nsource\n\n\nto_bool\n\n to_bool (arg)\n\n\nsource\n\n\ntyped\n\n typed (_func=None, cast=False)\n\nDecorator to check param and return types at runtime, with optional casting\ntyped validates argument types at runtime. This is in contrast to MyPy which only offers static type checking.\nFor example, a TypeError will be raised if we try to pass an integer into the first argument of the below function:\n\n@typed\ndef discount(price:int, pct:float) -> float:\n return (1-pct) * price\n\nwith ExceptionExpected(TypeError): discount(100.0, .1)\n\nYou can have automatic casting based on heuristics by specifying typed(cast=True). If casting is not possible, a TypeError is raised.\n\n@typed(cast=True)\ndef discount(price:int, pct:float) -> float:\n return (1-pct) * price\n\nassert 90.0 == discount(100.5, .1) # will auto cast 100.5 to the int 100\nassert 90.0 == discount(' 100 ', .1) # will auto cast the str \"100\" to the int 100\nwith ExceptionExpected(TypeError): discount(\"a\", .1)\n\nWe can also optionally allow multiple types by enumarating the types in a tuple as illustrated below:\n\n@typed\ndef discount(price:int|float, pct:float): \n return (1-pct) * price\n\nassert 90.0 == discount(100.0, .1)\n\n@typed(cast=True)\ndef discount(price:int|None, pct:float):\n return (1-pct) * price\n\nassert 90.0 == discount(100.0, .1)\n\nWe currently do not support union types when casting.\n\n@typed(cast=True)\ndef discount(price:int|float, pct:float):\n return (1-pct) * price\n\nwith ExceptionExpected(AssertionError): assert 90.0 == discount(\"100.0\", .1)\n\ntyped works with classes, too:\n\nclass Foo:\n @typed\n def __init__(self, a:int, b: int, c:str): pass\n @typed(cast=True)\n def test(cls, d:str): return d\n\nwith ExceptionExpected(TypeError): Foo(1, 2, 3) \nassert isinstance(Foo(1,2, 'a string').test(10), str)\n\nIt also works with custom types.\n\n@typed\ndef test_foo(foo: Foo): pass\n\nwith ExceptionExpected(TypeError): test_foo(1)\ntest_foo(Foo(1, 2, 'a string'))\n\n\nclass Bar:\n @typed\n def __init__(self, a:int): self.a = a\n@typed(cast=True)\ndef test_bar(bar: Bar): return bar\n\nassert isinstance(test_bar(1), Bar)\ntest_eq(test_bar(1).a, 1)\nwith ExceptionExpected(TypeError): test_bar(\"foobar\")\n\n\nsource\n\n\nexec_new\n\n exec_new (code)\n\nExecute code in a new environment and return it\n\ng = exec_new('a=1')\ntest_eq(g['a'], 1)\n\n\nsource\n\n\nexec_import\n\n exec_import (mod, sym)\n\nImport sym from mod in a new environment", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "basics.html#notebook-functions", + "href": "basics.html#notebook-functions", + "title": "Basic functionality", + "section": "Notebook functions", + "text": "Notebook functions\n\n\nipython_shell\n\n ipython_shell ()\n\nSame as get_ipython but returns False if not in IPython\n\n\n\nin_ipython\n\n in_ipython ()\n\nCheck if code is running in some kind of IPython environment\n\n\n\nin_colab\n\n in_colab ()\n\nCheck if the code is running in Google Colaboratory\n\n\n\nin_jupyter\n\n in_jupyter ()\n\nCheck if the code is running in a jupyter notebook\n\n\n\nin_notebook\n\n in_notebook ()\n\nCheck if the code is running in a jupyter notebook\nThese variables are available as booleans in fastcore.basics as IN_IPYTHON, IN_JUPYTER, IN_COLAB and IN_NOTEBOOK.\n\nIN_IPYTHON, IN_JUPYTER, IN_COLAB, IN_NOTEBOOK\n\n(True, True, False, True)", + "crumbs": [ + "Basic functionality" + ] + }, + { + "objectID": "dispatch.html", + "href": "dispatch.html", + "title": "Type dispatch", + "section": "", + "text": "from nbdev.showdoc import *\nfrom fastcore.test import *\nfrom fastcore.nb_imports import *", + "crumbs": [ + "Type dispatch" + ] + }, + { + "objectID": "dispatch.html#helpers", + "href": "dispatch.html#helpers", + "title": "Type dispatch", + "section": "Helpers", + "text": "Helpers\n\nsource\n\nlenient_issubclass\n\n lenient_issubclass (cls, types)\n\nIf possible return whether cls is a subclass of types, otherwise return False.\n\nassert not lenient_issubclass(typing.Collection, list)\nassert lenient_issubclass(list, typing.Collection)\nassert lenient_issubclass(typing.Collection, object)\nassert lenient_issubclass(typing.List, typing.Collection)\nassert not lenient_issubclass(typing.Collection, typing.List)\nassert not lenient_issubclass(object, typing.Callable)\n\n\nsource\n\n\nsorted_topologically\n\n sorted_topologically (iterable, cmp=<built-in function lt>,\n reverse=False)\n\nReturn a new list containing all items from the iterable sorted topologically\n\ntd = [3, 1, 2, 5]\ntest_eq(sorted_topologically(td), [1, 2, 3, 5])\ntest_eq(sorted_topologically(td, reverse=True), [5, 3, 2, 1])\n\n\ntd = {int:1, numbers.Number:2, numbers.Integral:3}\ntest_eq(sorted_topologically(td, cmp=lenient_issubclass), [int, numbers.Integral, numbers.Number])\n\n\ntd = [numbers.Integral, tuple, list, int, dict]\ntd = sorted_topologically(td, cmp=lenient_issubclass)\nassert td.index(int) < td.index(numbers.Integral)", + "crumbs": [ + "Type dispatch" + ] + }, + { + "objectID": "dispatch.html#typedispatch", + "href": "dispatch.html#typedispatch", + "title": "Type dispatch", + "section": "TypeDispatch", + "text": "TypeDispatch\nType dispatch, or Multiple dispatch, allows you to change the way a function behaves based upon the input types it recevies. This is a prominent feature in some programming languages like Julia. For example, this is a conceptual example of how multiple dispatch works in Julia, returning different values depending on the input types of x and y:\ncollide_with(x::Asteroid, y::Asteroid) = ... \n# deal with asteroid hitting asteroid\n\ncollide_with(x::Asteroid, y::Spaceship) = ... \n# deal with asteroid hitting spaceship\n\ncollide_with(x::Spaceship, y::Asteroid) = ... \n# deal with spaceship hitting asteroid\n\ncollide_with(x::Spaceship, y::Spaceship) = ... \n# deal with spaceship hitting spaceship\nType dispatch can be especially useful in data science, where you might allow different input types (i.e. numpy arrays and pandas dataframes) to function that processes data. Type dispatch allows you to have a common API for functions that do similar tasks.\nThe TypeDispatch class allows us to achieve type dispatch in Python. It contains a dictionary that maps types from type annotations to functions, which ensures that the proper function is called when passed inputs.\n\nsource\n\nTypeDispatch\n\n TypeDispatch (funcs=(), bases=())\n\nDictionary-like object; __getitem__ matches keys of types using issubclass\nTo demonstrate how TypeDispatch works, we define a set of functions that accept a variety of input types, specified with different type annotations:\n\ndef f2(x:int, y:float): return x+y #int and float for 2nd arg\ndef f_nin(x:numbers.Integral)->int: return x+1 #integral numeric\ndef f_ni2(x:int): return x #integer\ndef f_bll(x:bool|list): return x #bool or list\ndef f_num(x:numbers.Number): return x #Number (root of numerics)\n\nWe can optionally initialize TypeDispatch with a list of functions we want to search. Printing an instance of TypeDispatch will display convenient mapping of types -> functions:\n\nt = TypeDispatch([f_nin,f_ni2,f_num,f_bll,None])\nt\n\n(bool,object) -> f_bll\n(int,object) -> f_ni2\n(Integral,object) -> f_nin\n(Number,object) -> f_num\n(list,object) -> f_bll\n(object,object) -> NoneType\n\n\nNote that only the first two arguments are used for TypeDispatch. If your function only contains one argument, the second parameter will be shown as object. If you pass None into TypeDispatch, then this will be displayed as (object, object) -> NoneType.\nTypeDispatch is a dictionary-like object, which means that you can retrieve a function by the associated type annotation. For example, the statement:\nt[float]\nWill return f_num because that is the matching function that has a type annotation that is a super-class of of float - numbers.Number:\n\nassert issubclass(float, numbers.Number)\ntest_eq(t[float], f_num)\n\nThe same is true for other types as well:\n\ntest_eq(t[np.int32], f_nin)\ntest_eq(t[bool], f_bll)\ntest_eq(t[list], f_bll)\ntest_eq(t[np.int32], f_nin)\n\nIf you try to get a type that doesn’t match, TypeDispatch will return None:\n\ntest_eq(t[str], None)\n\n\nsource\n\n\nTypeDispatch.add\n\n TypeDispatch.add (f)\n\nAdd type t and function f\nThis method allows you to add an additional function to an existing TypeDispatch instance :\n\ndef f_col(x:typing.Collection): return x\nt.add(f_col)\ntest_eq(t[str], f_col)\nt\n\n(bool,object) -> f_bll\n(int,object) -> f_ni2\n(Integral,object) -> f_nin\n(Number,object) -> f_num\n(list,object) -> f_bll\n(typing.Collection,object) -> f_col\n(object,object) -> NoneType\n\n\nIf you accidentally add the same function more than once things will still work as expected:\n\nt.add(f_ni2) \ntest_eq(t[int], f_ni2)\n\nHowever, if you add a function that has a type collision that raises an ambiguity, this will automatically resolve to the latest function added:\n\ndef f_ni3(z:int): return z # collides with f_ni2 with same type annotations\nt.add(f_ni3) \ntest_eq(t[int], f_ni3)\n\n\nUsing bases:\nThe argument bases can optionally accept a single instance of TypeDispatch or a collection (i.e. a tuple or list) of TypeDispatch objects. This can provide functionality similar to multiple inheritance.\nThese are searched for matching functions if no match in your list of functions:\n\ndef f_str(x:str): return x+'1'\n\nt = TypeDispatch([f_nin,f_ni2,f_num,f_bll,None])\nt2 = TypeDispatch(f_str, bases=t) # you can optionally supply a list of TypeDispatch objects for `bases`.\nt2\n\n(str,object) -> f_str\n(bool,object) -> f_bll\n(int,object) -> f_ni2\n(Integral,object) -> f_nin\n(Number,object) -> f_num\n(list,object) -> f_bll\n(object,object) -> NoneType\n\n\n\ntest_eq(t2[int], f_ni2) # searches `t` b/c not found in `t2`\ntest_eq(t2[np.int32], f_nin) # searches `t` b/c not found in `t2`\ntest_eq(t2[float], f_num) # searches `t` b/c not found in `t2`\ntest_eq(t2[bool], f_bll) # searches `t` b/c not found in `t2`\ntest_eq(t2[str], f_str) # found in `t`!\ntest_eq(t2('a'), 'a1') # found in `t`!, and uses __call__\n\no = np.int32(1)\ntest_eq(t2(o), 2) # found in `t2` and uses __call__\n\n\n\nUp To Two Arguments\nTypeDispatch supports up to two arguments when searching for the appropriate function. The following functions f1 and f2 both have two parameters:\n\ndef f1(x:numbers.Integral, y): return x+1 #Integral is a numeric type\ndef f2(x:int, y:float): return x+y\nt = TypeDispatch([f1,f2])\nt\n\n(int,float) -> f2\n(Integral,object) -> f1\n\n\nYou can lookup functions from a TypeDispatch instance with two parameters like this:\n\ntest_eq(t[np.int32], f1)\ntest_eq(t[int,float], f2)\n\nKeep in mind that anything beyond the first two parameters are ignored, and any collisions will be resolved in favor of the most recent function added. In the below example, f1 is ignored in favor of f2 because the first two parameters have identical type hints:\n\ndef f1(a:str, b:int, c:list): return a\ndef f2(a: str, b:int): return b\nt = TypeDispatch([f1,f2])\ntest_eq(t[str, int], f2)\nt\n\n(str,int) -> f2\n\n\n\n\nMatching\nType Dispatch matches types with functions according to whether the supplied class is a subclass or the same class of the type annotation(s) of associated functions.\nLet’s consider an example where we try to retrieve the function corresponding to types of [np.int32, float].\nIn this scenario, f2 will not be matched. This is because the first type annotation of f2, int, is not a superclass (or the same class) of np.int32:\n\ndef f1(x:numbers.Integral, y): return x+1\ndef f2(x:int, y:float): return x+y\nt = TypeDispatch([f1,f2])\n\nassert not issubclass(np.int32, int)\n\nInstead, f1 is a valid match, as its first argument is annoted with the type numbers.Integeral, which np.int32 is a subclass of:\n\nassert issubclass(np.int32, numbers.Integral)\ntest_eq(t[np.int32,float], f1)\n\nIn f1 , the 2nd parameter y is not annotated, which means TypeDispatch will match anything where the first argument matches int that is not matched with anything else:\n\nassert issubclass(int, numbers.Integral) # int is a subclass of numbers.Integral\ntest_eq(t[int], f1)\ntest_eq(t[int,int], f1)\n\nIf no match is possible, None is returned:\n\ntest_eq(t[float,float], None)\n\n\nsource\n\n\n\nTypeDispatch.__call__\n\n TypeDispatch.__call__ (*args, **kwargs)\n\nCall self as a function.\nTypeDispatch is also callable. When you call an instance of TypeDispatch, it will execute the relevant function:\n\ndef f_arr(x:np.ndarray): return x.sum()\ndef f_int(x:np.int32): return x+1\nt = TypeDispatch([f_arr, f_int])\n\narr = np.array([5,4,3,2,1])\ntest_eq(t(arr), 15) # dispatches to f_arr\n\no = np.int32(1)\ntest_eq(t(o), 2) # dispatches to f_int\nassert t.first() is not None\n\nYou can also call an instance of of TypeDispatch when there are two parameters:\n\ndef f1(x:numbers.Integral, y): return x+1\ndef f2(x:int, y:float): return x+y\nt = TypeDispatch([f1,f2])\n\ntest_eq(t(3,2.0), 5)\ntest_eq(t(3,2), 4)\n\nWhen no match is found, a TypeDispatch instance becomes an identity function. This default behavior is leveraged by fasatai for data transformations to provide a sensible default when a matching function cannot be found.\n\ntest_eq(t('a'), 'a')\n\n\nsource\n\n\nTypeDispatch.returns\n\n TypeDispatch.returns (x)\n\nGet the return type of annotation of x.\nYou can optionally pass an object to TypeDispatch.returns and get the return type annotation back:\n\ndef f1(x:int) -> np.ndarray: return np.array(x)\ndef f2(x:str) -> float: return List\ndef f3(x:float): return List # f3 has no return type annotation\n\nt = TypeDispatch([f1, f2, f3])\n\ntest_eq(t.returns(1), np.ndarray) # dispatched to f1\ntest_eq(t.returns('Hello'), float) # dispatched to f2\ntest_eq(t.returns(1.0), None) # dispatched to f3\n\nclass _Test: pass\n_test = _Test()\ntest_eq(t.returns(_test), None) # type `_Test` not found, so None returned\n\n\nUsing TypeDispatch With Methods\nYou can use TypeDispatch when defining methods as well:\n\ndef m_nin(self, x:str|numbers.Integral): return str(x)+'1'\ndef m_bll(self, x:bool): self.foo='a'\ndef m_num(self, x:numbers.Number): return x*2\n\nt = TypeDispatch([m_nin,m_num,m_bll])\nclass A: f = t # set class attribute `f` equal to a TypeDispatch instance\n \na = A()\ntest_eq(a.f(1), '11') #dispatch to m_nin\ntest_eq(a.f(1.), 2.) #dispatch to m_num\ntest_is(a.f.inst, a)\n\na.f(False) # this triggers t.m_bll to run, which sets self.foo to 'a'\ntest_eq(a.foo, 'a')\n\nAs discussed in TypeDispatch.__call__, when there is not a match, TypeDispatch.__call__ becomes an identity function. In the below example, a tuple does not match any type annotations so a tuple is returned:\n\ntest_eq(a.f(()), ())\n\nWe extend the previous example by using bases to add an additional method that supports tuples:\n\ndef m_tup(self, x:tuple): return x+(1,)\nt2 = TypeDispatch(m_tup, bases=t)\n\nclass A2: f = t2\na2 = A2()\ntest_eq(a2.f(1), '11')\ntest_eq(a2.f(1.), 2.)\ntest_is(a2.f.inst, a2)\na2.f(False)\ntest_eq(a2.foo, 'a')\ntest_eq(a2.f(()), (1,))\n\n\n\nUsing TypeDispatch With Class Methods\nYou can use TypeDispatch when defining class methods too:\n\ndef m_nin(cls, x:str|numbers.Integral): return str(x)+'1'\ndef m_bll(cls, x:bool): cls.foo='a'\ndef m_num(cls, x:numbers.Number): return x*2\n\nt = TypeDispatch([m_nin,m_num,m_bll])\nclass A: f = t # set class attribute `f` equal to a TypeDispatch\n\ntest_eq(A.f(1), '11') #dispatch to m_nin\ntest_eq(A.f(1.), 2.) #dispatch to m_num\ntest_is(A.f.owner, A)\n\nA.f(False) # this triggers t.m_bll to run, which sets A.foo to 'a'\ntest_eq(A.foo, 'a')", + "crumbs": [ + "Type dispatch" + ] + }, + { + "objectID": "dispatch.html#typedispatch-decorator", + "href": "dispatch.html#typedispatch-decorator", + "title": "Type dispatch", + "section": "typedispatch Decorator", + "text": "typedispatch Decorator\n\nsource\n\nDispatchReg\n\n DispatchReg ()\n\nA global registry for TypeDispatch objects keyed by function name\n\n@typedispatch\ndef f_td_test(x, y): return f'{x}{y}'\n@typedispatch\ndef f_td_test(x:numbers.Integral|int, y): return x+1\n@typedispatch\ndef f_td_test(x:int, y:float): return x+y\n@typedispatch\ndef f_td_test(x:int, y:int): return x*y\n\ntest_eq(f_td_test(3,2.0), 5)\nassert issubclass(int, numbers.Integral)\ntest_eq(f_td_test(3,2), 6)\n\ntest_eq(f_td_test('a','b'), 'ab')\n\n\nUsing typedispatch With other decorators\nYou can use typedispatch with classmethod and staticmethod decorator\n\nclass A:\n @typedispatch\n def f_td_test(self, x:numbers.Integral, y): return x+1\n @typedispatch\n @classmethod\n def f_td_test(cls, x:int, y:float): return x+y\n @typedispatch\n @staticmethod\n def f_td_test(x:int, y:int): return x*y\n \ntest_eq(A.f_td_test(3,2), 6)\ntest_eq(A.f_td_test(3,2.0), 5)\ntest_eq(A().f_td_test(3,'2.0'), 4)", + "crumbs": [ + "Type dispatch" + ] + }, + { + "objectID": "dispatch.html#casting", + "href": "dispatch.html#casting", + "title": "Type dispatch", + "section": "Casting", + "text": "Casting\nNow that we can dispatch on types, let’s make it easier to cast objects to a different type.\n\nsource\n\nretain_meta\n\n retain_meta (x, res, as_copy=False)\n\nCall res.set_meta(x), if it exists\n\nsource\n\n\ndefault_set_meta\n\n default_set_meta (x, as_copy=False)\n\nCopy over _meta from x to res, if it’s missing\n\n\n\n(object,object) -> cast\nDictionary-like object; __getitem__ matches keys of types using issubclass\nThis works both for plain python classes:…\n\nmk_class('_T1', 'a') # mk_class is a fastai utility that constructs a class.\nclass _T2(_T1): pass\n\nt = _T1(a=1)\nt2 = cast(t, _T2) \nassert t2 is t # t2 refers to the same object as t\nassert isinstance(t, _T2) # t also changed in-place\nassert isinstance(t2, _T2)\n\ntest_eq_type(_T2(a=1), t2)\n\n…as well as for arrays and tensors.\n\nclass _T1(ndarray): pass\n\nt = array([1])\nt2 = cast(t, _T1)\ntest_eq(array([1]), t2)\ntest_eq(_T1, type(t2))\n\nTo customize casting for other types, define a separate cast function with typedispatch for your type.\n\nsource\n\n\nretain_type\n\n retain_type (new, old=None, typ=None, as_copy=False)\n\nCast new to type of old or typ if it’s a superclass\n\nclass _T(tuple): pass\na = _T((1,2))\nb = tuple((1,2))\nc = retain_type(b, typ=_T)\ntest_eq_type(c, a)\n\nIf old has a _meta attribute, its content is passed when casting new to the type of old. In the below example, only the attribute a, but not other_attr is kept, because other_attr is not in _meta:\n\nclass _A():\n set_meta = default_set_meta\n def __init__(self, t): self.t=t\n\nclass _B1(_A):\n def __init__(self, t, a=1):\n super().__init__(t)\n self._meta = {'a':a}\n self.other_attr = 'Hello' # will not be kept after casting.\n \nx = _B1(1, a=2)\nb = _A(1)\nc = retain_type(b, old=x)\ntest_eq(c._meta, {'a': 2})\nassert not getattr(c, 'other_attr', None)\n\n\nsource\n\n\nretain_types\n\n retain_types (new, old=None, typs=None)\n\nCast each item of new to type of matching item in old if it’s a superclass\n\nclass T(tuple): pass\n\nt1,t2 = retain_types((1,(1,(1,1))), (2,T((2,T((3,4))))))\ntest_eq_type(t1, 1)\ntest_eq_type(t2, T((1,T((1,1)))))\n\nt1,t2 = retain_types((1,(1,(1,1))), typs = {tuple: [int, {T: [int, {T: [int,int]}]}]})\ntest_eq_type(t1, 1)\ntest_eq_type(t2, T((1,T((1,1)))))\n\n\nsource\n\n\nexplode_types\n\n explode_types (o)\n\nReturn the type of o, potentially in nested dictionaries for thing that are listy\n\ntest_eq(explode_types((2,T((2,T((3,4)))))), {tuple: [int, {T: [int, {T: [int,int]}]}]})", + "crumbs": [ + "Type dispatch" + ] + }, + { + "objectID": "meta.html", + "href": "meta.html", + "title": "Meta", + "section": "", + "text": "from fastcore.foundation import *\nfrom nbdev.showdoc import *\nfrom fastcore.nb_imports import *\nSee this blog post for more information about metaclasses.\nsource", + "crumbs": [ + "Meta" + ] + }, + { + "objectID": "meta.html#metaprogramming", + "href": "meta.html#metaprogramming", + "title": "Meta", + "section": "Metaprogramming", + "text": "Metaprogramming\n\nsource\n\nempty2none\n\n empty2none (p)\n\nReplace Parameter.empty with None\n\nsource\n\n\nanno_dict\n\n anno_dict (f)\n\n__annotation__ dictionary withemptycast toNone`, returning empty if doesn’t exist\n\ndef _f(a:int, b:L)->str: ...\ntest_eq(anno_dict(_f), {'a': int, 'b': L, 'return': str})\n\n\nsource\n\n\nuse_kwargs_dict\n\n use_kwargs_dict (keep=False, **kwargs)\n\nDecorator: replace **kwargs in signature with names params\nReplace all **kwargs with named arguments like so:\n\n@use_kwargs_dict(y=1,z=None)\ndef foo(a, b=1, **kwargs): pass\n\ntest_sig(foo, '(a, b=1, *, y=1, z=None)')\n\nAdd named arguments, but optionally keep **kwargs by setting keep=True:\n\n@use_kwargs_dict(y=1,z=None, keep=True)\ndef foo(a, b=1, **kwargs): pass\n\ntest_sig(foo, '(a, b=1, *, y=1, z=None, **kwargs)')\n\n\nsource\n\n\nuse_kwargs\n\n use_kwargs (names, keep=False)\n\nDecorator: replace **kwargs in signature with names params\nuse_kwargs is different than use_kwargs_dict as it only replaces **kwargs with named parameters without any default values:\n\n@use_kwargs(['y', 'z'])\ndef foo(a, b=1, **kwargs): pass\n\ntest_sig(foo, '(a, b=1, *, y=None, z=None)')\n\nYou may optionally keep the **kwargs argument in your signature by setting keep=True:\n\n@use_kwargs(['y', 'z'], keep=True)\ndef foo(a, *args, b=1, **kwargs): pass\ntest_sig(foo, '(a, *args, b=1, y=None, z=None, **kwargs)')\n\n\nsource\n\n\ndelegates\n\n delegates (to:function=None, keep=False, but:list=None)\n\nDecorator: replace **kwargs in signature with params from to\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nto\nfunction\nNone\nDelegatee\n\n\nkeep\nbool\nFalse\nKeep kwargs in decorated function?\n\n\nbut\nlist\nNone\nExclude these parameters from signature\n\n\n\nA common Python idiom is to accept **kwargs in addition to named parameters that are passed onto other function calls. It is especially common to use **kwargs when you want to give the user an option to override default parameters of any functions or methods being called by the parent function.\nFor example, suppose we have have a function foo that passes arguments to baz like so:\n\ndef baz(a, b:int=2, c:int=3): return a + b + c\n\ndef foo(c, a, **kwargs):\n return c + baz(a, **kwargs)\n\nassert foo(c=1, a=1) == 7\n\nThe problem with this approach is the api for foo is obfuscated. Users cannot introspect what the valid arguments for **kwargs are without reading the source code. When a user tries tries to introspect the signature of foo, they are presented with this:\n\ninspect.signature(foo)\n\n<Signature (c, a, **kwargs)>\n\n\nWe can address this issue by using the decorator delegates to include parameters from other functions. For example, if we apply the delegates decorator to foo to include parameters from baz:\n\n@delegates(baz)\ndef foo(c, a, **kwargs):\n return c + baz(a, **kwargs)\n\ntest_sig(foo, '(c, a, *, b: int = 2)')\ninspect.signature(foo)\n\n<Signature (c, a, *, b: int = 2)>\n\n\nWe can optionally decide to keep **kwargs by setting keep=True:\n\n@delegates(baz, keep=True)\ndef foo(c, a, **kwargs):\n return c + baz(a, **kwargs)\n\ninspect.signature(foo)\n\n<Signature (c, a, *, b: int = 2, **kwargs)>\n\n\nIt is important to note that only parameters with default parameters are included. For example, in the below scenario only c, but NOT e and d are included in the signature of foo after applying delegates:\n\ndef basefoo(e, d, c=2): pass\n\n@delegates(basefoo)\ndef foo(a, b=1, **kwargs): pass\ninspect.signature(foo) # e and d are not included b/c they don't have default parameters.\n\n<Signature (a, b=1, *, c=2)>\n\n\nThe reason that required arguments (i.e. those without default parameters) are automatically excluded is that you should be explicitly implementing required arguments into your function’s signature rather than relying on delegates.\nAdditionally, you can exclude specific parameters from being included in the signature with the but parameter. In the example below, we exclude the parameter d:\n\ndef basefoo(e, c=2, d=3): pass\n\n@delegates(basefoo, but= ['d'])\ndef foo(a, b=1, **kwargs): pass\n\ntest_sig(foo, '(a, b=1, *, c=2)')\ninspect.signature(foo)\n\n<Signature (a, b=1, *, c=2)>\n\n\nYou can also use delegates between methods in a class. Here is an example of delegates with class methods:\n\n# example 1: class methods\nclass _T():\n @classmethod\n def foo(cls, a=1, b=2):\n pass\n \n @classmethod\n @delegates(foo)\n def bar(cls, c=3, **kwargs):\n pass\n\ntest_sig(_T.bar, '(c=3, *, a=1, b=2)')\n\nHere is the same example with instance methods:\n\n# example 2: instance methods\nclass _T():\n def foo(self, a=1, b=2):\n pass\n \n @delegates(foo)\n def bar(self, c=3, **kwargs):\n pass\n\nt = _T()\ntest_sig(t.bar, '(c=3, *, a=1, b=2)')\n\nYou can also delegate between classes. By default, the delegates decorator will delegate to the superclass:\n\nclass BaseFoo:\n def __init__(self, e, c=2): pass\n\n@delegates()# since no argument was passsed here we delegate to the superclass\nclass Foo(BaseFoo):\n def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs)\n\ntest_sig(Foo, '(a, b=1, *, c=2)')\n\n\nsource\n\n\nmethod\n\n method (f)\n\nMark f as a method\nThe method function is used to change a function’s type to a method. In the below example we change the type of a from a function to a method:\n\ndef a(x=2): return x + 1\nassert type(a).__name__ == 'function'\n\na = method(a)\nassert type(a).__name__ == 'method'\n\n\nsource\n\n\nfuncs_kwargs\n\n funcs_kwargs (as_method=False)\n\nReplace methods in cls._methods with those from kwargs\nThe func_kwargs decorator allows you to add a list of functions or methods to an existing class. You must set this list as a class attribute named _methods when defining your class. Additionally, you must incldue the **kwargs argument in the ___init__ method of your class.\nAfter defining your class this way, you can add functions to your class upon instantation as illusrated below.\nFor example, we define class T to allow adding the function b to class T as follows (note that this function is stored as an attribute of T and doesn’t have access to cls or self):\n\n@funcs_kwargs\nclass T:\n _methods=['b'] # allows you to add method b upon instantiation\n def __init__(self, f=1, **kwargs): pass # don't forget to include **kwargs in __init__\n def a(self): return 1\n def b(self): return 2\n \nt = T()\ntest_eq(t.a(), 1)\ntest_eq(t.b(), 2)\n\nBecause we defined the class T this way, the signature of T indicates the option to add the function or method(s) specified in _methods. In this example, b is added to the signature:\n\ntest_sig(T, '(f=1, *, b=None)')\ninspect.signature(T)\n\n<Signature (f=1, *, b=None)>\n\n\nYou can now add the function b to class T upon instantiation:\n\ndef _new_func(): return 5\n\nt = T(b = _new_func)\ntest_eq(t.b(), 5)\n\nIf you try to add a function with a name not listed in _methods it will be ignored. In the below example, the attempt to add a function named a is ignored:\n\nt = T(a = lambda:3)\ntest_eq(t.a(), 1) # the attempt to add a is ignored and uses the original method instead.\n\nNote that you can also add methods not defined in the original class as long it is specified in the _methods attribute:\n\n@funcs_kwargs\nclass T:\n _methods=['c']\n def __init__(self, f=1, **kwargs): pass\n\nt = T(c = lambda: 4)\ntest_eq(t.c(), 4)\n\nUntil now, these examples showed how to add functions stored as an instance attribute without access to self. However, if you need access to self you can set as_method=True in the func_kwargs decorator to add a method instead:\n\ndef _f(self,a=1): return self.num + a # access the num attribute from the instance\n\n@funcs_kwargs(as_method=True)\nclass T: \n _methods=['b']\n num = 5\n \nt = T(b = _f) # adds method b\ntest_eq(t.b(5), 10) # self.num + 5 = 10\n\nHere is an example of how you might use this functionality with inheritence:\n\ndef _f(self,a=1): return self.num * a #multiply instead of add \n\nclass T2(T):\n def __init__(self,num):\n super().__init__(b = _f) # add method b from the super class\n self.num=num\n \nt = T2(num=3)\ntest_eq(t.b(a=5), 15) # 3 * 5 = 15\ntest_sig(T2, '(num)')", + "crumbs": [ + "Meta" + ] + }, + { + "objectID": "xml.html", + "href": "xml.html", + "title": "XML", + "section": "", + "text": "from IPython.display import Markdown\nfrom pprint import pprint\n\nfrom fastcore.test import test_eq", + "crumbs": [ + "XML" + ] + }, + { + "objectID": "xml.html#ft-functions", + "href": "xml.html#ft-functions", + "title": "XML", + "section": "FT functions", + "text": "FT functions\n\nsource\n\nattrmap\n\n attrmap (o)\n\n\nsource\n\n\nvalmap\n\n valmap (o)\n\n\nsource\n\n\nFT\n\n FT (tag:str, cs:tuple, attrs:dict=None, void_=False, **kwargs)\n\nA ‘Fast Tag’ structure, containing tag,children,and attrs\n\nsource\n\n\nft\n\n ft (tag:str, *c, void_:bool=False, attrmap:<built-\n infunctioncallable>=<function attrmap>, valmap:<built-\n infunctioncallable>=<function valmap>, ft_cls=<class '__main__.FT'>,\n **kw)\n\nCreate an FT structure for to_xml()\nThe main HTML tags are exported as ft partials.\nAttributes are passed as keywords. Use ‘klass’ and ‘fr’ instead of ‘class’ and ‘for’, to avoid Python reserved word clashes.\n\nsource\n\n\nHtml\n\n Html (*c, doctype=True, **kwargs)\n\nAn HTML tag, optionally preceeded by !DOCTYPE HTML\n\nsamp = Html(\n Head(Title('Some page')),\n Body(Div('Some text\\nanother line', (Input(name=\"jph's\"), Img(src=\"filename\", data=1)),\n cls=['myclass', 'another'],\n style={'padding':1, 'margin':2}))\n)\npprint(samp)\n\n(!doctype((),{'html': True}),\n html((head((title(('Some page',),{}),),{}), body((div(('Some text\\nanother line', input((),{'name': \"jph's\"}), img((),{'src': 'filename', 'data': 1})),{'class': 'myclass another', 'style': 'padding:1; margin:2'}),),{})),{}))\n\n\n\nelem = P('Some text', id=\"myid\")\nprint(elem.tag)\nprint(elem.children)\nprint(elem.attrs)\n\np\n('Some text',)\n{'id': 'myid'}\n\n\nYou can get and set attrs directly:\n\nelem.id = 'newid'\nprint(elem.id, elem.get('id'), elem.get('foo', 'missing'))\nelem\n\nnewid newid missing\n\n\np(('Some text',),{'id': 'newid'})\n\n\n\nsource\n\n\nSafe\n*str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str\nCreate a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.*", + "crumbs": [ + "XML" + ] + }, + { + "objectID": "xml.html#conversion-to-xmlhtml", + "href": "xml.html#conversion-to-xmlhtml", + "title": "XML", + "section": "Conversion to XML/HTML", + "text": "Conversion to XML/HTML\n\nsource\n\nto_xml\n\n to_xml (elm, lvl=0, indent=True, do_escape=True)\n\nConvert ft element tree into an XML string\n\nh = to_xml(samp, do_escape=False)\nprint(h)\n\n<!doctype html>\n<html>\n <head>\n <title>Some page</title>\n </head>\n <body>\n <div class=\"myclass another\" style=\"padding:1; margin:2\">\nSome text\nanother line <input name=\"jph's\">\n<img src=\"filename\" data=\"1\"> </div>\n </body>\n</html>\n\n\n\n\nclass PageTitle:\n def __ft__(self): return H1(\"Hello\")\n\nclass HomePage:\n def __ft__(self): return Div(PageTitle(), Div('hello'))\n\nh = to_xml(Div(HomePage()))\nexpected_output = \"\"\"<div>\n <div>\n <h1>Hello</h1>\n <div>hello</div>\n </div>\n</div>\n\"\"\"\nassert h == expected_output\n\n\nprint(h)\n\n<div>\n <div>\n <h1>Hello</h1>\n <div>hello</div>\n </div>\n</div>\n\n\n\n\nh = to_xml(samp, indent=False)\nprint(h)\n\n<!doctype html><html><head><title>Some page</title></head><body><div class=\"myclass another\" style=\"padding:1; margin:2\">Some text\nanother line<input name=\"jph's\"><img src=\"filename\" data=\"1\"></div></body></html>\n\n\nInteroperability both directions with Django and Jinja using the html() protocol:\n\ndef _esc(s): return s.__html__() if hasattr(s, '__html__') else Safe(escape(s))\n\nr = Safe('<b>Hello from Django</b>')\nprint(to_xml(Div(r)))\nprint(_esc(Div(P('Hello from fastcore <3'))))\n\n<div><b>Hello from Django</b></div>\n\n<div>\n <p>Hello from fastcore <3</p>\n</div>", + "crumbs": [ + "XML" + ] + }, + { + "objectID": "xml.html#display", + "href": "xml.html#display", + "title": "XML", + "section": "Display", + "text": "Display\n\nsource\n\nhighlight\n\n highlight (s, lang='html')\n\nMarkdown to syntax-highlight s in language lang\n\nsource\n\n\nshowtags\n\n showtags (s)\n\nYou can also reorder the children to come after the attrs, if you use this alternative syntax for FT where the children are in a second pair of () (behind the scenes this is because FT implements __call__ to add children).\n\nBody(klass='myclass')(\n Div(style='padding:3px')(\n 'Some text 1<2',\n I(spurious=True)('in italics'),\n Input(name='me'),\n Img(src=\"filename\", data=1)\n )\n)\n\n<body class=\"myclass\">\n <div style=\"padding:3px\">\nSome text 1<2<i spurious>in italics</i> <input name=\"me\">\n<img src=\"filename\" data=\"1\"> </div>\n</body>\n\n\n\nsource\n\n\ngetattr\n\n __getattr__ (tag)", + "crumbs": [ + "XML" + ] + }, + { + "objectID": "transform.html", + "href": "transform.html", + "title": "Transforms", + "section": "", + "text": "from __future__ import annotations\nfrom nbdev.showdoc import *\nfrom fastcore.test import *\nfrom fastcore.nb_imports import *\n\nThe classes here provide functionality for creating a composition of partially reversible functions. By “partially reversible” we mean that a transform can be decoded, creating a form suitable for display. This is not necessarily identical to the original form (e.g. a transform that changes a byte tensor to a float tensor does not recreate a byte tensor when decoded, since that may lose precision, and a float tensor can be displayed already).\nClasses are also provided and for composing transforms, and mapping them over collections. Pipeline is a transform which composes several Transform, knowing how to decode them or show an encoded item.\n\nsource\n\nTransform\n\n Transform (enc=None, dec=None, split_idx=None, order=None)\n\nDelegates (__call__,decode,setup) to (encodes,decodes,setups) if split_idx matches\nA Transform is the main building block of the fastai data pipelines. In the most general terms a transform can be any function you want to apply to your data, however the Transform class provides several mechanisms that make the process of building them easy and flexible.\n\n\nThe main Transform features:\n\nType dispatch - Type annotations are used to determine if a transform should be applied to the given argument. It also gives an option to provide several implementations and it choses the one to run based on the type. This is useful for example when running both independent and dependent variables through the pipeline where some transforms only make sense for one and not the other. Another usecase is designing a transform that handles different data formats. Note that if a transform takes multiple arguments only the type of the first one is used for dispatch.\nHandling of tuples - When a tuple (or a subclass of tuple) of data is passed to a transform it will get applied to each element separately. You can opt out of this behavior by passing a list or an L, as only tuples gets this specific behavior. An alternative is to use ItemTransform defined below, which will always take the input as a whole.\nReversability - A transform can be made reversible by implementing the decodes method. This is mainly used to turn something like a category which is encoded as a number back into a label understandable by humans for showing purposes. Like the regular call method, the decode method that is used to decode will be applied over each element of a tuple separately.\nType propagation - Whenever possible a transform tries to return data of the same type it received. Mainly used to maintain semantics of things like ArrayImage which is a thin wrapper of pytorch’s Tensor. You can opt out of this behavior by adding ->None return type annotation.\nPreprocessing - The setup method can be used to perform any one-time calculations to be later used by the transform, for example generating a vocabulary to encode categorical data.\nFiltering based on the dataset type - By setting the split_idx flag you can make the transform be used only in a specific DataSource subset like in training, but not validation.\nOrdering - You can set the order attribute which the Pipeline uses when it needs to merge two lists of transforms.\nAppending new behavior with decorators - You can easily extend an existing Transform by creating encodes or decodes methods for new data types. You can put those new methods outside the original transform definition and decorate them with the class you wish them patched into. This can be used by the fastai library users to add their own behavior, or multiple modules contributing to the same transform.\n\n\n\nDefining a Transform\nThere are a few ways to create a transform with different ratios of simplicity to flexibility. - Extending the Transform class - Use inheritence to implement the methods you want. - Passing methods to the constructor - Instantiate the Transform class and pass your functions as enc and dec arguments. - @Transform decorator - Turn any function into a Transform by just adding a decorator - very straightforward if all you need is a single encodes implementation. - Passing a function to fastai APIs - Same as above, but when passing a function to other transform aware classes like Pipeline or TfmdDS you don’t even need a decorator. Your function will get converted to a Transform automatically.\nA simple way to create a Transform is to pass a function to the constructor. In the below example, we pass an anonymous function that does integer division by 2:\n\nf = Transform(lambda o:o//2)\n\nIf you call this transform, it will apply the transformation:\n\ntest_eq_type(f(2), 1)\n\nAnother way to define a Transform is to extend the Transform class:\n\nclass A(Transform): pass\n\nHowever, to enable your transform to do something, you have to define an encodes method. Note that we can use the class name as a decorator to add this method to the original class.\n\n@A\ndef encodes(self, x): return x+1\n\nf1 = A()\ntest_eq(f1(1), 2) # f1(1) is the same as f1.encode(1)\n\nIn addition to adding an encodes method, we can also add a decodes method. This enables you to call the decode method (without an s). For more information about the purpose of decodes, see the discussion about Reversibility in the above section.\nJust like with encodes, you can add a decodes method to the original class by using the class name as a decorator:\n\nclass B(A): pass\n\n@B\ndef decodes(self, x): return x-1\n\nf2 = B()\ntest_eq(f2.decode(2), 1)\n\ntest_eq(f2(1), 2) # uses A's encode method from the parent class\n\nIf you do not define an encodes or decodes method the original value will be returned:\n\nclass _Tst(Transform): pass \n\nf3 = _Tst() # no encodes or decodes method have been defined\ntest_eq_type(f3.decode(2.0), 2.0)\ntest_eq_type(f3(2), 2)\n\nTransforms can be created from class methods too:\n\nclass A:\n @classmethod\n def create(cls, x:int): return x+1\ntest_eq(Transform(A.create)(1), 2)\n\n\nDefining Transforms With A Decorator\nTransform can be used as a decorator to turn a function into a Transform.\n\n@Transform\ndef f(x): return x//2\ntest_eq_type(f(2), 1)\ntest_eq_type(f.decode(2.0), 2.0)\n\n@Transform\ndef f(x): return x*2\ntest_eq_type(f(2), 4)\ntest_eq_type(f.decode(2.0), 2.0)\n\n\n\nTyped Dispatch and Transforms\nWe can also apply different transformations depending on the type of the input passed by using TypedDispatch. TypedDispatch automatically works with Transform when using type hints:\n\nclass A(Transform): pass\n\n@A\ndef encodes(self, x:int): return x//2\n\n@A\ndef encodes(self, x:float): return x+1\n\nWhen we pass in an int, this calls the first encodes method:\n\nf = A()\ntest_eq_type(f(3), 1)\n\nWhen we pass in a float, this calls the second encodes method:\n\ntest_eq_type(f(2.), 3.)\n\nWhen we pass in a type that is not specified in encodes, the original value is returned:\n\ntest_eq(f('a'), 'a')\n\nIf the type annotation is a tuple, then any type in the tuple will match:\n\nclass MyClass(int): pass\n\nclass A(Transform):\n def encodes(self, x:MyClass|float): return x/2\n def encodes(self, x:str|list): return str(x)+'_1'\n\nf = A()\n\nThe below two examples match the first encodes, with a type of MyClass and float, respectively:\n\ntest_eq(f(MyClass(2)), 1.) # input is of type MyClass \ntest_eq(f(6.0), 3.0) # input is of type float\n\nThe next two examples match the second encodes method, with a type of str and list, respectively:\n\ntest_eq(f('a'), 'a_1') # input is of type str\ntest_eq(f(['a','b','c']), \"['a', 'b', 'c']_1\") # input is of type list\n\n\n\nCasting Types With Transform\nWithout any intervention it is easy for operations to change types in Python. For example, FloatSubclass (defined below) becomes a float after performing multiplication:\n\nclass FloatSubclass(float): pass\ntest_eq_type(FloatSubclass(3.0) * 2, 6.0)\n\nThis behavior is often not desirable when performing transformations on data. Therefore, Transform will attempt to cast the output to be of the same type as the input by default. In the below example, the output will be cast to a FloatSubclass type to match the type of the input:\n\n@Transform\ndef f(x): return x*2\n\ntest_eq_type(f(FloatSubclass(3.0)), FloatSubclass(6.0))\n\nWe can optionally turn off casting by annotating the transform function with a return type of None:\n\n@Transform\ndef f(x)-> None: return x*2 # Same transform as above, but with a -> None annotation\n\ntest_eq_type(f(FloatSubclass(3.0)), 6.0) # Casting is turned off because of -> None annotation\n\nHowever, Transform will only cast output back to the input type when the input is a subclass of the output. In the below example, the input is of type FloatSubclass which is not a subclass of the output which is of type str. Therefore, the output doesn’t get cast back to FloatSubclass and stays as type str:\n\n@Transform\ndef f(x): return str(x)\n \ntest_eq_type(f(Float(2.)), '2.0')\n\nJust like encodes, the decodes method will cast outputs to match the input type in the same way. In the below example, the output of decodes remains of type MySubclass:\n\nclass MySubclass(int): pass\n\ndef enc(x): return MySubclass(x+1)\ndef dec(x): return x-1\n\n\nf = Transform(enc,dec)\nt = f(1) # t is of type MySubclass\ntest_eq_type(f.decode(t), MySubclass(1)) # the output of decode is cast to MySubclass to match the input type.\n\n\n\nApply Transforms On Subsets With split_idx\nYou can apply transformations to subsets of data by specifying a split_idx property. If a transform has a split_idx then it’s only applied if the split_idx param matches. In the below example, we set split_idx equal to 1:\n\ndef enc(x): return x+1\ndef dec(x): return x-1\nf = Transform(enc,dec)\nf.split_idx = 1\n\nThe transformations are applied when a matching split_idx parameter is passed:\n\ntest_eq(f(1, split_idx=1),2)\ntest_eq(f.decode(2, split_idx=1),1)\n\nOn the other hand, transformations are ignored when the split_idx parameter does not match:\n\ntest_eq(f(1, split_idx=0), 1)\ntest_eq(f.decode(2, split_idx=0), 2)\n\n\n\nTransforms on Lists\nTransform operates on lists as a whole, not element-wise:\n\nclass A(Transform):\n def encodes(self, x): return dict(x)\n def decodes(self, x): return list(x.items())\n \nf = A()\n_inp = [(1,2), (3,4)]\nt = f(_inp)\n\ntest_eq(t, dict(_inp))\ntest_eq(f.decodes(t), _inp)\n\nIf you want a transform to operate on a list elementwise, you must implement this appropriately in the encodes and decodes methods:\n\nclass AL(Transform): pass\n\n@AL\ndef encodes(self, x): return [x_+1 for x_ in x]\n\n@AL\ndef decodes(self, x): return [x_-1 for x_ in x]\n\nf = AL()\nt = f([1,2])\n\ntest_eq(t, [2,3])\ntest_eq(f.decode(t), [1,2])\n\n\n\nTransforms on Tuples\nUnlike lists, Transform operates on tuples element-wise.\n\ndef neg_int(x): return -x\nf = Transform(neg_int)\n\ntest_eq(f((1,2,3)), (-1,-2,-3))\n\nTransforms will also apply TypedDispatch element-wise on tuples when an input type annotation is specified. In the below example, the values 1.0 and 3.0 are ignored because they are of type float, not int:\n\ndef neg_int(x:int): return -x\nf = Transform(neg_int)\n\ntest_eq(f((1.0, 2, 3.0)), (1.0, -2, 3.0))\n\nAnother example of how Transform can use TypedDispatch with tuples is shown below:\n\nclass B(Transform): pass\n\n@B\ndef encodes(self, x:int): return x+1\n\n@B\ndef encodes(self, x:str): return x+'hello'\n\n@B\ndef encodes(self, x): return str(x)+'!'\n\nIf the input is not an int or str, the third encodes method will apply:\n\nb = B()\ntest_eq(b([1]), '[1]!') \ntest_eq(b([1.0]), '[1.0]!')\n\nHowever, if the input is a tuple, then the appropriate method will apply according to the type of each element in the tuple:\n\ntest_eq(b(('1',)), ('1hello',))\ntest_eq(b((1,2)), (2,3))\ntest_eq(b(('a',1.0)), ('ahello','1.0!'))\n\nDispatching over tuples works recursively, by the way:\n\nclass B(Transform):\n def encodes(self, x:int): return x+1\n def encodes(self, x:str): return x+'_hello'\n def decodes(self, x:int): return x-1\n def decodes(self, x:str): return x.replace('_hello', '')\n\nf = B()\nstart = (1.,(2,'3'))\nt = f(start)\ntest_eq_type(t, (1.,(3,'3_hello')))\ntest_eq(f.decode(t), start)\n\nDispatching also works with typing module type classes, like numbers.integral:\n\n@Transform\ndef f(x:numbers.Integral): return x+1\n\nt = f((1,'1',1))\ntest_eq(t, (2, '1', 2))\n\n\nsource\n\n\n\nInplaceTransform\n\n InplaceTransform (enc=None, dec=None, split_idx=None, order=None)\n\nA Transform that modifies in-place and just returns whatever it’s passed\n\nclass A(InplaceTransform): pass\n\n@A\ndef encodes(self, x:pd.Series): x.fillna(10, inplace=True)\n \nf = A()\n\ntest_eq_type(f(pd.Series([1,2,None])),pd.Series([1,2,10],dtype=np.float64)) #fillna fills with floats.\n\n\nsource\n\n\nDisplayedTransform\n\n DisplayedTransform (enc=None, dec=None, split_idx=None, order=None)\n\nA transform with a __repr__ that shows its attrs\nTransforms normally are represented by just their class name and a list of encodes and decodes implementations:\n\nclass A(Transform): encodes,decodes = noop,noop\nf = A()\nf\n\nA:\nencodes: (object,object) -> noop\ndecodes: (object,object) -> noop\n\n\nA DisplayedTransform will in addition show the contents of all attributes listed in the comma-delimited string self.store_attrs:\n\nclass A(DisplayedTransform):\n encodes = noop\n def __init__(self, a, b=2):\n super().__init__()\n store_attr()\n \nA(a=1,b=2)\n\nA -- {'a': 1, 'b': 2}:\nencodes: (object,object) -> noop\ndecodes: \n\n\n\nsource\n\n\nItemTransform\n\n ItemTransform (enc=None, dec=None, split_idx=None, order=None)\n\nA transform that always take tuples as items\nItemTransform is the class to use to opt out of the default behavior of Transform.\n\nclass AIT(ItemTransform): \n def encodes(self, xy): x,y=xy; return (x+y,y)\n def decodes(self, xy): x,y=xy; return (x-y,y)\n \nf = AIT()\ntest_eq(f((1,2)), (3,2))\ntest_eq(f.decode((3,2)), (1,2))\n\nIf you pass a special tuple subclass, the usual retain type behavior of Transform will keep it:\n\nclass _T(tuple): pass\nx = _T((1,2))\ntest_eq_type(f(x), _T((3,2)))\n\n\nsource\n\n\nget_func\n\n get_func (t, name, *args, **kwargs)\n\nGet the t.name (potentially partial-ized with args and kwargs) or noop if not defined\nThis works for any kind of t supporting getattr, so a class or a module.\n\ntest_eq(get_func(operator, 'neg', 2)(), -2)\ntest_eq(get_func(operator.neg, '__call__')(2), -2)\ntest_eq(get_func(list, 'foobar')([2]), [2])\na = [2,1]\nget_func(list, 'sort')(a)\ntest_eq(a, [1,2])\n\nTransforms are built with multiple-dispatch: a given function can have several methods depending on the type of the object received. This is done directly with the TypeDispatch module and type-annotation in Transform, but you can also use the following class.\n\nsource\n\n\nFunc\n\n Func (name, *args, **kwargs)\n\nBasic wrapper around a name with args and kwargs to call on a given type\nYou can call the Func object on any module name or type, even a list of types. It will return the corresponding function (with a default to noop if nothing is found) or list of functions.\n\ntest_eq(Func('sqrt')(math), math.sqrt)\n\n\n\n\nSig\n\n Sig (*args, **kwargs)\n\nSig is just sugar-syntax to create a Func object more easily with the syntax Sig.name(*args, **kwargs).\n\nf = Sig.sqrt()\ntest_eq(f(math), math.sqrt)\n\n\nsource\n\n\ncompose_tfms\n\n compose_tfms (x, tfms, is_enc=True, reverse=False, **kwargs)\n\nApply all func_nm attribute of tfms on x, maybe in reverse order\n\ndef to_int (x): return Int(x)\ndef to_float(x): return Float(x)\ndef double (x): return x*2\ndef half(x)->None: return x/2\n\n\ndef test_compose(a, b, *fs): test_eq_type(compose_tfms(a, tfms=map(Transform,fs)), b)\n\ntest_compose(1, Int(1), to_int)\ntest_compose(1, Float(1), to_int,to_float)\ntest_compose(1, Float(2), to_int,to_float,double)\ntest_compose(2.0, 2.0, to_int,double,half)\n\n\nclass A(Transform):\n def encodes(self, x:float): return Float(x+1)\n def decodes(self, x): return x-1\n \ntfms = [A(), Transform(math.sqrt)]\nt = compose_tfms(3., tfms=tfms)\ntest_eq_type(t, Float(2.))\ntest_eq(compose_tfms(t, tfms=tfms, is_enc=False), 1.)\ntest_eq(compose_tfms(4., tfms=tfms, reverse=True), 3.)\n\n\ntfms = [A(), Transform(math.sqrt)]\ntest_eq(compose_tfms((9,3.), tfms=tfms), (3,2.))\n\n\nsource\n\n\nmk_transform\n\n mk_transform (f)\n\nConvert function f to Transform if it isn’t already one\n\nsource\n\n\ngather_attrs\n\n gather_attrs (o, k, nm)\n\nUsed in getattr to collect all attrs k from self.{nm}\n\nsource\n\n\ngather_attr_names\n\n gather_attr_names (o, nm)\n\nUsed in dir to collect all attrs k from self.{nm}\n\nsource\n\n\nPipeline\n\n Pipeline (funcs=None, split_idx=None)\n\nA pipeline of composed (for encode/decode) transforms, setup with types\n\nadd_docs(Pipeline,\n __call__=\"Compose `__call__` of all `fs` on `o`\",\n decode=\"Compose `decode` of all `fs` on `o`\",\n show=\"Show `o`, a single item from a tuple, decoding as needed\",\n add=\"Add transforms `ts`\",\n setup=\"Call each tfm's `setup` in order\")\n\nPipeline is a wrapper for compose_tfms. You can pass instances of Transform or regular functions in funcs, the Pipeline will wrap them all in Transform (and instantiate them if needed) during the initialization. It handles the transform setup by adding them one at a time and calling setup on each, goes through them in order in __call__ or decode and can show an object by applying decoding the transforms up until the point it gets an object that knows how to show itself.\n\n# Empty pipeline is noop\npipe = Pipeline()\ntest_eq(pipe(1), 1)\ntest_eq(pipe((1,)), (1,))\n# Check pickle works\nassert pickle.loads(pickle.dumps(pipe))\n\n\nclass IntFloatTfm(Transform):\n def encodes(self, x): return Int(x)\n def decodes(self, x): return Float(x)\n foo=1\n\nint_tfm=IntFloatTfm()\n\ndef neg(x): return -x\nneg_tfm = Transform(neg, neg)\n\n\npipe = Pipeline([neg_tfm, int_tfm])\n\nstart = 2.0\nt = pipe(start)\ntest_eq_type(t, Int(-2))\ntest_eq_type(pipe.decode(t), Float(start))\ntest_stdout(lambda:pipe.show(t), '-2')\n\n\npipe = Pipeline([neg_tfm, int_tfm])\nt = pipe(start)\ntest_stdout(lambda:pipe.show(pipe((1.,2.))), '-1\\n-2')\ntest_eq(pipe.foo, 1)\nassert 'foo' in dir(pipe)\nassert 'int_float_tfm' in dir(pipe)\n\nYou can add a single transform or multiple transforms ts using Pipeline.add. Transforms will be ordered by Transform.order.\n\npipe = Pipeline([neg_tfm, int_tfm])\nclass SqrtTfm(Transform):\n order=-1\n def encodes(self, x): \n return x**(.5)\n def decodes(self, x): return x**2\npipe.add(SqrtTfm())\ntest_eq(pipe(4),-2)\ntest_eq(pipe.decode(-2),4)\npipe.add([SqrtTfm(),SqrtTfm()])\ntest_eq(pipe(256),-2)\ntest_eq(pipe.decode(-2),256)\n\nTransforms are available as attributes named with the snake_case version of the names of their types. Attributes in transforms can be directly accessed as attributes of the pipeline.\n\ntest_eq(pipe.int_float_tfm, int_tfm)\ntest_eq(pipe.foo, 1)\n\npipe = Pipeline([int_tfm, int_tfm])\npipe.int_float_tfm\ntest_eq(pipe.int_float_tfm[0], int_tfm)\ntest_eq(pipe.foo, [1,1])\n\n\n# Check opposite order\npipe = Pipeline([int_tfm,neg_tfm])\nt = pipe(start)\ntest_eq(t, -2)\ntest_stdout(lambda:pipe.show(t), '-2')\n\n\nclass A(Transform):\n def encodes(self, x): return int(x)\n def decodes(self, x): return Float(x)\n\npipe = Pipeline([neg_tfm, A])\nt = pipe(start)\ntest_eq_type(t, -2)\ntest_eq_type(pipe.decode(t), Float(start))\ntest_stdout(lambda:pipe.show(t), '-2.0')\n\n\ns2 = (1,2)\npipe = Pipeline([neg_tfm, A])\nt = pipe(s2)\ntest_eq_type(t, (-1,-2))\ntest_eq_type(pipe.decode(t), (Float(1.),Float(2.)))\ntest_stdout(lambda:pipe.show(t), '-1.0\\n-2.0')\n\n\nfrom PIL import Image\n\n\nclass ArrayImage(ndarray):\n _show_args = {'cmap':'viridis'}\n def __new__(cls, x, *args, **kwargs):\n if isinstance(x,tuple): super().__new__(cls, x, *args, **kwargs)\n if args or kwargs: raise RuntimeError('Unknown array init args')\n if not isinstance(x,ndarray): x = array(x)\n return x.view(cls)\n \n def show(self, ctx=None, figsize=None, **kwargs):\n if ctx is None: _,ctx = plt.subplots(figsize=figsize)\n ctx.imshow(im, **{**self._show_args, **kwargs})\n ctx.axis('off')\n return ctx\n \nim = Image.open(TEST_IMAGE)\nim_t = ArrayImage(im)\n\n\ndef f1(x:ArrayImage): return -x\ndef f2(x): return Image.open(x).resize((128,128))\ndef f3(x:Image.Image): return(ArrayImage(array(x)))\n\n\npipe = Pipeline([f2,f3,f1])\nt = pipe(TEST_IMAGE)\ntest_eq(type(t), ArrayImage)\ntest_eq(t, -array(f3(f2(TEST_IMAGE))))\n\n\npipe = Pipeline([f2,f3])\nt = pipe(TEST_IMAGE)\nax = pipe.show(t)\n\n\n\n\n\n\n\n\n\n#test_fig_exists(ax)\n\n\n#Check filtering is properly applied\nadd1 = B()\nadd1.split_idx = 1\npipe = Pipeline([neg_tfm, A(), add1])\ntest_eq(pipe(start), -2)\npipe.split_idx=1\ntest_eq(pipe(start), -1)\npipe.split_idx=0\ntest_eq(pipe(start), -2)\nfor t in [None, 0, 1]:\n pipe.split_idx=t\n test_eq(pipe.decode(pipe(start)), start)\n test_stdout(lambda: pipe.show(pipe(start)), \"-2.0\")\n\n\ndef neg(x): return -x\ntest_eq(type(mk_transform(neg)), Transform)\ntest_eq(type(mk_transform(math.sqrt)), Transform)\ntest_eq(type(mk_transform(lambda a:a*2)), Transform)\ntest_eq(type(mk_transform(Pipeline([neg]))), Pipeline)\n\n\n\nMethods\n\n#TODO: method examples\n\n\nsource\n\n\nPipeline.__call__\n\n Pipeline.__call__ (o)\n\nCall self as a function.\n\nsource\n\n\nPipeline.decode\n\n Pipeline.decode (o, full=True)\n\n\nsource\n\n\nPipeline.setup\n\n Pipeline.setup (items=None, train_setup=False)\n\nDuring the setup, the Pipeline starts with no transform and adds them one at a time, so that during its setup, each transform gets the items processed up to its point and not after.", + "crumbs": [ + "Transforms" + ] + }, + { + "objectID": "parallel.html", + "href": "parallel.html", + "title": "Parallel", + "section": "", + "text": "from fastcore.test import *\nfrom nbdev.showdoc import *\nfrom fastcore.nb_imports import *\n\n\nsource\n\nthreaded\n\n threaded (process=False)\n\nRun f in a Thread (or Process if process=True), and returns it\n\n@threaded\ndef _1():\n time.sleep(0.05)\n print(\"second\")\n return 5\n\n@threaded\ndef _2():\n time.sleep(0.01)\n print(\"first\")\n\na = _1()\n_2()\ntime.sleep(0.1)\n\nfirst\nsecond\n\n\nAfter the thread is complete, the return value is stored in the result attr.\n\na.result\n\n5\n\n\n\nsource\n\n\nstartthread\n\n startthread (f)\n\nLike threaded, but start thread immediately\n\n@startthread\ndef _():\n time.sleep(0.05)\n print(\"second\")\n\n@startthread\ndef _():\n time.sleep(0.01)\n print(\"first\")\n\ntime.sleep(0.1)\n\nfirst\nsecond\n\n\n\nsource\n\n\nstartproc\n\n startproc (f)\n\nLike threaded(True), but start Process immediately\n\n@startproc\ndef _():\n time.sleep(0.05)\n print(\"second\")\n\n@startproc\ndef _():\n time.sleep(0.01)\n print(\"first\")\n\ntime.sleep(0.1)\n\nfirst\nsecond\n\n\n\nsource\n\n\nparallelable\n\n parallelable (param_name, num_workers, f=None)\n\n\nsource\n\nThreadPoolExecutor\n\n ThreadPoolExecutor (max_workers=4, on_exc=<built-in function print>,\n pause=0, **kwargs)\n\nSame as Python’s ThreadPoolExecutor, except can pass max_workers==0 for serial execution\n\nsource\n\n\nProcessPoolExecutor\n\n ProcessPoolExecutor (max_workers=4, on_exc=<built-in function print>,\n pause=0, mp_context=None, initializer=None,\n initargs=())\n\nSame as Python’s ProcessPoolExecutor, except can pass max_workers==0 for serial execution\n\nsource\n\n\n\nparallel\n\n parallel (f, items, *args, n_workers=4, total=None, progress=None,\n pause=0, method=None, threadpool=False, timeout=None,\n chunksize=1, **kwargs)\n\nApplies func in parallel to items, using n_workers\n\ninp,exp = range(50),range(1,51)\n\ntest_eq(parallel(_add_one, inp, n_workers=2), exp)\ntest_eq(parallel(_add_one, inp, threadpool=True, n_workers=2), exp)\ntest_eq(parallel(_add_one, inp, n_workers=1, a=2), range(2,52))\ntest_eq(parallel(_add_one, inp, n_workers=0), exp)\ntest_eq(parallel(_add_one, inp, n_workers=0, a=2), range(2,52))\n\nUse the pause parameter to ensure a pause of pause seconds between processes starting. This is in case there are race conditions in starting some process, or to stagger the time each process starts, for example when making many requests to a webserver. Set threadpool=True to use ThreadPoolExecutor instead of ProcessPoolExecutor.\n\nfrom datetime import datetime\n\n\ndef print_time(i): \n time.sleep(random.random()/1000)\n print(i, datetime.now())\n\nparallel(print_time, range(5), n_workers=2, pause=0.25);\n\n0 2024-10-11 23:06:05.920741\n1 2024-10-11 23:06:06.171470\n2 2024-10-11 23:06:06.431925\n3 2024-10-11 23:06:06.689940\n4 2024-10-11 23:06:06.937109\n\n\n\nsource\n\n\nparallel_async\n\n parallel_async (f, items, *args, n_workers=16, timeout=None, chunksize=1,\n on_exc=<built-in function print>, **kwargs)\n\nApplies f to items in parallel using asyncio and a semaphore to limit concurrency.\n\nimport asyncio\n\n\nasync def print_time_async(i): \n wait = random.random()\n await asyncio.sleep(wait)\n print(i, datetime.now(), wait)\n\nawait parallel_async(print_time_async, range(6), n_workers=3);\n\n0 2024-10-11 23:06:39.545583 0.10292732609738675\n3 2024-10-11 23:06:39.900393 0.3516179734831676\n4 2024-10-11 23:06:39.941094 0.03699593757956876\n2 2024-10-11 23:06:39.957677 0.5148658606540902\n1 2024-10-11 23:06:40.099716 0.6574035385815227\n5 2024-10-11 23:06:40.654097 0.7116319667399102\n\n\n\nsource\n\n\nrun_procs\n\n run_procs (f, f_done, args)\n\nCall f for each item in args in parallel, yielding f_done\n\nsource\n\n\nparallel_gen\n\n parallel_gen (cls, items, n_workers=4, **kwargs)\n\nInstantiate cls in n_workers procs & call each on a subset of items in parallel.\n\n# class _C:\n# def __call__(self, o): return ((i+1) for i in o)\n\n# items = range(5)\n\n# res = L(parallel_gen(_C, items, n_workers=0))\n# idxs,dat1 = zip(*res.sorted(itemgetter(0)))\n# test_eq(dat1, range(1,6))\n\n# res = L(parallel_gen(_C, items, n_workers=3))\n# idxs,dat2 = zip(*res.sorted(itemgetter(0)))\n# test_eq(dat2, dat1)\n\ncls is any class with __call__. It will be passed args and kwargs when initialized. Note that n_workers instances of cls are created, one in each process. items are then split in n_workers batches and one is sent to each cls. The function then returns a generator of tuples of item indices and results.\n\nclass TestSleepyBatchFunc:\n \"For testing parallel processes that run at different speeds\"\n def __init__(self): self.a=1\n def __call__(self, batch):\n for k in batch:\n time.sleep(random.random()/4)\n yield k+self.a\n\nx = np.linspace(0,0.99,20)\n\nres = L(parallel_gen(TestSleepyBatchFunc, x, n_workers=2))\ntest_eq(res.sorted().itemgot(1), x+1)\n\n\n\n\n\n\n\n\n\n# #|hide\n# from subprocess import Popen, PIPE\n# # test num_workers > 0 in scripts works when python process start method is spawn\n# process = Popen([\"python\", \"parallel_test.py\"], stdout=PIPE)\n# _, err = process.communicate(timeout=10)\n# exit_code = process.wait()\n# test_eq(exit_code, 0)", + "crumbs": [ + "Parallel" + ] + }, + { + "objectID": "py2pyi.html#basics", + "href": "py2pyi.html#basics", + "title": "Create delegated pyi", + "section": "Basics", + "text": "Basics\n\nsource\n\nimp_mod\n\n imp_mod (module_path, package=None)\n\nImport dynamically the module referenced in fn\n\nfn = Path('test_py2pyi.py')\n\n\nmod = imp_mod(fn)\na = mod.A()\na.h()\n\n1\n\n\n\ntree = _get_tree(mod)\n\n\n\n\nAST.__repr__\n\n AST.__repr__ ()\n\n\n# for o in enumerate(tree.body): print(o)\n\n\nnode = tree.body[4]\nnode\n\ndef f(a: int, b: str='a') -> str:\n \"\"\"I am f\"\"\"\n return 1\n\n\n\nisinstance(node, functypes)\n\nTrue\n\n\n\nsource\n\n\nhas_deco\n\n has_deco (node:Union[ast.FunctionDef,ast.AsyncFunctionDef], name:str)\n\nCheck if a function node node has a decorator named name\n\nnm = 'delegates'\nhas_deco(node, nm)\n\nFalse\n\n\n\nnode = tree.body[5]\nnode\n\n@delegates(f)\ndef g(c, d: X, **kwargs) -> str:\n \"\"\"I am g\"\"\"\n return 2\n\n\n\nhas_deco(node, nm)\n\nTrue", + "crumbs": [ + "Create delegated pyi" + ] + }, + { + "objectID": "py2pyi.html#function-processing", + "href": "py2pyi.html#function-processing", + "title": "Create delegated pyi", + "section": "Function processing", + "text": "Function processing\n\ndef _proc_body (node, mod): print('_proc_body', type(node))\ndef _proc_func (node, mod): print('_proc_func', type(node))\ndef _proc_class (node, mod): print('_proc_class', type(node))\ndef _proc_patched(node, mod): print('_proc_patched', type(node))\n\n\nparent_node = copy.deepcopy(tree.body[7])\npatched_node = copy.deepcopy(tree.body[10])\ntest_is(has_deco(patched_node, \"patch\"), True)\ntest_eq(str(patched_node.args.args[0].annotation), parent_node.name)\n\n_clean_patched_node(patched_node)\ntest_is(has_deco(patched_node, \"patch\"), False)\ntest_eq(patched_node.args.args[0].annotation, None)\n\n\nempty_cls1, empty_cls2, empty_cls3 = ast.parse('''\nclass A: \n \"\"\"An empty class.\"\"\"\nclass B: \n pass\nclass C: \n ...\n''').body\n\ntest_is(_is_empty_class(empty_cls1), True)\ntest_is(_is_empty_class(empty_cls2), True)\ntest_is(_is_empty_class(empty_cls3), True)\n\nnon_empty_cls, empty_func = ast.parse('''\nclass A: \n a = 1\ndef f():\n ...\n''').body\ntest_is(_is_empty_class(non_empty_cls), False)\ntest_is(_is_empty_class(empty_func), False)\n\n\n# we could have reused `parent_node` and `patched_node` from the previous cells.\n# copying them here allows us to run this cell multiple times which makes it a little easier to write tests.\n\nparent_node = copy.deepcopy(tree.body[7])\npatched_node = copy.deepcopy(tree.body[11])\ntest_eq(len(parent_node.body),1)\n_add_patched_node_to_parent(patched_node, parent_node)\ntest_eq(len(parent_node.body),2)\ntest_eq(parent_node.body[-1], patched_node)\n\n# patched node replaces an existing class method (A.h)\npatched_h_node = ast.parse(\"\"\"\n@patch\ndef h(self: A, *args, **kwargs):\n ...\n\"\"\", mode='single').body[0]\n\n_add_patched_node_to_parent(patched_h_node, parent_node)\ntest_eq(len(parent_node.body), 2)\ntest_eq(parent_node.body[0], patched_h_node)\n\n# patched node is added to an empty class\nempty_cls, patched_node = ast.parse('''\nclass Z: \n \"\"\"An empty class.\"\"\"\n\n@patch\ndef a(self: Z, *args, **kwargs):\n ...\n''').body\n\ntest_eq(len(empty_cls.body), 1)\ntest_ne(empty_cls.body[0], patched_node)\n_add_patched_node_to_parent(patched_node, empty_cls)\ntest_eq(len(empty_cls.body), 1)\ntest_eq(empty_cls.body[0], patched_node)\n\n\nraw_tree = _get_tree(mod)\nprocessed_tree = _proc_mod(mod)\nn_raw_tree_nodes = len(raw_tree.body)\n# mod contains 3 patch methods so our processed_tree should have 3 less nodes \ntest_eq(len(processed_tree.body), n_raw_tree_nodes-3)\n\n_proc_class <class 'ast.ClassDef'>\n_proc_body <class 'ast.FunctionDef'>\n_proc_func <class 'ast.FunctionDef'>\n_proc_body <class 'ast.FunctionDef'>\n_proc_class <class 'ast.ClassDef'>\n_proc_class <class 'ast.ClassDef'>\n_proc_patched <class 'ast.FunctionDef'>\n_proc_patched <class 'ast.FunctionDef'>\n_proc_body <class 'ast.FunctionDef'>\n\n\n\n_proc_mod(mod);\n\n_proc_class <class 'ast.ClassDef'>\n_proc_body <class 'ast.FunctionDef'>\n_proc_func <class 'ast.FunctionDef'>\n_proc_body <class 'ast.FunctionDef'>\n_proc_class <class 'ast.ClassDef'>\n_proc_class <class 'ast.ClassDef'>\n_proc_patched <class 'ast.FunctionDef'>\n_proc_patched <class 'ast.FunctionDef'>\n_proc_body <class 'ast.FunctionDef'>\n\n\n\nnode.name\n\n'g'\n\n\n\nsym = getattr(mod, node.name)\nsym\n\n<function test_py2pyi.g(c, d: test_py2pyi.X, *, b: str = 'a') -> str>\n\n\n\nsig = signature(sym)\nprint(sig)\n\n(c, d: test_py2pyi.X, *, b: str = 'a') -> str\n\n\n\nsource\n\nsig2str\n\n sig2str (sig)\n\n\nsource\n\n\nast_args\n\n ast_args (func)\n\n\nnewargs = ast_args(sym)\nnewargs\n\nc, d: test_py2pyi.X, *, b: str='a'\n\n\n\nnode.args\n\nc, d: X, **kwargs\n\n\n\nnode.args = newargs\nnode\n\n@delegates(f)\ndef g(c, d: test_py2pyi.X, *, b: str='a') -> str:\n \"\"\"I am g\"\"\"\n return 2\n\n\n\n_body_ellip(node)\nnode\n\n@delegates(f)\ndef g(c, d: test_py2pyi.X, *, b: str='a') -> str:\n \"\"\"I am g\"\"\"\n ...\n\n\n\ntree = _get_tree(mod)\nnode = tree.body[5]\nnode\n\n@delegates(f)\ndef g(c, d: X, **kwargs) -> str:\n \"\"\"I am g\"\"\"\n return 2\n\n\n\n_update_func(node, sym)\nnode\n\ndef g(c, d: X, *, b: str='a') -> str:\n \"\"\"I am g\"\"\"\n ...\n\n\n\ntree = _proc_mod(mod)\ntree.body[5]\n\n_proc_class <class 'ast.ClassDef'>\n_proc_class <class 'ast.ClassDef'>\n_proc_class <class 'ast.ClassDef'>\n_proc_patched <class 'ast.FunctionDef'>\n_proc_patched <class 'ast.FunctionDef'>\n\n\ndef g(c, d: X, *, b: str='a') -> str:\n \"\"\"I am g\"\"\"\n ...", + "crumbs": [ + "Create delegated pyi" + ] + }, + { + "objectID": "py2pyi.html#patch", + "href": "py2pyi.html#patch", + "title": "Create delegated pyi", + "section": "Patch", + "text": "Patch\n\ntree = _get_tree(mod)\nnode = tree.body[9]\nnode\n\n@patch\n@delegates(j)\ndef k(self: (A, B), b: bool=False, **kwargs):\n return 1\n\n\n\nann = node.args.args[0].annotation\n\n\nif hasattr(ann, 'elts'): ann = ann.elts[0]\n\n\nnm = ann.id\nnm\n\n'A'\n\n\n\ncls = getattr(mod, nm)\nsym = getattr(cls, node.name)\n\n\nsig2str(signature(sym))\n\n\"(self: (test_py2pyi.A, test_py2pyi.B), b: bool = False, *, d: str = 'a')\"\n\n\n\n_update_func(node, sym)\n\n\nnode\n\n@patch\ndef k(self: (A, B), b: bool=False, *, d: str='a'):\n ...\n\n\n\ntree = _get_tree(mod)\ntree.body[9]\n\n@patch\n@delegates(j)\ndef k(self: (A, B), b: bool=False, **kwargs):\n return 1", + "crumbs": [ + "Create delegated pyi" + ] + }, + { + "objectID": "py2pyi.html#class-and-file", + "href": "py2pyi.html#class-and-file", + "title": "Create delegated pyi", + "section": "Class and file", + "text": "Class and file\n\ntree = _get_tree(mod)\nnode = tree.body[7]\nnode\n\nclass A:\n\n @delegates(j)\n def h(self, b: bool=False, **kwargs):\n a = 1\n return a\n\n\n\nnode.body\n\n[@delegates(j)\n def h(self, b: bool=False, **kwargs):\n a = 1\n return a]\n\n\n\ntree = _proc_mod(mod)\ntree.body[7]\n\nclass A:\n\n def h(self, b: bool=False, *, d: str='a'):\n ...\n\n def k(self, b: bool=False, *, d: str='a'):\n ...\n\n def m(self, b: bool=False, *, d: str='a'):\n ...\n\n def n(self, b: bool=False, **kwargs):\n \"\"\"No delegates here mmm'k?\"\"\"\n ...\n\n\n\nsource\n\ncreate_pyi\n\n create_pyi (fn, package=None)\n\nConvert fname.py to fname.pyi by removing function bodies and expanding delegates kwargs\n\ncreate_pyi(fn)\n\n\n# fn = Path('/Users/jhoward/git/fastcore/fastcore/docments.py')\n# create_pyi(fn, 'fastcore')", + "crumbs": [ + "Create delegated pyi" + ] + }, + { + "objectID": "py2pyi.html#script", + "href": "py2pyi.html#script", + "title": "Create delegated pyi", + "section": "Script", + "text": "Script\n\nsource\n\npy2pyi\n\n py2pyi (fname:str, package:str=None)\n\nConvert fname.py to fname.pyi by removing function bodies and expanding delegates kwargs\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nfname\nstr\n\nThe file name to convert\n\n\npackage\nstr\nNone\nThe parent package\n\n\n\n\nsource\n\n\nreplace_wildcards\n\n replace_wildcards (path:str)\n\nExpand wildcard imports in the specified Python file.\n\n\n\n\nType\nDetails\n\n\n\n\npath\nstr\nPath to the Python file to process", + "crumbs": [ + "Create delegated pyi" + ] + }, + { + "objectID": "test.html", + "href": "test.html", + "title": "Test", + "section": "", + "text": "We can check that code raises an exception when that’s expected (test_fail).\nTo test for equality or inequality (with different types of things) we define a simple function test that compares two objects with a given cmp operator.\n\nsource\n\n\n\n test_fail (f, msg='', contains='', args=None, kwargs=None)\n\nFails with msg unless f() raises an exception and (optionally) has contains in e.args\n\ndef _fail(): raise Exception(\"foobar\")\ntest_fail(_fail, contains=\"foo\")\n\ndef _fail(): raise Exception()\ntest_fail(_fail)\n\nWe can also pass args and kwargs to function to check if it fails with special inputs.\n\ndef _fail_args(a):\n if a == 5:\n raise ValueError\ntest_fail(_fail_args, args=(5,))\ntest_fail(_fail_args, kwargs=dict(a=5))\n\n\nsource\n\n\n\n\n test (a, b, cmp, cname=None)\n\nassert that cmp(a,b); display inputs and cname or cmp.__name__ if it fails\n\ntest([1,2],[1,2], operator.eq)\ntest_fail(lambda: test([1,2],[1], operator.eq))\ntest([1,2],[1], operator.ne)\ntest_fail(lambda: test([1,2],[1,2], operator.ne))\n\n\n\n\n\n\n all_equal (a, b)\n\nCompares whether a and b are the same length and have the same contents\n\ntest(['abc'], ['abc'], all_equal)\ntest_fail(lambda: test(['abc'],['cab'], all_equal))\n\n\n\n\n\n\n equals (a, b)\n\nCompares a and b for equality; supports sublists, tensors and arrays too\n\ntest([['abc'],['a']], [['abc'],['a']], equals)\ntest([['abc'],['a'],'b', [['x']]], [['abc'],['a'],'b', [['x']]], equals) # supports any depth and nested structure\n\n\nsource\n\n\n\n\n nequals (a, b)\n\nCompares a and b for not equals\n\ntest(['abc'], ['ab' ], nequals)", + "crumbs": [ + "Test" + ] + }, + { + "objectID": "test.html#simple-test-functions", + "href": "test.html#simple-test-functions", + "title": "Test", + "section": "", + "text": "We can check that code raises an exception when that’s expected (test_fail).\nTo test for equality or inequality (with different types of things) we define a simple function test that compares two objects with a given cmp operator.\n\nsource\n\n\n\n test_fail (f, msg='', contains='', args=None, kwargs=None)\n\nFails with msg unless f() raises an exception and (optionally) has contains in e.args\n\ndef _fail(): raise Exception(\"foobar\")\ntest_fail(_fail, contains=\"foo\")\n\ndef _fail(): raise Exception()\ntest_fail(_fail)\n\nWe can also pass args and kwargs to function to check if it fails with special inputs.\n\ndef _fail_args(a):\n if a == 5:\n raise ValueError\ntest_fail(_fail_args, args=(5,))\ntest_fail(_fail_args, kwargs=dict(a=5))\n\n\nsource\n\n\n\n\n test (a, b, cmp, cname=None)\n\nassert that cmp(a,b); display inputs and cname or cmp.__name__ if it fails\n\ntest([1,2],[1,2], operator.eq)\ntest_fail(lambda: test([1,2],[1], operator.eq))\ntest([1,2],[1], operator.ne)\ntest_fail(lambda: test([1,2],[1,2], operator.ne))\n\n\n\n\n\n\n all_equal (a, b)\n\nCompares whether a and b are the same length and have the same contents\n\ntest(['abc'], ['abc'], all_equal)\ntest_fail(lambda: test(['abc'],['cab'], all_equal))\n\n\n\n\n\n\n equals (a, b)\n\nCompares a and b for equality; supports sublists, tensors and arrays too\n\ntest([['abc'],['a']], [['abc'],['a']], equals)\ntest([['abc'],['a'],'b', [['x']]], [['abc'],['a'],'b', [['x']]], equals) # supports any depth and nested structure\n\n\nsource\n\n\n\n\n nequals (a, b)\n\nCompares a and b for not equals\n\ntest(['abc'], ['ab' ], nequals)", + "crumbs": [ + "Test" + ] + }, + { + "objectID": "test.html#test_eq-test_ne-etc", + "href": "test.html#test_eq-test_ne-etc", + "title": "Test", + "section": "test_eq test_ne, etc…", + "text": "test_eq test_ne, etc…\nJust use test_eq/test_ne to test for ==/!=. test_eq_type checks things are equal and of the same type. We define them using test:\n\nsource\n\ntest_eq\n\n test_eq (a, b)\n\ntest that a==b\n\ntest_eq([1,2],[1,2])\ntest_eq([1,2],map(int,[1,2]))\ntest_eq(array([1,2]),array([1,2]))\ntest_eq(array([1,2]),array([1,2]))\ntest_eq([array([1,2]),3],[array([1,2]),3])\ntest_eq(dict(a=1,b=2), dict(b=2,a=1))\ntest_fail(lambda: test_eq([1,2], 1), contains=\"==\")\ntest_fail(lambda: test_eq(None, np.array([1,2])), contains=\"==\")\ntest_eq({'a', 'b', 'c'}, {'c', 'a', 'b'})\n\n\ndf1 = pd.DataFrame(dict(a=[1,2],b=['a','b']))\ndf2 = pd.DataFrame(dict(a=[1,2],b=['a','b']))\ndf3 = pd.DataFrame(dict(a=[1,2],b=['a','c']))\n\ntest_eq(df1,df2)\ntest_eq(df1.a,df2.a)\ntest_fail(lambda: test_eq(df1,df3), contains='==')\nclass T(pd.Series): pass\ntest_eq(df1.iloc[0], T(df2.iloc[0])) # works with subclasses\n\n\ntest_eq(torch.zeros(10), torch.zeros(10, dtype=torch.float64))\ntest_eq(torch.zeros(10), torch.ones(10)-1)\ntest_fail(lambda:test_eq(torch.zeros(10), torch.ones(1, 10)), contains='==')\ntest_eq(torch.zeros(3), [0,0,0])\n\n\nsource\n\n\ntest_eq_type\n\n test_eq_type (a, b)\n\ntest that a==b and are same type\n\ntest_eq_type(1,1)\ntest_fail(lambda: test_eq_type(1,1.))\ntest_eq_type([1,1],[1,1])\ntest_fail(lambda: test_eq_type([1,1],(1,1)))\ntest_fail(lambda: test_eq_type([1,1],[1,1.]))\n\n\nsource\n\n\ntest_ne\n\n test_ne (a, b)\n\ntest that a!=b\n\ntest_ne([1,2],[1])\ntest_ne([1,2],[1,3])\ntest_ne(array([1,2]),array([1,1]))\ntest_ne(array([1,2]),array([1,1]))\ntest_ne([array([1,2]),3],[array([1,2])])\ntest_ne([3,4],array([3]))\ntest_ne([3,4],array([3,5]))\ntest_ne(dict(a=1,b=2), ['a', 'b'])\ntest_ne(['a', 'b'], dict(a=1,b=2))\n\n\nsource\n\n\nis_close\n\n is_close (a, b, eps=1e-05)\n\nIs a within eps of b\n\nsource\n\n\ntest_close\n\n test_close (a, b, eps=1e-05)\n\ntest that a is within eps of b\n\ntest_close(1,1.001,eps=1e-2)\ntest_fail(lambda: test_close(1,1.001))\ntest_close([-0.001,1.001], [0.,1.], eps=1e-2)\ntest_close(np.array([-0.001,1.001]), np.array([0.,1.]), eps=1e-2)\ntest_close(array([-0.001,1.001]), array([0.,1.]), eps=1e-2)\n\n\nsource\n\n\ntest_is\n\n test_is (a, b)\n\ntest that a is b\n\ntest_fail(lambda: test_is([1], [1]))\na = [1]\ntest_is(a, a)\nb = [2]; test_fail(lambda: test_is(a, b))\n\n\nsource\n\n\ntest_shuffled\n\n test_shuffled (a, b)\n\ntest that a and b are shuffled versions of the same sequence of items\n\na = list(range(50))\nb = copy(a)\nrandom.shuffle(b)\ntest_shuffled(a,b)\ntest_fail(lambda:test_shuffled(a,a))\n\n\na = 'abc'\nb = 'abcabc'\ntest_fail(lambda:test_shuffled(a,b))\n\n\na = ['a', 42, True] \nb = [42, True, 'a']\ntest_shuffled(a,b)\n\n\nsource\n\n\ntest_stdout\n\n test_stdout (f, exp, regex=False)\n\nTest that f prints exp to stdout, optionally checking as regex\n\ntest_stdout(lambda: print('hi'), 'hi')\ntest_fail(lambda: test_stdout(lambda: print('hi'), 'ho'))\ntest_stdout(lambda: 1+1, '')\ntest_stdout(lambda: print('hi there!'), r'^hi.*!$', regex=True)\n\n\nsource\n\n\ntest_warns\n\n test_warns (f, show=False)\n\n\ntest_warns(lambda: warnings.warn(\"Oh no!\"))\ntest_fail(lambda: test_warns(lambda: 2+2), contains='No warnings raised')\n\n\ntest_warns(lambda: warnings.warn(\"Oh no!\"), show=True)\n\n<class 'UserWarning'>: Oh no!\n\n\n\nim = Image.open(TEST_IMAGE).resize((128,128)); im\n\n\n\n\n\n\n\n\n\nim = Image.open(TEST_IMAGE_BW).resize((128,128)); im\n\n\n\n\n\n\n\n\n\nsource\n\n\ntest_fig_exists\n\n test_fig_exists (ax)\n\nTest there is a figure displayed in ax\n\nfig,ax = plt.subplots()\nax.imshow(array(im));\n\n\n\n\n\n\n\n\n\ntest_fig_exists(ax)\n\n\nsource\n\n\nExceptionExpected\n\n ExceptionExpected (ex=<class 'Exception'>, regex='')\n\nContext manager that tests if an exception is raised\n\ndef _tst_1(): assert False, \"This is a test\"\ndef _tst_2(): raise SyntaxError\n\nwith ExceptionExpected(): _tst_1()\nwith ExceptionExpected(ex=AssertionError, regex=\"This is a test\"): _tst_1()\nwith ExceptionExpected(ex=SyntaxError): _tst_2()\n\nexception is an abbreviation for ExceptionExpected().\n\nwith exception: _tst_1()", + "crumbs": [ + "Test" + ] + }, + { + "objectID": "script.html", + "href": "script.html", + "title": "Script - CLI", + "section": "", + "text": "Part of fast.ai’s toolkit for delightful developer experiences.", + "crumbs": [ + "Script - CLI" + ] + }, + { + "objectID": "script.html#overview", + "href": "script.html#overview", + "title": "Script - CLI", + "section": "Overview", + "text": "Overview\nSometimes, you want to create a quick script, either for yourself, or for others. But in Python, that involves a whole lot of boilerplate and ceremony, especially if you want to support command line arguments, provide help, and other niceties. You can use argparse for this purpose, which comes with Python, but it’s complex and verbose.\nfastcore.script makes life easier. There are much fancier modules to help you write scripts (we recommend Python Fire, and Click is also popular), but fastcore.script is very fast and very simple. In fact, it’s <50 lines of code! Basically, it’s just a little wrapper around argparse that uses modern Python features and some thoughtful defaults to get rid of the boilerplate.\nFor full details, see the docs for core.", + "crumbs": [ + "Script - CLI" + ] + }, + { + "objectID": "script.html#example", + "href": "script.html#example", + "title": "Script - CLI", + "section": "Example", + "text": "Example\nHere’s a complete example (available in examples/test_fastcore.py):\nfrom fastcore.script import *\n@call_parse\ndef main(msg:str, # The message\n upper:bool): # Convert to uppercase?\n \"Print `msg`, optionally converting to uppercase\"\n print(msg.upper() if upper else msg)\nIf you copy that info a file and run it, you’ll see:\n$ examples/test_fastcore.py --help\nusage: test_fastcore.py [-h] [--upper] msg\n\nPrint `msg`, optionally converting to uppercase\n\npositional arguments:\n msg The message\n\noptional arguments:\n -h, --help show this help message and exit\n --upper Convert to uppercase? (default: False)\nAs you see, we didn’t need any if __name__ == \"__main__\", we didn’t have to parse arguments, we just wrote a function, added a decorator to it, and added some annotations to our function’s parameters. As a bonus, we can also use this function directly from a REPL such as Jupyter Notebook - it’s not just for command line scripts!\nYou should provide a default (after the =) for any optional parameters. If you don’t provide a default for a parameter, then it will be a positional parameter.", + "crumbs": [ + "Script - CLI" + ] + }, + { + "objectID": "script.html#param-annotations", + "href": "script.html#param-annotations", + "title": "Script - CLI", + "section": "Param annotations", + "text": "Param annotations\nIf you want to use the full power of argparse, you can do so by using Param annotations instead of type annotations and docments, like so:\nfrom fastcore.script import *\n@call_parse\ndef main(msg:Param(\"The message\", str),\n upper:Param(\"Convert to uppercase?\", store_true)):\n \"Print `msg`, optionally converting to uppercase\"\n print(msg.upper() if upper else msg)\nIf you use this approach, then each parameter in your function should have an annotation Param(...) (as in the example above). You can pass the following when calling Param: help,type,opt,action,nargs,const,choices,required . Except for opt, all of these are just passed directly to argparse, so you have all the power of that module at your disposal. Generally you’ll want to pass at least help (since this is provided as the help string for that parameter) and type (to ensure that you get the type of data you expect). opt is a bool that defines whether a param is optional or required (positional) - but you’ll generally not need to set this manually, because fastcore.script will set it for you automatically based on default values.", + "crumbs": [ + "Script - CLI" + ] + }, + { + "objectID": "script.html#setuptools-scripts", + "href": "script.html#setuptools-scripts", + "title": "Script - CLI", + "section": "setuptools scripts", + "text": "setuptools scripts\nThere’s a really nice feature of pip/setuptools that lets you create commandline scripts directly from functions, makes them available in the PATH, and even makes your scripts cross-platform (e.g. in Windows it creates an exe). fastcore.script supports this feature too. The trick to making a function available as a script is to add a console_scripts section to your setup file, of the form: script_name=module:function_name. E.g. in this case we use: test_fastcore.script=fastcore.script.test_cli:main. With this, you can then just type test_fastcore.script at any time, from any directory, and your script will be called (once it’s installed using one of the methods below).\nYou don’t actually have to write a setup.py yourself. Instead, just use nbdev. Then modify settings.ini as appropriate for your module/script. To install your script directly, you can type pip install -e .. Your script, when installed this way (it’s called an editable install), will automatically be up to date even if you edit it - there’s no need to reinstall it after editing. With nbdev you can even make your module and script available for installation directly from pip and conda by running make release.", + "crumbs": [ + "Script - CLI" + ] + }, + { + "objectID": "script.html#api-details", + "href": "script.html#api-details", + "title": "Script - CLI", + "section": "API details", + "text": "API details\n\nsource\n\nstore_true\n\n store_true ()\n\nPlaceholder to pass to Param for store_true action\n\nsource\n\n\nstore_false\n\n store_false ()\n\nPlaceholder to pass to Param for store_false action\n\nsource\n\n\nbool_arg\n\n bool_arg (v)\n\nUse as type for Param to get bool behavior\n\nsource\n\n\nclean_type_str\n\n clean_type_str (x:str)\n\n\nclass Test: pass\n\ntest_eq(clean_type_str(argparse.ArgumentParser), 'argparse.ArgumentParser')\ntest_eq(clean_type_str(Test), 'Test')\ntest_eq(clean_type_str(int), 'int')\ntest_eq(clean_type_str(float), 'float')\ntest_eq(clean_type_str(store_false), 'store_false')\n\n\nsource\n\n\nParam\n\n Param (help='', type=None, opt=True, action=None, nargs=None, const=None,\n choices=None, required=None, default=None)\n\nA parameter in a function used in anno_parser or call_parse\n\ntest_eq(repr(Param(\"Help goes here\")), '<Help goes here>')\ntest_eq(repr(Param(\"Help\", int)), 'int <Help>')\ntest_eq(repr(Param(help=None, type=int)), 'int')\ntest_eq(repr(Param(help=None, type=None)), '')\n\nEach parameter in your function should have an annotation Param(...). You can pass the following when calling Param: help,type,opt,action,nargs,const,choices,required (i.e. it takes the same parameters as argparse.ArgumentParser.add_argument, plus opt). Except for opt, all of these are just passed directly to argparse, so you have all the power of that module at your disposal. Generally you’ll want to pass at least help (since this is provided as the help string for that parameter) and type (to ensure that you get the type of data you expect).\nopt is a bool that defines whether a param is optional or required (positional) - but you’ll generally not need to set this manually, because fastcore.script will set it for you automatically based on default values. You should provide a default (after the =) for any optional parameters. If you don’t provide a default for a parameter, then it will be a positional parameter.\nParam’s __repr__ also allows for more informative function annotation when looking up the function’s doc using shift+tab. You see the type annotation (if there is one) and the accompanying help documentation with it.\n\ndef f(required:Param(\"Required param\", int),\n a:Param(\"param 1\", bool_arg),\n b:Param(\"param 2\", str)=\"test\"):\n \"my docs\"\n ...\n\n\nhelp(f)\n\nHelp on function f in module __main__:\n\nf(required: int <Required param>, a: bool_arg <param 1>, b: str <param 2> = 'test')\n my docs\n\n\n\n\np = Param(help=\"help\", type=int)\np.set_default(1)\ntest_eq(p.kwargs, {'help': 'help (default: 1)', 'type': int, 'default': 1})\n\n\nsource\n\n\nanno_parser\n\n anno_parser (func, prog:str=None)\n\nLook at params (annotated with Param) in func and return an ArgumentParser\n\n\n\n\nType\nDefault\nDetails\n\n\n\n\nfunc\n\n\nFunction to get arguments from\n\n\nprog\nstr\nNone\nThe name of the program\n\n\n\nThis converts a function with parameter annotations of type Param into an argparse.ArgumentParser object. Function arguments with a default provided are optional, and other arguments are positional.\n\n_en = str_enum('_en', 'aa','bb','cc')\ndef f(required:Param(\"Required param\", int),\n a:Param(\"param 1\", bool_arg),\n b:Param(\"param 2\", str)=\"test\",\n c:Param(\"param 3\", _en)=_en.aa):\n \"my docs\"\n ...\n\np = anno_parser(f, 'progname')\np.print_help()\n\nusage: progname [-h] [--b B] [--c {aa,bb,cc}] required a\n\nmy docs\n\npositional arguments:\n required Required param\n a param 1\n\noptional arguments:\n -h, --help show this help message and exit\n --b B param 2 (default: test)\n --c {aa,bb,cc} param 3 (default: aa)\n\n\nIt also works with type annotations and docments:\n\ndef g(required:int, # Required param\n a:bool_arg, # param 1\n b=\"test\", # param 2\n c:_en=_en.aa): # param 3\n \"my docs\"\n ...\n\np = anno_parser(g, 'progname')\np.print_help()\n\nusage: progname [-h] [--b B] [--c {aa,bb,cc}] required a\n\nmy docs\n\npositional arguments:\n required Required param\n a param 1\n\noptional arguments:\n -h, --help show this help message and exit\n --b B param 2 (default: test)\n --c {aa,bb,cc} param 3 (default: aa)\n\n\n\nsource\n\n\nargs_from_prog\n\n args_from_prog (func, prog)\n\nExtract args from prog\nSometimes it’s convenient to extract arguments from the actual name of the called program. args_from_prog will do this, assuming that names and values of the params are separated by a #. Optionally there can also be a prefix separated by ## (double underscore).\n\nexp = {'a': False, 'b': 'baa'}\ntest_eq(args_from_prog(f, 'foo##a#0#b#baa'), exp)\ntest_eq(args_from_prog(f, 'a#0#b#baa'), exp)\n\n\nsource\n\n\ncall_parse\n\n call_parse (func=None, nested=False)\n\nDecorator to create a simple CLI from func using anno_parser\n\n@call_parse\ndef test_add(\n a:int=0, # param a\n b:int=0 # param 1\n):\n \"Add up `a` and `b`\"\n return a + b\n\ncall_parse decorated functions work as regular functions and also as command-line interface functions.\n\ntest_eq(test_add(1,2), 3)\n\nThis is the main way to use fastcore.script; decorate your function with call_parse, add Param annotations (as shown above) or type annotations and docments, and it can then be used as a script.\nUse the nested keyword argument to create nested parsers, where earlier parsers consume only their known args from sys.argv before later parsers are used. This is useful to create one command line application that executes another. For example:\nmyrunner --keyword 1 script.py -- <script.py args>\nA separating -- after the first application’s args is recommended though not always required, otherwise args may be parsed in unexpected ways. For example:\nmyrunner script.py -h\nwould display myrunner’s help and not script.py’s.", + "crumbs": [ + "Script - CLI" + ] + }, + { + "objectID": "xdg.html", + "href": "xdg.html", + "title": "XDG", + "section": "", + "text": "See the XDG Base Directory Specification for more information.", + "crumbs": [ + "XDG" + ] + }, + { + "objectID": "xdg.html#overview", + "href": "xdg.html#overview", + "title": "XDG", + "section": "Overview", + "text": "Overview\nxdg_cache_home, xdg_config_home, xdg_data_home, and xdg_state_home return pathlib.Path objects containing the value of the environment variable named XDG_CACHE_HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME respectively, or the default defined in the specification if the environment variable is unset, empty, or contains a relative path rather than absolute path.\nxdg_config_dirs and xdg_data_dirs return a list of pathlib.Path objects containing the value, split on colons, of the environment variable named XDG_CONFIG_DIRS and XDG_DATA_DIRS respectively, or the default defined in the specification if the environment variable is unset or empty. Relative paths are ignored, as per the specification.\nxdg_runtime_dir returns a pathlib.Path object containing the value of the XDG_RUNTIME_DIR environment variable, or None if the environment variable is not set, or contains a relative path rather than absolute path.", + "crumbs": [ + "XDG" + ] + }, + { + "objectID": "xdg.html#helpers", + "href": "xdg.html#helpers", + "title": "XDG", + "section": "Helpers", + "text": "Helpers\nWe’ll start by defining a context manager that temporarily sets an environment variable to demonstrate the behaviour of each helper function:\n\nfrom contextlib import contextmanager\n\n\n@contextmanager\ndef env(variable, value):\n old = os.environ.get(variable, None)\n try:\n os.environ[variable] = value\n yield\n finally:\n if old is None: del os.environ[variable]\n else: os.environ[variable] = old\n\n\nsource\n\nxdg_cache_home\n\n xdg_cache_home ()\n\nPath corresponding to XDG_CACHE_HOME\n\nfrom fastcore.test import *\n\n\ntest_eq(xdg_cache_home(), Path.home()/'.cache')\nwith env('XDG_CACHE_HOME', '/home/fastai/.cache'):\n test_eq(xdg_cache_home(), Path('/home/fastai/.cache'))\n\n\nsource\n\n\nxdg_config_dirs\n\n xdg_config_dirs ()\n\nPaths corresponding to XDG_CONFIG_DIRS\n\ntest_eq(xdg_config_dirs(), [Path('/etc/xdg')])\nwith env('XDG_CONFIG_DIRS', '/home/fastai/.xdg:/home/fastai/.config'):\n test_eq(xdg_config_dirs(), [Path('/home/fastai/.xdg'), Path('/home/fastai/.config')])\n\n\nsource\n\n\nxdg_config_home\n\n xdg_config_home ()\n\nPath corresponding to XDG_CONFIG_HOME\n\ntest_eq(xdg_config_home(), Path.home()/'.config')\nwith env('XDG_CONFIG_HOME', '/home/fastai/.config'):\n test_eq(xdg_config_home(), Path('/home/fastai/.config'))\n\n\nsource\n\n\nxdg_data_dirs\n\n xdg_data_dirs ()\n\nPaths corresponding to XDG_DATA_DIRS`\n\nsource\n\n\nxdg_data_home\n\n xdg_data_home ()\n\nPath corresponding to XDG_DATA_HOME\n\ntest_eq(xdg_data_home(), Path.home()/'.local/share')\nwith env('XDG_DATA_HOME', '/home/fastai/.data'):\n test_eq(xdg_data_home(), Path('/home/fastai/.data'))\n\n\nsource\n\n\nxdg_runtime_dir\n\n xdg_runtime_dir ()\n\nPath corresponding to XDG_RUNTIME_DIR\n\nsource\n\n\nxdg_state_home\n\n xdg_state_home ()\n\nPath corresponding to XDG_STATE_HOME\n\ntest_eq(xdg_state_home(), Path.home()/'.local/state')\nwith env('XDG_STATE_HOME', '/home/fastai/.state'):\n test_eq(xdg_state_home(), Path('/home/fastai/.state'))\n\n\nCopyright © 2016-2021 Scott Stevenson scott@stevenson.io\nModifications copyright © 2022 onwards Jeremy Howard", + "crumbs": [ + "XDG" + ] + }, + { + "objectID": "docments.html", + "href": "docments.html", + "title": "Docments", + "section": "", + "text": "docments provides programmatic access to comments in function parameters and return types. It can be used to create more developer-friendly documentation, CLI, etc tools.", + "crumbs": [ + "Docments" + ] + }, + { + "objectID": "docments.html#why", + "href": "docments.html#why", + "title": "Docments", + "section": "Why?", + "text": "Why?\nWithout docments, if you want to document your parameters, you have to repeat param names in docstrings, since they’re already in the function signature. The parameters have to be kept synchronized in the two places as you change your code. Readers of your code have to look back and forth between two places to understand what’s happening. So it’s more work for you, and for your users.\nFurthermore, to have parameter documentation formatted nicely without docments, you have to use special magic docstring formatting, often with odd quirks, which is a pain to create and maintain, and awkward to read in code. For instance, using numpy-style documentation:\n\ndef add_np(a:int, b:int=0)->int:\n \"\"\"The sum of two numbers.\n \n Used to demonstrate numpy-style docstrings.\n\nParameters\n----------\na : int\n the 1st number to add\nb : int\n the 2nd number to add (default: 0)\n\nReturns\n-------\nint\n the result of adding `a` to `b`\"\"\"\n return a+b\n\nBy comparison, here’s the same thing using docments:\n\ndef add(\n a:int, # the 1st number to add\n b=0, # the 2nd number to add\n)->int: # the result of adding `a` to `b`\n \"The sum of two numbers.\"\n return a+b", + "crumbs": [ + "Docments" + ] + }, + { + "objectID": "docments.html#numpy-docstring-helper-functions", + "href": "docments.html#numpy-docstring-helper-functions", + "title": "Docments", + "section": "Numpy docstring helper functions", + "text": "Numpy docstring helper functions\ndocments also supports numpy-style docstrings, or a mix or numpy-style and docments parameter documentation. The functions in this section help get and parse this information.\n\nsource\n\ndocstring\n\n docstring (sym)\n\nGet docstring for sym for functions ad classes\n\ntest_eq(docstring(add), \"The sum of two numbers.\")\n\n\nsource\n\n\nparse_docstring\n\n parse_docstring (sym)\n\nParse a numpy-style docstring in sym\n\n# parse_docstring(add_np)\n\n\nsource\n\n\nisdataclass\n\n isdataclass (s)\n\nCheck if s is a dataclass but not a dataclass’ instance\n\nsource\n\n\nget_dataclass_source\n\n get_dataclass_source (s)\n\nGet source code for dataclass s\n\nsource\n\n\nget_source\n\n get_source (s)\n\nGet source code for string, function object or dataclass s\n\nsource\n\n\nget_name\n\n get_name (obj)\n\nGet the name of obj\n\ntest_eq(get_name(in_ipython), 'in_ipython')\ntest_eq(get_name(L.map), 'map')\n\n\nsource\n\n\nqual_name\n\n qual_name (obj)\n\nGet the qualified name of obj\n\nassert qual_name(docscrape) == 'fastcore.docscrape'", + "crumbs": [ + "Docments" + ] + }, + { + "objectID": "docments.html#docments", + "href": "docments.html#docments", + "title": "Docments", + "section": "Docments", + "text": "Docments\n\nsource\n\ndocments\n\n docments (elt, full=False, returns=True, eval_str=False)\n\nGenerates a docment\nThe returned dict has parameter names as keys, docments as values. The return value comment appears in the return, unless returns=False. Using the add definition above, we get:\n\ndef add(\n a:int, # the 1st number to add\n b=0, # the 2nd number to add\n)->int: # the result of adding `a` to `b`\n \"The sum of two numbers.\"\n return a+b\n\ndocments(add)\n\n{ 'a': 'the 1st number to add',\n 'b': 'the 2nd number to add',\n 'return': 'the result of adding `a` to `b`'}\n\n\nIf you pass full=True, the values are dict of defaults, types, and docments as values. Note that the type annotation is inferred from the default value, if the annotation is empty and a default is supplied.\n\ndocments(add, full=True)\n\n{ 'a': { 'anno': <class 'int'>,\n 'default': <class 'inspect._empty'>,\n 'docment': 'the 1st number to add'},\n 'b': { 'anno': <class 'int'>,\n 'default': 0,\n 'docment': 'the 2nd number to add'},\n 'return': { 'anno': <class 'int'>,\n 'default': <class 'inspect._empty'>,\n 'docment': 'the result of adding `a` to `b`'}}\n\n\nTo evaluate stringified annotations (from python 3.10), use eval_str:\n\ndocments(add, full=True, eval_str=True)['a']\n\n{ 'anno': <class 'int'>,\n 'default': <class 'inspect._empty'>,\n 'docment': 'the 1st number to add'}\n\n\nIf you need more space to document a parameter, place one or more lines of comments above the parameter, or above the return type. You can mix-and-match these docment styles:\n\ndef add(\n # The first operand\n a:int,\n # This is the second of the operands to the *addition* operator.\n # Note that passing a negative value here is the equivalent of the *subtraction* operator.\n b:int,\n)->int: # The result is calculated using Python's builtin `+` operator.\n \"Add `a` to `b`\"\n return a+b\n\n\ndocments(add)\n\n{ 'a': 'The first operand',\n 'b': 'This is the second of the operands to the *addition* operator.\\n'\n 'Note that passing a negative value here is the equivalent of the '\n '*subtraction* operator.',\n 'return': \"The result is calculated using Python's builtin `+` operator.\"}\n\n\nDocments works with async functions, too:\n\nasync def add_async(\n # The first operand\n a:int,\n # This is the second of the operands to the *addition* operator.\n # Note that passing a negative value here is the equivalent of the *subtraction* operator.\n b:int,\n)->int: # The result is calculated using Python's builtin `+` operator.\n \"Add `a` to `b`\"\n return a+b\n\n\ntest_eq(docments(add_async), docments(add))\n\nYou can also use docments with classes and methods:\n\nclass Adder:\n \"An addition calculator\"\n def __init__(self,\n a:int, # First operand\n b:int, # 2nd operand\n ): self.a,self.b = a,b\n \n def calculate(self\n )->int: # Integral result of addition operator\n \"Add `a` to `b`\"\n return a+b\n\n\ndocments(Adder)\n\n{'a': 'First operand', 'b': '2nd operand', 'return': None}\n\n\n\ndocments(Adder.calculate)\n\n{'return': 'Integral result of addition operator', 'self': None}\n\n\ndocments can also be extracted from numpy-style docstrings:\n\nprint(add_np.__doc__)\n\nThe sum of two numbers.\n \n Used to demonstrate numpy-style docstrings.\n\nParameters\n----------\na : int\n the 1st number to add\nb : int\n the 2nd number to add (default: 0)\n\nReturns\n-------\nint\n the result of adding `a` to `b`\n\n\n\ndocments(add_np)\n\n{ 'a': 'the 1st number to add',\n 'b': 'the 2nd number to add (default: 0)',\n 'return': 'the result of adding `a` to `b`'}\n\n\nYou can even mix and match docments and numpy parameters:\n\ndef add_mixed(a:int, # the first number to add\n b\n )->int: # the result\n \"\"\"The sum of two numbers.\n\nParameters\n----------\nb : int\n the 2nd number to add (default: 0)\"\"\"\n return a+b\n\n\ndocments(add_mixed, full=True)\n\n{ 'a': { 'anno': <class 'int'>,\n 'default': <class 'inspect._empty'>,\n 'docment': 'the first number to add'},\n 'b': { 'anno': 'int',\n 'default': <class 'inspect._empty'>,\n 'docment': 'the 2nd number to add (default: 0)'},\n 'return': { 'anno': <class 'int'>,\n 'default': <class 'inspect._empty'>,\n 'docment': 'the result'}}\n\n\nYou can use docments with dataclasses, however if the class was defined in online notebook, docments will not contain parameters’ comments. This is because the source code is not available in the notebook. After converting the notebook to a module, the docments will be available. Thus, documentation will have correct parameters’ comments.\nDocments even works with delegates:\n\nfrom fastcore.meta import delegates\n\n\ndef _a(a:int=2): return a # First\n\n@delegates(_a)\ndef _b(b:str, **kwargs): return b, (_a(**kwargs)) # Second\n\ndocments(_b)\n\n{'a': 'First', 'b': 'Second', 'return': None}", + "crumbs": [ + "Docments" + ] + }, + { + "objectID": "docments.html#extract-docstrings", + "href": "docments.html#extract-docstrings", + "title": "Docments", + "section": "Extract docstrings", + "text": "Extract docstrings\n\nsource\n\nextract_docstrings\n\n extract_docstrings (code)\n\nCreate a dict from function/class/method names to tuples of docstrings and param lists\n\nsample_code = \"\"\"\n\"This is a module.\"\n\ndef top_func(a, b, *args, **kw):\n \"This is top-level.\"\n pass\n\nclass SampleClass:\n \"This is a class.\"\n\n def __init__(self, x, y):\n \"Constructor for SampleClass.\"\n pass\n\n def method1(self, param1):\n \"This is method1.\"\n pass\n\n def _private_method(self):\n \"This should not be included.\"\n pass\n\nclass AnotherClass:\n def __init__(self, a, b):\n \"This class has no separate docstring.\"\n pass\"\"\"\n\nexp = {'_module': ('This is a module.', ''),\n 'top_func': ('This is top-level.', 'a, b, *args, **kw'),\n 'SampleClass': ('This is a class.', 'self, x, y'),\n 'SampleClass.method1': ('This is method1.', 'self, param1'),\n 'AnotherClass': ('This class has no separate docstring.', 'self, a, b')}\ntest_eq(extract_docstrings(sample_code), exp)", + "crumbs": [ + "Docments" + ] + } +] \ No newline at end of file diff --git a/site_libs/bootstrap/bootstrap-icons.css b/site_libs/bootstrap/bootstrap-icons.css new file mode 100644 index 00000000..285e4448 --- /dev/null +++ b/site_libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2078 @@ +/*! + * Bootstrap Icons v1.11.1 (https://icons.getbootstrap.com/) + * Copyright 2019-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE) + */ + +@font-face { + font-display: block; + font-family: "bootstrap-icons"; + src: +url("./bootstrap-icons.woff?2820a3852bdb9a5832199cc61cec4e65") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-fill::before { content: "\f691"; } +.bi-envelope-exclamation::before { content: "\f692"; } +.bi-envelope-plus-fill::before { content: "\f693"; } +.bi-envelope-plus::before { content: "\f694"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.bi-filetype-json::before { content: "\f791"; } +.bi-filetype-pptx::before { content: "\f792"; } +.bi-filetype-xlsx::before { content: "\f793"; } +.bi-1-circle-fill::before { content: "\f796"; } +.bi-1-circle::before { content: "\f797"; } +.bi-1-square-fill::before { content: "\f798"; } +.bi-1-square::before { content: "\f799"; } +.bi-2-circle-fill::before { content: "\f79c"; } +.bi-2-circle::before { content: "\f79d"; } +.bi-2-square-fill::before { content: "\f79e"; } +.bi-2-square::before { content: "\f79f"; } +.bi-3-circle-fill::before { content: "\f7a2"; } +.bi-3-circle::before { content: "\f7a3"; } +.bi-3-square-fill::before { content: "\f7a4"; } +.bi-3-square::before { content: "\f7a5"; } +.bi-4-circle-fill::before { content: "\f7a8"; } +.bi-4-circle::before { content: "\f7a9"; } +.bi-4-square-fill::before { content: "\f7aa"; } +.bi-4-square::before { content: "\f7ab"; } +.bi-5-circle-fill::before { content: "\f7ae"; } +.bi-5-circle::before { content: "\f7af"; } +.bi-5-square-fill::before { content: "\f7b0"; } +.bi-5-square::before { content: "\f7b1"; } +.bi-6-circle-fill::before { content: "\f7b4"; } +.bi-6-circle::before { content: "\f7b5"; } +.bi-6-square-fill::before { content: "\f7b6"; } +.bi-6-square::before { content: "\f7b7"; } +.bi-7-circle-fill::before { content: "\f7ba"; } +.bi-7-circle::before { content: "\f7bb"; } +.bi-7-square-fill::before { content: "\f7bc"; } +.bi-7-square::before { content: "\f7bd"; } +.bi-8-circle-fill::before { content: "\f7c0"; } +.bi-8-circle::before { content: "\f7c1"; } +.bi-8-square-fill::before { content: "\f7c2"; } +.bi-8-square::before { content: "\f7c3"; } +.bi-9-circle-fill::before { content: "\f7c6"; } +.bi-9-circle::before { content: "\f7c7"; } +.bi-9-square-fill::before { content: "\f7c8"; } +.bi-9-square::before { content: "\f7c9"; } +.bi-airplane-engines-fill::before { content: "\f7ca"; } +.bi-airplane-engines::before { content: "\f7cb"; } +.bi-airplane-fill::before { content: "\f7cc"; } +.bi-airplane::before { content: "\f7cd"; } +.bi-alexa::before { content: "\f7ce"; } +.bi-alipay::before { content: "\f7cf"; } +.bi-android::before { content: "\f7d0"; } +.bi-android2::before { content: "\f7d1"; } +.bi-box-fill::before { content: "\f7d2"; } +.bi-box-seam-fill::before { content: "\f7d3"; } +.bi-browser-chrome::before { content: "\f7d4"; } +.bi-browser-edge::before { content: "\f7d5"; } +.bi-browser-firefox::before { content: "\f7d6"; } +.bi-browser-safari::before { content: "\f7d7"; } +.bi-c-circle-fill::before { content: "\f7da"; } +.bi-c-circle::before { content: "\f7db"; } +.bi-c-square-fill::before { content: "\f7dc"; } +.bi-c-square::before { content: "\f7dd"; } +.bi-capsule-pill::before { content: "\f7de"; } +.bi-capsule::before { content: "\f7df"; } +.bi-car-front-fill::before { content: "\f7e0"; } +.bi-car-front::before { content: "\f7e1"; } +.bi-cassette-fill::before { content: "\f7e2"; } +.bi-cassette::before { content: "\f7e3"; } +.bi-cc-circle-fill::before { content: "\f7e6"; } +.bi-cc-circle::before { content: "\f7e7"; } +.bi-cc-square-fill::before { content: "\f7e8"; } +.bi-cc-square::before { content: "\f7e9"; } +.bi-cup-hot-fill::before { content: "\f7ea"; } +.bi-cup-hot::before { content: "\f7eb"; } +.bi-currency-rupee::before { content: "\f7ec"; } +.bi-dropbox::before { content: "\f7ed"; } +.bi-escape::before { content: "\f7ee"; } +.bi-fast-forward-btn-fill::before { content: "\f7ef"; } +.bi-fast-forward-btn::before { content: "\f7f0"; } +.bi-fast-forward-circle-fill::before { content: "\f7f1"; } +.bi-fast-forward-circle::before { content: "\f7f2"; } +.bi-fast-forward-fill::before { content: "\f7f3"; } +.bi-fast-forward::before { content: "\f7f4"; } +.bi-filetype-sql::before { content: "\f7f5"; } +.bi-fire::before { content: "\f7f6"; } +.bi-google-play::before { content: "\f7f7"; } +.bi-h-circle-fill::before { content: "\f7fa"; } +.bi-h-circle::before { content: "\f7fb"; } +.bi-h-square-fill::before { content: "\f7fc"; } +.bi-h-square::before { content: "\f7fd"; } +.bi-indent::before { content: "\f7fe"; } +.bi-lungs-fill::before { content: "\f7ff"; } +.bi-lungs::before { content: "\f800"; } +.bi-microsoft-teams::before { content: "\f801"; } +.bi-p-circle-fill::before { content: "\f804"; } +.bi-p-circle::before { content: "\f805"; } +.bi-p-square-fill::before { content: "\f806"; } +.bi-p-square::before { content: "\f807"; } +.bi-pass-fill::before { content: "\f808"; } +.bi-pass::before { content: "\f809"; } +.bi-prescription::before { content: "\f80a"; } +.bi-prescription2::before { content: "\f80b"; } +.bi-r-circle-fill::before { content: "\f80e"; } +.bi-r-circle::before { content: "\f80f"; } +.bi-r-square-fill::before { content: "\f810"; } +.bi-r-square::before { content: "\f811"; } +.bi-repeat-1::before { content: "\f812"; } +.bi-repeat::before { content: "\f813"; } +.bi-rewind-btn-fill::before { content: "\f814"; } +.bi-rewind-btn::before { content: "\f815"; } +.bi-rewind-circle-fill::before { content: "\f816"; } +.bi-rewind-circle::before { content: "\f817"; } +.bi-rewind-fill::before { content: "\f818"; } +.bi-rewind::before { content: "\f819"; } +.bi-train-freight-front-fill::before { content: "\f81a"; } +.bi-train-freight-front::before { content: "\f81b"; } +.bi-train-front-fill::before { content: "\f81c"; } +.bi-train-front::before { content: "\f81d"; } +.bi-train-lightrail-front-fill::before { content: "\f81e"; } +.bi-train-lightrail-front::before { content: "\f81f"; } +.bi-truck-front-fill::before { content: "\f820"; } +.bi-truck-front::before { content: "\f821"; } +.bi-ubuntu::before { content: "\f822"; } +.bi-unindent::before { content: "\f823"; } +.bi-unity::before { content: "\f824"; } +.bi-universal-access-circle::before { content: "\f825"; } +.bi-universal-access::before { content: "\f826"; } +.bi-virus::before { content: "\f827"; } +.bi-virus2::before { content: "\f828"; } +.bi-wechat::before { content: "\f829"; } +.bi-yelp::before { content: "\f82a"; } +.bi-sign-stop-fill::before { content: "\f82b"; } +.bi-sign-stop-lights-fill::before { content: "\f82c"; } +.bi-sign-stop-lights::before { content: "\f82d"; } +.bi-sign-stop::before { content: "\f82e"; } +.bi-sign-turn-left-fill::before { content: "\f82f"; } +.bi-sign-turn-left::before { content: "\f830"; } +.bi-sign-turn-right-fill::before { content: "\f831"; } +.bi-sign-turn-right::before { content: "\f832"; } +.bi-sign-turn-slight-left-fill::before { content: "\f833"; } +.bi-sign-turn-slight-left::before { content: "\f834"; } +.bi-sign-turn-slight-right-fill::before { content: "\f835"; } +.bi-sign-turn-slight-right::before { content: "\f836"; } +.bi-sign-yield-fill::before { content: "\f837"; } +.bi-sign-yield::before { content: "\f838"; } +.bi-ev-station-fill::before { content: "\f839"; } +.bi-ev-station::before { content: "\f83a"; } +.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } +.bi-fuel-pump-diesel::before { content: "\f83c"; } +.bi-fuel-pump-fill::before { content: "\f83d"; } +.bi-fuel-pump::before { content: "\f83e"; } +.bi-0-circle-fill::before { content: "\f83f"; } +.bi-0-circle::before { content: "\f840"; } +.bi-0-square-fill::before { content: "\f841"; } +.bi-0-square::before { content: "\f842"; } +.bi-rocket-fill::before { content: "\f843"; } +.bi-rocket-takeoff-fill::before { content: "\f844"; } +.bi-rocket-takeoff::before { content: "\f845"; } +.bi-rocket::before { content: "\f846"; } +.bi-stripe::before { content: "\f847"; } +.bi-subscript::before { content: "\f848"; } +.bi-superscript::before { content: "\f849"; } +.bi-trello::before { content: "\f84a"; } +.bi-envelope-at-fill::before { content: "\f84b"; } +.bi-envelope-at::before { content: "\f84c"; } +.bi-regex::before { content: "\f84d"; } +.bi-text-wrap::before { content: "\f84e"; } +.bi-sign-dead-end-fill::before { content: "\f84f"; } +.bi-sign-dead-end::before { content: "\f850"; } +.bi-sign-do-not-enter-fill::before { content: "\f851"; } +.bi-sign-do-not-enter::before { content: "\f852"; } +.bi-sign-intersection-fill::before { content: "\f853"; } +.bi-sign-intersection-side-fill::before { content: "\f854"; } +.bi-sign-intersection-side::before { content: "\f855"; } +.bi-sign-intersection-t-fill::before { content: "\f856"; } +.bi-sign-intersection-t::before { content: "\f857"; } +.bi-sign-intersection-y-fill::before { content: "\f858"; } +.bi-sign-intersection-y::before { content: "\f859"; } +.bi-sign-intersection::before { content: "\f85a"; } +.bi-sign-merge-left-fill::before { content: "\f85b"; } +.bi-sign-merge-left::before { content: "\f85c"; } +.bi-sign-merge-right-fill::before { content: "\f85d"; } +.bi-sign-merge-right::before { content: "\f85e"; } +.bi-sign-no-left-turn-fill::before { content: "\f85f"; } +.bi-sign-no-left-turn::before { content: "\f860"; } +.bi-sign-no-parking-fill::before { content: "\f861"; } +.bi-sign-no-parking::before { content: "\f862"; } +.bi-sign-no-right-turn-fill::before { content: "\f863"; } +.bi-sign-no-right-turn::before { content: "\f864"; } +.bi-sign-railroad-fill::before { content: "\f865"; } +.bi-sign-railroad::before { content: "\f866"; } +.bi-building-add::before { content: "\f867"; } +.bi-building-check::before { content: "\f868"; } +.bi-building-dash::before { content: "\f869"; } +.bi-building-down::before { content: "\f86a"; } +.bi-building-exclamation::before { content: "\f86b"; } +.bi-building-fill-add::before { content: "\f86c"; } +.bi-building-fill-check::before { content: "\f86d"; } +.bi-building-fill-dash::before { content: "\f86e"; } +.bi-building-fill-down::before { content: "\f86f"; } +.bi-building-fill-exclamation::before { content: "\f870"; } +.bi-building-fill-gear::before { content: "\f871"; } +.bi-building-fill-lock::before { content: "\f872"; } +.bi-building-fill-slash::before { content: "\f873"; } +.bi-building-fill-up::before { content: "\f874"; } +.bi-building-fill-x::before { content: "\f875"; } +.bi-building-fill::before { content: "\f876"; } +.bi-building-gear::before { content: "\f877"; } +.bi-building-lock::before { content: "\f878"; } +.bi-building-slash::before { content: "\f879"; } +.bi-building-up::before { content: "\f87a"; } +.bi-building-x::before { content: "\f87b"; } +.bi-buildings-fill::before { content: "\f87c"; } +.bi-buildings::before { content: "\f87d"; } +.bi-bus-front-fill::before { content: "\f87e"; } +.bi-bus-front::before { content: "\f87f"; } +.bi-ev-front-fill::before { content: "\f880"; } +.bi-ev-front::before { content: "\f881"; } +.bi-globe-americas::before { content: "\f882"; } +.bi-globe-asia-australia::before { content: "\f883"; } +.bi-globe-central-south-asia::before { content: "\f884"; } +.bi-globe-europe-africa::before { content: "\f885"; } +.bi-house-add-fill::before { content: "\f886"; } +.bi-house-add::before { content: "\f887"; } +.bi-house-check-fill::before { content: "\f888"; } +.bi-house-check::before { content: "\f889"; } +.bi-house-dash-fill::before { content: "\f88a"; } +.bi-house-dash::before { content: "\f88b"; } +.bi-house-down-fill::before { content: "\f88c"; } +.bi-house-down::before { content: "\f88d"; } +.bi-house-exclamation-fill::before { content: "\f88e"; } +.bi-house-exclamation::before { content: "\f88f"; } +.bi-house-gear-fill::before { content: "\f890"; } +.bi-house-gear::before { content: "\f891"; } +.bi-house-lock-fill::before { content: "\f892"; } +.bi-house-lock::before { content: "\f893"; } +.bi-house-slash-fill::before { content: "\f894"; } +.bi-house-slash::before { content: "\f895"; } +.bi-house-up-fill::before { content: "\f896"; } +.bi-house-up::before { content: "\f897"; } +.bi-house-x-fill::before { content: "\f898"; } +.bi-house-x::before { content: "\f899"; } +.bi-person-add::before { content: "\f89a"; } +.bi-person-down::before { content: "\f89b"; } +.bi-person-exclamation::before { content: "\f89c"; } +.bi-person-fill-add::before { content: "\f89d"; } +.bi-person-fill-check::before { content: "\f89e"; } +.bi-person-fill-dash::before { content: "\f89f"; } +.bi-person-fill-down::before { content: "\f8a0"; } +.bi-person-fill-exclamation::before { content: "\f8a1"; } +.bi-person-fill-gear::before { content: "\f8a2"; } +.bi-person-fill-lock::before { content: "\f8a3"; } +.bi-person-fill-slash::before { content: "\f8a4"; } +.bi-person-fill-up::before { content: "\f8a5"; } +.bi-person-fill-x::before { content: "\f8a6"; } +.bi-person-gear::before { content: "\f8a7"; } +.bi-person-lock::before { content: "\f8a8"; } +.bi-person-slash::before { content: "\f8a9"; } +.bi-person-up::before { content: "\f8aa"; } +.bi-scooter::before { content: "\f8ab"; } +.bi-taxi-front-fill::before { content: "\f8ac"; } +.bi-taxi-front::before { content: "\f8ad"; } +.bi-amd::before { content: "\f8ae"; } +.bi-database-add::before { content: "\f8af"; } +.bi-database-check::before { content: "\f8b0"; } +.bi-database-dash::before { content: "\f8b1"; } +.bi-database-down::before { content: "\f8b2"; } +.bi-database-exclamation::before { content: "\f8b3"; } +.bi-database-fill-add::before { content: "\f8b4"; } +.bi-database-fill-check::before { content: "\f8b5"; } +.bi-database-fill-dash::before { content: "\f8b6"; } +.bi-database-fill-down::before { content: "\f8b7"; } +.bi-database-fill-exclamation::before { content: "\f8b8"; } +.bi-database-fill-gear::before { content: "\f8b9"; } +.bi-database-fill-lock::before { content: "\f8ba"; } +.bi-database-fill-slash::before { content: "\f8bb"; } +.bi-database-fill-up::before { content: "\f8bc"; } +.bi-database-fill-x::before { content: "\f8bd"; } +.bi-database-fill::before { content: "\f8be"; } +.bi-database-gear::before { content: "\f8bf"; } +.bi-database-lock::before { content: "\f8c0"; } +.bi-database-slash::before { content: "\f8c1"; } +.bi-database-up::before { content: "\f8c2"; } +.bi-database-x::before { content: "\f8c3"; } +.bi-database::before { content: "\f8c4"; } +.bi-houses-fill::before { content: "\f8c5"; } +.bi-houses::before { content: "\f8c6"; } +.bi-nvidia::before { content: "\f8c7"; } +.bi-person-vcard-fill::before { content: "\f8c8"; } +.bi-person-vcard::before { content: "\f8c9"; } +.bi-sina-weibo::before { content: "\f8ca"; } +.bi-tencent-qq::before { content: "\f8cb"; } +.bi-wikipedia::before { content: "\f8cc"; } +.bi-alphabet-uppercase::before { content: "\f2a5"; } +.bi-alphabet::before { content: "\f68a"; } +.bi-amazon::before { content: "\f68d"; } +.bi-arrows-collapse-vertical::before { content: "\f690"; } +.bi-arrows-expand-vertical::before { content: "\f695"; } +.bi-arrows-vertical::before { content: "\f698"; } +.bi-arrows::before { content: "\f6a2"; } +.bi-ban-fill::before { content: "\f6a3"; } +.bi-ban::before { content: "\f6b6"; } +.bi-bing::before { content: "\f6c2"; } +.bi-cake::before { content: "\f6e0"; } +.bi-cake2::before { content: "\f6ed"; } +.bi-cookie::before { content: "\f6ee"; } +.bi-copy::before { content: "\f759"; } +.bi-crosshair::before { content: "\f769"; } +.bi-crosshair2::before { content: "\f794"; } +.bi-emoji-astonished-fill::before { content: "\f795"; } +.bi-emoji-astonished::before { content: "\f79a"; } +.bi-emoji-grimace-fill::before { content: "\f79b"; } +.bi-emoji-grimace::before { content: "\f7a0"; } +.bi-emoji-grin-fill::before { content: "\f7a1"; } +.bi-emoji-grin::before { content: "\f7a6"; } +.bi-emoji-surprise-fill::before { content: "\f7a7"; } +.bi-emoji-surprise::before { content: "\f7ac"; } +.bi-emoji-tear-fill::before { content: "\f7ad"; } +.bi-emoji-tear::before { content: "\f7b2"; } +.bi-envelope-arrow-down-fill::before { content: "\f7b3"; } +.bi-envelope-arrow-down::before { content: "\f7b8"; } +.bi-envelope-arrow-up-fill::before { content: "\f7b9"; } +.bi-envelope-arrow-up::before { content: "\f7be"; } +.bi-feather::before { content: "\f7bf"; } +.bi-feather2::before { content: "\f7c4"; } +.bi-floppy-fill::before { content: "\f7c5"; } +.bi-floppy::before { content: "\f7d8"; } +.bi-floppy2-fill::before { content: "\f7d9"; } +.bi-floppy2::before { content: "\f7e4"; } +.bi-gitlab::before { content: "\f7e5"; } +.bi-highlighter::before { content: "\f7f8"; } +.bi-marker-tip::before { content: "\f802"; } +.bi-nvme-fill::before { content: "\f803"; } +.bi-nvme::before { content: "\f80c"; } +.bi-opencollective::before { content: "\f80d"; } +.bi-pci-card-network::before { content: "\f8cd"; } +.bi-pci-card-sound::before { content: "\f8ce"; } +.bi-radar::before { content: "\f8cf"; } +.bi-send-arrow-down-fill::before { content: "\f8d0"; } +.bi-send-arrow-down::before { content: "\f8d1"; } +.bi-send-arrow-up-fill::before { content: "\f8d2"; } +.bi-send-arrow-up::before { content: "\f8d3"; } +.bi-sim-slash-fill::before { content: "\f8d4"; } +.bi-sim-slash::before { content: "\f8d5"; } +.bi-sourceforge::before { content: "\f8d6"; } +.bi-substack::before { content: "\f8d7"; } +.bi-threads-fill::before { content: "\f8d8"; } +.bi-threads::before { content: "\f8d9"; } +.bi-transparency::before { content: "\f8da"; } +.bi-twitter-x::before { content: "\f8db"; } +.bi-type-h4::before { content: "\f8dc"; } +.bi-type-h5::before { content: "\f8dd"; } +.bi-type-h6::before { content: "\f8de"; } +.bi-backpack-fill::before { content: "\f8df"; } +.bi-backpack::before { content: "\f8e0"; } +.bi-backpack2-fill::before { content: "\f8e1"; } +.bi-backpack2::before { content: "\f8e2"; } +.bi-backpack3-fill::before { content: "\f8e3"; } +.bi-backpack3::before { content: "\f8e4"; } +.bi-backpack4-fill::before { content: "\f8e5"; } +.bi-backpack4::before { content: "\f8e6"; } +.bi-brilliance::before { content: "\f8e7"; } +.bi-cake-fill::before { content: "\f8e8"; } +.bi-cake2-fill::before { content: "\f8e9"; } +.bi-duffle-fill::before { content: "\f8ea"; } +.bi-duffle::before { content: "\f8eb"; } +.bi-exposure::before { content: "\f8ec"; } +.bi-gender-neuter::before { content: "\f8ed"; } +.bi-highlights::before { content: "\f8ee"; } +.bi-luggage-fill::before { content: "\f8ef"; } +.bi-luggage::before { content: "\f8f0"; } +.bi-mailbox-flag::before { content: "\f8f1"; } +.bi-mailbox2-flag::before { content: "\f8f2"; } +.bi-noise-reduction::before { content: "\f8f3"; } +.bi-passport-fill::before { content: "\f8f4"; } +.bi-passport::before { content: "\f8f5"; } +.bi-person-arms-up::before { content: "\f8f6"; } +.bi-person-raised-hand::before { content: "\f8f7"; } +.bi-person-standing-dress::before { content: "\f8f8"; } +.bi-person-standing::before { content: "\f8f9"; } +.bi-person-walking::before { content: "\f8fa"; } +.bi-person-wheelchair::before { content: "\f8fb"; } +.bi-shadows::before { content: "\f8fc"; } +.bi-suitcase-fill::before { content: "\f8fd"; } +.bi-suitcase-lg-fill::before { content: "\f8fe"; } +.bi-suitcase-lg::before { content: "\f8ff"; } +.bi-suitcase::before { content: "\f900"; } +.bi-suitcase2-fill::before { content: "\f901"; } +.bi-suitcase2::before { content: "\f902"; } +.bi-vignette::before { content: "\f903"; } diff --git a/site_libs/bootstrap/bootstrap-icons.woff b/site_libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 0000000000000000000000000000000000000000..dbeeb055674125ad78fda0f3d166b36e5cc92336 GIT binary patch literal 176200 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

2+UrCZlNAp`)&G@jg{m-!Dn; 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πR(XEyYV3az{Ot$Y*f93mYq^Lnu{qbYp!X!TA9&! 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 F?~0aZ)an2L literal 0 HcmV?d00001 diff --git a/site_libs/bootstrap/bootstrap.min.css b/site_libs/bootstrap/bootstrap.min.css new file mode 100644 index 00000000..988da14e --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * Bootstrap v5.3.1 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root,[data-bs-theme=light]{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-black: #000;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-default: #343a40;--bs-primary: #2780e3;--bs-secondary: #343a40;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #343a40;--bs-default-rgb: 52, 58, 64;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 52, 58, 64;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 52, 58, 64;--bs-primary-text-emphasis: #10335b;--bs-secondary-text-emphasis: #15171a;--bs-success-text-emphasis: #19490a;--bs-info-text-emphasis: #3d224b;--bs-warning-text-emphasis: #662f0a;--bs-danger-text-emphasis: #660017;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4e6f9;--bs-secondary-bg-subtle: #d6d8d9;--bs-success-bg-subtle: #d9f0d1;--bs-info-bg-subtle: #ebddf1;--bs-warning-bg-subtle: #ffe3d1;--bs-danger-bg-subtle: #ffccd7;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9ccf4;--bs-secondary-border-subtle: #aeb0b3;--bs-success-border-subtle: #b2e2a3;--bs-info-border-subtle: #d6bbe4;--bs-warning-border-subtle: #ffc8a3;--bs-danger-border-subtle: #ff99b0;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 17px;--bs-body-font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #343a40;--bs-body-color-rgb: 52, 58, 64;--bs-body-bg: #fff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(52, 58, 64, 0.75);--bs-secondary-color-rgb: 52, 58, 64;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(52, 58, 64, 0.5);--bs-tertiary-color-rgb: 52, 58, 64;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #2761e3;--bs-link-color-rgb: 39, 97, 227;--bs-link-decoration: underline;--bs-link-hover-color: #1f4eb6;--bs-link-hover-color-rgb: 31, 78, 182;--bs-code-color: #7d12ba;--bs-highlight-bg: #ffe3d1;--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #dee2e6;--bs-border-color-translucent: rgba(0, 0, 0, 0.175);--bs-border-radius: 0.25rem;--bs-border-radius-sm: 0.2em;--bs-border-radius-lg: 0.5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width: 0.25rem;--bs-focus-ring-opacity: 0.25;--bs-focus-ring-color: rgba(39, 128, 227, 0.25);--bs-form-valid-color: #3fb618;--bs-form-valid-border-color: #3fb618;--bs-form-invalid-color: #ff0039;--bs-form-invalid-border-color: #ff0039}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #212529;--bs-body-bg-rgb: 33, 37, 41;--bs-emphasis-color: #fff;--bs-emphasis-color-rgb: 255, 255, 255;--bs-secondary-color: rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb: 222, 226, 230;--bs-secondary-bg: #343a40;--bs-secondary-bg-rgb: 52, 58, 64;--bs-tertiary-color: rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb: 222, 226, 230;--bs-tertiary-bg: #2b3035;--bs-tertiary-bg-rgb: 43, 48, 53;--bs-primary-text-emphasis: #7db3ee;--bs-secondary-text-emphasis: #85898c;--bs-success-text-emphasis: #8cd374;--bs-info-text-emphasis: #c298d6;--bs-warning-text-emphasis: #ffac74;--bs-danger-text-emphasis: #ff6688;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #081a2d;--bs-secondary-bg-subtle: #0a0c0d;--bs-success-bg-subtle: #0d2405;--bs-info-bg-subtle: #1f1125;--bs-warning-bg-subtle: #331705;--bs-danger-bg-subtle: #33000b;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #174d88;--bs-secondary-border-subtle: #1f2326;--bs-success-border-subtle: #266d0e;--bs-info-border-subtle: #5c3270;--bs-warning-border-subtle: #99460e;--bs-danger-border-subtle: #990022;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #7db3ee;--bs-link-hover-color: #97c2f1;--bs-link-color-rgb: 125, 179, 238;--bs-link-hover-color-rgb: 151, 194, 241;--bs-code-color: white;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #8cd374;--bs-form-valid-border-color: #8cd374;--bs-form-invalid-color: #ff6688;--bs-form-invalid-border-color: #ff6688}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:var(--bs-heading-color)}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{--bs-link-color-rgb: var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6)}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:var(--bs-code-color);background-color:#f8f9fa;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#343a40}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:rgba(52,58,64,.75);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none !important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:rgba(52,58,64,.75)}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;width:100%;padding-right:calc(var(--bs-gutter-x)*.5);padding-left:calc(var(--bs-gutter-x)*.5);margin-right:auto;margin-left:auto}@media(min-width: 576px){.container-sm,.container{max-width:540px}}@media(min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media(min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media(min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media(min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs: 0;--bs-breakpoint-sm: 576px;--bs-breakpoint-md: 768px;--bs-breakpoint-lg: 992px;--bs-breakpoint-xl: 1200px;--bs-breakpoint-xxl: 1400px}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-color-type: initial;--bs-table-bg-type: initial;--bs-table-color-state: initial;--bs-table-bg-state: initial;--bs-table-color: #343a40;--bs-table-bg: #fff;--bs-table-border-color: #dee2e6;--bs-table-accent-bg: transparent;--bs-table-striped-color: #343a40;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #343a40;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #343a40;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(1px*2) solid #b2bac1}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(even){--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-active{--bs-table-color-state: var(--bs-table-active-color);--bs-table-bg-state: var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state: var(--bs-table-hover-color);--bs-table-bg-state: var(--bs-table-hover-bg)}.table-primary{--bs-table-color: #000;--bs-table-bg: #d4e6f9;--bs-table-border-color: #bfcfe0;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #000;--bs-table-bg: #d6d8d9;--bs-table-border-color: #c1c2c3;--bs-table-striped-bg: #cbcdce;--bs-table-striped-color: #000;--bs-table-active-bg: #c1c2c3;--bs-table-active-color: #000;--bs-table-hover-bg: #c6c8c9;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #000;--bs-table-bg: #d9f0d1;--bs-table-border-color: #c3d8bc;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #000;--bs-table-bg: #ebddf1;--bs-table-border-color: #d4c7d9;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #000;--bs-table-bg: #ffe3d1;--bs-table-border-color: #e6ccbc;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #000;--bs-table-bg: #ffccd7;--bs-table-border-color: #e6b8c2;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #000;--bs-table-bg: #f8f9fa;--bs-table-border-color: #dfe0e1;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #343a40;--bs-table-border-color: #484e53;--bs-table-striped-bg: #3e444a;--bs-table-striped-color: #fff;--bs-table-active-bg: #484e53;--bs-table-active-color: #fff;--bs-table-hover-bg: #43494e;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:rgba(52,58,64,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#343a40;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-clip:padding-box;border:1px solid #dee2e6;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#343a40;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::placeholder{color:rgba(52,58,64,.75);opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#343a40;background-color:#f8f9fa;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#e9ecef}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#343a40;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2));padding:.25rem .5rem;font-size:0.875rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2));padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + calc(1px * 2))}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2))}.form-control-color{width:3rem;height:calc(1.5em + 0.75rem + calc(1px * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important}.form-control-color::-webkit-color-swatch{border:0 !important}.form-control-color.form-control-sm{height:calc(1.5em + 0.5rem + calc(1px * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(1px * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#343a40;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #dee2e6;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #343a40}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-reverse{padding-right:0;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:0;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{--bs-form-check-bg: #fff;width:1em;height:1em;margin-top:.25em;vertical-align:top;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid #dee2e6;print-color-adjust:exact}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#2780e3;border-color:#2780e3}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#2780e3;border-color:#2780e3;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{cursor:default;opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2393c0f1'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:rgba(0,0,0,0)}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bed9f7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0)}.form-range::-moz-range-thumb{width:1rem;height:1rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#bed9f7}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(52,58,64,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(52,58,64,.75)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(1px * 2));min-height:calc(3.5rem + calc(1px * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-control-plaintext~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:#fff}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.form-floating>:disabled~label::after,.form-floating>.form-control:disabled~label::after{background-color:#e9ecef}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#343a40;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #dee2e6}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(1px*-1)}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#3fb618}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#3fb618;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#3fb618}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#ff0039}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#ff0039}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff0039;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#ff0039}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: 0.75rem;--bs-btn-padding-y: 0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight: 400;--bs-btn-line-height: 1.5;--bs-btn-color: #343a40;--bs-btn-bg: transparent;--bs-btn-border-width: 1px;--bs-btn-border-color: transparent;--bs-btn-border-radius: 0.25rem;--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity: 0.65;--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-default{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c3136;--bs-btn-hover-border-color: #2a2e33;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a2e33;--bs-btn-active-border-color: #272c30;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #216dc1;--bs-btn-hover-border-color: #1f66b6;--bs-btn-focus-shadow-rgb: 71, 147, 231;--bs-btn-active-color: #fff;--bs-btn-active-bg: #1f66b6;--bs-btn-active-border-color: #1d60aa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2780e3;--bs-btn-disabled-border-color: #2780e3}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c3136;--bs-btn-hover-border-color: #2a2e33;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a2e33;--bs-btn-active-border-color: #272c30;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #369b14;--bs-btn-hover-border-color: #329213;--bs-btn-focus-shadow-rgb: 92, 193, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #329213;--bs-btn-active-border-color: #2f8912;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3fb618;--bs-btn-disabled-border-color: #3fb618}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #82479f;--bs-btn-hover-border-color: #7a4396;--bs-btn-focus-shadow-rgb: 168, 110, 197;--bs-btn-active-color: #fff;--bs-btn-active-bg: #7a4396;--bs-btn-active-border-color: #733f8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #9954bb;--bs-btn-disabled-border-color: #9954bb}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d96314;--bs-btn-hover-border-color: #cc5e13;--bs-btn-focus-shadow-rgb: 255, 138, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc5e13;--bs-btn-active-border-color: #bf5812;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff7518;--bs-btn-disabled-border-color: #ff7518}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d90030;--bs-btn-hover-border-color: #cc002e;--bs-btn-focus-shadow-rgb: 255, 38, 87;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc002e;--bs-btn-active-border-color: #bf002b;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff0039;--bs-btn-disabled-border-color: #ff0039}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #d3d4d5;--bs-btn-hover-border-color: #c6c7c8;--bs-btn-focus-shadow-rgb: 211, 212, 213;--bs-btn-active-color: #000;--bs-btn-active-bg: #c6c7c8;--bs-btn-active-border-color: #babbbc;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #52585d;--bs-btn-hover-border-color: #484e53;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-outline-default{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2780e3;--bs-btn-hover-border-color: #2780e3;--bs-btn-focus-shadow-rgb: 39, 128, 227;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2780e3;--bs-btn-active-border-color: #2780e3;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2780e3;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2780e3;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3fb618;--bs-btn-hover-border-color: #3fb618;--bs-btn-focus-shadow-rgb: 63, 182, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3fb618;--bs-btn-active-border-color: #3fb618;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3fb618;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3fb618;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #9954bb;--bs-btn-hover-border-color: #9954bb;--bs-btn-focus-shadow-rgb: 153, 84, 187;--bs-btn-active-color: #fff;--bs-btn-active-bg: #9954bb;--bs-btn-active-border-color: #9954bb;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #9954bb;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #9954bb;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff7518;--bs-btn-hover-border-color: #ff7518;--bs-btn-focus-shadow-rgb: 255, 117, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff7518;--bs-btn-active-border-color: #ff7518;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff7518;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff7518;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff0039;--bs-btn-hover-border-color: #ff0039;--bs-btn-focus-shadow-rgb: 255, 0, 57;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff0039;--bs-btn-active-border-color: #ff0039;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff0039;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff0039;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248, 249, 250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #2761e3;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #1f4eb6;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #1f4eb6;--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 71, 121, 231;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: 0.5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius: 0.5rem}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: 0.25rem;--bs-btn-padding-x: 0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius: 0.2em}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex: 1000;--bs-dropdown-min-width: 10rem;--bs-dropdown-padding-x: 0;--bs-dropdown-padding-y: 0.5rem;--bs-dropdown-spacer: 0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color: #343a40;--bs-dropdown-bg: #fff;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-border-radius: 0.25rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.25rem - 1px);--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-divider-margin-y: 0.5rem;--bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color: #343a40;--bs-dropdown-link-hover-color: #343a40;--bs-dropdown-link-hover-bg: #f8f9fa;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: rgba(52, 58, 64, 0.5);--bs-dropdown-item-padding-x: 1rem;--bs-dropdown-item-padding-y: 0.25rem;--bs-dropdown-header-color: #6c757d;--bs-dropdown-header-padding-x: 1rem;--bs-dropdown-header-padding-y: 0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:0.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color: #dee2e6;--bs-dropdown-bg: #343a40;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: #adb5bd;--bs-dropdown-header-color: #adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(1px*-1)}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(1px*-1)}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #2761e3;--bs-nav-link-hover-color: #1f4eb6;--bs-nav-link-disabled-color: rgba(52, 58, 64, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background:none;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width: 1px;--bs-nav-tabs-border-color: #dee2e6;--bs-nav-tabs-border-radius: 0.25rem;--bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #dee2e6;--bs-nav-tabs-link-active-color: #000;--bs-nav-tabs-link-active-bg: #fff;--bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff;border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1*var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid rgba(0,0,0,0)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1*var(--bs-nav-tabs-border-width))}.nav-pills{--bs-nav-pills-border-radius: 0.25rem;--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #2780e3}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap: 1rem;--bs-nav-underline-border-width: 0.125rem;--bs-nav-underline-link-active-color: #000;gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid rgba(0,0,0,0)}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x: 0;--bs-navbar-padding-y: 0.5rem;--bs-navbar-color: #fdfeff;--bs-navbar-hover-color: rgba(253, 253, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 255, 0.75);--bs-navbar-active-color: #fdfdff;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #fdfeff;--bs-navbar-brand-hover-color: #fdfdff;--bs-navbar-nav-link-padding-x: 0.5rem;--bs-navbar-toggler-padding-y: 0.25;--bs-navbar-toggler-padding-x: 0;--bs-navbar-toggler-font-size: 1.25rem;--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(253, 254, 255, 0);--bs-navbar-toggler-border-radius: 0.25rem;--bs-navbar-toggler-focus-width: 0.25rem;--bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x: 0;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-navbar-color);--bs-nav-link-hover-color: var(--bs-navbar-hover-color);--bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:rgba(0,0,0,0);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);transition:var(--bs-navbar-toggler-transition)}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color: #fdfeff;--bs-navbar-hover-color: rgba(253, 253, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 255, 0.75);--bs-navbar-active-color: #fdfdff;--bs-navbar-brand-color: #fdfeff;--bs-navbar-brand-hover-color: #fdfdff;--bs-navbar-toggler-border-color: rgba(253, 254, 255, 0);--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y: 1rem;--bs-card-spacer-x: 1rem;--bs-card-title-spacer-y: 0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width: 1px;--bs-card-border-color: rgba(0, 0, 0, 0.175);--bs-card-border-radius: 0.25rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.25rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(52, 58, 64, 0.25);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg: #fff;--bs-card-img-overlay-padding: 1rem;--bs-card-group-margin: 0.75rem;position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-0.5*var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header-tabs{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-bottom:calc(-1*var(--bs-card-cap-padding-y));margin-left:calc(-0.5*var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-left:calc(-0.5*var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion{--bs-accordion-color: #343a40;--bs-accordion-bg: #fff;--bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;--bs-accordion-border-color: #dee2e6;--bs-accordion-border-width: 1px;--bs-accordion-border-radius: 0.25rem;--bs-accordion-inner-border-radius: calc(0.25rem - 1px);--bs-accordion-btn-padding-x: 1.25rem;--bs-accordion-btn-padding-y: 1rem;--bs-accordion-btn-color: #343a40;--bs-accordion-btn-bg: #fff;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23343a40'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width: 1.25rem;--bs-accordion-btn-icon-transform: rotate(-180deg);--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2310335b'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color: #93c0f1;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #10335b;--bs-accordion-active-bg: #d4e6f9}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1*var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237db3ee'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237db3ee'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x: 0;--bs-breadcrumb-padding-y: 0;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color: rgba(52, 58, 64, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(52, 58, 64, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x: 0.75rem;--bs-pagination-padding-y: 0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color: #2761e3;--bs-pagination-bg: #fff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.25rem;--bs-pagination-hover-color: #1f4eb6;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #1f4eb6;--bs-pagination-focus-bg: #e9ecef;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #2780e3;--bs-pagination-active-border-color: #2780e3;--bs-pagination-disabled-color: rgba(52, 58, 64, 0.75);--bs-pagination-disabled-bg: #e9ecef;--bs-pagination-disabled-border-color: #dee2e6;display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(1px*-1)}.pagination-lg{--bs-pagination-padding-x: 1.5rem;--bs-pagination-padding-y: 0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius: 0.5rem}.pagination-sm{--bs-pagination-padding-x: 0.5rem;--bs-pagination-padding-y: 0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius: 0.2em}.badge{--bs-badge-padding-x: 0.65em;--bs-badge-padding-y: 0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight: 700;--bs-badge-color: #fff;--bs-badge-border-radius: 0.25rem;display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg: transparent;--bs-alert-padding-x: 1rem;--bs-alert-padding-y: 1rem;--bs-alert-margin-bottom: 1rem;--bs-alert-color: inherit;--bs-alert-border-color: transparent;--bs-alert-border: 0 solid var(--bs-alert-border-color);--bs-alert-border-radius: 0.25rem;--bs-alert-link-color: inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{--bs-alert-color: var(--bs-default-text-emphasis);--bs-alert-bg: var(--bs-default-bg-subtle);--bs-alert-border-color: var(--bs-default-border-subtle);--bs-alert-link-color: var(--bs-default-text-emphasis)}.alert-primary{--bs-alert-color: var(--bs-primary-text-emphasis);--bs-alert-bg: var(--bs-primary-bg-subtle);--bs-alert-border-color: var(--bs-primary-border-subtle);--bs-alert-link-color: var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color: var(--bs-secondary-text-emphasis);--bs-alert-bg: var(--bs-secondary-bg-subtle);--bs-alert-border-color: var(--bs-secondary-border-subtle);--bs-alert-link-color: var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color: var(--bs-success-text-emphasis);--bs-alert-bg: var(--bs-success-bg-subtle);--bs-alert-border-color: var(--bs-success-border-subtle);--bs-alert-link-color: var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color: var(--bs-info-text-emphasis);--bs-alert-bg: var(--bs-info-bg-subtle);--bs-alert-border-color: var(--bs-info-border-subtle);--bs-alert-link-color: var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color: var(--bs-warning-text-emphasis);--bs-alert-bg: var(--bs-warning-bg-subtle);--bs-alert-border-color: var(--bs-warning-border-subtle);--bs-alert-link-color: var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color: var(--bs-danger-text-emphasis);--bs-alert-bg: var(--bs-danger-bg-subtle);--bs-alert-border-color: var(--bs-danger-border-subtle);--bs-alert-link-color: var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color: var(--bs-light-text-emphasis);--bs-alert-bg: var(--bs-light-bg-subtle);--bs-alert-border-color: var(--bs-light-border-subtle);--bs-alert-link-color: var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color: var(--bs-dark-text-emphasis);--bs-alert-bg: var(--bs-dark-bg-subtle);--bs-alert-border-color: var(--bs-dark-border-subtle);--bs-alert-link-color: var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:.5rem}}.progress,.progress-stacked{--bs-progress-height: 0.5rem;--bs-progress-font-size:0.75rem;--bs-progress-bg: #e9ecef;--bs-progress-border-radius: 0.25rem;--bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #2780e3;--bs-progress-bar-transition: width 0.6s ease;display:flex;display:-webkit-flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg)}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color: #343a40;--bs-list-group-bg: #fff;--bs-list-group-border-color: #dee2e6;--bs-list-group-border-width: 1px;--bs-list-group-border-radius: 0.25rem;--bs-list-group-item-padding-x: 1rem;--bs-list-group-item-padding-y: 0.5rem;--bs-list-group-action-color: rgba(52, 58, 64, 0.75);--bs-list-group-action-hover-color: #000;--bs-list-group-action-hover-bg: #f8f9fa;--bs-list-group-action-active-color: #343a40;--bs-list-group-action-active-bg: #e9ecef;--bs-list-group-disabled-color: rgba(52, 58, 64, 0.75);--bs-list-group-disabled-bg: #fff;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #2780e3;--bs-list-group-active-border-color: #2780e3;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1*var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{--bs-list-group-color: var(--bs-default-text-emphasis);--bs-list-group-bg: var(--bs-default-bg-subtle);--bs-list-group-border-color: var(--bs-default-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-default-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-default-border-subtle);--bs-list-group-active-color: var(--bs-default-bg-subtle);--bs-list-group-active-bg: var(--bs-default-text-emphasis);--bs-list-group-active-border-color: var(--bs-default-text-emphasis)}.list-group-item-primary{--bs-list-group-color: var(--bs-primary-text-emphasis);--bs-list-group-bg: var(--bs-primary-bg-subtle);--bs-list-group-border-color: var(--bs-primary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-primary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-primary-border-subtle);--bs-list-group-active-color: var(--bs-primary-bg-subtle);--bs-list-group-active-bg: var(--bs-primary-text-emphasis);--bs-list-group-active-border-color: var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color: var(--bs-secondary-text-emphasis);--bs-list-group-bg: var(--bs-secondary-bg-subtle);--bs-list-group-border-color: var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);--bs-list-group-active-color: var(--bs-secondary-bg-subtle);--bs-list-group-active-bg: var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color: var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color: var(--bs-success-text-emphasis);--bs-list-group-bg: var(--bs-success-bg-subtle);--bs-list-group-border-color: var(--bs-success-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-success-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-success-border-subtle);--bs-list-group-active-color: var(--bs-success-bg-subtle);--bs-list-group-active-bg: var(--bs-success-text-emphasis);--bs-list-group-active-border-color: var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color: var(--bs-info-text-emphasis);--bs-list-group-bg: var(--bs-info-bg-subtle);--bs-list-group-border-color: var(--bs-info-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-info-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-info-border-subtle);--bs-list-group-active-color: var(--bs-info-bg-subtle);--bs-list-group-active-bg: var(--bs-info-text-emphasis);--bs-list-group-active-border-color: var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color: var(--bs-warning-text-emphasis);--bs-list-group-bg: var(--bs-warning-bg-subtle);--bs-list-group-border-color: var(--bs-warning-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-warning-border-subtle);--bs-list-group-active-color: var(--bs-warning-bg-subtle);--bs-list-group-active-bg: var(--bs-warning-text-emphasis);--bs-list-group-active-border-color: var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color: var(--bs-danger-text-emphasis);--bs-list-group-bg: var(--bs-danger-bg-subtle);--bs-list-group-border-color: var(--bs-danger-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-danger-border-subtle);--bs-list-group-active-color: var(--bs-danger-bg-subtle);--bs-list-group-active-bg: var(--bs-danger-text-emphasis);--bs-list-group-active-border-color: var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color: var(--bs-light-text-emphasis);--bs-list-group-bg: var(--bs-light-bg-subtle);--bs-list-group-border-color: var(--bs-light-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-light-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-light-border-subtle);--bs-list-group-active-color: var(--bs-light-bg-subtle);--bs-list-group-active-bg: var(--bs-light-text-emphasis);--bs-list-group-active-border-color: var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color: var(--bs-dark-text-emphasis);--bs-list-group-bg: var(--bs-dark-bg-subtle);--bs-list-group-border-color: var(--bs-dark-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-dark-border-subtle);--bs-list-group-active-color: var(--bs-dark-bg-subtle);--bs-list-group-active-bg: var(--bs-dark-text-emphasis);--bs-list-group-active-border-color: var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color: #000;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity: 0.5;--bs-btn-close-hover-opacity: 0.75;--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-btn-close-focus-opacity: 1;--bs-btn-close-disabled-opacity: 0.25;--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:rgba(0,0,0,0) var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex: 1090;--bs-toast-padding-x: 0.75rem;--bs-toast-padding-y: 0.5rem;--bs-toast-spacing: 1.5rem;--bs-toast-max-width: 350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg: rgba(255, 255, 255, 0.85);--bs-toast-border-width: 1px;--bs-toast-border-color: rgba(0, 0, 0, 0.175);--bs-toast-border-radius: 0.25rem;--bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-toast-header-color: rgba(52, 58, 64, 0.75);--bs-toast-header-bg: rgba(255, 255, 255, 0.85);--bs-toast-header-border-color: rgba(0, 0, 0, 0.175);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex: 1090;position:absolute;z-index:var(--bs-toast-zindex);width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color)}.toast-header .btn-close{margin-right:calc(-0.5*var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex: 1055;--bs-modal-width: 500px;--bs-modal-padding: 1rem;--bs-modal-margin: 0.5rem;--bs-modal-color: ;--bs-modal-bg: #fff;--bs-modal-border-color: rgba(0, 0, 0, 0.175);--bs-modal-border-width: 1px;--bs-modal-border-radius: 0.5rem;--bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius: calc(0.5rem - 1px);--bs-modal-header-padding-x: 1rem;--bs-modal-header-padding-y: 1rem;--bs-modal-header-padding: 1rem 1rem;--bs-modal-header-border-color: #dee2e6;--bs-modal-header-border-width: 1px;--bs-modal-title-line-height: 1.5;--bs-modal-footer-gap: 0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color: #dee2e6;--bs-modal-footer-border-width: 1px;position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin)*2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - var(--bs-modal-margin)*2)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);outline:0}.modal-backdrop{--bs-backdrop-zindex: 1050;--bs-backdrop-bg: #000;--bs-backdrop-opacity: 0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5);margin:calc(-0.5*var(--bs-modal-header-padding-y)) calc(-0.5*var(--bs-modal-header-padding-x)) calc(-0.5*var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap)*.5)}@media(min-width: 576px){.modal{--bs-modal-margin: 1.75rem;--bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width: 300px}}@media(min-width: 992px){.modal-lg,.modal-xl{--bs-modal-width: 800px}}@media(min-width: 1200px){.modal-xl{--bs-modal-width: 1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex: 1080;--bs-tooltip-max-width: 200px;--bs-tooltip-padding-x: 0.5rem;--bs-tooltip-padding-y: 0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color: #fff;--bs-tooltip-bg: #000;--bs-tooltip-border-radius: 0.25rem;--bs-tooltip-opacity: 0.9;--bs-tooltip-arrow-width: 0.8rem;--bs-tooltip-arrow-height: 0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) 0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg)}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: 276px;--bs-popover-font-size:0.875rem;--bs-popover-bg: #fff;--bs-popover-border-width: 1px;--bs-popover-border-color: rgba(0, 0, 0, 0.175);--bs-popover-border-radius: 0.5rem;--bs-popover-inner-border-radius: calc(0.5rem - 1px);--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x: 1rem;--bs-popover-header-padding-y: 0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: inherit;--bs-popover-header-bg: #e9ecef;--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: #343a40;--bs-popover-arrow-width: 1rem;--bs-popover-arrow-height: 0.5rem;--bs-popover-arrow-border: var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid;border-width:0}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{border-width:0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-0.5*var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) 0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-grow,.spinner-border{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-border-width: 0.25em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:rgba(0,0,0,0)}.spinner-border-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem;--bs-spinner-border-width: 0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed: 1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex: 1045;--bs-offcanvas-width: 400px;--bs-offcanvas-height: 30vh;--bs-offcanvas-padding-x: 1rem;--bs-offcanvas-padding-y: 1rem;--bs-offcanvas-color: #343a40;--bs-offcanvas-bg: #fff;--bs-offcanvas-border-width: 1px;--bs-offcanvas-border-color: rgba(0, 0, 0, 0.175);--bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-offcanvas-transition: transform 0.3s ease-in-out;--bs-offcanvas-title-line-height: 1.5}@media(max-width: 575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 575.98px)and (prefers-reduced-motion: reduce){.offcanvas-sm{transition:none}}@media(max-width: 575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media(min-width: 576px){.offcanvas-sm{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 767.98px)and (prefers-reduced-motion: reduce){.offcanvas-md{transition:none}}@media(max-width: 767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media(min-width: 768px){.offcanvas-md{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 991.98px)and (prefers-reduced-motion: reduce){.offcanvas-lg{transition:none}}@media(max-width: 991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media(min-width: 992px){.offcanvas-lg{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1199.98px)and (prefers-reduced-motion: reduce){.offcanvas-xl{transition:none}}@media(max-width: 1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media(min-width: 1200px){.offcanvas-xl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1399.98px)and (prefers-reduced-motion: reduce){.offcanvas-xxl{transition:none}}@media(max-width: 1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media(min-width: 1400px){.offcanvas-xxl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y)*.5) calc(var(--bs-offcanvas-padding-x)*.5);margin-top:calc(-0.5*var(--bs-offcanvas-padding-y));margin-right:calc(-0.5*var(--bs-offcanvas-padding-x));margin-bottom:calc(-0.5*var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-default{color:#fff !important;background-color:RGBA(var(--bs-default-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-primary{color:#fff !important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-secondary{color:#fff !important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-success{color:#fff !important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-info{color:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-danger{color:#fff !important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-light{color:#000 !important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-dark{color:#fff !important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important}.link-default{color:RGBA(var(--bs-default-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-default-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-default:hover,.link-default:focus{color:RGBA(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, var(--bs-link-underline-opacity, 1)) !important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-primary:hover,.link-primary:focus{color:RGBA(31, 102, 182, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(31, 102, 182, var(--bs-link-underline-opacity, 1)) !important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-secondary:hover,.link-secondary:focus{color:RGBA(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, var(--bs-link-underline-opacity, 1)) !important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-success:hover,.link-success:focus{color:RGBA(50, 146, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(50, 146, 19, var(--bs-link-underline-opacity, 1)) !important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-info:hover,.link-info:focus{color:RGBA(122, 67, 150, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(122, 67, 150, var(--bs-link-underline-opacity, 1)) !important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-warning:hover,.link-warning:focus{color:RGBA(204, 94, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 94, 19, var(--bs-link-underline-opacity, 1)) !important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-danger:hover,.link-danger:focus{color:RGBA(204, 0, 46, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 0, 46, var(--bs-link-underline-opacity, 1)) !important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-dark:hover,.link-dark:focus{color:RGBA(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-align-items:center;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-underline-offset:.25em;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;-webkit-flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media(prefers-reduced-motion: reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform, translate3d(0.25em, 0, 0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}.sticky-bottom{position:sticky;bottom:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.object-fit-contain{object-fit:contain !important}.object-fit-cover{object-fit:cover !important}.object-fit-fill{object-fit:fill !important}.object-fit-scale{object-fit:scale-down !important}.object-fit-none{object-fit:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.overflow-x-auto{overflow-x:auto !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-x-visible{overflow-x:visible !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-auto{overflow-y:auto !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-y-visible{overflow-y:visible !important}.overflow-y-scroll{overflow-y:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-inline-grid{display:inline-grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.focus-ring-default{--bs-focus-ring-color: rgba(var(--bs-default-rgb), var(--bs-focus-ring-opacity))}.focus-ring-primary{--bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-0{border:0 !important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-top-0{border-top:0 !important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-start-0{border-left:0 !important}.border-default{--bs-border-opacity: 1;border-color:rgba(var(--bs-default-rgb), var(--bs-border-opacity)) !important}.border-primary{--bs-border-opacity: 1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important}.border-secondary{--bs-border-opacity: 1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important}.border-success{--bs-border-opacity: 1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important}.border-info{--bs-border-opacity: 1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important}.border-warning{--bs-border-opacity: 1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important}.border-danger{--bs-border-opacity: 1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important}.border-light{--bs-border-opacity: 1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important}.border-dark{--bs-border-opacity: 1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important}.border-black{--bs-border-opacity: 1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important}.border-white{--bs-border-opacity: 1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle) !important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle) !important}.border-success-subtle{border-color:var(--bs-success-border-subtle) !important}.border-info-subtle{border-color:var(--bs-info-border-subtle) !important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle) !important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle) !important}.border-light-subtle{border-color:var(--bs-light-border-subtle) !important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle) !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.border-opacity-10{--bs-border-opacity: 0.1}.border-opacity-25{--bs-border-opacity: 0.25}.border-opacity-50{--bs-border-opacity: 0.5}.border-opacity-75{--bs-border-opacity: 0.75}.border-opacity-100{--bs-border-opacity: 1}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.row-gap-0{row-gap:0 !important}.row-gap-1{row-gap:.25rem !important}.row-gap-2{row-gap:.5rem !important}.row-gap-3{row-gap:1rem !important}.row-gap-4{row-gap:1.5rem !important}.row-gap-5{row-gap:3rem !important}.column-gap-0{column-gap:0 !important}.column-gap-1{column-gap:.25rem !important}.column-gap-2{column-gap:.5rem !important}.column-gap-3{column-gap:1rem !important}.column-gap-4{column-gap:1.5rem !important}.column-gap-5{column-gap:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-lighter{font-weight:lighter !important}.fw-light{font-weight:300 !important}.fw-normal{font-weight:400 !important}.fw-medium{font-weight:500 !important}.fw-semibold{font-weight:600 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-body-secondary{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-body-tertiary{--bs-text-opacity: 1;color:var(--bs-tertiary-color) !important}.text-body-emphasis{--bs-text-opacity: 1;color:var(--bs-emphasis-color) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis) !important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis) !important}.text-success-emphasis{color:var(--bs-success-text-emphasis) !important}.text-info-emphasis{color:var(--bs-info-text-emphasis) !important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis) !important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis) !important}.text-light-emphasis{color:var(--bs-light-text-emphasis) !important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis) !important}.link-opacity-10{--bs-link-opacity: 0.1}.link-opacity-10-hover:hover{--bs-link-opacity: 0.1}.link-opacity-25{--bs-link-opacity: 0.25}.link-opacity-25-hover:hover{--bs-link-opacity: 0.25}.link-opacity-50{--bs-link-opacity: 0.5}.link-opacity-50-hover:hover{--bs-link-opacity: 0.5}.link-opacity-75{--bs-link-opacity: 0.75}.link-opacity-75-hover:hover{--bs-link-opacity: 0.75}.link-opacity-100{--bs-link-opacity: 1}.link-opacity-100-hover:hover{--bs-link-opacity: 1}.link-offset-1{text-underline-offset:.125em !important}.link-offset-1-hover:hover{text-underline-offset:.125em !important}.link-offset-2{text-underline-offset:.25em !important}.link-offset-2-hover:hover{text-underline-offset:.25em !important}.link-offset-3{text-underline-offset:.375em !important}.link-offset-3-hover:hover{text-underline-offset:.375em !important}.link-underline-default{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-default-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-primary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-secondary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-success{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-info{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-warning{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-danger{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-light{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-dark{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important}.link-underline{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-underline-opacity-0{--bs-link-underline-opacity: 0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity: 0}.link-underline-opacity-10{--bs-link-underline-opacity: 0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity: 0.1}.link-underline-opacity-25{--bs-link-underline-opacity: 0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity: 0.25}.link-underline-opacity-50{--bs-link-underline-opacity: 0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity: 0.5}.link-underline-opacity-75{--bs-link-underline-opacity: 0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity: 0.75}.link-underline-opacity-100{--bs-link-underline-opacity: 1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-body-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-body-tertiary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle) !important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle) !important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle) !important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle) !important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle) !important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle) !important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle) !important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:var(--bs-border-radius) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--bs-border-radius-sm) !important}.rounded-2{border-radius:var(--bs-border-radius) !important}.rounded-3{border-radius:var(--bs-border-radius-lg) !important}.rounded-4{border-radius:var(--bs-border-radius-xl) !important}.rounded-5{border-radius:var(--bs-border-radius-xxl) !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:var(--bs-border-radius-pill) !important}.rounded-top{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm) !important;border-top-right-radius:var(--bs-border-radius-sm) !important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg) !important;border-top-right-radius:var(--bs-border-radius-lg) !important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl) !important;border-top-right-radius:var(--bs-border-radius-xl) !important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl) !important;border-top-right-radius:var(--bs-border-radius-xxl) !important}.rounded-top-circle{border-top-left-radius:50% !important;border-top-right-radius:50% !important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill) !important;border-top-right-radius:var(--bs-border-radius-pill) !important}.rounded-end{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm) !important;border-bottom-right-radius:var(--bs-border-radius-sm) !important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg) !important;border-bottom-right-radius:var(--bs-border-radius-lg) !important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl) !important;border-bottom-right-radius:var(--bs-border-radius-xl) !important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-right-radius:var(--bs-border-radius-xxl) !important}.rounded-end-circle{border-top-right-radius:50% !important;border-bottom-right-radius:50% !important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill) !important;border-bottom-right-radius:var(--bs-border-radius-pill) !important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm) !important;border-bottom-left-radius:var(--bs-border-radius-sm) !important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg) !important;border-bottom-left-radius:var(--bs-border-radius-lg) !important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl) !important;border-bottom-left-radius:var(--bs-border-radius-xl) !important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-left-radius:var(--bs-border-radius-xxl) !important}.rounded-bottom-circle{border-bottom-right-radius:50% !important;border-bottom-left-radius:50% !important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill) !important;border-bottom-left-radius:var(--bs-border-radius-pill) !important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm) !important;border-top-left-radius:var(--bs-border-radius-sm) !important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg) !important;border-top-left-radius:var(--bs-border-radius-lg) !important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl) !important;border-top-left-radius:var(--bs-border-radius-xl) !important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl) !important;border-top-left-radius:var(--bs-border-radius-xxl) !important}.rounded-start-circle{border-bottom-left-radius:50% !important;border-top-left-radius:50% !important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill) !important;border-top-left-radius:var(--bs-border-radius-pill) !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.z-n1{z-index:-1 !important}.z-0{z-index:0 !important}.z-1{z-index:1 !important}.z-2{z-index:2 !important}.z-3{z-index:3 !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.object-fit-sm-contain{object-fit:contain !important}.object-fit-sm-cover{object-fit:cover !important}.object-fit-sm-fill{object-fit:fill !important}.object-fit-sm-scale{object-fit:scale-down !important}.object-fit-sm-none{object-fit:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-inline-grid{display:inline-grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.row-gap-sm-0{row-gap:0 !important}.row-gap-sm-1{row-gap:.25rem !important}.row-gap-sm-2{row-gap:.5rem !important}.row-gap-sm-3{row-gap:1rem !important}.row-gap-sm-4{row-gap:1.5rem !important}.row-gap-sm-5{row-gap:3rem !important}.column-gap-sm-0{column-gap:0 !important}.column-gap-sm-1{column-gap:.25rem !important}.column-gap-sm-2{column-gap:.5rem !important}.column-gap-sm-3{column-gap:1rem !important}.column-gap-sm-4{column-gap:1.5rem !important}.column-gap-sm-5{column-gap:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.object-fit-md-contain{object-fit:contain !important}.object-fit-md-cover{object-fit:cover !important}.object-fit-md-fill{object-fit:fill !important}.object-fit-md-scale{object-fit:scale-down !important}.object-fit-md-none{object-fit:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-inline-grid{display:inline-grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.row-gap-md-0{row-gap:0 !important}.row-gap-md-1{row-gap:.25rem !important}.row-gap-md-2{row-gap:.5rem !important}.row-gap-md-3{row-gap:1rem !important}.row-gap-md-4{row-gap:1.5rem !important}.row-gap-md-5{row-gap:3rem !important}.column-gap-md-0{column-gap:0 !important}.column-gap-md-1{column-gap:.25rem !important}.column-gap-md-2{column-gap:.5rem !important}.column-gap-md-3{column-gap:1rem !important}.column-gap-md-4{column-gap:1.5rem !important}.column-gap-md-5{column-gap:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.object-fit-lg-contain{object-fit:contain !important}.object-fit-lg-cover{object-fit:cover !important}.object-fit-lg-fill{object-fit:fill !important}.object-fit-lg-scale{object-fit:scale-down !important}.object-fit-lg-none{object-fit:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-inline-grid{display:inline-grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.row-gap-lg-0{row-gap:0 !important}.row-gap-lg-1{row-gap:.25rem !important}.row-gap-lg-2{row-gap:.5rem !important}.row-gap-lg-3{row-gap:1rem !important}.row-gap-lg-4{row-gap:1.5rem !important}.row-gap-lg-5{row-gap:3rem !important}.column-gap-lg-0{column-gap:0 !important}.column-gap-lg-1{column-gap:.25rem !important}.column-gap-lg-2{column-gap:.5rem !important}.column-gap-lg-3{column-gap:1rem !important}.column-gap-lg-4{column-gap:1.5rem !important}.column-gap-lg-5{column-gap:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.object-fit-xl-contain{object-fit:contain !important}.object-fit-xl-cover{object-fit:cover !important}.object-fit-xl-fill{object-fit:fill !important}.object-fit-xl-scale{object-fit:scale-down !important}.object-fit-xl-none{object-fit:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-inline-grid{display:inline-grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.row-gap-xl-0{row-gap:0 !important}.row-gap-xl-1{row-gap:.25rem !important}.row-gap-xl-2{row-gap:.5rem !important}.row-gap-xl-3{row-gap:1rem !important}.row-gap-xl-4{row-gap:1.5rem !important}.row-gap-xl-5{row-gap:3rem !important}.column-gap-xl-0{column-gap:0 !important}.column-gap-xl-1{column-gap:.25rem !important}.column-gap-xl-2{column-gap:.5rem !important}.column-gap-xl-3{column-gap:1rem !important}.column-gap-xl-4{column-gap:1.5rem !important}.column-gap-xl-5{column-gap:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.object-fit-xxl-contain{object-fit:contain !important}.object-fit-xxl-cover{object-fit:cover !important}.object-fit-xxl-fill{object-fit:fill !important}.object-fit-xxl-scale{object-fit:scale-down !important}.object-fit-xxl-none{object-fit:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-inline-grid{display:inline-grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.row-gap-xxl-0{row-gap:0 !important}.row-gap-xxl-1{row-gap:.25rem !important}.row-gap-xxl-2{row-gap:.5rem !important}.row-gap-xxl-3{row-gap:1rem !important}.row-gap-xxl-4{row-gap:1.5rem !important}.row-gap-xxl-5{row-gap:3rem !important}.column-gap-xxl-0{column-gap:0 !important}.column-gap-xxl-1{column-gap:.25rem !important}.column-gap-xxl-2{column-gap:.5rem !important}.column-gap-xxl-3{column-gap:1rem !important}.column-gap-xxl-4{column-gap:1.5rem !important}.column-gap-xxl-5{column-gap:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-inline-grid{display:inline-grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #343a40}.bg-default{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #343a40}.bg-secondary{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #343a40}.bg-dark{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #343a40}.bg-default{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #343a40}.bg-secondary{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #343a40}.bg-dark{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}html{height:100%}.bslib-page-fill{width:100%;height:100%;margin:0;padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}@media(max-width: 575.98px){.bslib-page-fill{height:var(--bslib-page-fill-mobile-height, auto)}}.bslib-grid{display:grid !important;gap:var(--bslib-spacer, 1rem);height:var(--bslib-grid-height)}.bslib-grid.grid{grid-template-columns:repeat(var(--bs-columns, 12), minmax(0, 1fr));grid-template-rows:unset;grid-auto-rows:var(--bslib-grid--row-heights);--bslib-grid--row-heights--xs: unset;--bslib-grid--row-heights--sm: unset;--bslib-grid--row-heights--md: unset;--bslib-grid--row-heights--lg: unset;--bslib-grid--row-heights--xl: unset;--bslib-grid--row-heights--xxl: unset}.bslib-grid.grid.bslib-grid--row-heights--xs{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xs)}@media(min-width: 576px){.bslib-grid.grid.bslib-grid--row-heights--sm{--bslib-grid--row-heights: var(--bslib-grid--row-heights--sm)}}@media(min-width: 768px){.bslib-grid.grid.bslib-grid--row-heights--md{--bslib-grid--row-heights: var(--bslib-grid--row-heights--md)}}@media(min-width: 992px){.bslib-grid.grid.bslib-grid--row-heights--lg{--bslib-grid--row-heights: var(--bslib-grid--row-heights--lg)}}@media(min-width: 1200px){.bslib-grid.grid.bslib-grid--row-heights--xl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xl)}}@media(min-width: 1400px){.bslib-grid.grid.bslib-grid--row-heights--xxl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xxl)}}.bslib-grid>*>.shiny-input-container{width:100%}.bslib-grid-item{grid-column:auto/span 1}@media(max-width: 767.98px){.bslib-grid-item{grid-column:1/-1}}@media(max-width: 575.98px){.bslib-grid{grid-template-columns:1fr !important;height:var(--bslib-grid-height-mobile)}.bslib-grid.grid{height:unset !important;grid-auto-rows:var(--bslib-grid--row-heights--xs, auto)}}.navbar+.container-fluid:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-sm:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-md:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-lg:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xl:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xxl:has(>.tab-content>.tab-pane.active.html-fill-container){padding-left:0;padding-right:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container{padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child){padding:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]){border-left:none;border-right:none;border-bottom:none}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]){border-radius:0}.navbar+div>.bslib-sidebar-layout{border-top:var(--bslib-sidebar-border)}.bslib-card{overflow:auto}.bslib-card .card-body+.card-body{padding-top:0}.bslib-card .card-body{overflow:auto}.bslib-card .card-body p{margin-top:0}.bslib-card .card-body p:last-child{margin-bottom:0}.bslib-card .card-body{max-height:var(--bslib-card-body-max-height, none)}.bslib-card[data-full-screen=true]>.card-body{max-height:var(--bslib-card-body-max-height-full-screen, none)}.bslib-card .card-header .form-group{margin-bottom:0}.bslib-card .card-header .selectize-control{margin-bottom:0}.bslib-card .card-header .selectize-control .item{margin-right:1.15rem}.bslib-card .card-footer{margin-top:auto}.bslib-card .bslib-navs-card-title{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center}.bslib-card .bslib-navs-card-title .nav{margin-left:auto}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border=true]){border:none}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border-radius=true]){border-top-left-radius:0;border-top-right-radius:0}[data-full-screen=true]{position:fixed;inset:3.5rem 1rem 1rem;height:auto !important;max-height:none !important;width:auto !important;z-index:1070}.bslib-full-screen-enter{display:none;position:absolute;bottom:var(--bslib-full-screen-enter-bottom, 0.2rem);right:var(--bslib-full-screen-enter-right, 0);top:var(--bslib-full-screen-enter-top);left:var(--bslib-full-screen-enter-left);color:var(--bslib-color-fg, var(--bs-card-color));background-color:var(--bslib-color-bg, var(--bs-card-bg, var(--bs-body-bg)));border:var(--bs-card-border-width) solid var(--bslib-color-fg, var(--bs-card-border-color));box-shadow:0 2px 4px rgba(0,0,0,.15);margin:.2rem .4rem;padding:.55rem !important;font-size:.8rem;cursor:pointer;opacity:.7;z-index:1070}.bslib-full-screen-enter:hover{opacity:1}.card[data-full-screen=false]:hover>*>.bslib-full-screen-enter{display:block}.bslib-has-full-screen .card:hover>*>.bslib-full-screen-enter{display:none}@media(max-width: 575.98px){.bslib-full-screen-enter{display:none !important}}.bslib-full-screen-exit{position:relative;top:1.35rem;font-size:.9rem;cursor:pointer;text-decoration:none;display:flex;float:right;margin-right:2.15rem;align-items:center;color:rgba(var(--bs-body-bg-rgb), 0.8)}.bslib-full-screen-exit:hover{color:rgba(var(--bs-body-bg-rgb), 1)}.bslib-full-screen-exit svg{margin-left:.5rem;font-size:1.5rem}#bslib-full-screen-overlay{position:fixed;inset:0;background-color:rgba(var(--bs-body-color-rgb), 0.6);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);z-index:1069;animation:bslib-full-screen-overlay-enter 400ms cubic-bezier(0.6, 0.02, 0.65, 1) forwards}@keyframes bslib-full-screen-overlay-enter{0%{opacity:0}100%{opacity:1}}.accordion .accordion-header{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:var(--bs-heading-color);margin-bottom:0}@media(min-width: 1200px){.accordion .accordion-header{font-size:1.65rem}}.accordion .accordion-icon:not(:empty){margin-right:.75rem;display:flex}.accordion .accordion-button:not(.collapsed){box-shadow:none}.accordion .accordion-button:not(.collapsed):focus{box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.bslib-sidebar-layout{--bslib-sidebar-transition-duration: 500ms;--bslib-sidebar-transition-easing-x: cubic-bezier(0.8, 0.78, 0.22, 1.07);--bslib-sidebar-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-border-radius: var(--bs-border-radius);--bslib-sidebar-vert-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.05);--bslib-sidebar-fg: var(--bs-emphasis-color, black);--bslib-sidebar-main-fg: var(--bs-card-color, var(--bs-body-color));--bslib-sidebar-main-bg: var(--bs-card-bg, var(--bs-body-bg));--bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.1);--bslib-sidebar-padding: calc(var(--bslib-spacer) * 1.5);--bslib-sidebar-icon-size: var(--bslib-spacer, 1rem);--bslib-sidebar-icon-button-size: calc(var(--bslib-sidebar-icon-size, 1rem) * 2);--bslib-sidebar-padding-icon: calc(var(--bslib-sidebar-icon-button-size, 2rem) * 1.5);--bslib-collapse-toggle-border-radius: var(--bs-border-radius, 0.25rem);--bslib-collapse-toggle-transform: 0deg;--bslib-sidebar-toggle-transition-easing: cubic-bezier(1, 0, 0, 1);--bslib-collapse-toggle-right-transform: 180deg;--bslib-sidebar-column-main: minmax(0, 1fr);display:grid !important;grid-template-columns:min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px)) var(--bslib-sidebar-column-main);position:relative;transition:grid-template-columns ease-in-out var(--bslib-sidebar-transition-duration);border:var(--bslib-sidebar-border);border-radius:var(--bslib-sidebar-border-radius)}@media(prefers-reduced-motion: reduce){.bslib-sidebar-layout{transition:none}}.bslib-sidebar-layout[data-bslib-sidebar-border=false]{border:none}.bslib-sidebar-layout[data-bslib-sidebar-border-radius=false]{border-radius:initial}.bslib-sidebar-layout>.main,.bslib-sidebar-layout>.sidebar{grid-row:1/2;border-radius:inherit;overflow:auto}.bslib-sidebar-layout>.main{grid-column:2/3;border-top-left-radius:0;border-bottom-left-radius:0;padding:var(--bslib-sidebar-padding);transition:padding var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);color:var(--bslib-sidebar-main-fg);background-color:var(--bslib-sidebar-main-bg)}.bslib-sidebar-layout>.sidebar{grid-column:1/2;width:100%;height:100%;border-right:var(--bslib-sidebar-vert-border);border-top-right-radius:0;border-bottom-right-radius:0;color:var(--bslib-sidebar-fg);background-color:var(--bslib-sidebar-bg);backdrop-filter:blur(5px)}.bslib-sidebar-layout>.sidebar>.sidebar-content{display:flex;flex-direction:column;gap:var(--bslib-spacer, 1rem);padding:var(--bslib-sidebar-padding);padding-top:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout>.sidebar>.sidebar-content>:last-child:not(.sidebar-title){margin-bottom:0}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion{margin-left:calc(-1*var(--bslib-sidebar-padding));margin-right:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:last-child{margin-bottom:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child){margin-bottom:1rem}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion .accordion-body{display:flex;flex-direction:column}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:first-child) .accordion-item:first-child{border-top:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child) .accordion-item:last-child{border-bottom:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content.has-accordion>.sidebar-title{border-bottom:none;padding-bottom:0}.bslib-sidebar-layout>.sidebar .shiny-input-container{width:100%}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar>.sidebar-content{padding-top:var(--bslib-sidebar-padding)}.bslib-sidebar-layout>.collapse-toggle{grid-row:1/2;grid-column:1/2;display:inline-flex;align-items:center;position:absolute;right:calc(var(--bslib-sidebar-icon-size));top:calc(var(--bslib-sidebar-icon-size, 1rem)/2);border:none;border-radius:var(--bslib-collapse-toggle-border-radius);height:var(--bslib-sidebar-icon-button-size, 2rem);width:var(--bslib-sidebar-icon-button-size, 2rem);display:flex;align-items:center;justify-content:center;padding:0;color:var(--bslib-sidebar-fg);background-color:unset;transition:color var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),top var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),right var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),left var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover{background-color:var(--bslib-sidebar-toggle-bg)}.bslib-sidebar-layout>.collapse-toggle>.collapse-icon{opacity:.8;width:var(--bslib-sidebar-icon-size);height:var(--bslib-sidebar-icon-size);transform:rotateY(var(--bslib-collapse-toggle-transform));transition:transform var(--bslib-sidebar-toggle-transition-easing) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover>.collapse-icon{opacity:1}.bslib-sidebar-layout .sidebar-title{font-size:1.25rem;line-height:1.25;margin-top:0;margin-bottom:1rem;padding-bottom:1rem;border-bottom:var(--bslib-sidebar-border)}.bslib-sidebar-layout.sidebar-right{grid-template-columns:var(--bslib-sidebar-column-main) min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px))}.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/2;border-top-right-radius:0;border-bottom-right-radius:0;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.bslib-sidebar-layout.sidebar-right>.sidebar{grid-column:2/3;border-right:none;border-left:var(--bslib-sidebar-vert-border);border-top-left-radius:0;border-bottom-left-radius:0}.bslib-sidebar-layout.sidebar-right>.collapse-toggle{grid-column:2/3;left:var(--bslib-sidebar-icon-size);right:unset;border:var(--bslib-collapse-toggle-border)}.bslib-sidebar-layout.sidebar-right>.collapse-toggle>.collapse-icon{transform:rotateY(var(--bslib-collapse-toggle-right-transform))}.bslib-sidebar-layout.sidebar-collapsed{--bslib-collapse-toggle-transform: 180deg;--bslib-collapse-toggle-right-transform: 0deg;--bslib-sidebar-vert-border: none;grid-template-columns:0 minmax(0, 1fr)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right{grid-template-columns:minmax(0, 1fr) 0}.bslib-sidebar-layout.sidebar-collapsed:not(.transitioning)>.sidebar>*{display:none}.bslib-sidebar-layout.sidebar-collapsed>.main{border-radius:inherit}.bslib-sidebar-layout.sidebar-collapsed:not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed>.collapse-toggle{color:var(--bslib-sidebar-main-fg);top:calc(var(--bslib-sidebar-overlap-counter, 0)*(var(--bslib-sidebar-icon-size) + var(--bslib-sidebar-padding)) + var(--bslib-sidebar-icon-size, 1rem)/2);right:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px))}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.collapse-toggle{left:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px));right:unset}@media(min-width: 576px){.bslib-sidebar-layout.transitioning>.sidebar>.sidebar-content{display:none}}@media(max-width: 575.98px){.bslib-sidebar-layout[data-bslib-sidebar-open=desktop]{--bslib-sidebar-js-init-collapsed: true}.bslib-sidebar-layout>.sidebar,.bslib-sidebar-layout.sidebar-right>.sidebar{border:none}.bslib-sidebar-layout>.main,.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/3}.bslib-sidebar-layout[data-bslib-sidebar-open=always]{display:block !important}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar{max-height:var(--bslib-sidebar-max-height-mobile);overflow-y:auto;border-top:var(--bslib-sidebar-vert-border)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]){grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.sidebar{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.collapse-toggle{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed.sidebar-right{grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always])>.main{opacity:0;transition:opacity var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed>.main{opacity:1}}:root{--bslib-value-box-shadow: none;--bslib-value-box-border-width-auto-yes: var(--bslib-value-box-border-width-baseline);--bslib-value-box-border-width-auto-no: 0;--bslib-value-box-border-width-baseline: 1px}.bslib-value-box{border-width:var(--bslib-value-box-border-width-auto-no, var(--bslib-value-box-border-width-baseline));container-name:bslib-value-box;container-type:inline-size}.bslib-value-box.card{box-shadow:var(--bslib-value-box-shadow)}.bslib-value-box.border-auto{border-width:var(--bslib-value-box-border-width-auto-yes, var(--bslib-value-box-border-width-baseline))}.bslib-value-box.default{--bslib-value-box-bg-default: var(--bs-card-bg, #fff);--bslib-value-box-border-color-default: var(--bs-card-border-color, rgba(0, 0, 0, 0.175));color:var(--bslib-value-box-color);background-color:var(--bslib-value-box-bg, var(--bslib-value-box-bg-default));border-color:var(--bslib-value-box-border-color, var(--bslib-value-box-border-color-default))}.bslib-value-box .value-box-grid{display:grid;grid-template-areas:"left right";align-items:center;overflow:hidden}.bslib-value-box .value-box-showcase{height:100%;max-height:var(---bslib-value-box-showcase-max-h, 100%)}.bslib-value-box .value-box-showcase,.bslib-value-box .value-box-showcase>.html-fill-item{width:100%}.bslib-value-box[data-full-screen=true] .value-box-showcase{max-height:var(---bslib-value-box-showcase-max-h-fs, 100%)}@media screen and (min-width: 575.98px){@container bslib-value-box (max-width: 300px){.bslib-value-box:not(.showcase-bottom) .value-box-grid{grid-template-columns:1fr !important;grid-template-rows:auto auto;grid-template-areas:"top" "bottom"}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-showcase{grid-area:top !important}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-area{grid-area:bottom !important;justify-content:end}}}.bslib-value-box .value-box-area{justify-content:center;padding:1.5rem 1rem;font-size:.9rem;font-weight:500}.bslib-value-box .value-box-area *{margin-bottom:0;margin-top:0}.bslib-value-box .value-box-title{font-size:1rem;margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}.bslib-value-box .value-box-title:empty::after{content:" "}.bslib-value-box .value-box-value{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}@media(min-width: 1200px){.bslib-value-box .value-box-value{font-size:1.65rem}}.bslib-value-box .value-box-value:empty::after{content:" "}.bslib-value-box .value-box-showcase{align-items:center;justify-content:center;margin-top:auto;margin-bottom:auto;padding:1rem}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{opacity:.85;min-width:50px;max-width:125%}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{font-size:4rem}.bslib-value-box.showcase-top-right .value-box-grid{grid-template-columns:1fr var(---bslib-value-box-showcase-w, 50%)}.bslib-value-box.showcase-top-right .value-box-grid .value-box-showcase{grid-area:right;margin-left:auto;align-self:start;align-items:end;padding-left:0;padding-bottom:0}.bslib-value-box.showcase-top-right .value-box-grid .value-box-area{grid-area:left;align-self:end}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid{grid-template-columns:auto var(---bslib-value-box-showcase-w-fs, 1fr)}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid>div{align-self:center}.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-showcase{margin-top:0}@container bslib-value-box (max-width: 300px){.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-grid .value-box-showcase{padding-left:1rem}}.bslib-value-box.showcase-left-center .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w, 30%) auto}.bslib-value-box.showcase-left-center[data-full-screen=true] .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w-fs, 1fr) auto}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-showcase{grid-area:left}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-area{grid-area:right}.bslib-value-box.showcase-bottom .value-box-grid{grid-template-columns:1fr;grid-template-rows:1fr var(---bslib-value-box-showcase-h, auto);grid-template-areas:"top" "bottom";overflow:hidden}.bslib-value-box.showcase-bottom .value-box-grid .value-box-showcase{grid-area:bottom;padding:0;margin:0}.bslib-value-box.showcase-bottom .value-box-grid .value-box-area{grid-area:top}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid{grid-template-rows:1fr var(---bslib-value-box-showcase-h-fs, 2fr)}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid .value-box-showcase{padding:1rem}[data-bs-theme=dark] .bslib-value-box{--bslib-value-box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 50%)}:root{--bslib-page-sidebar-title-bg: #2780e3;--bslib-page-sidebar-title-color: #fff}.bslib-page-title{background-color:var(--bslib-page-sidebar-title-bg);color:var(--bslib-page-sidebar-title-color);font-size:1.25rem;font-weight:300;padding:var(--bslib-spacer, 1rem);padding-left:1.5rem;margin-bottom:0;border-bottom:1px solid #dee2e6}@media(min-width: 576px){.nav:not(.nav-hidden){display:flex !important;display:-webkit-flex !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column){float:none !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.bslib-nav-spacer{margin-left:auto !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.form-inline{margin-top:auto;margin-bottom:auto}.nav:not(.nav-hidden).nav-stacked{flex-direction:column;-webkit-flex-direction:column;height:100%}.nav:not(.nav-hidden).nav-stacked>.bslib-nav-spacer{margin-top:auto !important}}.html-fill-container{display:flex;flex-direction:column;min-height:0;min-width:0}.html-fill-container>.html-fill-item{flex:1 1 auto;min-height:0;min-width:0}.html-fill-container>:not(.html-fill-item){flex:0 0 auto}.quarto-container{min-height:calc(100vh - 132px)}body.hypothesis-enabled #quarto-header{margin-right:16px}footer.footer .nav-footer,#quarto-header>nav{padding-left:1em;padding-right:1em}footer.footer div.nav-footer p:first-child{margin-top:0}footer.footer div.nav-footer p:last-child{margin-bottom:0}#quarto-content>*{padding-top:14px}#quarto-content>#quarto-sidebar-glass{padding-top:0px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:position 200ms linear;transition:all 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 115px);min-width:0;display:flex;align-items:center}@media(min-width: 992px){.navbar-brand-container{margin-right:1em}}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar .navbar-brand-container{order:2}.navbar .navbar-toggler{order:1}.navbar .navbar-container>.navbar-nav{order:20}.navbar .navbar-container>.navbar-brand-container{margin-left:0 !important;margin-right:0 !important}.navbar .navbar-collapse{order:20}.navbar #quarto-search{order:4;margin-left:auto}.navbar .navbar-toggler{margin-right:.5em}.navbar-collapse .quarto-navbar-tools{margin-left:.5em}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar .quarto-navbar-tools{order:3}.navbar .quarto-navbar-tools div.dropdown{display:inline-block}.navbar .quarto-navbar-tools .quarto-navigation-tool{color:#fdfeff}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fdfdff}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}@media(max-width: 991.98px){#quarto-sidebar div.sidebar-header{padding-top:.2em}}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .quarto-navigation-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{display:inline-flex;margin-left:0px;order:2}.sidebar-tools-main:not(.tools-wide){vertical-align:middle}.sidebar-navigation .quarto-navigation-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em;line-height:1rem;margin-top:.4rem}.sidebar-section{padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between;cursor:pointer}.sidebar-item-toggle:hover{cursor:pointer}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-item .sidebar-item-toggle .bi-chevron-right::before{transition:transform 200ms ease}.sidebar-item .sidebar-item-toggle[aria-expanded=false] .bi-chevron-right::before{transform:none}.sidebar-item .sidebar-item-toggle[aria-expanded=true] .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-item-text{width:100%}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 991.98px){.quarto-secondary-nav{display:block}.quarto-secondary-nav button.quarto-search-button{padding-right:0em;padding-left:2em}.quarto-secondary-nav button.quarto-btn-toggle{margin-left:-0.75rem;margin-right:.15rem}.quarto-secondary-nav nav.quarto-title-breadcrumbs{display:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs{display:flex;align-items:center;padding-right:1em;margin-left:-0.25em}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{text-decoration:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs ol.breadcrumb{margin-bottom:0}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-title-breadcrumbs .breadcrumb{margin-bottom:.5em;font-size:.9rem}.quarto-title-breadcrumbs .breadcrumb li:last-of-type a{color:#6c757d}.quarto-secondary-nav .quarto-btn-toggle{color:#595959}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.no-decor{text-decoration:none}.quarto-secondary-nav-title{margin-top:.3em;color:#595959;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(33,81,191,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#8c8c8c}.breadcrumb-item{line-height:1.2rem}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(33,81,191,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#2151bf}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{min-height:100%}nav.quarto-secondary-nav{background-color:#fff;border-bottom:1px solid #dee2e6}.quarto-banner nav.quarto-secondary-nav{background-color:#2780e3;color:#fdfeff;border-top:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}#quarto-sidebar{transition:width .15s ease-in}#quarto-sidebar>*{padding-right:1em}@media(max-width: 991.98px){#quarto-sidebar .sidebar-menu-container{white-space:nowrap;min-width:225px}#quarto-sidebar.show{transition:width .15s ease-out}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}@media(min-width: 992px){#quarto-sidebar-glass{display:none}}@media(max-width: 991.98px){#quarto-sidebar-glass{position:fixed;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);transition:background-color .15s ease-in;z-index:-1}#quarto-sidebar-glass.collapsing{z-index:1000}#quarto-sidebar-glass.show{transition:background-color .15s ease-out;background-color:rgba(102,102,102,.4);z-index:1000}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}.quarto-page-breadcrumbs .breadcrumb-item+.breadcrumb-item,.quarto-page-breadcrumbs .breadcrumb-item{padding-right:.33em;padding-left:0}.quarto-page-breadcrumbs .breadcrumb-item::before{padding-right:.33em}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section .bi-chevron-right::before{font-size:.9em}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#1f4eb6}.nav-footer .toc-actions{padding-bottom:.5em;padding-top:.5em}.nav-footer .toc-actions a,.nav-footer .toc-actions a:hover{text-decoration:none}.nav-footer .toc-actions ul{display:flex;list-style:none}.nav-footer .toc-actions ul :first-child{margin-left:auto}.nav-footer .toc-actions ul :last-child{margin-right:auto}.nav-footer .toc-actions ul li{padding-right:1.5em}.nav-footer .toc-actions ul li i.bi{padding-right:.4em}.nav-footer .toc-actions ul li:last-of-type{padding-right:0}.nav-footer{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:baseline;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:inline-flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}@media(min-width: 768px){.nav-footer-left{flex:1 1 0px;text-align:left}}@media(max-width: 575.98px){.nav-footer-left{margin-bottom:1em;flex:100%}}@media(min-width: 768px){.nav-footer-right{flex:1 1 0px;text-align:right}}@media(max-width: 575.98px){.nav-footer-right{margin-bottom:1em;flex:100%}}.nav-footer-center{text-align:center;min-height:3em}@media(min-width: 768px){.nav-footer-center{flex:1 1 0px}}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-bottom:1em;flex:100%}}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em;order:10}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fdfeff;border-radius:3px}@media(max-width: 991.98px){.quarto-reader-toggle{display:none}}.quarto-reader-toggle.reader.quarto-navigation-tool .quarto-reader-toggle-btn{background-color:#595959;border-radius:3px}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.2em;padding-right:.2em;margin-left:-0.2em;margin-right:-0.2em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}#quarto-back-to-top{display:none;position:fixed;bottom:50px;background-color:#fff;border-radius:.25rem;box-shadow:0 .2rem .5rem #6c757d,0 0 .05rem #6c757d;color:#6c757d;text-decoration:none;font-size:.9em;text-align:center;left:50%;padding:.4rem .8rem;transform:translate(-50%, 0)}#quarto-announcement{padding:.5em;display:flex;justify-content:space-between;margin-bottom:0;font-size:.9em}#quarto-announcement .quarto-announcement-content{margin-right:auto}#quarto-announcement .quarto-announcement-content p{margin-bottom:0}#quarto-announcement .quarto-announcement-icon{margin-right:.5em;font-size:1.2em;margin-top:-0.15em}#quarto-announcement .quarto-announcement-action{cursor:pointer}.aa-DetachedSearchButtonQuery{display:none}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:.25rem;order:999}}.navbar.navbar-expand-sm #quarto-search,.navbar.navbar-expand-md #quarto-search{order:999}@media(min-width: 992px){.navbar .quarto-navbar-tools{order:900}}@media(min-width: 992px){.navbar .quarto-navbar-tools.tools-end{margin-left:auto !important}}@media(max-width: 991.98px){#quarto-sidebar .sidebar-search{display:none}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fdfeff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fdfeff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;color:#343a40;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#343a40;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#343a40;font:inherit;height:calc(1.5em + .1rem + 2px);padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#343a40;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#343a40;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#343a40;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + calc(1px * 2))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#343a40;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#343a40;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}.aa-PanelLayout:empty{display:none}.quarto-search-no-results.no-query{display:none}.aa-Source:has(.no-query){display:none}#quarto-search-results .aa-Panel{border:solid #dee2e6 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#4b95e8}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#343a40}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e5effc}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#343a40}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#dee2e6;color:#343a40}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:0em}.aa-DetachedOverlay .aa-Item .search-item .search-result-crumbs,#quarto-search-results .aa-Item .search-item .search-result-crumbs{white-space:nowrap;text-overflow:ellipsis;font-size:.8em;font-weight:300;margin-right:1em}.aa-DetachedOverlay .aa-Item .search-item .search-result-crumbs:not(.search-result-crumbs-wrap),#quarto-search-results .aa-Item .search-item .search-result-crumbs:not(.search-result-crumbs-wrap){max-width:30%;margin-left:auto;margin-top:.5em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-item .search-result-crumbs.search-result-crumbs-wrap,#quarto-search-results .aa-Item .search-item .search-result-crumbs.search-result-crumbs-wrap{flex-basis:100%;margin-top:0em;margin-bottom:.2em;margin-left:37px}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;flex-wrap:wrap;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:42px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #dee2e6}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fdfeff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#595959}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(222,226,230,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #dee2e6;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#343a40;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{bottom:inherit;height:auto;margin:0 auto;position:absolute;top:100px;border-radius:6px;max-width:850px}@media(max-width: 575.98px){.aa-DetachedContainer--modal{width:100%;top:0px;border-radius:0px;border:none}}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(52,58,64,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-dashboard.nav-fixed.dashboard-sidebar #quarto-content.quarto-dashboard-content{padding:0em}.quarto-dashboard #quarto-content.quarto-dashboard-content{padding:1em}.quarto-dashboard #quarto-content.quarto-dashboard-content>*{padding-top:0}@media(min-width: 576px){.quarto-dashboard{height:100%}}.quarto-dashboard .card.valuebox.bslib-card.bg-primary{background-color:#5397e9 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-secondary{background-color:#343a40 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-success{background-color:#3aa716 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-info{background-color:rgba(153,84,187,.7019607843) !important}.quarto-dashboard .card.valuebox.bslib-card.bg-warning{background-color:#fa6400 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-danger{background-color:rgba(255,0,57,.7019607843) !important}.quarto-dashboard .card.valuebox.bslib-card.bg-light{background-color:#f8f9fa !important}.quarto-dashboard .card.valuebox.bslib-card.bg-dark{background-color:#343a40 !important}.quarto-dashboard.dashboard-fill{display:flex;flex-direction:column}.quarto-dashboard #quarto-appendix{display:none}.quarto-dashboard #quarto-header #quarto-dashboard-header{border-top:solid 1px #549be9;border-bottom:solid 1px #549be9}.quarto-dashboard #quarto-header #quarto-dashboard-header>nav{padding-left:1em;padding-right:1em}.quarto-dashboard #quarto-header #quarto-dashboard-header>nav .navbar-brand-container{padding-left:0}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-toggler{margin-right:0}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-toggler-icon{height:1em;width:1em;background-image:url('data:image/svg+xml,')}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-brand-container{padding-right:1em}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-title{font-size:1.1em}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-nav{font-size:.9em}.quarto-dashboard #quarto-dashboard-header .navbar{padding:0}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-container{padding-left:1em}.quarto-dashboard #quarto-dashboard-header .navbar.slim .navbar-brand-container .nav-link,.quarto-dashboard #quarto-dashboard-header .navbar.slim .navbar-nav .nav-link{padding:.7em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-color-scheme-toggle{order:9}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-toggler{margin-left:.5em;order:10}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-nav .nav-link{padding:.5em;height:100%;display:flex;align-items:center}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-nav .active{background-color:#4b95e8}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-brand-container{padding:.5em .5em .5em 0;display:flex;flex-direction:row;margin-right:2em;align-items:center}@media(max-width: 767.98px){.quarto-dashboard #quarto-dashboard-header .navbar .navbar-brand-container{margin-right:auto}}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse{align-self:stretch}@media(min-width: 768px){.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse{order:8}}@media(max-width: 767.98px){.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse{order:1000;padding-bottom:.5em}}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse .navbar-nav{align-self:stretch}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-title{font-size:1.25em;line-height:1.1em;display:flex;flex-direction:row;flex-wrap:wrap;align-items:baseline}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-title .navbar-title-text{margin-right:.4em}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-title a{text-decoration:none;color:inherit}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-subtitle,.quarto-dashboard #quarto-dashboard-header .navbar .navbar-author{font-size:.9rem;margin-right:.5em}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-author{margin-left:auto}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-logo{max-height:48px;min-height:30px;object-fit:cover;margin-right:1em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-dashboard-links{order:9;padding-right:1em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-dashboard-link-text{margin-left:.25em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-dashboard-link{padding-right:0em;padding-left:.7em;text-decoration:none;color:#fdfeff}.quarto-dashboard .page-layout-custom .tab-content{padding:0;border:none}.quarto-dashboard-img-contain{height:100%;width:100%;object-fit:contain}@media(max-width: 575.98px){.quarto-dashboard .bslib-grid{grid-template-rows:minmax(1em, max-content) !important}.quarto-dashboard .sidebar-content{height:inherit}.quarto-dashboard .page-layout-custom{min-height:100vh}}.quarto-dashboard.dashboard-toolbar>.page-layout-custom,.quarto-dashboard.dashboard-sidebar>.page-layout-custom{padding:0}.quarto-dashboard .quarto-dashboard-content.quarto-dashboard-pages{padding:0}.quarto-dashboard .callout{margin-bottom:0;margin-top:0}.quarto-dashboard .html-fill-container figure{overflow:hidden}.quarto-dashboard bslib-tooltip .rounded-pill{border:solid #6c757d 1px}.quarto-dashboard bslib-tooltip .rounded-pill .svg{fill:#343a40}.quarto-dashboard .tabset .dashboard-card-no-title .nav-tabs{margin-left:0;margin-right:auto}.quarto-dashboard .tabset .tab-content{border:none}.quarto-dashboard .tabset .card-header .nav-link[role=tab]{margin-top:-6px;padding-top:6px;padding-bottom:6px}.quarto-dashboard .card.valuebox,.quarto-dashboard .card.bslib-value-box{min-height:3rem}.quarto-dashboard .card.valuebox .card-body,.quarto-dashboard .card.bslib-value-box .card-body{padding:0}.quarto-dashboard .bslib-value-box .value-box-value{font-size:clamp(.1em,15cqw,5em)}.quarto-dashboard .bslib-value-box .value-box-showcase .bi{font-size:clamp(.1em,max(18cqw,5.2cqh),5em);text-align:center;height:1em}.quarto-dashboard .bslib-value-box .value-box-showcase .bi::before{vertical-align:1em}.quarto-dashboard .bslib-value-box .value-box-area{margin-top:auto;margin-bottom:auto}.quarto-dashboard .card figure.quarto-float{display:flex;flex-direction:column;align-items:center}.quarto-dashboard .dashboard-scrolling{padding:1em}.quarto-dashboard .full-height{height:100%}.quarto-dashboard .showcase-bottom .value-box-grid{display:grid;grid-template-columns:1fr;grid-template-rows:1fr auto;grid-template-areas:"top" "bottom"}.quarto-dashboard .showcase-bottom .value-box-grid .value-box-showcase{grid-area:bottom;padding:0;margin:0}.quarto-dashboard .showcase-bottom .value-box-grid .value-box-showcase i.bi{font-size:4rem}.quarto-dashboard .showcase-bottom .value-box-grid .value-box-area{grid-area:top}.quarto-dashboard .tab-content{margin-bottom:0}.quarto-dashboard .bslib-card .bslib-navs-card-title{justify-content:stretch;align-items:end}.quarto-dashboard .card-header{display:flex;flex-wrap:wrap;justify-content:space-between}.quarto-dashboard .card-header .card-title{display:flex;flex-direction:column;justify-content:center;margin-bottom:0}.quarto-dashboard .tabset .card-toolbar{margin-bottom:1em}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout{border:none;gap:var(--bslib-spacer, 1rem)}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.main{padding:0}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.sidebar{border-radius:.25rem;border:1px solid rgba(0,0,0,.175)}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.collapse-toggle{display:none}@media(max-width: 767.98px){.quarto-dashboard .bslib-grid>.bslib-sidebar-layout{grid-template-columns:1fr;grid-template-rows:max-content 1fr}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.main{grid-column:1;grid-row:2}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout .sidebar{grid-column:1;grid-row:1}}.quarto-dashboard .sidebar-right .sidebar{padding-left:2.5em}.quarto-dashboard .sidebar-right .collapse-toggle{left:2px}.quarto-dashboard .quarto-dashboard .sidebar-right button.collapse-toggle:not(.transitioning){left:unset}.quarto-dashboard aside.sidebar{padding-left:1em;padding-right:1em;background-color:rgba(52,58,64,.25);color:#343a40}.quarto-dashboard .bslib-sidebar-layout>div.main{padding:.7em}.quarto-dashboard .bslib-sidebar-layout button.collapse-toggle{margin-top:.3em}.quarto-dashboard .bslib-sidebar-layout .collapse-toggle{top:0}.quarto-dashboard .bslib-sidebar-layout.sidebar-collapsed:not(.transitioning):not(.sidebar-right) .collapse-toggle{left:2px}.quarto-dashboard .sidebar>section>.h3:first-of-type{margin-top:0em}.quarto-dashboard .sidebar .h3,.quarto-dashboard .sidebar .h4,.quarto-dashboard .sidebar .h5,.quarto-dashboard .sidebar .h6{margin-top:.5em}.quarto-dashboard .sidebar form{flex-direction:column;align-items:start;margin-bottom:1em}.quarto-dashboard .sidebar form div[class*=oi-][class$=-input]{flex-direction:column}.quarto-dashboard .sidebar form[class*=oi-][class$=-toggle]{flex-direction:row-reverse;align-items:center;justify-content:start}.quarto-dashboard .sidebar form input[type=range]{margin-top:.5em;margin-right:.8em;margin-left:1em}.quarto-dashboard .sidebar label{width:fit-content}.quarto-dashboard .sidebar .card-body{margin-bottom:2em}.quarto-dashboard .sidebar .shiny-input-container{margin-bottom:1em}.quarto-dashboard .sidebar .shiny-options-group{margin-top:0}.quarto-dashboard .sidebar .control-label{margin-bottom:.3em}.quarto-dashboard .card .card-body .quarto-layout-row{align-items:stretch}.quarto-dashboard .toolbar{font-size:.9em;display:flex;flex-direction:row;border-top:solid 1px #bcbfc0;padding:1em;flex-wrap:wrap;background-color:rgba(52,58,64,.25)}.quarto-dashboard .toolbar .cell-output-display{display:flex}.quarto-dashboard .toolbar .shiny-input-container{padding-bottom:.5em;margin-bottom:.5em;width:inherit}.quarto-dashboard .toolbar .shiny-input-container>.checkbox:first-child{margin-top:6px}.quarto-dashboard .toolbar>*:last-child{margin-right:0}.quarto-dashboard .toolbar>*>*{margin-right:1em;align-items:baseline}.quarto-dashboard .toolbar>*>*>a{text-decoration:none;margin-top:auto;margin-bottom:auto}.quarto-dashboard .toolbar .shiny-input-container{padding-bottom:0;margin-bottom:0}.quarto-dashboard .toolbar .shiny-input-container>*{flex-shrink:0;flex-grow:0}.quarto-dashboard .toolbar .form-group.shiny-input-container:not([role=group])>label{margin-bottom:0}.quarto-dashboard .toolbar .shiny-input-container.no-baseline{align-items:start;padding-top:6px}.quarto-dashboard .toolbar .shiny-input-container{display:flex;align-items:baseline}.quarto-dashboard .toolbar .shiny-input-container label{padding-right:.4em}.quarto-dashboard .toolbar .shiny-input-container .bslib-input-switch{margin-top:6px}.quarto-dashboard .toolbar input[type=text]{line-height:1;width:inherit}.quarto-dashboard .toolbar .input-daterange{width:inherit}.quarto-dashboard .toolbar .input-daterange input[type=text]{height:2.4em;width:10em}.quarto-dashboard .toolbar .input-daterange .input-group-addon{height:auto;padding:0;margin-left:-5px !important;margin-right:-5px}.quarto-dashboard .toolbar .input-daterange .input-group-addon .input-group-text{padding-top:0;padding-bottom:0;height:100%}.quarto-dashboard .toolbar span.irs.irs--shiny{width:10em}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-line{top:9px}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-min,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-max,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-from,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-to,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-single{top:20px}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-bar{top:8px}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-handle{top:0px}.quarto-dashboard .toolbar .shiny-input-checkboxgroup>label{margin-top:6px}.quarto-dashboard .toolbar .shiny-input-checkboxgroup>.shiny-options-group{margin-top:0;align-items:baseline}.quarto-dashboard .toolbar .shiny-input-radiogroup>label{margin-top:6px}.quarto-dashboard .toolbar .shiny-input-radiogroup>.shiny-options-group{align-items:baseline;margin-top:0}.quarto-dashboard .toolbar .shiny-input-radiogroup>.shiny-options-group>.radio{margin-right:.3em}.quarto-dashboard .toolbar .form-select{padding-top:.2em;padding-bottom:.2em}.quarto-dashboard .toolbar .shiny-input-select{min-width:6em}.quarto-dashboard .toolbar div.checkbox{margin-bottom:0px}.quarto-dashboard .toolbar>.checkbox:first-child{margin-top:6px}.quarto-dashboard .toolbar form{width:fit-content}.quarto-dashboard .toolbar form label{padding-top:.2em;padding-bottom:.2em;width:fit-content}.quarto-dashboard .toolbar form input[type=date]{width:fit-content}.quarto-dashboard .toolbar form input[type=color]{width:3em}.quarto-dashboard .toolbar form button{padding:.4em}.quarto-dashboard .toolbar form select{width:fit-content}.quarto-dashboard .toolbar>*{font-size:.9em;flex-grow:0}.quarto-dashboard .toolbar .shiny-input-container label{margin-bottom:1px}.quarto-dashboard .toolbar-bottom{margin-top:1em;margin-bottom:0 !important;order:2}.quarto-dashboard .quarto-dashboard-content>.dashboard-toolbar-container>.toolbar-content>.tab-content>.tab-pane>*:not(.bslib-sidebar-layout){padding:1em}.quarto-dashboard .quarto-dashboard-content>.dashboard-toolbar-container>.toolbar-content>*:not(.tab-content){padding:1em}.quarto-dashboard .quarto-dashboard-content>.tab-content>.dashboard-page>.dashboard-toolbar-container>.toolbar-content,.quarto-dashboard .quarto-dashboard-content>.tab-content>.dashboard-page:not(.dashboard-sidebar-container)>*:not(.dashboard-toolbar-container){padding:1em}.quarto-dashboard .toolbar-content{padding:0}.quarto-dashboard .quarto-dashboard-content.quarto-dashboard-pages .tab-pane>.dashboard-toolbar-container .toolbar{border-radius:0;margin-bottom:0}.quarto-dashboard .dashboard-toolbar-container.toolbar-toplevel .toolbar{border-bottom:1px solid rgba(0,0,0,.175)}.quarto-dashboard .dashboard-toolbar-container.toolbar-toplevel .toolbar-bottom{margin-top:0}.quarto-dashboard .dashboard-toolbar-container:not(.toolbar-toplevel) .toolbar{margin-bottom:1em;border-top:none;border-radius:.25rem;border:1px solid rgba(0,0,0,.175)}.quarto-dashboard .vega-embed.has-actions details{width:1.7em;height:2em;position:absolute !important;top:0;right:0}.quarto-dashboard .dashboard-toolbar-container{padding:0}.quarto-dashboard .card .card-header p:last-child,.quarto-dashboard .card .card-footer p:last-child{margin-bottom:0}.quarto-dashboard .card .card-body>.h4:first-child{margin-top:0}.quarto-dashboard .card .card-body{z-index:4}@media(max-width: 767.98px){.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_length,.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_info,.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_paginate{text-align:initial}.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_filter{text-align:right}.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_paginate ul.pagination{justify-content:initial}}.quarto-dashboard .card .card-body .itables .dataTables_wrapper{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;padding-top:0}.quarto-dashboard .card .card-body .itables .dataTables_wrapper table{flex-shrink:0}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons{margin-bottom:.5em;margin-left:auto;width:fit-content;float:right}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons.btn-group{background:#fff;border:none}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons .btn-secondary{background-color:#fff;background-image:none;border:solid #dee2e6 1px;padding:.2em .7em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons .btn span{font-size:.8em;color:#343a40}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_info{margin-left:.5em;margin-bottom:.5em;padding-top:0}@media(min-width: 768px){.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_info{font-size:.875em}}@media(max-width: 767.98px){.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_info{font-size:.8em}}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_filter{margin-bottom:.5em;font-size:.875em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_filter input[type=search]{padding:1px 5px 1px 5px;font-size:.875em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_length{flex-basis:1 1 50%;margin-bottom:.5em;font-size:.875em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_length select{padding:.4em 3em .4em .5em;font-size:.875em;margin-left:.2em;margin-right:.2em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_paginate{flex-shrink:0}@media(min-width: 768px){.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_paginate{margin-left:auto}}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_paginate ul.pagination .paginate_button .page-link{font-size:.8em}.quarto-dashboard .card .card-footer{font-size:.9em}.quarto-dashboard .card .card-toolbar{display:flex;flex-grow:1;flex-direction:row;width:100%;flex-wrap:wrap}.quarto-dashboard .card .card-toolbar>*{font-size:.8em;flex-grow:0}.quarto-dashboard .card .card-toolbar>.card-title{font-size:1em;flex-grow:1;align-self:flex-start;margin-top:.1em}.quarto-dashboard .card .card-toolbar .cell-output-display{display:flex}.quarto-dashboard .card .card-toolbar .shiny-input-container{padding-bottom:.5em;margin-bottom:.5em;width:inherit}.quarto-dashboard .card .card-toolbar .shiny-input-container>.checkbox:first-child{margin-top:6px}.quarto-dashboard .card .card-toolbar>*:last-child{margin-right:0}.quarto-dashboard .card .card-toolbar>*>*{margin-right:1em;align-items:baseline}.quarto-dashboard .card .card-toolbar>*>*>a{text-decoration:none;margin-top:auto;margin-bottom:auto}.quarto-dashboard .card .card-toolbar form{width:fit-content}.quarto-dashboard .card .card-toolbar form label{padding-top:.2em;padding-bottom:.2em;width:fit-content}.quarto-dashboard .card .card-toolbar form input[type=date]{width:fit-content}.quarto-dashboard .card .card-toolbar form input[type=color]{width:3em}.quarto-dashboard .card .card-toolbar form button{padding:.4em}.quarto-dashboard .card .card-toolbar form select{width:fit-content}.quarto-dashboard .card .card-toolbar .cell-output-display{display:flex}.quarto-dashboard .card .card-toolbar .shiny-input-container{padding-bottom:.5em;margin-bottom:.5em;width:inherit}.quarto-dashboard .card .card-toolbar .shiny-input-container>.checkbox:first-child{margin-top:6px}.quarto-dashboard .card .card-toolbar>*:last-child{margin-right:0}.quarto-dashboard .card .card-toolbar>*>*{margin-right:1em;align-items:baseline}.quarto-dashboard .card .card-toolbar>*>*>a{text-decoration:none;margin-top:auto;margin-bottom:auto}.quarto-dashboard .card .card-toolbar .shiny-input-container{padding-bottom:0;margin-bottom:0}.quarto-dashboard .card .card-toolbar .shiny-input-container>*{flex-shrink:0;flex-grow:0}.quarto-dashboard .card .card-toolbar .form-group.shiny-input-container:not([role=group])>label{margin-bottom:0}.quarto-dashboard .card .card-toolbar .shiny-input-container.no-baseline{align-items:start;padding-top:6px}.quarto-dashboard .card .card-toolbar .shiny-input-container{display:flex;align-items:baseline}.quarto-dashboard .card .card-toolbar .shiny-input-container label{padding-right:.4em}.quarto-dashboard .card .card-toolbar .shiny-input-container .bslib-input-switch{margin-top:6px}.quarto-dashboard .card .card-toolbar input[type=text]{line-height:1;width:inherit}.quarto-dashboard .card .card-toolbar .input-daterange{width:inherit}.quarto-dashboard .card .card-toolbar .input-daterange input[type=text]{height:2.4em;width:10em}.quarto-dashboard .card .card-toolbar .input-daterange .input-group-addon{height:auto;padding:0;margin-left:-5px !important;margin-right:-5px}.quarto-dashboard .card .card-toolbar .input-daterange .input-group-addon .input-group-text{padding-top:0;padding-bottom:0;height:100%}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny{width:10em}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-line{top:9px}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-min,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-max,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-from,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-to,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-single{top:20px}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-bar{top:8px}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-handle{top:0px}.quarto-dashboard .card .card-toolbar .shiny-input-checkboxgroup>label{margin-top:6px}.quarto-dashboard .card .card-toolbar .shiny-input-checkboxgroup>.shiny-options-group{margin-top:0;align-items:baseline}.quarto-dashboard .card .card-toolbar .shiny-input-radiogroup>label{margin-top:6px}.quarto-dashboard .card .card-toolbar .shiny-input-radiogroup>.shiny-options-group{align-items:baseline;margin-top:0}.quarto-dashboard .card .card-toolbar .shiny-input-radiogroup>.shiny-options-group>.radio{margin-right:.3em}.quarto-dashboard .card .card-toolbar .form-select{padding-top:.2em;padding-bottom:.2em}.quarto-dashboard .card .card-toolbar .shiny-input-select{min-width:6em}.quarto-dashboard .card .card-toolbar div.checkbox{margin-bottom:0px}.quarto-dashboard .card .card-toolbar>.checkbox:first-child{margin-top:6px}.quarto-dashboard .card-body>table>thead{border-top:none}.quarto-dashboard .card-body>.table>:not(caption)>*>*{background-color:#fff}.tableFloatingHeaderOriginal{background-color:#fff;position:sticky !important;top:0 !important}.dashboard-data-table{margin-top:-1px}div.value-box-area span.observablehq--number{font-size:calc(clamp(.1em,15cqw,5em)*1.25);line-height:1.2;color:inherit;font-family:var(--bs-body-font-family)}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#fff;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item p.card-img-top>img{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item .listing-item-img-placeholder{background-color:rgba(52,58,64,.25);flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none;word-break:keep-all}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post .body pre code{white-space:pre-wrap}div.quarto-post a{color:#343a40;text-decoration:none}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#626d78;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#2761e3}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#626d78;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#2761e3}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#626d78;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#2761e3}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#626d78;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#2761e3}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#626d78;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#2761e3}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.25rem;color:#343a40;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#dee2e6;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#dee2e6;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#dee2e6;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#dee2e6}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#343a40}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.visually-hidden{border:0;clip:rect(0 0 0 0);height:auto;margin:0;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}figure.figure{display:block}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}.quarto-figure>figure>div.cell-annotation,.quarto-figure>figure>div code{text-align:left}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption.quarto-float-caption-bottom{margin-bottom:.5em}figure>figcaption.quarto-float-caption-top{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,table.table{margin-top:.5rem;margin-bottom:.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-top{margin-top:.5rem;margin-bottom:.25rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-bottom{padding-top:.25rem;margin-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}dd code:not(.sourceCode),p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.footnote-back{margin-left:.2em}.tippy-content{overflow-x:auto}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}a{text-underline-offset:3px}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +* we also add `bright-[color]-` synonyms for the `-[color]-intense` classes since +* that seems to be what ansi_up emits +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-black,.ansi-bright-black-fg{color:#282c36}.ansi-black-intense-black,.ansi-bright-black-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-red,.ansi-bright-red-fg{color:#b22b31}.ansi-red-intense-red,.ansi-bright-red-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-green,.ansi-bright-green-fg{color:#007427}.ansi-green-intense-green,.ansi-bright-green-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-yellow,.ansi-bright-yellow-fg{color:#b27d12}.ansi-yellow-intense-yellow,.ansi-bright-yellow-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-blue,.ansi-bright-blue-fg{color:#0065ca}.ansi-blue-intense-blue,.ansi-bright-blue-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-magenta,.ansi-bright-magenta-fg{color:#a03196}.ansi-magenta-intense-magenta,.ansi-bright-magenta-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-cyan,.ansi-bright-cyan-fg{color:#258f8f}.ansi-cyan-intense-cyan,.ansi-bright-cyan-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-white,.ansi-bright-white-fg{color:#a1a6b2}.ansi-white-intense-white,.ansi-bright-white-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #fff;--quarto-body-color: #343a40;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:relative;float:right;background-color:rgba(0,0,0,0)}input[type=checkbox]{margin-right:.5ch}:root{--mermaid-bg-color: #fff;--mermaid-edge-color: #343a40;--mermaid-node-fg-color: #343a40;--mermaid-fg-color: #343a40;--mermaid-fg-color--lighter: #4b545c;--mermaid-fg-color--lightest: #626d78;--mermaid-font-family: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #fff;--mermaid-label-fg-color: #2780e3;--mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--mermaid-node-fg-color: #343a40}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1250px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left .page-columns.page-full>*,.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right .page-columns.page-full>*,.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;opacity:.999}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}.zindex-content{z-index:998;opacity:.999}.zindex-modal{z-index:1055;opacity:.999}.zindex-over-content{z-index:999;opacity:.999}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside:not(.footnotes):not(.sidebar),.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{color:inherit;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}main.content>section:first-of-type>h2:first-child,main.content>section:first-of-type>.h2:first-child{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#6d7a86}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,.figure-caption,.subfigure-caption,.table-caption,figcaption,caption{font-size:.9rem;color:#6d7a86}.quarto-layout-cell[data-ref-parent] caption{color:#6d7a86}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#6d7a86;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse):first-child{padding-bottom:.5em;display:block}.column-margin.column-container>*:not(.collapse):not(:first-child){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:0}.tab-pane>p:nth-child(1){padding-top:0}.tab-pane>p:last-child{margin-bottom:0}.tab-pane>pre:last-child{margin-bottom:0}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#6d7a86}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p pre code:not(.sourceCode),li pre code:not(.sourceCode),pre code:not(.sourceCode){background-color:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f8f9fa;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#2761e3}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.toc-actions i.bi,.quarto-code-links i.bi,.quarto-other-links i.bi,.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em;font-size:.8rem}.quarto-other-links-text-target .quarto-code-links i.bi,.quarto-other-links-text-target .quarto-other-links i.bi{margin-right:.2em}.quarto-other-formats-text-target .quarto-alternate-formats i.bi{margin-right:.1em}.toc-actions i.bi.empty,.quarto-code-links i.bi.empty,.quarto-other-links i.bi.empty,.quarto-alternate-notebooks i.bi.empty,.quarto-alternate-formats i.bi.empty{padding-left:1em}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook .cell-container.code-fold .cell-decorator{padding-top:3em}.quarto-notebook .cell-code code{white-space:pre-wrap}.quarto-notebook .cell .cell-output-stderr pre code,.quarto-notebook .cell .cell-output-stdout pre code{white-space:pre-wrap;overflow-wrap:anywhere}.toc-actions,.quarto-alternate-formats,.quarto-other-links,.quarto-code-links,.quarto-alternate-notebooks{padding-left:0em}.sidebar .toc-actions a,.sidebar .quarto-alternate-formats a,.sidebar .quarto-other-links a,.sidebar .quarto-code-links a,.sidebar .quarto-alternate-notebooks a,.sidebar nav[role=doc-toc] a{text-decoration:none}.sidebar .toc-actions a:hover,.sidebar .quarto-other-links a:hover,.sidebar .quarto-code-links a:hover,.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#2761e3}.sidebar .toc-actions h2,.sidebar .toc-actions .h2,.sidebar .quarto-code-links h2,.sidebar .quarto-code-links .h2,.sidebar .quarto-other-links h2,.sidebar .quarto-other-links .h2,.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-weight:500;margin-bottom:.2rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .toc-actions>h2,.sidebar .toc-actions>.h2,.sidebar .quarto-code-links>h2,.sidebar .quarto-code-links>.h2,.sidebar .quarto-other-links>h2,.sidebar .quarto-other-links>.h2,.sidebar .quarto-alternate-notebooks>h2,.sidebar .quarto-alternate-notebooks>.h2,.sidebar .quarto-alternate-formats>h2,.sidebar .quarto-alternate-formats>.h2{font-size:.8rem}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar .toc-actions h2>ul a,.sidebar .toc-actions .h2>ul a,.sidebar .quarto-code-links h2>ul a,.sidebar .quarto-code-links .h2>ul a,.sidebar .quarto-other-links h2>ul a,.sidebar .quarto-other-links .h2>ul a,.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .toc-actions ul a:empty,.sidebar .quarto-code-links ul a:empty,.sidebar .quarto-other-links ul a:empty,.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .toc-actions ul,.sidebar .quarto-code-links ul,.sidebar .quarto-other-links ul,.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul{padding-left:0;list-style:none}.sidebar nav[role=doc-toc] ul{list-style:none;padding-left:0;list-style:none}.sidebar nav[role=doc-toc]>ul{margin-left:.45em}.quarto-margin-sidebar nav[role=doc-toc]{padding-left:.5em}.sidebar .toc-actions>ul,.sidebar .quarto-code-links>ul,.sidebar .quarto-other-links>ul,.sidebar .quarto-alternate-notebooks>ul,.sidebar .quarto-alternate-formats>ul{font-size:.8rem}.sidebar nav[role=doc-toc]>ul{font-size:.875rem}.sidebar .toc-actions ul li a,.sidebar .quarto-code-links ul li a,.sidebar .quarto-other-links ul li a,.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2761e3;color:#2761e3 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2761e3 !important}kbd,.kbd{color:#343a40;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}.quarto-appendix-contents div.hanging-indent{margin-left:0em}.quarto-appendix-contents div.hanging-indent div.csl-entry{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body>:first-child{padding-top:.5rem;margin-top:0}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){padding-bottom:.5rem;margin-bottom:0}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar{background-color:#2780e3;color:#fdfeff}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#343a40}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}@media(max-width: 767.98px){.sidebar-menu-container{padding-bottom:5em}}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .footnotes ol{margin-left:.5em}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{--bs-btn-color: #cacccd;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #cacccd;--bs-btn-hover-bg: #52585d;--bs-btn-hover-border-color: #484e53;--bs-btn-focus-shadow-rgb: 75, 80, 85;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}nav.quarto-secondary-nav.color-navbar{background-color:#2780e3;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:1em}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:rgba(233,236,239,.65)}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#4b545c;border:solid #4b545c 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table{border-top:1px solid #ebedee;border-bottom:1px solid #ebedee}.table>thead{border-top-width:0;border-bottom:1px solid #b2bac1}.table a{word-break:break-word}.table>:not(caption)>*>*{background-color:unset;color:unset}#quarto-document-content .crosstalk-input .checkbox input[type=checkbox],#quarto-document-content .crosstalk-input .checkbox-inline input[type=checkbox]{position:unset;margin-top:unset;margin-left:unset}#quarto-document-content .row{margin-left:unset;margin-right:unset}.quarto-xref{white-space:nowrap}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}a.external:after{content:"";background-image:url('data:image/svg+xml,');background-size:contain;background-repeat:no-repeat;background-position:center center;margin-left:.2em;padding-right:.75em}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#2780e3}.quarto-title-banner a{color:#fdfeff}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#fdfeff}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}@media(max-width: 767.98px){body.hypothesis-enabled #title-block-header>*{padding-right:20px}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}.quarto-title-meta-container{display:grid;grid-template-columns:1fr auto}.quarto-title-meta-column-end{display:flex;flex-direction:column;padding-left:1em}.quarto-title-meta-column-end a .bi{margin-right:.3em}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr);grid-column-gap:1em}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-0.2em;height:.8em;width:.8em}#title-block-header.quarto-title-block.default .quarto-title-author-email{opacity:.7}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.1em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .keywords,#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .keywords>p,#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .keywords>p:last-of-type,#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .keywords .block-title,#title-block-header.quarto-title-block.default .description .block-title,#title-block-header.quarto-title-block.default .abstract .block-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:minmax(max-content, 1fr) 1fr;grid-column-gap:1em}.quarto-title-tools-only{display:flex;justify-content:right}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#343a40}.progress .progress-bar{font-size:8px;line-height:8px} diff --git a/site_libs/bootstrap/bootstrap.min.js b/site_libs/bootstrap/bootstrap.min.js new file mode 100644 index 00000000..e8f21f70 --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.1 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"

"},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/site_libs/clipboard/clipboard.min.js b/site_libs/clipboard/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/site_libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/site_libs/quarto-html/popper.min.js b/site_libs/quarto-html/popper.min.js new file mode 100644 index 00000000..e3726d72 --- /dev/null +++ b/site_libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.7 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-html/quarto-syntax-highlighting.css b/site_libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 00000000..b30ce576 --- /dev/null +++ b/site_libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,205 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; + font-style: inherit; +} + +code span.at { + color: #657422; + font-style: inherit; +} + +code span.ss { + color: #20794D; + font-style: inherit; +} + +code span.an { + color: #5E5E5E; + font-style: inherit; +} + +code span.fu { + color: #4758AB; + font-style: inherit; +} + +code span.st { + color: #20794D; + font-style: inherit; +} + +code span.cf { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +code span.op { + color: #5E5E5E; + font-style: inherit; +} + +code span.er { + color: #AD0000; + font-style: inherit; +} + +code span.bn { + color: #AD0000; + font-style: inherit; +} + +code span.al { + color: #AD0000; + font-style: inherit; +} + +code span.va { + color: #111111; + font-style: inherit; +} + +code span.bu { + font-style: inherit; +} + +code span.ex { + font-style: inherit; +} + +code span.pp { + color: #AD0000; + font-style: inherit; +} + +code span.in { + color: #5E5E5E; + font-style: inherit; +} + +code span.vs { + color: #20794D; + font-style: inherit; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; + font-style: inherit; +} + +code span.ch { + color: #20794D; + font-style: inherit; +} + +code span.dt { + color: #AD0000; + font-style: inherit; +} + +code span.fl { + color: #AD0000; + font-style: inherit; +} + +code span.co { + color: #5E5E5E; + font-style: inherit; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; + font-style: inherit; +} + +code span.sc { + color: #5E5E5E; + font-style: inherit; +} + +code span.dv { + color: #AD0000; + font-style: inherit; +} + +code span.kw { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > *, .margin-caption, .aside" + ); + + let lastBottom = 0; + for (const marginChild of marginChildren) { + if (marginChild.offsetParent !== null) { + // clear the top margin so we recompute it + marginChild.style.marginTop = null; + const top = marginChild.getBoundingClientRect().top + window.scrollY; + if (top < lastBottom) { + const marginChildStyle = window.getComputedStyle(marginChild); + const marginBottom = parseFloat(marginChildStyle["marginBottom"]); + const margin = lastBottom - top + marginBottom; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Recompute the position of margin elements anytime the body size changes + if (window.ResizeObserver) { + const resizeObserver = new window.ResizeObserver( + throttle(() => { + layoutMarginEls(); + if ( + window.document.body.getBoundingClientRect().width < 990 && + isReaderMode() + ) { + quartoToggleReader(); + } + }, 50) + ); + resizeObserver.observe(window.document.body); + } + + const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); + const sidebarEl = window.document.getElementById("quarto-sidebar"); + const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); + const marginSidebarEl = window.document.getElementById( + "quarto-margin-sidebar" + ); + // function to determine whether the element has a previous sibling that is active + const prevSiblingIsActiveLink = (el) => { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id="${anchor}"]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + // This is the no-scroll case where last section should be the active one + sectionIndex = 0; + } else { + // This finds the last section visible on screen that should be made active + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + let elRect; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + child.style.pointerEvents = "none"; + } + + nexttick(() => { + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append( + titleEl.textContent || titleEl.innerText, + toggleIcon + ); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.pointerEvents = null; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + if (!elRect) { + elRect = el.getBoundingClientRect(); + } + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + positionToggle(); + + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + positionToggle(); + }, 50) + ); + + window.addEventListener("quarto-hrChanged", () => { + elRect = undefined; + }); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }); + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + child.style.pointerEvents = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); + for (const tabEl of tabEls) { + const id = tabEl.getAttribute("data-bs-target"); + if (id) { + const columnEl = document.querySelector( + `${id} .column-margin, .tabset-margin-content` + ); + if (columnEl) + tabEl.addEventListener("shown.bs.tab", function (event) { + const el = event.srcElement; + if (el) { + const visibleCls = `${el.id}-margin-content`; + // walk up until we find a parent tabset + let panelTabsetEl = el.parentElement; + while (panelTabsetEl) { + if (panelTabsetEl.classList.contains("panel-tabset")) { + break; + } + panelTabsetEl = panelTabsetEl.parentElement; + } + + if (panelTabsetEl) { + const prevSib = panelTabsetEl.previousElementSibling; + if ( + prevSib && + prevSib.classList.contains("tabset-margin-container") + ) { + const childNodes = prevSib.querySelectorAll( + ".tabset-margin-content" + ); + for (const childEl of childNodes) { + if (childEl.classList.contains(visibleCls)) { + childEl.classList.remove("collapse"); + } else { + childEl.classList.add("collapse"); + } + } + } + } + } + + layoutMarginEls(); + }); + } + } + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const boundRect = el.getBoundingClientRect(); + const top = + boundRect.top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + let hasObserved = false; + const visibleItemObserver = (els) => { + let visibleElements = [...els]; + const intersectionObserver = new IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (visibleElements.indexOf(entry.target) === -1) { + visibleElements.push(entry.target); + } + } else { + visibleElements = visibleElements.filter((visibleEntry) => { + return visibleEntry !== entry; + }); + } + }); + + if (!hasObserved) { + hideOverlappedSidebars(); + } + hasObserved = true; + }, + {} + ); + els.forEach((el) => { + intersectionObserver.observe(el); + }); + + return { + getVisibleEntries: () => { + return visibleElements; + }, + }; + }; + + const rightElementObserver = visibleItemObserver(rightSideConflictEls); + const leftElementObserver = visibleItemObserver(leftSideConflictEls); + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); + sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility( + toRegions(leftElementObserver.getVisibleEntries()) + ); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); + const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (tocOpenDepth === -1 && depth > 1) { + // toc-expand: false + el.classList.add("collapse"); + } else if ( + depth <= tocOpenDepth || + hasActiveChild || + prevSiblingIsActiveLink(el) + ) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/site_libs/quarto-html/tippy.css b/site_libs/quarto-html/tippy.css new file mode 100644 index 00000000..e6ae635c --- /dev/null +++ b/site_libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/site_libs/quarto-html/tippy.umd.min.js b/site_libs/quarto-html/tippy.umd.min.js new file mode 100644 index 00000000..ca292be3 --- /dev/null +++ b/site_libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/site_libs/quarto-nav/headroom.min.js b/site_libs/quarto-nav/headroom.min.js new file mode 100644 index 00000000..b08f1dff --- /dev/null +++ b/site_libs/quarto-nav/headroom.min.js @@ -0,0 +1,7 @@ +/*! + * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it + * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js + * License: MIT + */ + +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).Headroom=n()}(this,function(){"use strict";function t(){return"undefined"!=typeof window}function d(t){return function(t){return t&&t.document&&function(t){return 9===t.nodeType}(t.document)}(t)?function(t){var n=t.document,o=n.body,s=n.documentElement;return{scrollHeight:function(){return Math.max(o.scrollHeight,s.scrollHeight,o.offsetHeight,s.offsetHeight,o.clientHeight,s.clientHeight)},height:function(){return t.innerHeight||s.clientHeight||o.clientHeight},scrollY:function(){return void 0!==t.pageYOffset?t.pageYOffset:(s||o.parentNode||o).scrollTop}}}(t):function(t){return{scrollHeight:function(){return Math.max(t.scrollHeight,t.offsetHeight,t.clientHeight)},height:function(){return Math.max(t.offsetHeight,t.clientHeight)},scrollY:function(){return t.scrollTop}}}(t)}function n(t,s,e){var n,o=function(){var n=!1;try{var t={get passive(){n=!0}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){n=!1}return n}(),i=!1,r=d(t),l=r.scrollY(),a={};function c(){var t=Math.round(r.scrollY()),n=r.height(),o=r.scrollHeight();a.scrollY=t,a.lastScrollY=l,a.direction=ls.tolerance[a.direction],e(a),l=t,i=!1}function h(){i||(i=!0,n=requestAnimationFrame(c))}var u=!!o&&{passive:!0,capture:!1};return t.addEventListener("scroll",h,u),c(),{destroy:function(){cancelAnimationFrame(n),t.removeEventListener("scroll",h,u)}}}function o(t){return t===Object(t)?t:{down:t,up:t}}function s(t,n){n=n||{},Object.assign(this,s.options,n),this.classes=Object.assign({},s.options.classes,n.classes),this.elem=t,this.tolerance=o(this.tolerance),this.offset=o(this.offset),this.initialised=!1,this.frozen=!1}return s.prototype={constructor:s,init:function(){return s.cutsTheMustard&&!this.initialised&&(this.addClass("initial"),this.initialised=!0,setTimeout(function(t){t.scrollTracker=n(t.scroller,{offset:t.offset,tolerance:t.tolerance},t.update.bind(t))},100,this)),this},destroy:function(){this.initialised=!1,Object.keys(this.classes).forEach(this.removeClass,this),this.scrollTracker.destroy()},unpin:function(){!this.hasClass("pinned")&&this.hasClass("unpinned")||(this.addClass("unpinned"),this.removeClass("pinned"),this.onUnpin&&this.onUnpin.call(this))},pin:function(){this.hasClass("unpinned")&&(this.addClass("pinned"),this.removeClass("unpinned"),this.onPin&&this.onPin.call(this))},freeze:function(){this.frozen=!0,this.addClass("frozen")},unfreeze:function(){this.frozen=!1,this.removeClass("frozen")},top:function(){this.hasClass("top")||(this.addClass("top"),this.removeClass("notTop"),this.onTop&&this.onTop.call(this))},notTop:function(){this.hasClass("notTop")||(this.addClass("notTop"),this.removeClass("top"),this.onNotTop&&this.onNotTop.call(this))},bottom:function(){this.hasClass("bottom")||(this.addClass("bottom"),this.removeClass("notBottom"),this.onBottom&&this.onBottom.call(this))},notBottom:function(){this.hasClass("notBottom")||(this.addClass("notBottom"),this.removeClass("bottom"),this.onNotBottom&&this.onNotBottom.call(this))},shouldUnpin:function(t){return"down"===t.direction&&!t.top&&t.toleranceExceeded},shouldPin:function(t){return"up"===t.direction&&t.toleranceExceeded||t.top},addClass:function(t){this.elem.classList.add.apply(this.elem.classList,this.classes[t].split(" "))},removeClass:function(t){this.elem.classList.remove.apply(this.elem.classList,this.classes[t].split(" "))},hasClass:function(t){return this.classes[t].split(" ").every(function(t){return this.classList.contains(t)},this.elem)},update:function(t){t.isOutOfBounds||!0!==this.frozen&&(t.top?this.top():this.notTop(),t.bottom?this.bottom():this.notBottom(),this.shouldUnpin(t)?this.unpin():this.shouldPin(t)&&this.pin())}},s.options={tolerance:{up:0,down:0},offset:0,scroller:t()?window:null,classes:{frozen:"headroom--frozen",pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},s.cutsTheMustard=!!(t()&&function(){}.bind&&"classList"in document.documentElement&&Object.assign&&Object.keys&&requestAnimationFrame),s}); diff --git a/site_libs/quarto-nav/quarto-nav.js b/site_libs/quarto-nav/quarto-nav.js new file mode 100644 index 00000000..38cc4305 --- /dev/null +++ b/site_libs/quarto-nav/quarto-nav.js @@ -0,0 +1,325 @@ +const headroomChanged = new CustomEvent("quarto-hrChanged", { + detail: {}, + bubbles: true, + cancelable: false, + composed: false, +}); + +const announceDismiss = () => { + const annEl = window.document.getElementById("quarto-announcement"); + if (annEl) { + annEl.remove(); + + const annId = annEl.getAttribute("data-announcement-id"); + window.localStorage.setItem(`quarto-announce-${annId}`, "true"); + } +}; + +const announceRegister = () => { + const annEl = window.document.getElementById("quarto-announcement"); + if (annEl) { + const annId = annEl.getAttribute("data-announcement-id"); + const isDismissed = + window.localStorage.getItem(`quarto-announce-${annId}`) || false; + if (isDismissed) { + announceDismiss(); + return; + } else { + annEl.classList.remove("hidden"); + } + + const actionEl = annEl.querySelector(".quarto-announcement-action"); + if (actionEl) { + actionEl.addEventListener("click", function (e) { + e.preventDefault(); + // Hide the bar immediately + announceDismiss(); + }); + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function () { + let init = false; + + announceRegister(); + + // Manage the back to top button, if one is present. + let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; + const scrollDownBuffer = 5; + const scrollUpBuffer = 35; + const btn = document.getElementById("quarto-back-to-top"); + const hideBackToTop = () => { + btn.style.display = "none"; + }; + const showBackToTop = () => { + btn.style.display = "inline-block"; + }; + if (btn) { + window.document.addEventListener( + "scroll", + function () { + const currentScrollTop = + window.pageYOffset || document.documentElement.scrollTop; + + // Shows and hides the button 'intelligently' as the user scrolls + if (currentScrollTop - scrollDownBuffer > lastScrollTop) { + hideBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } else if (currentScrollTop < lastScrollTop - scrollUpBuffer) { + showBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } + + // Show the button at the bottom, hides it at the top + if (currentScrollTop <= 0) { + hideBackToTop(); + } else if ( + window.innerHeight + currentScrollTop >= + document.body.offsetHeight + ) { + showBackToTop(); + } + }, + false + ); + } + + function throttle(func, wait) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + clearTimeout(timeout); + timeout = null; + func.apply(context, args); + }; + + if (!timeout) { + timeout = setTimeout(later, wait); + } + }; + } + + function headerOffset() { + // Set an offset if there is are fixed top navbar + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl) { + return headerEl.clientHeight; + } else { + return 0; + } + } + + function footerOffset() { + const footerEl = window.document.querySelector("footer.footer"); + if (footerEl) { + return footerEl.clientHeight; + } else { + return 0; + } + } + + function dashboardOffset() { + const dashboardNavEl = window.document.getElementById( + "quarto-dashboard-header" + ); + if (dashboardNavEl !== null) { + return dashboardNavEl.clientHeight; + } else { + return 0; + } + } + + function updateDocumentOffsetWithoutAnimation() { + updateDocumentOffset(false); + } + + function updateDocumentOffset(animated) { + // set body offset + const topOffset = headerOffset(); + const bodyOffset = topOffset + footerOffset() + dashboardOffset(); + const bodyEl = window.document.body; + bodyEl.setAttribute("data-bs-offset", topOffset); + bodyEl.style.paddingTop = topOffset + "px"; + + // deal with sidebar offsets + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + if (!animated) { + sidebar.classList.add("notransition"); + // Remove the no transition class after the animation has time to complete + setTimeout(function () { + sidebar.classList.remove("notransition"); + }, 201); + } + + if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { + sidebar.style.top = "0"; + sidebar.style.maxHeight = "100vh"; + } else { + sidebar.style.top = topOffset + "px"; + sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; + } + }); + + // allow space for footer + const mainContainer = window.document.querySelector(".quarto-container"); + if (mainContainer) { + mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; + } + + // link offset + let linkStyle = window.document.querySelector("#quarto-target-style"); + if (!linkStyle) { + linkStyle = window.document.createElement("style"); + linkStyle.setAttribute("id", "quarto-target-style"); + window.document.head.appendChild(linkStyle); + } + while (linkStyle.firstChild) { + linkStyle.removeChild(linkStyle.firstChild); + } + if (topOffset > 0) { + linkStyle.appendChild( + window.document.createTextNode(` + section:target::before { + content: ""; + display: block; + height: ${topOffset}px; + margin: -${topOffset}px 0 0; + }`) + ); + } + if (init) { + window.dispatchEvent(headroomChanged); + } + init = true; + } + + // initialize headroom + var header = window.document.querySelector("#quarto-header"); + if (header && window.Headroom) { + const headroom = new window.Headroom(header, { + tolerance: 5, + onPin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.remove("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + onUnpin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.add("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + }); + headroom.init(); + + let frozen = false; + window.quartoToggleHeadroom = function () { + if (frozen) { + headroom.unfreeze(); + frozen = false; + } else { + headroom.freeze(); + frozen = true; + } + }; + } + + window.addEventListener( + "hashchange", + function (e) { + if ( + getComputedStyle(document.documentElement).scrollBehavior !== "smooth" + ) { + window.scrollTo(0, window.pageYOffset - headerOffset()); + } + }, + false + ); + + // Observe size changed for the header + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl && window.ResizeObserver) { + const observer = new window.ResizeObserver(() => { + setTimeout(updateDocumentOffsetWithoutAnimation, 0); + }); + observer.observe(headerEl, { + attributes: true, + childList: true, + characterData: true, + }); + } else { + window.addEventListener( + "resize", + throttle(updateDocumentOffsetWithoutAnimation, 50) + ); + } + setTimeout(updateDocumentOffsetWithoutAnimation, 250); + + // fixup index.html links if we aren't on the filesystem + if (window.location.protocol !== "file:") { + const links = window.document.querySelectorAll("a"); + for (let i = 0; i < links.length; i++) { + if (links[i].href) { + links[i].dataset.originalHref = links[i].href; + links[i].href = links[i].href.replace(/\/index\.html/, "/"); + } + } + + // Fixup any sharing links that require urls + // Append url to any sharing urls + const sharingLinks = window.document.querySelectorAll( + "a.sidebar-tools-main-item, a.quarto-navigation-tool, a.quarto-navbar-tools, a.quarto-navbar-tools-item" + ); + for (let i = 0; i < sharingLinks.length; i++) { + const sharingLink = sharingLinks[i]; + const href = sharingLink.getAttribute("href"); + if (href) { + sharingLink.setAttribute( + "href", + href.replace("|url|", window.location.href) + ); + } + } + + // Scroll the active navigation item into view, if necessary + const navSidebar = window.document.querySelector("nav#quarto-sidebar"); + if (navSidebar) { + // Find the active item + const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); + if (activeItem) { + // Wait for the scroll height and height to resolve by observing size changes on the + // nav element that is scrollable + const resizeObserver = new ResizeObserver((_entries) => { + // The bottom of the element + const elBottom = activeItem.offsetTop; + const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; + + // The element height and scroll height are the same, then we are still loading + if (viewBottom !== navSidebar.scrollHeight) { + // Determine if the item isn't visible and scroll to it + if (elBottom >= viewBottom) { + navSidebar.scrollTop = elBottom; + } + + // stop observing now since we've completed the scroll + resizeObserver.unobserve(navSidebar); + } + }); + resizeObserver.observe(navSidebar); + } + } + } +}); diff --git a/site_libs/quarto-search/autocomplete.umd.js b/site_libs/quarto-search/autocomplete.umd.js new file mode 100644 index 00000000..ae0063aa --- /dev/null +++ b/site_libs/quarto-search/autocomplete.umd.js @@ -0,0 +1,3 @@ +/*! @algolia/autocomplete-js 1.11.1 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@algolia/autocomplete-js"]={})}(this,(function(e){"use strict";function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(e){for(var n=1;n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function a(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i,u,a=[],l=!0,c=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;l=!1}else for(;!(l=(r=i.call(n)).done)&&(a.push(r.value),a.length!==t);l=!0);}catch(e){c=!0,o=e}finally{try{if(!l&&null!=n.return&&(u=n.return(),Object(u)!==u))return}finally{if(c)throw o}}return a}}(e,t)||c(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(e){return function(e){if(Array.isArray(e))return s(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||c(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e,t){if(e){if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?s(e,t):void 0}}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function x(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function N(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:20,n=[],r=0;r=3||2===n&&r>=4||1===n&&r>=10);function i(t,n,r){if(o&&void 0!==r){var i=r[0].__autocomplete_algoliaCredentials,u={"X-Algolia-Application-Id":i.appId,"X-Algolia-API-Key":i.apiKey};e.apply(void 0,[t].concat(D(n),[{headers:u}]))}else e.apply(void 0,[t].concat(D(n)))}return{init:function(t,n){e("init",{appId:t,apiKey:n})},setUserToken:function(t){e("setUserToken",t)},clickedObjectIDsAfterSearch:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("clickedObjectIDsAfterSearch",B(t),t[0].items)},clickedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("clickedObjectIDs",B(t),t[0].items)},clickedFilters:function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&e.apply(void 0,["clickedFilters"].concat(n))},convertedObjectIDsAfterSearch:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("convertedObjectIDsAfterSearch",B(t),t[0].items)},convertedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("convertedObjectIDs",B(t),t[0].items)},convertedFilters:function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&e.apply(void 0,["convertedFilters"].concat(n))},viewedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&t.reduce((function(e,t){var n=t.items,r=k(t,A);return[].concat(D(e),D(q(N(N({},r),{},{objectIDs:(null==n?void 0:n.map((function(e){return e.objectID})))||r.objectIDs})).map((function(e){return{items:n,payload:e}}))))}),[]).forEach((function(e){var t=e.items;return i("viewedObjectIDs",[e.payload],t)}))},viewedFilters:function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&e.apply(void 0,["viewedFilters"].concat(n))}}}function F(e){var t=e.items.reduce((function(e,t){var n;return e[t.__autocomplete_indexName]=(null!==(n=e[t.__autocomplete_indexName])&&void 0!==n?n:[]).concat(t),e}),{});return Object.keys(t).map((function(e){return{index:e,items:t[e],algoliaSource:["autocomplete"]}}))}function L(e){return e.objectID&&e.__autocomplete_indexName&&e.__autocomplete_queryID}function U(e){return U="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},U(e)}function M(e){return function(e){if(Array.isArray(e))return H(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return H(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return H(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function H(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&z({onItemsChange:r,items:n,insights:a,state:t}))}}),0);return{name:"aa.algoliaInsightsPlugin",subscribe:function(e){var t=e.setContext,n=e.onSelect,r=e.onActive;function l(e){t({algoliaInsightsPlugin:{__algoliaSearchParameters:W({clickAnalytics:!0},e?{userToken:e}:{}),insights:a}})}u("addAlgoliaAgent","insights-plugin"),l(),u("onUserTokenChange",l),u("getUserToken",null,(function(e,t){l(t)})),n((function(e){var t=e.item,n=e.state,r=e.event,i=e.source;L(t)&&o({state:n,event:r,insights:a,item:t,insightsEvents:[W({eventName:"Item Selected"},j({item:t,items:i.getItems().filter(L)}))]})})),r((function(e){var t=e.item,n=e.source,r=e.state,o=e.event;L(t)&&i({state:r,event:o,insights:a,item:t,insightsEvents:[W({eventName:"Item Active"},j({item:t,items:n.getItems().filter(L)}))]})}))},onStateChange:function(e){var t=e.state;c({state:t})},__autocomplete_pluginOptions:e}}function J(e,t){var n=t;return{then:function(t,r){return J(e.then(Y(t,n,e),Y(r,n,e)),n)},catch:function(t){return J(e.catch(Y(t,n,e)),n)},finally:function(t){return t&&n.onCancelList.push(t),J(e.finally(Y(t&&function(){return n.onCancelList=[],t()},n,e)),n)},cancel:function(){n.isCanceled=!0;var e=n.onCancelList;n.onCancelList=[],e.forEach((function(e){e()}))},isCanceled:function(){return!0===n.isCanceled}}}function X(e){return J(e,{isCanceled:!1,onCancelList:[]})}function Y(e,t,n){return e?function(n){return t.isCanceled?n:e(n)}:n}function Z(e,t,n,r){if(!n)return null;if(e<0&&(null===t||null!==r&&0===t))return n+e;var o=(null===t?-1:t)+e;return o<=-1||o>=n?null===r?null:0:o}function ee(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function te(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:d(),plugins:o,initialState:he({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(ye(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return function(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t={getItemInputValue:function(e){return e.state.query},getItemUrl:function(){},onSelect:function(e){(0,e.setIsOpen)(!1)},onActive:O,onResolve:O};Object.keys(t).forEach((function(e){t[e].__default=!0}));var r=te(te({},t),e);return Promise.resolve(r)})))}))}(e,n)}))).then((function(e){return m(e)})).then((function(e){return e.map((function(e){return he(he({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))},onResolve:function(n){e.onResolve(n),t.forEach((function(e){var t;return null===(t=e.onResolve)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:he({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}function Se(e){return Se="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Se(e)}function je(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Pe(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var He,Ve,We,Ke=null,Qe=(He=-1,Ve=-1,We=void 0,function(e){var t=++He;return Promise.resolve(e).then((function(e){return We&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function et(e){return et="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},et(e)}var tt=["props","refresh","store"],nt=["inputElement","formElement","panelElement"],rt=["inputElement"],ot=["inputElement","maxLength"],it=["source"],ut=["item","source"];function at(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function lt(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ft(e){var t=e.props,n=e.refresh,r=e.store,o=st(e,tt);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;function u(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())}return lt({onTouchStart:u,onMouseDown:u,onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},st(e,nt))},getRootProps:function(e){return lt({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?r.getState().collections.map((function(e){var n=e.source;return ie(t.id,"list",n)})).join(" "):void 0,"aria-labelledby":ie(t.id,"label")},e)},getFormProps:function(e){return e.inputElement,lt({action:"",noValidate:!0,role:"search",onSubmit:function(i){var u;i.preventDefault(),t.onSubmit(lt({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(u=e.inputElement)||void 0===u||u.blur()},onReset:function(i){var u;i.preventDefault(),t.onReset(lt({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(u=e.inputElement)||void 0===u||u.focus()}},st(e,rt))},getLabelProps:function(e){return lt({htmlFor:ie(t.id,"input"),id:ie(t.id,"label")},e)},getInputProps:function(e){var i;function u(e){(t.openOnFocus||Boolean(r.getState().query))&&$e(lt({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var a=e||{};a.inputElement;var l=a.maxLength,c=void 0===l?512:l,s=st(a,ot),f=oe(r.getState()),p=function(e){return Boolean(e&&e.match(ue))}((null===(i=t.environment.navigator)||void 0===i?void 0:i.userAgent)||""),m=t.enterKeyHint||(null!=f&&f.itemUrl&&!p?"go":"search");return lt({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?ie(t.id,"item-".concat(r.getState().activeItemId),null==f?void 0:f.source):void 0,"aria-controls":r.getState().isOpen?r.getState().collections.map((function(e){var n=e.source;return ie(t.id,"list",n)})).join(" "):void 0,"aria-labelledby":ie(t.id,"label"),value:r.getState().completion||r.getState().query,id:ie(t.id,"input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:m,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:c,type:"search",onChange:function(e){$e(lt({event:e,props:t,query:e.currentTarget.value.slice(0,c),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=Ze(e,Ge);if("ArrowUp"===t.key||"ArrowDown"===t.key){var u=function(){var e=oe(o.getState()),t=n.environment.document.getElementById(ie(n.id,"item-".concat(o.getState().activeItemId),null==e?void 0:e.source));t&&(t.scrollIntoViewIfNeeded?t.scrollIntoViewIfNeeded(!1):t.scrollIntoView(!1))},a=function(){var e=oe(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,u=e.itemInputValue,a=e.itemUrl,l=e.source;l.onActive(Xe({event:t,item:n,itemInputValue:u,itemUrl:a,refresh:r,source:l,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?$e(Xe({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),a(),setTimeout(u,0)})):(o.dispatch(t.key,{}),a(),u())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(n.debug||o.pendingRequests.cancelAll());t.preventDefault();var l=oe(o.getState()),c=l.item,s=l.itemInputValue,f=l.itemUrl,p=l.source;if(t.metaKey||t.ctrlKey)void 0!==f&&(p.onSelect(Xe({event:t,item:c,itemInputValue:s,itemUrl:f,refresh:r,source:p,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:f,item:c,state:o.getState()}));else if(t.shiftKey)void 0!==f&&(p.onSelect(Xe({event:t,item:c,itemInputValue:s,itemUrl:f,refresh:r,source:p,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:f,item:c,state:o.getState()}));else if(t.altKey);else{if(void 0!==f)return p.onSelect(Xe({event:t,item:c,itemInputValue:s,itemUrl:f,refresh:r,source:p,state:o.getState()},i)),void n.navigator.navigate({itemUrl:f,item:c,state:o.getState()});$e(Xe({event:t,nextState:{isOpen:!1},props:n,query:s,refresh:r,store:o},i)).then((function(){p.onSelect(Xe({event:t,item:c,itemInputValue:s,itemUrl:f,refresh:r,source:p,state:o.getState()},i))}))}}}(lt({event:e,props:t,refresh:n,store:r},o))},onFocus:u,onBlur:O,onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||u(n)}},s)},getPanelProps:function(e){return lt({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){var n=e||{},r=n.source,o=st(n,it);return lt({role:"listbox","aria-labelledby":ie(t.id,"label"),id:ie(t.id,"list",r)},o)},getItemProps:function(e){var i=e.item,u=e.source,a=st(e,ut);return lt({id:ie(t.id,"item-".concat(i.__autocomplete_id),u),role:"option","aria-selected":r.getState().activeItemId===i.__autocomplete_id,onMouseMove:function(e){if(i.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",i.__autocomplete_id);var t=oe(r.getState());if(null!==r.getState().activeItemId&&t){var u=t.item,a=t.itemInputValue,l=t.itemUrl,c=t.source;c.onActive(lt({event:e,item:u,itemInputValue:a,itemUrl:l,refresh:n,source:c,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var a=u.getItemInputValue({item:i,state:r.getState()}),l=u.getItemUrl({item:i,state:r.getState()});(l?Promise.resolve():$e(lt({event:e,nextState:{isOpen:!1},props:t,query:a,refresh:n,store:r},o))).then((function(){u.onSelect(lt({event:e,item:i,itemInputValue:a,itemUrl:l,refresh:n,source:u,state:r.getState()},o))}))}},a)}}}function pt(e){return pt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},pt(e)}function mt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function vt(e){for(var t=1;t=5&&((o||!e&&5===r)&&(u.push(r,0,o,n),r=6),e&&(u.push(r,e,0,n),r=6)),o=""},l=0;l"===t?(r=1,o=""):o=t+o[0]:i?t===i?i="":o+=t:'"'===t||"'"===t?i=t:">"===t?(a(),r=1):r&&("="===t?(r=5,n=o,o=""):"/"===t&&(r<5||">"===e[l][c+1])?(a(),3===r&&(u=u[0]),r=u,(u=u[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(a(),r=2):o+=t),3===r&&"!--"===o&&(r=4,u=u[0])}return a(),u}(e)),t),arguments,[])).length>1?t:t[0]}var kt=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n};function xt(e,t){if("string"==typeof t){var n=e.document.querySelector(t);return"The element ".concat(JSON.stringify(t)," is not in the document."),n}return t}function Nt(){for(var e=arguments.length,t=new Array(e),n=0;n2&&(u.children=arguments.length>3?Jt.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(i in e.defaultProps)void 0===u[i]&&(u[i]=e.defaultProps[i]);return sn(e,u,r,o,null)}function sn(e,t,n,r,o){var i={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++Yt:o};return null==o&&null!=Xt.vnode&&Xt.vnode(i),i}function fn(e){return e.children}function pn(e,t){this.props=e,this.context=t}function mn(e,t){if(null==t)return e.__?mn(e.__,e.__.__k.indexOf(e)+1):null;for(var n;tt&&Zt.sort(nn));yn.__r=0}function bn(e,t,n,r,o,i,u,a,l,c){var s,f,p,m,v,d,y,b=r&&r.__k||on,g=b.length;for(n.__k=[],s=0;s0?sn(m.type,m.props,m.key,m.ref?m.ref:null,m.__v):m)){if(m.__=n,m.__b=n.__b+1,null===(p=b[s])||p&&m.key==p.key&&m.type===p.type)b[s]=void 0;else for(f=0;f=0;t--)if((n=e.__k[t])&&(r=On(n)))return r;return null}function _n(e,t,n){"-"===t[0]?e.setProperty(t,null==n?"":n):e[t]=null==n?"":"number"!=typeof n||un.test(t)?n:n+"px"}function Sn(e,t,n,r,o){var i;e:if("style"===t)if("string"==typeof n)e.style.cssText=n;else{if("string"==typeof r&&(e.style.cssText=r=""),r)for(t in r)n&&t in n||_n(e.style,t,"");if(n)for(t in n)r&&n[t]===r[t]||_n(e.style,t,n[t])}else if("o"===t[0]&&"n"===t[1])i=t!==(t=t.replace(/Capture$/,"")),t=t.toLowerCase()in e?t.toLowerCase().slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=n,n?r||e.addEventListener(t,i?Pn:jn,i):e.removeEventListener(t,i?Pn:jn,i);else if("dangerouslySetInnerHTML"!==t){if(o)t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==t&&"height"!==t&&"href"!==t&&"list"!==t&&"form"!==t&&"tabIndex"!==t&&"download"!==t&&t in e)try{e[t]=null==n?"":n;break e}catch(e){}"function"==typeof n||(null==n||!1===n&&"-"!==t[4]?e.removeAttribute(t):e.setAttribute(t,n))}}function jn(e){return this.l[e.type+!1](Xt.event?Xt.event(e):e)}function Pn(e){return this.l[e.type+!0](Xt.event?Xt.event(e):e)}function wn(e,t,n,r,o,i,u,a,l){var c,s,f,p,m,v,d,y,b,g,h,O,_,S,j,P=t.type;if(void 0!==t.constructor)return null;null!=n.__h&&(l=n.__h,a=t.__e=n.__e,t.__h=null,i=[a]),(c=Xt.__b)&&c(t);try{e:if("function"==typeof P){if(y=t.props,b=(c=P.contextType)&&r[c.__c],g=c?b?b.props.value:c.__:r,n.__c?d=(s=t.__c=n.__c).__=s.__E:("prototype"in P&&P.prototype.render?t.__c=s=new P(y,g):(t.__c=s=new pn(y,g),s.constructor=P,s.render=Cn),b&&b.sub(s),s.props=y,s.state||(s.state={}),s.context=g,s.__n=r,f=s.__d=!0,s.__h=[],s._sb=[]),null==s.__s&&(s.__s=s.state),null!=P.getDerivedStateFromProps&&(s.__s==s.state&&(s.__s=an({},s.__s)),an(s.__s,P.getDerivedStateFromProps(y,s.__s))),p=s.props,m=s.state,s.__v=t,f)null==P.getDerivedStateFromProps&&null!=s.componentWillMount&&s.componentWillMount(),null!=s.componentDidMount&&s.__h.push(s.componentDidMount);else{if(null==P.getDerivedStateFromProps&&y!==p&&null!=s.componentWillReceiveProps&&s.componentWillReceiveProps(y,g),!s.__e&&null!=s.shouldComponentUpdate&&!1===s.shouldComponentUpdate(y,s.__s,g)||t.__v===n.__v){for(t.__v!==n.__v&&(s.props=y,s.state=s.__s,s.__d=!1),s.__e=!1,t.__e=n.__e,t.__k=n.__k,t.__k.forEach((function(e){e&&(e.__=t)})),h=0;h0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split(xn);r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Tn(e){return function(e){if(Array.isArray(e))return qn(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return qn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return qn(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function qn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},Fn=new RegExp(/\w/i),Ln=/&(amp|quot|lt|gt|#39);/g,Un=RegExp(Ln.source);function Mn(e,t){var n,r,o,i=e[t],u=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,a=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return Fn.test((o=i.value)&&Un.test(o)?o.replace(Ln,(function(e){return Rn[e]})):o)||a!==u?i.isHighlighted:a}function Hn(e){return Hn="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Hn(e)}function Vn(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Wn(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ur(e){return function(e){if(Array.isArray(e))return ar(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return ar(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return ar(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function ar(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0;if(!O.value.core.openOnFocus&&!t.query)return n;var r=Boolean(y.current||O.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:br,options:e}}))})),j=f(n({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},O.value.core.initialState)),P={getEnvironmentProps:O.value.renderer.getEnvironmentProps,getFormProps:O.value.renderer.getFormProps,getInputProps:O.value.renderer.getInputProps,getItemProps:O.value.renderer.getItemProps,getLabelProps:O.value.renderer.getLabelProps,getListProps:O.value.renderer.getListProps,getPanelProps:O.value.renderer.getPanelProps,getRootProps:O.value.renderer.getRootProps},w={setActiveItemId:S.value.setActiveItemId,setQuery:S.value.setQuery,setCollections:S.value.setCollections,setIsOpen:S.value.setIsOpen,setStatus:S.value.setStatus,setContext:S.value.setContext,refresh:S.value.refresh,navigator:S.value.navigator},I=m((function(){return Ct.bind(O.value.renderer.renderer.createElement)})),A=m((function(){return Gt({autocomplete:S.value,autocompleteScopeApi:w,classNames:O.value.renderer.classNames,environment:O.value.core.environment,isDetached:_.value,placeholder:O.value.core.placeholder,propGetters:P,setIsModalOpen:k,state:j.current,translations:O.value.renderer.translations})}));function E(){Ht(A.value.panel,{style:_.value?{}:yr({panelPlacement:O.value.renderer.panelPlacement,container:A.value.root,form:A.value.form,environment:O.value.core.environment})})}function D(e){j.current=e;var t={autocomplete:S.value,autocompleteScopeApi:w,classNames:O.value.renderer.classNames,components:O.value.renderer.components,container:O.value.renderer.container,html:I.value,dom:A.value,panelContainer:_.value?A.value.detachedContainer:O.value.renderer.panelContainer,propGetters:P,state:j.current,renderer:O.value.renderer.renderer},r=!b(e)&&!y.current&&O.value.renderer.renderNoResults||O.value.renderer.render;!function(e){var t=e.autocomplete,r=e.autocompleteScopeApi,o=e.dom,i=e.propGetters,u=e.state;Vt(o.root,i.getRootProps(n({state:u,props:t.getRootProps({})},r))),Vt(o.input,i.getInputProps(n({state:u,props:t.getInputProps({inputElement:o.input}),inputElement:o.input},r))),Ht(o.label,{hidden:"stalled"===u.status}),Ht(o.loadingIndicator,{hidden:"stalled"!==u.status}),Ht(o.clearButton,{hidden:!u.query}),Ht(o.detachedSearchButtonQuery,{textContent:u.query}),Ht(o.detachedSearchButtonPlaceholder,{hidden:Boolean(u.query)})}(t),function(e,t){var r=t.autocomplete,o=t.autocompleteScopeApi,u=t.classNames,a=t.html,l=t.dom,c=t.panelContainer,s=t.propGetters,f=t.state,p=t.components,m=t.renderer;if(f.isOpen){c.contains(l.panel)||"loading"===f.status||c.appendChild(l.panel),l.panel.classList.toggle("aa-Panel--stalled","stalled"===f.status);var v=f.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var l=e.source,c=e.items;return m.createElement("section",{key:t,className:u.source,"data-autocomplete-source-id":l.sourceId},l.templates.header&&m.createElement("div",{className:u.sourceHeader},l.templates.header({components:p,createElement:m.createElement,Fragment:m.Fragment,items:c,source:l,state:f,html:a})),l.templates.noResults&&0===c.length?m.createElement("div",{className:u.sourceNoResults},l.templates.noResults({components:p,createElement:m.createElement,Fragment:m.Fragment,source:l,state:f,html:a})):m.createElement("ul",i({className:u.list},s.getListProps(n({state:f,props:r.getListProps({source:l})},o))),c.map((function(e){var t=r.getItemProps({item:e,source:l});return m.createElement("li",i({key:t.id,className:u.item},s.getItemProps(n({state:f,props:t},o))),l.templates.item({components:p,createElement:m.createElement,Fragment:m.Fragment,item:e,state:f,html:a}))}))),l.templates.footer&&m.createElement("div",{className:u.sourceFooter},l.templates.footer({components:p,createElement:m.createElement,Fragment:m.Fragment,items:c,source:l,state:f,html:a})))})),d=m.createElement(m.Fragment,null,m.createElement("div",{className:u.panelLayout},v),m.createElement("div",{className:"aa-GradientBottom"})),y=v.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(n(n({children:d,state:f,sections:v,elements:y},m),{},{components:p,html:a},o),l.panel)}else c.contains(l.panel)&&c.removeChild(l.panel)}(r,t)}function C(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};l();var t=O.value.renderer,n=t.components,r=u(t,gr);g.current=qt(r,O.value.core,{components:Bt(n,(function(e){return!e.value.hasOwnProperty("__autocomplete_componentName")})),initialState:j.current},e),v(),c(),S.value.refresh().then((function(){D(j.current)}))}function k(e){requestAnimationFrame((function(){var t=O.value.core.environment.document.body.contains(A.value.detachedOverlay);e!==t&&(e?(O.value.core.environment.document.body.appendChild(A.value.detachedOverlay),O.value.core.environment.document.body.classList.add("aa-Detached"),A.value.input.focus()):(O.value.core.environment.document.body.removeChild(A.value.detachedOverlay),O.value.core.environment.document.body.classList.remove("aa-Detached")))}))}return a((function(){var e=S.value.getEnvironmentProps({formElement:A.value.form,panelElement:A.value.panel,inputElement:A.value.input});return Ht(O.value.core.environment,e),function(){Ht(O.value.core.environment,Object.keys(e).reduce((function(e,t){return n(n({},e),{},o({},t,void 0))}),{}))}})),a((function(){var e=_.value?O.value.core.environment.document.body:O.value.renderer.panelContainer,t=_.value?A.value.detachedOverlay:A.value.panel;return _.value&&j.current.isOpen&&k(!0),D(j.current),function(){e.contains(t)&&e.removeChild(t)}})),a((function(){var e=O.value.renderer.container;return e.appendChild(A.value.root),function(){e.removeChild(A.value.root)}})),a((function(){var e=p((function(e){D(e.state)}),0);return h.current=function(t){var n=t.state,r=t.prevState;(_.value&&r.isOpen!==n.isOpen&&k(n.isOpen),_.value||!n.isOpen||r.isOpen||E(),n.query!==r.query)&&O.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){h.current=void 0}})),a((function(){var e=p((function(){var e=_.value;_.value=O.value.core.environment.matchMedia(O.value.renderer.detachedMediaQuery).matches,e!==_.value?C({}):requestAnimationFrame(E)}),20);return O.value.core.environment.addEventListener("resize",e),function(){O.value.core.environment.removeEventListener("resize",e)}})),a((function(){if(!_.value)return function(){};function e(e){A.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=O.value.core.environment.matchMedia(getComputedStyle(O.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),a((function(){return requestAnimationFrame(E),function(){}})),n(n({},w),{},{update:C,destroy:function(){l()}})},e.getAlgoliaFacets=function(e){var t=hr({transformResponse:function(e){return e.facetHits}}),r=e.queries.map((function(e){return n(n({},e),{},{type:"facet"})}));return t(n(n({},e),{},{queries:r}))},e.getAlgoliaResults=Or,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-search/fuse.min.js b/site_libs/quarto-search/fuse.min.js new file mode 100644 index 00000000..adc28356 --- /dev/null +++ b/site_libs/quarto-search/fuse.min.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,l=i.includeMatches,f=void 0===l?I.includeMatches:l,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,l=void 0===h?I.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?I.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,F&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=R(t,{errors:F,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(R(t,{errors:F+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:l}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(f(d),f(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=l(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,l=void 0===h?I.distance:h,f=o.includeMatches,d=void 0===f?I.includeMatches:f,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?I.findAllMatches:f,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||F(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return fe(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(le(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=le(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/site_libs/quarto-search/quarto-search.js b/site_libs/quarto-search/quarto-search.js new file mode 100644 index 00000000..d788a958 --- /dev/null +++ b/site_libs/quarto-search/quarto-search.js @@ -0,0 +1,1290 @@ +const kQueryArg = "q"; +const kResultsArg = "show-results"; + +// If items don't provide a URL, then both the navigator and the onSelect +// function aren't called (and therefore, the default implementation is used) +// +// We're using this sentinel URL to signal to those handlers that this +// item is a more item (along with the type) and can be handled appropriately +const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05"; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Ensure that search is available on this page. If it isn't, + // should return early and not do anything + var searchEl = window.document.getElementById("quarto-search"); + if (!searchEl) return; + + const { autocomplete } = window["@algolia/autocomplete-js"]; + + let quartoSearchOptions = {}; + let language = {}; + const searchOptionEl = window.document.getElementById( + "quarto-search-options" + ); + if (searchOptionEl) { + const jsonStr = searchOptionEl.textContent; + quartoSearchOptions = JSON.parse(jsonStr); + language = quartoSearchOptions.language; + } + + // note the search mode + if (quartoSearchOptions.type === "overlay") { + searchEl.classList.add("type-overlay"); + } else { + searchEl.classList.add("type-textbox"); + } + + // Used to determine highlighting behavior for this page + // A `q` query param is expected when the user follows a search + // to this page + const currentUrl = new URL(window.location); + const query = currentUrl.searchParams.get(kQueryArg); + const showSearchResults = currentUrl.searchParams.get(kResultsArg); + const mainEl = window.document.querySelector("main"); + + // highlight matches on the page + if (query && mainEl) { + // perform any highlighting + highlight(escapeRegExp(query), mainEl); + + // fix up the URL to remove the q query param + const replacementUrl = new URL(window.location); + replacementUrl.searchParams.delete(kQueryArg); + window.history.replaceState({}, "", replacementUrl); + } + + // function to clear highlighting on the page when the search query changes + // (e.g. if the user edits the query or clears it) + let highlighting = true; + const resetHighlighting = (searchTerm) => { + if (mainEl && highlighting && query && searchTerm !== query) { + clearHighlight(query, mainEl); + highlighting = false; + } + }; + + // Clear search highlighting when the user scrolls sufficiently + const resetFn = () => { + resetHighlighting(""); + window.removeEventListener("quarto-hrChanged", resetFn); + window.removeEventListener("quarto-sectionChanged", resetFn); + }; + + // Register this event after the initial scrolling and settling of events + // on the page + window.addEventListener("quarto-hrChanged", resetFn); + window.addEventListener("quarto-sectionChanged", resetFn); + + // Responsively switch to overlay mode if the search is present on the navbar + // Note that switching the sidebar to overlay mode requires more coordinate (not just + // the media query since we generate different HTML for sidebar overlays than we do + // for sidebar input UI) + const detachedMediaQuery = + quartoSearchOptions.type === "overlay" ? "all" : "(max-width: 991px)"; + + // If configured, include the analytics client to send insights + const plugins = configurePlugins(quartoSearchOptions); + + let lastState = null; + const { setIsOpen, setQuery, setCollections } = autocomplete({ + container: searchEl, + detachedMediaQuery: detachedMediaQuery, + defaultActiveItemId: 0, + panelContainer: "#quarto-search-results", + panelPlacement: quartoSearchOptions["panel-placement"], + debug: false, + openOnFocus: true, + plugins, + classNames: { + form: "d-flex", + }, + placeholder: language["search-text-placeholder"], + translations: { + clearButtonTitle: language["search-clear-button-title"], + detachedCancelButtonText: language["search-detached-cancel-button-title"], + submitButtonTitle: language["search-submit-button-title"], + }, + initialState: { + query, + }, + getItemUrl({ item }) { + return item.href; + }, + onStateChange({ state }) { + // If this is a file URL, note that + + // Perhaps reset highlighting + resetHighlighting(state.query); + + // If the panel just opened, ensure the panel is positioned properly + if (state.isOpen) { + if (lastState && !lastState.isOpen) { + setTimeout(() => { + positionPanel(quartoSearchOptions["panel-placement"]); + }, 150); + } + } + + // Perhaps show the copy link + showCopyLink(state.query, quartoSearchOptions); + + lastState = state; + }, + reshape({ sources, state }) { + return sources.map((source) => { + try { + const items = source.getItems(); + + // Validate the items + validateItems(items); + + // group the items by document + const groupedItems = new Map(); + items.forEach((item) => { + const hrefParts = item.href.split("#"); + const baseHref = hrefParts[0]; + const isDocumentItem = hrefParts.length === 1; + + const items = groupedItems.get(baseHref); + if (!items) { + groupedItems.set(baseHref, [item]); + } else { + // If the href for this item matches the document + // exactly, place this item first as it is the item that represents + // the document itself + if (isDocumentItem) { + items.unshift(item); + } else { + items.push(item); + } + groupedItems.set(baseHref, items); + } + }); + + const reshapedItems = []; + let count = 1; + for (const [_key, value] of groupedItems) { + const firstItem = value[0]; + reshapedItems.push({ + ...firstItem, + type: kItemTypeDoc, + }); + + const collapseMatches = quartoSearchOptions["collapse-after"]; + const collapseCount = + typeof collapseMatches === "number" ? collapseMatches : 1; + + if (value.length > 1) { + const target = `search-more-${count}`; + const isExpanded = + state.context.expanded && + state.context.expanded.includes(target); + + const remainingCount = value.length - collapseCount; + + for (let i = 1; i < value.length; i++) { + if (collapseMatches && i === collapseCount) { + reshapedItems.push({ + target, + title: isExpanded + ? language["search-hide-matches-text"] + : remainingCount === 1 + ? `${remainingCount} ${language["search-more-match-text"]}` + : `${remainingCount} ${language["search-more-matches-text"]}`, + type: kItemTypeMore, + href: kItemTypeMoreHref, + }); + } + + if (isExpanded || !collapseMatches || i < collapseCount) { + reshapedItems.push({ + ...value[i], + type: kItemTypeItem, + target, + }); + } + } + } + count += 1; + } + + return { + ...source, + getItems() { + return reshapedItems; + }, + }; + } catch (error) { + // Some form of error occurred + return { + ...source, + getItems() { + return [ + { + title: error.name || "An Error Occurred While Searching", + text: + error.message || + "An unknown error occurred while attempting to perform the requested search.", + type: kItemTypeError, + }, + ]; + }, + }; + } + }); + }, + navigator: { + navigate({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.location.assign(itemUrl); + } + }, + navigateNewTab({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + const windowReference = window.open(itemUrl, "_blank", "noopener"); + if (windowReference) { + windowReference.focus(); + } + } + }, + navigateNewWindow({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.open(itemUrl, "_blank", "noopener"); + } + }, + }, + getSources({ state, setContext, setActiveItemId, refresh }) { + return [ + { + sourceId: "documents", + getItemUrl({ item }) { + if (item.href) { + return offsetURL(item.href); + } else { + return undefined; + } + }, + onSelect({ + item, + state, + setContext, + setIsOpen, + setActiveItemId, + refresh, + }) { + if (item.type === kItemTypeMore) { + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + + // Toggle more + setIsOpen(true); + } + }, + getItems({ query }) { + if (query === null || query === "") { + return []; + } + + const limit = quartoSearchOptions.limit; + if (quartoSearchOptions.algolia) { + return algoliaSearch(query, limit, quartoSearchOptions.algolia); + } else { + // Fuse search options + const fuseSearchOptions = { + isCaseSensitive: false, + shouldSort: true, + minMatchCharLength: 2, + limit: limit, + }; + + return readSearchData().then(function (fuse) { + return fuseSearch(query, fuse, fuseSearchOptions); + }); + } + }, + templates: { + noResults({ createElement }) { + const hasQuery = lastState.query; + + return createElement( + "div", + { + class: `quarto-search-no-results${ + hasQuery ? "" : " no-query" + }`, + }, + language["search-no-results-text"] + ); + }, + header({ items, createElement }) { + // count the documents + const count = items.filter((item) => { + return item.type === kItemTypeDoc; + }).length; + + if (count > 0) { + return createElement( + "div", + { class: "search-result-header" }, + `${count} ${language["search-matching-documents-text"]}` + ); + } else { + return createElement( + "div", + { class: "search-result-header-no-results" }, + `` + ); + } + }, + footer({ _items, createElement }) { + if ( + quartoSearchOptions.algolia && + quartoSearchOptions.algolia["show-logo"] + ) { + const libDir = quartoSearchOptions.algolia["libDir"]; + const logo = createElement("img", { + src: offsetURL( + `${libDir}/quarto-search/search-by-algolia.svg` + ), + class: "algolia-search-logo", + }); + return createElement( + "a", + { href: "http://www.algolia.com/" }, + logo + ); + } + }, + + item({ item, createElement }) { + return renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh, + quartoSearchOptions + ); + }, + }, + }, + ]; + }, + }); + + window.quartoOpenSearch = () => { + setIsOpen(false); + setIsOpen(true); + focusSearchInput(); + }; + + document.addEventListener("keyup", (event) => { + const { key } = event; + const kbds = quartoSearchOptions["keyboard-shortcut"]; + const focusedEl = document.activeElement; + + const isFormElFocused = [ + "input", + "select", + "textarea", + "button", + "option", + ].find((tag) => { + return focusedEl.tagName.toLowerCase() === tag; + }); + + if ( + kbds && + kbds.includes(key) && + !isFormElFocused && + !document.activeElement.isContentEditable + ) { + event.preventDefault(); + window.quartoOpenSearch(); + } + }); + + // Remove the labeleledby attribute since it is pointing + // to a non-existent label + if (quartoSearchOptions.type === "overlay") { + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + if (inputEl) { + inputEl.removeAttribute("aria-labelledby"); + } + } + + function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; + } + + // If the main document scrolls dismiss the search results + // (otherwise, since they're floating in the document they can scroll with the document) + window.document.body.onscroll = throttle(() => { + // Only do this if we're not detached + // Bug #7117 + // This will happen when the keyboard is shown on ios (resulting in a scroll) + // which then closed the search UI + if (!window.matchMedia(detachedMediaQuery).matches) { + setIsOpen(false); + } + }, 50); + + if (showSearchResults) { + setIsOpen(true); + focusSearchInput(); + } +}); + +function configurePlugins(quartoSearchOptions) { + const autocompletePlugins = []; + const algoliaOptions = quartoSearchOptions.algolia; + if ( + algoliaOptions && + algoliaOptions["analytics-events"] && + algoliaOptions["search-only-api-key"] && + algoliaOptions["application-id"] + ) { + const apiKey = algoliaOptions["search-only-api-key"]; + const appId = algoliaOptions["application-id"]; + + // Aloglia insights may not be loaded because they require cookie consent + // Use deferred loading so events will start being recorded when/if consent + // is granted. + const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => { + if ( + window.aa && + window["@algolia/autocomplete-plugin-algolia-insights"] + ) { + window.aa("init", { + appId, + apiKey, + useCookie: true, + }); + + const { createAlgoliaInsightsPlugin } = + window["@algolia/autocomplete-plugin-algolia-insights"]; + // Register the insights client + const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ + insightsClient: window.aa, + onItemsChange({ insights, insightsEvents }) { + const events = insightsEvents.flatMap((event) => { + // This API limits the number of items per event to 20 + const chunkSize = 20; + const itemChunks = []; + const eventItems = event.items; + for (let i = 0; i < eventItems.length; i += chunkSize) { + itemChunks.push(eventItems.slice(i, i + chunkSize)); + } + // Split the items into multiple events that can be sent + const events = itemChunks.map((items) => { + return { + ...event, + items, + }; + }); + return events; + }); + + for (const event of events) { + insights.viewedObjectIDs(event); + } + }, + }); + return algoliaInsightsPlugin; + } + }); + + // Add the plugin + autocompletePlugins.push(algoliaInsightsDeferredPlugin); + return autocompletePlugins; + } +} + +// For plugins that may not load immediately, create a wrapper +// plugin and forward events and plugin data once the plugin +// is initialized. This is useful for cases like cookie consent +// which may prevent the analytics insights event plugin from initializing +// immediately. +function deferredLoadPlugin(createPlugin) { + let plugin = undefined; + let subscribeObj = undefined; + const wrappedPlugin = () => { + if (!plugin && subscribeObj) { + plugin = createPlugin(); + if (plugin && plugin.subscribe) { + plugin.subscribe(subscribeObj); + } + } + return plugin; + }; + + return { + subscribe: (obj) => { + subscribeObj = obj; + }, + onStateChange: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onStateChange) { + plugin.onStateChange(obj); + } + }, + onSubmit: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onSubmit) { + plugin.onSubmit(obj); + } + }, + onReset: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onReset) { + plugin.onReset(obj); + } + }, + getSources: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.getSources) { + return plugin.getSources(obj); + } else { + return Promise.resolve([]); + } + }, + data: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.data) { + plugin.data(obj); + } + }, + }; +} + +function validateItems(items) { + // Validate the first item + if (items.length > 0) { + const item = items[0]; + const missingFields = []; + if (item.href == undefined) { + missingFields.push("href"); + } + if (!item.title == undefined) { + missingFields.push("title"); + } + if (!item.text == undefined) { + missingFields.push("text"); + } + + if (missingFields.length === 1) { + throw { + name: `Error: Search index is missing the ${missingFields[0]} field.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the ${missingFields[0]} field or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } else if (missingFields.length > 1) { + const missingFieldList = missingFields + .map((field) => { + return `${field}`; + }) + .join(", "); + + throw { + name: `Error: Search index is missing the following fields: ${missingFieldList}.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } + } +} + +let lastQuery = null; +function showCopyLink(query, options) { + const language = options.language; + lastQuery = query; + // Insert share icon + const inputSuffixEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix" + ); + + if (inputSuffixEl) { + let copyButtonEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix .aa-CopyButton" + ); + + if (copyButtonEl === null) { + copyButtonEl = window.document.createElement("button"); + copyButtonEl.setAttribute("class", "aa-CopyButton"); + copyButtonEl.setAttribute("type", "button"); + copyButtonEl.setAttribute("title", language["search-copy-link-title"]); + copyButtonEl.onmousedown = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const linkIcon = "bi-clipboard"; + const checkIcon = "bi-check2"; + + const shareIconEl = window.document.createElement("i"); + shareIconEl.setAttribute("class", `bi ${linkIcon}`); + copyButtonEl.appendChild(shareIconEl); + inputSuffixEl.prepend(copyButtonEl); + + const clipboard = new window.ClipboardJS(".aa-CopyButton", { + text: function (_trigger) { + const copyUrl = new URL(window.location); + copyUrl.searchParams.set(kQueryArg, lastQuery); + copyUrl.searchParams.set(kResultsArg, "1"); + return copyUrl.toString(); + }, + }); + clipboard.on("success", function (e) { + // Focus the input + + // button target + const button = e.trigger; + const icon = button.querySelector("i.bi"); + + // flash "checked" + icon.classList.add(checkIcon); + icon.classList.remove(linkIcon); + setTimeout(function () { + icon.classList.remove(checkIcon); + icon.classList.add(linkIcon); + }, 1000); + }); + } + + // If there is a query, show the link icon + if (copyButtonEl) { + if (lastQuery && options["copy-button"]) { + copyButtonEl.style.display = "flex"; + } else { + copyButtonEl.style.display = "none"; + } + } + } +} + +/* Search Index Handling */ +// create the index +var fuseIndex = undefined; +var shownWarning = false; + +// fuse index options +const kFuseIndexOptions = { + keys: [ + { name: "title", weight: 20 }, + { name: "section", weight: 20 }, + { name: "text", weight: 10 }, + ], + ignoreLocation: true, + threshold: 0.1, +}; + +async function readSearchData() { + // Initialize the search index on demand + if (fuseIndex === undefined) { + if (window.location.protocol === "file:" && !shownWarning) { + window.alert( + "Search requires JavaScript features disabled when running in file://... URLs. In order to use search, please run this document in a web server." + ); + shownWarning = true; + return; + } + const fuse = new window.Fuse([], kFuseIndexOptions); + + // fetch the main search.json + const response = await fetch(offsetURL("search.json")); + if (response.status == 200) { + return response.json().then(function (searchDocs) { + searchDocs.forEach(function (searchDoc) { + fuse.add(searchDoc); + }); + fuseIndex = fuse; + return fuseIndex; + }); + } else { + return Promise.reject( + new Error( + "Unexpected status from search index request: " + response.status + ) + ); + } + } + + return fuseIndex; +} + +function inputElement() { + return window.document.body.querySelector(".aa-Form .aa-Input"); +} + +function focusSearchInput() { + setTimeout(() => { + const inputEl = inputElement(); + if (inputEl) { + inputEl.focus(); + } + }, 50); +} + +/* Panels */ +const kItemTypeDoc = "document"; +const kItemTypeMore = "document-more"; +const kItemTypeItem = "document-item"; +const kItemTypeError = "error"; + +function renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh, + quartoSearchOptions +) { + switch (item.type) { + case kItemTypeDoc: + return createDocumentCard( + createElement, + "file-richtext", + item.title, + item.section, + item.text, + item.href, + item.crumbs, + quartoSearchOptions + ); + case kItemTypeMore: + return createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh + ); + case kItemTypeItem: + return createSectionCard( + createElement, + item.section, + item.text, + item.href + ); + case kItemTypeError: + return createErrorCard(createElement, item.title, item.text); + default: + return undefined; + } +} + +function createDocumentCard( + createElement, + icon, + title, + section, + text, + href, + crumbs, + quartoSearchOptions +) { + const iconEl = createElement("i", { + class: `bi bi-${icon} search-result-icon`, + }); + const titleEl = createElement("p", { class: "search-result-title" }, title); + const titleContents = [iconEl, titleEl]; + const showParent = quartoSearchOptions["show-item-context"]; + if (crumbs && showParent) { + let crumbsOut = undefined; + const crumbClz = ["search-result-crumbs"]; + if (showParent === "root") { + crumbsOut = crumbs.length > 1 ? crumbs[0] : undefined; + } else if (showParent === "parent") { + crumbsOut = crumbs.length > 1 ? crumbs[crumbs.length - 2] : undefined; + } else { + crumbsOut = crumbs.length > 1 ? crumbs.join(" > ") : undefined; + crumbClz.push("search-result-crumbs-wrap"); + } + + const crumbEl = createElement( + "p", + { class: crumbClz.join(" ") }, + crumbsOut + ); + titleContents.push(crumbEl); + } + + const titleContainerEl = createElement( + "div", + { class: "search-result-title-container" }, + titleContents + ); + + const textEls = []; + if (section) { + const sectionEl = createElement( + "p", + { class: "search-result-section" }, + section + ); + textEls.push(sectionEl); + } + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + textEls.push(descEl); + + const textContainerEl = createElement( + "div", + { class: "search-result-text-container" }, + textEls + ); + + const containerEl = createElement( + "div", + { + class: "search-result-container", + }, + [titleContainerEl, textContainerEl] + ); + + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + containerEl + ); + + const classes = ["search-result-doc", "search-item"]; + if (!section) { + classes.push("document-selectable"); + } + + return createElement( + "div", + { + class: classes.join(" "), + }, + linkEl + ); +} + +function createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh +) { + const moreCardEl = createElement( + "div", + { + class: "search-result-more search-item", + onClick: (e) => { + // Handle expanding the sections by adding the expanded + // section to the list of expanded sections + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + e.stopPropagation(); + }, + }, + item.title + ); + + return moreCardEl; +} + +function toggleExpanded(item, state, setContext, setActiveItemId, refresh) { + const expanded = state.context.expanded || []; + if (expanded.includes(item.target)) { + setContext({ + expanded: expanded.filter((target) => target !== item.target), + }); + } else { + setContext({ expanded: [...expanded, item.target] }); + } + + refresh(); + setActiveItemId(item.__autocomplete_id); +} + +function createSectionCard(createElement, section, text, href) { + const sectionEl = createSection(createElement, section, text, href); + return createElement( + "div", + { + class: "search-result-doc-section search-item", + }, + sectionEl + ); +} + +function createSection(createElement, title, text, href) { + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { class: "search-result-section" }, title); + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + [titleEl, descEl] + ); + return linkEl; +} + +function createErrorCard(createElement, title, text) { + const descEl = createElement("p", { + class: "search-error-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { + class: "search-error-title", + dangerouslySetInnerHTML: { + __html: ` ${title}`, + }, + }); + const errorEl = createElement("div", { class: "search-error" }, [ + titleEl, + descEl, + ]); + return errorEl; +} + +function positionPanel(pos) { + const panelEl = window.document.querySelector( + "#quarto-search-results .aa-Panel" + ); + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + + if (panelEl && inputEl) { + panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`; + if (pos === "start") { + panelEl.style.left = `${Math.round(inputEl.left)}px`; + } else { + panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`; + } + } +} + +/* Highlighting */ +// highlighting functions +function highlightMatch(query, text) { + if (text) { + const start = text.toLowerCase().indexOf(query.toLowerCase()); + if (start !== -1) { + const startMark = ""; + const endMark = ""; + + const end = start + query.length; + text = + text.slice(0, start) + + startMark + + text.slice(start, end) + + endMark + + text.slice(end); + const startInfo = clipStart(text, start); + const endInfo = clipEnd( + text, + startInfo.position + startMark.length + endMark.length + ); + text = + startInfo.prefix + + text.slice(startInfo.position, endInfo.position) + + endInfo.suffix; + + return text; + } else { + return text; + } + } else { + return text; + } +} + +function clipStart(text, pos) { + const clipStart = pos - 50; + if (clipStart < 0) { + // This will just return the start of the string + return { + position: 0, + prefix: "", + }; + } else { + // We're clipping before the start of the string, walk backwards to the first space. + const spacePos = findSpace(text, pos, -1); + return { + position: spacePos.position, + prefix: "", + }; + } +} + +function clipEnd(text, pos) { + const clipEnd = pos + 200; + if (clipEnd > text.length) { + return { + position: text.length, + suffix: "", + }; + } else { + const spacePos = findSpace(text, clipEnd, 1); + return { + position: spacePos.position, + suffix: spacePos.clipped ? "…" : "", + }; + } +} + +function findSpace(text, start, step) { + let stepPos = start; + while (stepPos > -1 && stepPos < text.length) { + const char = text[stepPos]; + if (char === " " || char === "," || char === ":") { + return { + position: step === 1 ? stepPos : stepPos - step, + clipped: stepPos > 1 && stepPos < text.length, + }; + } + stepPos = stepPos + step; + } + + return { + position: stepPos - step, + clipped: false, + }; +} + +// removes highlighting as implemented by the mark tag +function clearHighlight(searchterm, el) { + const childNodes = el.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + if (node.nodeType === Node.ELEMENT_NODE) { + if ( + node.tagName === "MARK" && + node.innerText.toLowerCase() === searchterm.toLowerCase() + ) { + el.replaceChild(document.createTextNode(node.innerText), node); + } else { + clearHighlight(searchterm, node); + } + } + } +} + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +} + +// highlight matches +function highlight(term, el) { + const termRegex = new RegExp(term, "ig"); + const childNodes = el.childNodes; + + // walk back to front avoid mutating elements in front of us + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + + if (node.nodeType === Node.TEXT_NODE) { + // Search text nodes for text to highlight + const text = node.nodeValue; + + let startIndex = 0; + let matchIndex = text.search(termRegex); + if (matchIndex > -1) { + const markFragment = document.createDocumentFragment(); + while (matchIndex > -1) { + const prefix = text.slice(startIndex, matchIndex); + markFragment.appendChild(document.createTextNode(prefix)); + + const mark = document.createElement("mark"); + mark.appendChild( + document.createTextNode( + text.slice(matchIndex, matchIndex + term.length) + ) + ); + markFragment.appendChild(mark); + + startIndex = matchIndex + term.length; + matchIndex = text.slice(startIndex).search(new RegExp(term, "ig")); + if (matchIndex > -1) { + matchIndex = startIndex + matchIndex; + } + } + if (startIndex < text.length) { + markFragment.appendChild( + document.createTextNode(text.slice(startIndex, text.length)) + ); + } + + el.replaceChild(markFragment, node); + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + // recurse through elements + highlight(term, node); + } + } +} + +/* Link Handling */ +// get the offset from this page for a given site root relative url +function offsetURL(url) { + var offset = getMeta("quarto:offset"); + return offset ? offset + url : url; +} + +// read a meta tag value +function getMeta(metaName) { + var metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; +} + +function algoliaSearch(query, limit, algoliaOptions) { + const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"]; + + const applicationId = algoliaOptions["application-id"]; + const searchOnlyApiKey = algoliaOptions["search-only-api-key"]; + const indexName = algoliaOptions["index-name"]; + const indexFields = algoliaOptions["index-fields"]; + const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey); + const searchParams = algoliaOptions["params"]; + const searchAnalytics = !!algoliaOptions["analytics-events"]; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: indexName, + query, + params: { + hitsPerPage: limit, + clickAnalytics: searchAnalytics, + ...searchParams, + }, + }, + ], + transformResponse: (response) => { + if (!indexFields) { + return response.hits.map((hit) => { + return hit.map((item) => { + return { + ...item, + text: highlightMatch(query, item.text), + }; + }); + }); + } else { + const remappedHits = response.hits.map((hit) => { + return hit.map((item) => { + const newItem = { ...item }; + ["href", "section", "title", "text", "crumbs"].forEach( + (keyName) => { + const mappedName = indexFields[keyName]; + if ( + mappedName && + item[mappedName] !== undefined && + mappedName !== keyName + ) { + newItem[keyName] = item[mappedName]; + delete newItem[mappedName]; + } + } + ); + newItem.text = highlightMatch(query, newItem.text); + return newItem; + }); + }); + return remappedHits; + } + }, + }); +} + +let subSearchTerm = undefined; +let subSearchFuse = undefined; +const kFuseMaxWait = 125; + +async function fuseSearch(query, fuse, fuseOptions) { + let index = fuse; + // Fuse.js using the Bitap algorithm for text matching which runs in + // O(nm) time (no matter the structure of the text). In our case this + // means that long search terms mixed with large index gets very slow + // + // This injects a subIndex that will be used once the terms get long enough + // Usually making this subindex is cheap since there will typically be + // a subset of results matching the existing query + if (subSearchFuse !== undefined && query.startsWith(subSearchTerm)) { + // Use the existing subSearchFuse + index = subSearchFuse; + } else if (subSearchFuse !== undefined) { + // The term changed, discard the existing fuse + subSearchFuse = undefined; + subSearchTerm = undefined; + } + + // Search using the active fuse + const then = performance.now(); + const resultsRaw = await index.search(query, fuseOptions); + const now = performance.now(); + + const results = resultsRaw.map((result) => { + const addParam = (url, name, value) => { + const anchorParts = url.split("#"); + const baseUrl = anchorParts[0]; + const sep = baseUrl.search("\\?") > 0 ? "&" : "?"; + anchorParts[0] = baseUrl + sep + name + "=" + value; + return anchorParts.join("#"); + }; + + return { + title: result.item.title, + section: result.item.section, + href: addParam(result.item.href, kQueryArg, query), + text: highlightMatch(query, result.item.text), + crumbs: result.item.crumbs, + }; + }); + + // If we don't have a subfuse and the query is long enough, go ahead + // and create a subfuse to use for subsequent queries + if ( + now - then > kFuseMaxWait && + subSearchFuse === undefined && + resultsRaw.length < fuseOptions.limit + ) { + subSearchTerm = query; + subSearchFuse = new window.Fuse([], kFuseIndexOptions); + resultsRaw.forEach((rr) => { + subSearchFuse.add(rr.item); + }); + } + return results; +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..3b41a068 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,71 @@ + + + + https://fastcore.fast.ai/style.html + 2024-10-18T02:56:14.093Z + + + https://fastcore.fast.ai/xtras.html + 2024-10-18T02:56:14.221Z + + + https://fastcore.fast.ai/foundation.html + 2024-10-18T02:56:14.165Z + + + https://fastcore.fast.ai/tour.html + 2024-10-18T02:56:13.513Z + + + https://fastcore.fast.ai/net.html + 2024-10-18T02:56:13.421Z + + + https://fastcore.fast.ai/index.html + 2024-10-18T02:56:13.229Z + + + https://fastcore.fast.ai/basics.html + 2024-10-18T02:56:13.881Z + + + https://fastcore.fast.ai/dispatch.html + 2024-10-18T02:56:13.069Z + + + https://fastcore.fast.ai/meta.html + 2024-10-18T02:56:13.221Z + + + https://fastcore.fast.ai/xml.html + 2024-10-18T02:56:12.101Z + + + https://fastcore.fast.ai/transform.html + 2024-10-18T02:56:13.673Z + + + https://fastcore.fast.ai/parallel.html + 2024-10-18T02:56:13.201Z + + + https://fastcore.fast.ai/py2pyi.html + 2024-10-18T02:56:13.345Z + + + https://fastcore.fast.ai/test.html + 2024-10-18T02:56:15.221Z + + + https://fastcore.fast.ai/script.html + 2024-10-18T02:56:13.621Z + + + https://fastcore.fast.ai/xdg.html + 2024-10-18T02:56:13.753Z + + + https://fastcore.fast.ai/docments.html + 2024-10-18T02:56:14.021Z + + diff --git a/style.html b/style.html new file mode 100644 index 00000000..18b1761f --- /dev/null +++ b/style.html @@ -0,0 +1,893 @@ + + + + + + + + + + +Style – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+ + + +
+ +
+
+

Style

+
+ +
+
+ Fast styling for friendly CLIs. +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
+
+ +
+
+Note +
+
+
+

Styled outputs don’t show in Quarto documentation. Please use a notebook editor to correctly view this page.

+
+
+
+

source

+
+

StyleCode

+
+
 StyleCode (name, code, typ)
+
+

An escape sequence for styling terminal text.

+

The primary building block of the S API.

+
+
print(str(StyleCode('blue', 34, 'fg')) + 'hello' + str(StyleCode('default', 39, 'fg')) + ' world')
+
+
hello world
+
+
+
+

source

+
+
+

Style

+
+
 Style (codes=None)
+
+

A minimal terminal text styler.

+

The main way to use it is via the exported S object.

+
+
+Exported source +
S = Style()
+
+
+

We start with an empty style:

+
+
S
+
+
<Style: none>
+
+
+

Define a new style by chaining attributes:

+
+
s = S.blue.bold.underline
+s
+
+
<Style: blue bold underline>
+
+
+

You can see a full list of available styles with auto-complete by typing S . Tab.

+

Apply a style by calling it with a string:

+
+
s('hello world')
+
+
'\x1b[34m\x1b[1m\x1b[4mhello world\x1b[22m\x1b[24m\x1b[39m'
+
+
+

That’s a raw string with the underlying escape sequences that tell the terminal how to format text. To see the styled version we have to print it:

+
+
print(s('hello world'))
+
+
hello world
+
+
+

You can also nest styles:

+
+
print(S.bold(S.blue('key') + ' = value ') + S.light_gray(' ' + S.underline('# With a comment')) + ' and unstyled text')
+
+
key = value  # With a comment and unstyled text
+
+
+
+
print(S.blue('this '+S.bold('is')+' a test'))
+
+
this is a test
+
+
+
+

source

+
+
+

demo

+
+
 demo ()
+
+

Demonstrate all available styles and their codes.

+
+
demo()
+
+
 30    black           
+ 31    red             
+ 32    green           
+ 33    yellow          
+ 34    blue            
+ 35    magenta         
+ 36    cyan            
+ 37    light_gray      
+ 39    default         
+ 90    dark_gray       
+ 91    light_red       
+ 92    light_green     
+ 93    light_yellow    
+ 94    light_blue      
+ 95    light_magenta   
+ 96    light_cyan      
+ 97    white           
+ 40    black_bg        
+ 41    red_bg          
+ 42    green_bg        
+ 43    yellow_bg       
+ 44    blue_bg         
+ 45    magenta_bg      
+ 46    cyan_bg         
+ 47    light_gray_bg   
+ 49    default_bg      
+100    dark_gray_bg    
+101    light_red_bg    
+102    light_green_bg  
+103    light_yellow_bg 
+104    light_blue_bg   
+105    light_magenta_bg
+106    light_cyan_bg   
+107    white_bg        
+  1    bold            
+  2    dim             
+  3    italic          
+  4    underline       
+  5    blink           
+  7    invert          
+  8    hidden          
+  9    strikethrough   
+ 22    reset_bold      
+ 22    reset_dim       
+ 23    reset_italic    
+ 24    reset_underline 
+ 25    reset_blink     
+ 27    reset_invert    
+ 28    reset_hidden    
+ 29    reset_strikethrough
+  0    reset           
+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/style.html.md b/style.html.md new file mode 100644 index 00000000..861e494d --- /dev/null +++ b/style.html.md @@ -0,0 +1,176 @@ +# Style + + + + +
+ +> **Note** +> +> Styled outputs don’t show in Quarto documentation. Please use a +> notebook editor to correctly view this page. + +
+ +------------------------------------------------------------------------ + +source + +### StyleCode + +> StyleCode (name, code, typ) + +*An escape sequence for styling terminal text.* + +The primary building block of the `S` API. + +``` python +print(str(StyleCode('blue', 34, 'fg')) + 'hello' + str(StyleCode('default', 39, 'fg')) + ' world') +``` + + hello world + +------------------------------------------------------------------------ + +source + +### Style + +> Style (codes=None) + +*A minimal terminal text styler.* + +The main way to use it is via the exported `S` object. + +
+Exported source + +``` python +S = Style() +``` + +
+ +We start with an empty style: + +``` python +S +``` + + + +Define a new style by chaining attributes: + +``` python +s = S.blue.bold.underline +s +``` + + + +You can see a full list of available styles with auto-complete by typing +S . Tab. + +Apply a style by calling it with a string: + +``` python +s('hello world') +``` + + '\x1b[34m\x1b[1m\x1b[4mhello world\x1b[22m\x1b[24m\x1b[39m' + +That’s a raw string with the underlying escape sequences that tell the +terminal how to format text. To see the styled version we have to print +it: + +``` python +print(s('hello world')) +``` + + hello world + +You can also nest styles: + +``` python +print(S.bold(S.blue('key') + ' = value ') + S.light_gray(' ' + S.underline('# With a comment')) + ' and unstyled text') +``` + + key = value # With a comment and unstyled text + +``` python +print(S.blue('this '+S.bold('is')+' a test')) +``` + + this is a test + +------------------------------------------------------------------------ + +source + +### demo + +> demo () + +*Demonstrate all available styles and their codes.* + +``` python +demo() +``` + + 30 black + 31 red + 32 green + 33 yellow + 34 blue + 35 magenta + 36 cyan + 37 light_gray + 39 default + 90 dark_gray + 91 light_red + 92 light_green + 93 light_yellow + 94 light_blue + 95 light_magenta + 96 light_cyan + 97 white + 40 black_bg + 41 red_bg + 42 green_bg + 43 yellow_bg + 44 blue_bg + 45 magenta_bg + 46 cyan_bg + 47 light_gray_bg + 49 default_bg + 100 dark_gray_bg + 101 light_red_bg + 102 light_green_bg + 103 light_yellow_bg + 104 light_blue_bg + 105 light_magenta_bg + 106 light_cyan_bg + 107 white_bg + 1 bold + 2 dim + 3 italic + 4 underline + 5 blink + 7 invert + 8 hidden + 9 strikethrough + 22 reset_bold + 22 reset_dim + 23 reset_italic + 24 reset_underline + 25 reset_blink + 27 reset_invert + 28 reset_hidden + 29 reset_strikethrough + 0 reset diff --git a/styles.css b/styles.css new file mode 100644 index 00000000..b46c2093 --- /dev/null +++ b/styles.css @@ -0,0 +1,18 @@ +.cell-output pre { + margin-left: 0.8rem; + margin-top: 0; + background: none; + border-left: 2px solid lightsalmon; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .cell-output .sourceCode { + background: none; + margin-top: 0; + } + + .cell > .sourceCode { + margin-bottom: 0; + } + \ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 00000000..450c88dc --- /dev/null +++ b/test.html @@ -0,0 +1,1079 @@ + + + + + + + + + + +Test – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Test

+
+ +
+
+ Helper functions to quickly write tests in notebooks +
+
+ + +
+ + + + +
+ + + +
+ + + +
+

Simple test functions

+

We can check that code raises an exception when that’s expected (test_fail).

+

To test for equality or inequality (with different types of things) we define a simple function test that compares two objects with a given cmp operator.

+
+

source

+
+

test_fail

+
+
 test_fail (f, msg='', contains='', args=None, kwargs=None)
+
+

Fails with msg unless f() raises an exception and (optionally) has contains in e.args

+
+
def _fail(): raise Exception("foobar")
+test_fail(_fail, contains="foo")
+
+def _fail(): raise Exception()
+test_fail(_fail)
+
+

We can also pass args and kwargs to function to check if it fails with special inputs.

+
+
def _fail_args(a):
+    if a == 5:
+        raise ValueError
+test_fail(_fail_args, args=(5,))
+test_fail(_fail_args, kwargs=dict(a=5))
+
+
+

source

+
+
+

test

+
+
 test (a, b, cmp, cname=None)
+
+

assert that cmp(a,b); display inputs and cname or cmp.__name__ if it fails

+
+
test([1,2],[1,2], operator.eq)
+test_fail(lambda: test([1,2],[1], operator.eq))
+test([1,2],[1],   operator.ne)
+test_fail(lambda: test([1,2],[1,2], operator.ne))
+
+
+
+
+

all_equal

+
+
 all_equal (a, b)
+
+

Compares whether a and b are the same length and have the same contents

+
+
test(['abc'], ['abc'], all_equal)
+test_fail(lambda: test(['abc'],['cab'], all_equal))
+
+
+
+
+

equals

+
+
 equals (a, b)
+
+

Compares a and b for equality; supports sublists, tensors and arrays too

+
+
test([['abc'],['a']], [['abc'],['a']],  equals)
+test([['abc'],['a'],'b', [['x']]], [['abc'],['a'],'b', [['x']]],  equals) # supports any depth and nested structure
+
+
+

source

+
+
+

nequals

+
+
 nequals (a, b)
+
+

Compares a and b for not equals

+
+
test(['abc'], ['ab' ], nequals)
+
+
+
+
+

test_eq test_ne, etc…

+

Just use test_eq/test_ne to test for ==/!=. test_eq_type checks things are equal and of the same type. We define them using test:

+
+

source

+
+

test_eq

+
+
 test_eq (a, b)
+
+

test that a==b

+
+
test_eq([1,2],[1,2])
+test_eq([1,2],map(int,[1,2]))
+test_eq(array([1,2]),array([1,2]))
+test_eq(array([1,2]),array([1,2]))
+test_eq([array([1,2]),3],[array([1,2]),3])
+test_eq(dict(a=1,b=2), dict(b=2,a=1))
+test_fail(lambda: test_eq([1,2], 1), contains="==")
+test_fail(lambda: test_eq(None, np.array([1,2])), contains="==")
+test_eq({'a', 'b', 'c'}, {'c', 'a', 'b'})
+
+
+
df1 = pd.DataFrame(dict(a=[1,2],b=['a','b']))
+df2 = pd.DataFrame(dict(a=[1,2],b=['a','b']))
+df3 = pd.DataFrame(dict(a=[1,2],b=['a','c']))
+
+test_eq(df1,df2)
+test_eq(df1.a,df2.a)
+test_fail(lambda: test_eq(df1,df3), contains='==')
+class T(pd.Series): pass
+test_eq(df1.iloc[0], T(df2.iloc[0])) # works with subclasses
+
+
+
test_eq(torch.zeros(10), torch.zeros(10, dtype=torch.float64))
+test_eq(torch.zeros(10), torch.ones(10)-1)
+test_fail(lambda:test_eq(torch.zeros(10), torch.ones(1, 10)), contains='==')
+test_eq(torch.zeros(3), [0,0,0])
+
+
+

source

+
+
+

test_eq_type

+
+
 test_eq_type (a, b)
+
+

test that a==b and are same type

+
+
test_eq_type(1,1)
+test_fail(lambda: test_eq_type(1,1.))
+test_eq_type([1,1],[1,1])
+test_fail(lambda: test_eq_type([1,1],(1,1)))
+test_fail(lambda: test_eq_type([1,1],[1,1.]))
+
+
+

source

+
+
+

test_ne

+
+
 test_ne (a, b)
+
+

test that a!=b

+
+
test_ne([1,2],[1])
+test_ne([1,2],[1,3])
+test_ne(array([1,2]),array([1,1]))
+test_ne(array([1,2]),array([1,1]))
+test_ne([array([1,2]),3],[array([1,2])])
+test_ne([3,4],array([3]))
+test_ne([3,4],array([3,5]))
+test_ne(dict(a=1,b=2), ['a', 'b'])
+test_ne(['a', 'b'], dict(a=1,b=2))
+
+
+

source

+
+
+

is_close

+
+
 is_close (a, b, eps=1e-05)
+
+

Is a within eps of b

+
+

source

+
+
+

test_close

+
+
 test_close (a, b, eps=1e-05)
+
+

test that a is within eps of b

+
+
test_close(1,1.001,eps=1e-2)
+test_fail(lambda: test_close(1,1.001))
+test_close([-0.001,1.001], [0.,1.], eps=1e-2)
+test_close(np.array([-0.001,1.001]), np.array([0.,1.]), eps=1e-2)
+test_close(array([-0.001,1.001]), array([0.,1.]), eps=1e-2)
+
+
+

source

+
+
+

test_is

+
+
 test_is (a, b)
+
+

test that a is b

+
+
test_fail(lambda: test_is([1], [1]))
+a = [1]
+test_is(a, a)
+b = [2]; test_fail(lambda: test_is(a, b))
+
+
+

source

+
+
+

test_shuffled

+
+
 test_shuffled (a, b)
+
+

test that a and b are shuffled versions of the same sequence of items

+
+
a = list(range(50))
+b = copy(a)
+random.shuffle(b)
+test_shuffled(a,b)
+test_fail(lambda:test_shuffled(a,a))
+
+
+
a = 'abc'
+b = 'abcabc'
+test_fail(lambda:test_shuffled(a,b))
+
+
+
a = ['a', 42, True] 
+b = [42, True, 'a']
+test_shuffled(a,b)
+
+
+

source

+
+
+

test_stdout

+
+
 test_stdout (f, exp, regex=False)
+
+

Test that f prints exp to stdout, optionally checking as regex

+
+
test_stdout(lambda: print('hi'), 'hi')
+test_fail(lambda: test_stdout(lambda: print('hi'), 'ho'))
+test_stdout(lambda: 1+1, '')
+test_stdout(lambda: print('hi there!'), r'^hi.*!$', regex=True)
+
+
+

source

+
+
+

test_warns

+
+
 test_warns (f, show=False)
+
+
+
test_warns(lambda: warnings.warn("Oh no!"))
+test_fail(lambda: test_warns(lambda: 2+2), contains='No warnings raised')
+
+
+
test_warns(lambda: warnings.warn("Oh no!"), show=True)
+
+
<class 'UserWarning'>: Oh no!
+
+
+
+
im = Image.open(TEST_IMAGE).resize((128,128)); im
+
+
+
+

+
+
+
+
+
+
im = Image.open(TEST_IMAGE_BW).resize((128,128)); im
+
+
+
+

+
+
+
+
+
+

source

+
+
+

test_fig_exists

+
+
 test_fig_exists (ax)
+
+

Test there is a figure displayed in ax

+
+
fig,ax = plt.subplots()
+ax.imshow(array(im));
+
+
+
+

+
+
+
+
+
+
test_fig_exists(ax)
+
+
+

source

+
+
+

ExceptionExpected

+
+
 ExceptionExpected (ex=<class 'Exception'>, regex='')
+
+

Context manager that tests if an exception is raised

+
+
def _tst_1(): assert False, "This is a test"
+def _tst_2(): raise SyntaxError
+
+with ExceptionExpected(): _tst_1()
+with ExceptionExpected(ex=AssertionError, regex="This is a test"): _tst_1()
+with ExceptionExpected(ex=SyntaxError): _tst_2()
+
+

exception is an abbreviation for ExceptionExpected().

+
+
with exception: _tst_1()
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/test.html.md b/test.html.md new file mode 100644 index 00000000..eb2898ea --- /dev/null +++ b/test.html.md @@ -0,0 +1,394 @@ +# Test + + + + +## Simple test functions + +We can check that code raises an exception when that’s expected +([`test_fail`](https://fastcore.fast.ai/test.html#test_fail)). + +To test for equality or inequality (with different types of things) we +define a simple function +[`test`](https://fastcore.fast.ai/test.html#test) that compares two +objects with a given `cmp` operator. + +------------------------------------------------------------------------ + +source + +### test_fail + +> test_fail (f, msg='', contains='', args=None, kwargs=None) + +*Fails with `msg` unless `f()` raises an exception and (optionally) has +`contains` in `e.args`* + +``` python +def _fail(): raise Exception("foobar") +test_fail(_fail, contains="foo") + +def _fail(): raise Exception() +test_fail(_fail) +``` + +We can also pass `args` and `kwargs` to function to check if it fails +with special inputs. + +``` python +def _fail_args(a): + if a == 5: + raise ValueError +test_fail(_fail_args, args=(5,)) +test_fail(_fail_args, kwargs=dict(a=5)) +``` + +------------------------------------------------------------------------ + +source + +### test + +> test (a, b, cmp, cname=None) + +*`assert` that `cmp(a,b)`; display inputs and `cname or cmp.__name__` if +it fails* + +``` python +test([1,2],[1,2], operator.eq) +test_fail(lambda: test([1,2],[1], operator.eq)) +test([1,2],[1], operator.ne) +test_fail(lambda: test([1,2],[1,2], operator.ne)) +``` + +------------------------------------------------------------------------ + +### all_equal + +> all_equal (a, b) + +*Compares whether `a` and `b` are the same length and have the same +contents* + +``` python +test(['abc'], ['abc'], all_equal) +test_fail(lambda: test(['abc'],['cab'], all_equal)) +``` + +------------------------------------------------------------------------ + +### equals + +> equals (a, b) + +*Compares `a` and `b` for equality; supports sublists, tensors and +arrays too* + +``` python +test([['abc'],['a']], [['abc'],['a']], equals) +test([['abc'],['a'],'b', [['x']]], [['abc'],['a'],'b', [['x']]], equals) # supports any depth and nested structure +``` + +------------------------------------------------------------------------ + +source + +### nequals + +> nequals (a, b) + +*Compares `a` and `b` for `not equals`* + +``` python +test(['abc'], ['ab' ], nequals) +``` + +## test_eq test_ne, etc… + +Just use +[`test_eq`](https://fastcore.fast.ai/test.html#test_eq)/[`test_ne`](https://fastcore.fast.ai/test.html#test_ne) +to test for `==`/`!=`. +[`test_eq_type`](https://fastcore.fast.ai/test.html#test_eq_type) checks +things are equal and of the same type. We define them using +[`test`](https://fastcore.fast.ai/test.html#test): + +------------------------------------------------------------------------ + +source + +### test_eq + +> test_eq (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a==b`* + +``` python +test_eq([1,2],[1,2]) +test_eq([1,2],map(int,[1,2])) +test_eq(array([1,2]),array([1,2])) +test_eq(array([1,2]),array([1,2])) +test_eq([array([1,2]),3],[array([1,2]),3]) +test_eq(dict(a=1,b=2), dict(b=2,a=1)) +test_fail(lambda: test_eq([1,2], 1), contains="==") +test_fail(lambda: test_eq(None, np.array([1,2])), contains="==") +test_eq({'a', 'b', 'c'}, {'c', 'a', 'b'}) +``` + +``` python +df1 = pd.DataFrame(dict(a=[1,2],b=['a','b'])) +df2 = pd.DataFrame(dict(a=[1,2],b=['a','b'])) +df3 = pd.DataFrame(dict(a=[1,2],b=['a','c'])) + +test_eq(df1,df2) +test_eq(df1.a,df2.a) +test_fail(lambda: test_eq(df1,df3), contains='==') +class T(pd.Series): pass +test_eq(df1.iloc[0], T(df2.iloc[0])) # works with subclasses +``` + +``` python +test_eq(torch.zeros(10), torch.zeros(10, dtype=torch.float64)) +test_eq(torch.zeros(10), torch.ones(10)-1) +test_fail(lambda:test_eq(torch.zeros(10), torch.ones(1, 10)), contains='==') +test_eq(torch.zeros(3), [0,0,0]) +``` + +------------------------------------------------------------------------ + +source + +### test_eq_type + +> test_eq_type (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a==b` and are +same type* + +``` python +test_eq_type(1,1) +test_fail(lambda: test_eq_type(1,1.)) +test_eq_type([1,1],[1,1]) +test_fail(lambda: test_eq_type([1,1],(1,1))) +test_fail(lambda: test_eq_type([1,1],[1,1.])) +``` + +------------------------------------------------------------------------ + +source + +### test_ne + +> test_ne (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a!=b`* + +``` python +test_ne([1,2],[1]) +test_ne([1,2],[1,3]) +test_ne(array([1,2]),array([1,1])) +test_ne(array([1,2]),array([1,1])) +test_ne([array([1,2]),3],[array([1,2])]) +test_ne([3,4],array([3])) +test_ne([3,4],array([3,5])) +test_ne(dict(a=1,b=2), ['a', 'b']) +test_ne(['a', 'b'], dict(a=1,b=2)) +``` + +------------------------------------------------------------------------ + +source + +### is_close + +> is_close (a, b, eps=1e-05) + +*Is `a` within `eps` of `b`* + +------------------------------------------------------------------------ + +source + +### test_close + +> test_close (a, b, eps=1e-05) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a` is within +`eps` of `b`* + +``` python +test_close(1,1.001,eps=1e-2) +test_fail(lambda: test_close(1,1.001)) +test_close([-0.001,1.001], [0.,1.], eps=1e-2) +test_close(np.array([-0.001,1.001]), np.array([0.,1.]), eps=1e-2) +test_close(array([-0.001,1.001]), array([0.,1.]), eps=1e-2) +``` + +------------------------------------------------------------------------ + +source + +### test_is + +> test_is (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a is b`* + +``` python +test_fail(lambda: test_is([1], [1])) +a = [1] +test_is(a, a) +b = [2]; test_fail(lambda: test_is(a, b)) +``` + +------------------------------------------------------------------------ + +source + +### test_shuffled + +> test_shuffled (a, b) + +*[`test`](https://fastcore.fast.ai/test.html#test) that `a` and `b` are +shuffled versions of the same sequence of items* + +``` python +a = list(range(50)) +b = copy(a) +random.shuffle(b) +test_shuffled(a,b) +test_fail(lambda:test_shuffled(a,a)) +``` + +``` python +a = 'abc' +b = 'abcabc' +test_fail(lambda:test_shuffled(a,b)) +``` + +``` python +a = ['a', 42, True] +b = [42, True, 'a'] +test_shuffled(a,b) +``` + +------------------------------------------------------------------------ + +source + +### test_stdout + +> test_stdout (f, exp, regex=False) + +*Test that `f` prints `exp` to stdout, optionally checking as `regex`* + +``` python +test_stdout(lambda: print('hi'), 'hi') +test_fail(lambda: test_stdout(lambda: print('hi'), 'ho')) +test_stdout(lambda: 1+1, '') +test_stdout(lambda: print('hi there!'), r'^hi.*!$', regex=True) +``` + +------------------------------------------------------------------------ + +source + +### test_warns + +> test_warns (f, show=False) + +``` python +test_warns(lambda: warnings.warn("Oh no!")) +test_fail(lambda: test_warns(lambda: 2+2), contains='No warnings raised') +``` + +``` python +test_warns(lambda: warnings.warn("Oh no!"), show=True) +``` + + : Oh no! + +``` python +im = Image.open(TEST_IMAGE).resize((128,128)); im +``` + +![](00_test_files/figure-commonmark/cell-35-output-1.png) + +``` python +im = Image.open(TEST_IMAGE_BW).resize((128,128)); im +``` + +![](00_test_files/figure-commonmark/cell-36-output-1.png) + +------------------------------------------------------------------------ + +source + +### test_fig_exists + +> test_fig_exists (ax) + +*Test there is a figure displayed in `ax`* + +``` python +fig,ax = plt.subplots() +ax.imshow(array(im)); +``` + +![](00_test_files/figure-commonmark/cell-38-output-1.png) + +``` python +test_fig_exists(ax) +``` + +------------------------------------------------------------------------ + +source + +### ExceptionExpected + +> ExceptionExpected (ex=, regex='') + +*Context manager that tests if an exception is raised* + +``` python +def _tst_1(): assert False, "This is a test" +def _tst_2(): raise SyntaxError + +with ExceptionExpected(): _tst_1() +with ExceptionExpected(ex=AssertionError, regex="This is a test"): _tst_1() +with ExceptionExpected(ex=SyntaxError): _tst_2() +``` + +`exception` is an abbreviation for `ExceptionExpected()`. + +``` python +with exception: _tst_1() +``` diff --git a/tour.html b/tour.html new file mode 100644 index 00000000..624aa54c --- /dev/null +++ b/tour.html @@ -0,0 +1,953 @@ + + + + + + + + + +A tour of fastcore – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

A tour of fastcore

+
+ + + +
+ + + + +
+ + + +
+ + + +

Here’s a (somewhat) quick tour of a few higlights from fastcore.

+
+

Documentation

+

All fast.ai projects, including this one, are built with nbdev, which is a full literate programming environment built on Jupyter Notebooks. That means that every piece of documentation, including the page you’re reading now, can be accessed as interactive Jupyter notebooks. In fact, you can even grab a link directly to a notebook running interactively on Google Colab - if you want to follow along with this tour, click the link below:

+
+
colab_link('index')
+ +
+

The full docs are available at fastcore.fast.ai. The code in the examples and in all fast.ai libraries follow the fast.ai style guide. In order to support interactive programming, all fast.ai libraries are designed to allow for import * to be used safely, particular by ensuring that __all__ is defined in all packages. In order to see where a function is from, just type it:

+
+
coll_repr
+
+
<function fastcore.foundation.coll_repr(c, max_n=10)>
+
+
+

For more details, including a link to the full documentation and source code, use doc, which pops up a window with this information:

+
doc(coll_repr)
+

+

The documentation also contains links to any related functions or classes, which appear like this: coll_repr (in the notebook itself you will just see a word with back-ticks around it; the links are auto-generated in the documentation site). The documentation will generally show one or more examples of use, along with any background context necessary to understand them. As you’ll see, the examples for each function and method are shown as tests, rather than example outputs, so let’s start by explaining that.

+
+
+

Testing

+

fastcore’s testing module is designed to work well with nbdev, which is a full literate programming environment built on Jupyter Notebooks. That means that your tests, docs, and code all live together in the same notebook. fastcore and nbdev’s approach to testing starts with the premise that all your tests should pass. If one fails, no more tests in a notebook are run.

+

Tests look like this:

+
+
test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]')
+
+

That’s an example from the docs for coll_repr. As you see, it’s not showing you the output directly. Here’s what that would look like:

+
+
coll_repr(range(1000), 5)
+
+
'(#1000) [0,1,2,3,4...]'
+
+
+

So, the test is actually showing you what the output looks like, because if the function call didn’t return '(#1000) [0,1,2,3,4...]', then the test would have failed.

+

So every test shown in the docs is also showing you the behavior of the library — and vice versa!

+

Test functions always start with test_, and then follow with the operation being tested. So test_eq tests for equality (as you saw in the example above). This includes tests for equality of arrays and tensors, lists and generators, and many more:

+
+
test_eq([0,1,2,3], np.arange(4))
+
+

When a test fails, it prints out information about what was expected:

+
test_eq([0,1,2,3], np.arange(3))
+
----
+  AssertionError: ==:
+  [0, 1, 2, 3]
+  [0 1 2]
+

If you want to check that objects are the same type, rather than the just contain the same collection, use test_eq_type.

+

You can test with any comparison function using test, e.g test whether an object is less than:

+
+
test(2, 3, operator.lt)
+
+

You can even test that exceptions are raised:

+
+
def divide_zero(): return 1/0
+test_fail(divide_zero)
+
+

…and test that things are printed to stdout:

+
+
test_stdout(lambda: print('hi'), 'hi')
+
+
+
+

Foundations

+

fast.ai is unusual in that we often use mixins in our code. Mixins are widely used in many programming languages, such as Ruby, but not so much in Python. We use mixins to attach new behavior to existing libraries, or to allow modules to add new behavior to our own classes, such as in extension modules. One useful example of a mixin we define is Path.ls, which lists a directory and returns an L (an extended list class which we’ll discuss shortly):

+
+
p = Path('images')
+p.ls()
+
+
(#6) [Path('images/mnist3.png'),Path('images/att_00000.png'),Path('images/puppy.jpg'),Path('images/att_00005.png'),Path('images/att_00007.png'),Path('images/att_00006.png')]
+
+
+

You can easily add you own mixins with the patch decorator, which takes advantage of Python 3 function annotations to say what class to patch:

+
+
@patch
+def num_items(self:Path): return len(self.ls())
+
+p.num_items()
+
+
6
+
+
+

We also use **kwargs frequently. In python **kwargs in a parameter like means “put any additional keyword arguments into a dict called kwargs”. Normally, using kwargs makes an API quite difficult to work with, because it breaks things like tab-completion and popup lists of signatures. utils provides use_kwargs and delegates to avoid this problem. See our detailed article on delegation on this topic.

+

GetAttr solves a similar problem (and is also discussed in the article linked above): it’s allows you to use Python’s exceptionally useful __getattr__ magic method, but avoids the problem that normally in Python tab-completion and docs break when using this. For instance, you can see here that Python’s dir function, which is used to find the attributes of a python object, finds everything inside the self.default attribute here:

+
+
class Author:
+    def __init__(self, name): self.name = name
+
+class ProductPage(GetAttr):
+    _default = 'author'
+    def __init__(self,author,price,cost): self.author,self.price,self.cost = author,price,cost
+
+p = ProductPage(Author("Jeremy"), 1.50, 0.50)
+[o for o in dir(p) if not o.startswith('_')]
+
+
['author', 'cost', 'name', 'price']
+
+
+

Looking at that ProductPage example, it’s rather verbose and duplicates a lot of attribute names, which can lead to bugs later if you change them only in one place. fastcore provides store_attr to simplify this common pattern. It also provides basic_repr to give simple objects a useful repr:

+
+
class ProductPage:
+    def __init__(self,author,price,cost): store_attr()
+    __repr__ = basic_repr('author,price,cost')
+
+ProductPage("Jeremy", 1.50, 0.50)
+
+
__main__.ProductPage(author='Jeremy', price=1.5, cost=0.5)
+
+
+

One of the most interesting fastcore functions is the funcs_kwargs decorator. This allows class behavior to be modified without sub-classing. This can allow folks that aren’t familiar with object-oriented programming to customize your class more easily. Here’s an example of a class that uses funcs_kwargs:

+
+
@funcs_kwargs
+class T:
+    _methods=['some_method']
+    def __init__(self, **kwargs): assert not kwargs, f'Passed unknown args: {kwargs}'
+
+p = T(some_method = print)
+p.some_method("hello")
+
+
hello
+
+
+

The assert not kwargs above is used to ensure that the user doesn’t pass an unknown parameter (i.e one that’s not in _methods). fastai uses funcs_kwargs in many places, for instance, you can customize any part of a DataLoader by passing your own methods.

+

fastcore also provides many utility functions that make a Python programmer’s life easier, in fastcore.utils. We won’t look at many here, since you can easily look at the docs yourself. To get you started, have a look at the docs for chunked (remember, if you’re in a notebook, type doc(chunked)), which is a handy function for creating lazily generated batches from a collection.

+

Python’s ProcessPoolExecutor is extended to allow max_workers to be set to 0, to easily turn off parallel processing. This makes it easy to debug your code in serial, then run it in parallel. It also allows you to pass arguments to your parallel function, and to ensure there’s a pause between calls, in case the process you are running has race conditions. parallel makes parallel processing even easier to use, and even adds an optional progress bar.

+
+
+

L

+

Like most languages, Python allows for very concise syntax for some very common types, such as list, which can be constructed with [1,2,3]. Perl’s designer Larry Wall explained the reasoning for this kind of syntax:

+
+

In metaphorical honor of Huffman’s compression code that assigns smaller numbers of bits to more common bytes. In terms of syntax, it simply means that commonly used things should be shorter, but you shouldn’t waste short sequences on less common constructs.

+
+

On this basis, fastcore has just one type that has a single letter name: L. The reason for this is that it is designed to be a replacement for list, so we want it to be just as easy to use as [1,2,3]. Here’s how to create that as an L:

+
+
L(1,2,3)
+
+
(#3) [1,2,3]
+
+
+

The first thing to notice is that an L object includes in its representation its number of elements; that’s the (#3) in the output above. If there’s more than 10 elements, it will automatically truncate the list:

+
+
p = L.range(20).shuffle()
+p
+
+
(#20) [5,1,9,10,18,13,6,17,3,16...]
+
+
+

L contains many of the same indexing ideas that NumPy’s array does, including indexing with a list of indexes, or a boolean mask list:

+
+
p[2,4,6]
+
+
(#3) [9,18,6]
+
+
+

It also contains other methods used in array, such as L.argwhere:

+
+
p.argwhere(ge(15))
+
+
(#5) [4,7,9,18,19]
+
+
+

As you can see from this example, fastcore also includes a number of features that make a functional style of programming easier, such as a full range of boolean functions (e.g ge, gt, etc) which give the same answer as the functions from Python’s operator module if given two parameters, but return a curried function if given one parameter.

+

There’s too much functionality to show it all here, so be sure to check the docs. Many little things are added that we thought should have been in list in the first place, such as making this do what you’d expect (which is an error with list, but works fine with L):

+
+
1 + L(2,3,4)
+
+
(#4) [1,2,3,4]
+
+
+
+
+

Transforms

+

A Transform is the main building block of the fastai data pipelines. In the most general terms a transform can be any function you want to apply to your data, however the Transform class provides several mechanisms that make the process of building them easy and flexible (see the docs for information about each of these):

+
    +
  • Type dispatch
  • +
  • Dispatch over tuples
  • +
  • Reversability
  • +
  • Type propagation
  • +
  • Preprocessing
  • +
  • Filtering based on the dataset type
  • +
  • Ordering
  • +
  • Appending new behavior with decorators
  • +
+

Transform looks for three special methods, encodes, decodes, and setups, which provide the implementation for __call__, decode, and setup respectively. For instance:

+
+
class A(Transform):
+    def encodes(self, x): return x+1
+
+A()(1)
+
+
2
+
+
+

For simple transforms like this, you can also use Transform as a decorator:

+
+
@Transform
+def f(x): return x+1
+
+f(1)
+
+
2
+
+
+

Transforms can be composed into a Pipeline:

+
+
@Transform
+def g(x): return x/2
+
+pipe = Pipeline([f,g])
+pipe(3)
+
+
2.0
+
+
+

The power of Transform and Pipeline is best understood by seeing how they’re used to create a complete data processing pipeline. This is explained in chapter 11 of the fastai book, which is available for free in Jupyter Notebook format.

+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/tour.html.md b/tour.html.md new file mode 100644 index 00000000..4b04a689 --- /dev/null +++ b/tour.html.md @@ -0,0 +1,417 @@ +# A tour of fastcore + + + + +Here’s a (somewhat) quick tour of a few higlights from fastcore. + +### Documentation + +All fast.ai projects, including this one, are built with +[nbdev](https://nbdev.fast.ai), which is a full literate programming +environment built on Jupyter Notebooks. That means that every piece of +documentation, including the page you’re reading now, can be accessed as +interactive Jupyter notebooks. In fact, you can even grab a link +directly to a notebook running interactively on Google Colab - if you +want to follow along with this tour, click the link below: + +``` python +colab_link('index') +``` + +[Open `index` in +Colab](https://colab.research.google.com/github/fastai/fastcore/blob/master/nbs/index.ipynb) + +The full docs are available at +[fastcore.fast.ai](https://fastcore.fast.ai). The code in the examples +and in all fast.ai libraries follow the [fast.ai style +guide](https://docs.fast.ai/dev/style.html). In order to support +interactive programming, all fast.ai libraries are designed to allow for +`import *` to be used safely, particular by ensuring that +[`__all__`](https://riptutorial.com/python/example/2894/the---all---special-variable) +is defined in all packages. In order to see where a function is from, +just type it: + +``` python +coll_repr +``` + + + +For more details, including a link to the full documentation and source +code, use `doc`, which pops up a window with this information: + +``` python +doc(coll_repr) +``` + + + +The documentation also contains links to any related functions or +classes, which appear like this: +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr) (in +the notebook itself you will just see a word with back-ticks around it; +the links are auto-generated in the documentation site). The +documentation will generally show one or more examples of use, along +with any background context necessary to understand them. As you’ll see, +the examples for each function and method are shown as tests, rather +than example outputs, so let’s start by explaining that. + +### Testing + +fastcore’s testing module is designed to work well with +[nbdev](https://nbdev.fast.ai), which is a full literate programming +environment built on Jupyter Notebooks. That means that your tests, +docs, and code all live together in the same notebook. fastcore and +nbdev’s approach to testing starts with the premise that all your tests +should pass. If one fails, no more tests in a notebook are run. + +Tests look like this: + +``` python +test_eq(coll_repr(range(1000), 5), '(#1000) [0,1,2,3,4...]') +``` + +That’s an example from the docs for +[`coll_repr`](https://fastcore.fast.ai/foundation.html#coll_repr). As +you see, it’s not showing you the output directly. Here’s what that +would look like: + +``` python +coll_repr(range(1000), 5) +``` + + '(#1000) [0,1,2,3,4...]' + +So, the test is actually showing you what the output looks like, because +if the function call didn’t return `'(#1000) [0,1,2,3,4...]'`, then the +test would have failed. + +So every test shown in the docs is also showing you the behavior of the +library — and vice versa! + +Test functions always start with `test_`, and then follow with the +operation being tested. So +[`test_eq`](https://fastcore.fast.ai/test.html#test_eq) tests for +equality (as you saw in the example above). This includes tests for +equality of arrays and tensors, lists and generators, and many more: + +``` python +test_eq([0,1,2,3], np.arange(4)) +``` + +When a test fails, it prints out information about what was expected: + +``` python +test_eq([0,1,2,3], np.arange(3)) +``` + + ---- + AssertionError: ==: + [0, 1, 2, 3] + [0 1 2] + +If you want to check that objects are the same type, rather than the +just contain the same collection, use +[`test_eq_type`](https://fastcore.fast.ai/test.html#test_eq_type). + +You can test with any comparison function using +[`test`](https://fastcore.fast.ai/test.html#test), e.g test whether an +object is less than: + +``` python +test(2, 3, operator.lt) +``` + +You can even test that exceptions are raised: + +``` python +def divide_zero(): return 1/0 +test_fail(divide_zero) +``` + +…and test that things are printed to stdout: + +``` python +test_stdout(lambda: print('hi'), 'hi') +``` + +### Foundations + +fast.ai is unusual in that we often use +[mixins](https://en.wikipedia.org/wiki/Mixin) in our code. Mixins are +widely used in many programming languages, such as Ruby, but not so much +in Python. We use mixins to attach new behavior to existing libraries, +or to allow modules to add new behavior to our own classes, such as in +extension modules. One useful example of a mixin we define is +[`Path.ls`](https://fastcore.fast.ai/xtras.html#path.ls), which lists a +directory and returns an +[`L`](https://fastcore.fast.ai/foundation.html#l) (an extended list +class which we’ll discuss shortly): + +``` python +p = Path('images') +p.ls() +``` + + (#6) [Path('images/mnist3.png'),Path('images/att_00000.png'),Path('images/puppy.jpg'),Path('images/att_00005.png'),Path('images/att_00007.png'),Path('images/att_00006.png')] + +You can easily add you own mixins with the +[`patch`](https://fastcore.fast.ai/basics.html#patch) +[decorator](https://realpython.com/primer-on-python-decorators/), which +takes advantage of Python 3 [function +annotations](https://www.python.org/dev/peps/pep-3107/#parameters) to +say what class to patch: + +``` python +@patch +def num_items(self:Path): return len(self.ls()) + +p.num_items() +``` + + 6 + +We also use `**kwargs` frequently. In python `**kwargs` in a parameter +like means “*put any additional keyword arguments into a dict called +`kwargs`*”. Normally, using `kwargs` makes an API quite difficult to +work with, because it breaks things like tab-completion and popup lists +of signatures. `utils` provides +[`use_kwargs`](https://fastcore.fast.ai/meta.html#use_kwargs) and +[`delegates`](https://fastcore.fast.ai/meta.html#delegates) to avoid +this problem. See our [detailed article on +delegation](https://www.fast.ai/2019/08/06/delegation/) on this topic. + +[`GetAttr`](https://fastcore.fast.ai/basics.html#getattr) solves a +similar problem (and is also discussed in the article linked above): +it’s allows you to use Python’s exceptionally useful +[`__getattr__`](https://fastcore.fast.ai/xml.html#__getattr__) magic +method, but avoids the problem that normally in Python tab-completion +and docs break when using this. For instance, you can see here that +Python’s `dir` function, which is used to find the attributes of a +python object, finds everything inside the `self.default` attribute +here: + +``` python +class Author: + def __init__(self, name): self.name = name + +class ProductPage(GetAttr): + _default = 'author' + def __init__(self,author,price,cost): self.author,self.price,self.cost = author,price,cost + +p = ProductPage(Author("Jeremy"), 1.50, 0.50) +[o for o in dir(p) if not o.startswith('_')] +``` + + ['author', 'cost', 'name', 'price'] + +Looking at that `ProductPage` example, it’s rather verbose and +duplicates a lot of attribute names, which can lead to bugs later if you +change them only in one place. `fastcore` provides +[`store_attr`](https://fastcore.fast.ai/basics.html#store_attr) to +simplify this common pattern. It also provides +[`basic_repr`](https://fastcore.fast.ai/basics.html#basic_repr) to give +simple objects a useful `repr`: + +``` python +class ProductPage: + def __init__(self,author,price,cost): store_attr() + __repr__ = basic_repr('author,price,cost') + +ProductPage("Jeremy", 1.50, 0.50) +``` + + __main__.ProductPage(author='Jeremy', price=1.5, cost=0.5) + +One of the most interesting `fastcore` functions is the +[`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs) +decorator. This allows class behavior to be modified without +sub-classing. This can allow folks that aren’t familiar with +object-oriented programming to customize your class more easily. Here’s +an example of a class that uses +[`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs): + +``` python +@funcs_kwargs +class T: + _methods=['some_method'] + def __init__(self, **kwargs): assert not kwargs, f'Passed unknown args: {kwargs}' + +p = T(some_method = print) +p.some_method("hello") +``` + + hello + +The `assert not kwargs` above is used to ensure that the user doesn’t +pass an unknown parameter (i.e one that’s not in `_methods`). `fastai` +uses [`funcs_kwargs`](https://fastcore.fast.ai/meta.html#funcs_kwargs) +in many places, for instance, you can customize any part of a +`DataLoader` by passing your own methods. + +`fastcore` also provides many utility functions that make a Python +programmer’s life easier, in `fastcore.utils`. We won’t look at many +here, since you can easily look at the docs yourself. To get you +started, have a look at the docs for +[`chunked`](https://fastcore.fast.ai/basics.html#chunked) (remember, if +you’re in a notebook, type `doc(chunked)`), which is a handy function +for creating lazily generated batches from a collection. + +Python’s +[`ProcessPoolExecutor`](https://fastcore.fast.ai/parallel.html#processpoolexecutor) +is extended to allow `max_workers` to be set to `0`, to easily turn off +parallel processing. This makes it easy to debug your code in serial, +then run it in parallel. It also allows you to pass arguments to your +parallel function, and to ensure there’s a pause between calls, in case +the process you are running has race conditions. +[`parallel`](https://fastcore.fast.ai/parallel.html#parallel) makes +parallel processing even easier to use, and even adds an optional +progress bar. + +### L + +Like most languages, Python allows for very concise syntax for some very +common types, such as `list`, which can be constructed with `[1,2,3]`. +Perl’s designer Larry Wall explained the reasoning for this kind of +syntax: + +> In metaphorical honor of Huffman’s compression code that assigns +> smaller numbers of bits to more common bytes. In terms of syntax, it +> simply means that commonly used things should be shorter, but you +> shouldn’t waste short sequences on less common constructs. + +On this basis, `fastcore` has just one type that has a single letter +name: [`L`](https://fastcore.fast.ai/foundation.html#l). The reason for +this is that it is designed to be a replacement for `list`, so we want +it to be just as easy to use as `[1,2,3]`. Here’s how to create that as +an [`L`](https://fastcore.fast.ai/foundation.html#l): + +``` python +L(1,2,3) +``` + + (#3) [1,2,3] + +The first thing to notice is that an +[`L`](https://fastcore.fast.ai/foundation.html#l) object includes in its +representation its number of elements; that’s the `(#3)` in the output +above. If there’s more than 10 elements, it will automatically truncate +the list: + +``` python +p = L.range(20).shuffle() +p +``` + + (#20) [5,1,9,10,18,13,6,17,3,16...] + +[`L`](https://fastcore.fast.ai/foundation.html#l) contains many of the +same indexing ideas that NumPy’s `array` does, including indexing with a +list of indexes, or a boolean mask list: + +``` python +p[2,4,6] +``` + + (#3) [9,18,6] + +It also contains other methods used in `array`, such as +[`L.argwhere`](https://fastcore.fast.ai/foundation.html#l.argwhere): + +``` python +p.argwhere(ge(15)) +``` + + (#5) [4,7,9,18,19] + +As you can see from this example, `fastcore` also includes a number of +features that make a functional style of programming easier, such as a +full range of boolean functions (e.g `ge`, `gt`, etc) which give the +same answer as the functions from Python’s `operator` module if given +two parameters, but return a [curried +function](https://en.wikipedia.org/wiki/Currying) if given one +parameter. + +There’s too much functionality to show it all here, so be sure to check +the docs. Many little things are added that we thought should have been +in `list` in the first place, such as making this do what you’d expect +(which is an error with `list`, but works fine with +[`L`](https://fastcore.fast.ai/foundation.html#l)): + +``` python +1 + L(2,3,4) +``` + + (#4) [1,2,3,4] + +### Transforms + +A [`Transform`](https://fastcore.fast.ai/transform.html#transform) is +the main building block of the fastai data pipelines. In the most +general terms a transform can be any function you want to apply to your +data, however the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) class +provides several mechanisms that make the process of building them easy +and flexible (see the docs for information about each of these): + +- Type dispatch +- Dispatch over tuples +- Reversability +- Type propagation +- Preprocessing +- Filtering based on the dataset type +- Ordering +- Appending new behavior with decorators + +[`Transform`](https://fastcore.fast.ai/transform.html#transform) looks +for three special methods, encodes, decodes, +and setups, which provide the implementation for +[`__call__`](https://www.python-course.eu/python3_magic_methods.php), +`decode`, and `setup` respectively. For instance: + +``` python +class A(Transform): + def encodes(self, x): return x+1 + +A()(1) +``` + + 2 + +For simple transforms like this, you can also use +[`Transform`](https://fastcore.fast.ai/transform.html#transform) as a +decorator: + +``` python +@Transform +def f(x): return x+1 + +f(1) +``` + + 2 + +Transforms can be composed into a +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline): + +``` python +@Transform +def g(x): return x/2 + +pipe = Pipeline([f,g]) +pipe(3) +``` + + 2.0 + +The power of +[`Transform`](https://fastcore.fast.ai/transform.html#transform) and +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) is best +understood by seeing how they’re used to create a complete data +processing pipeline. This is explained in [chapter +11](https://github.com/fastai/fastbook/blob/master/11_midlevel_data.ipynb) +of the [fastai +book](https://www.amazon.com/Deep-Learning-Coders-fastai-PyTorch/dp/1492045527), +which is [available for free](https://github.com/fastai/fastbook) in +Jupyter Notebook format. diff --git a/transform.html b/transform.html new file mode 100644 index 00000000..fe9eae9a --- /dev/null +++ b/transform.html @@ -0,0 +1,1450 @@ + + + + + + + + + + +Transforms – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Transforms

+
+ +
+
+ Definition of Transform and Pipeline +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from __future__ import annotations
+from nbdev.showdoc import *
+from fastcore.test import *
+from fastcore.nb_imports import *
+
+

The classes here provide functionality for creating a composition of partially reversible functions. By “partially reversible” we mean that a transform can be decoded, creating a form suitable for display. This is not necessarily identical to the original form (e.g. a transform that changes a byte tensor to a float tensor does not recreate a byte tensor when decoded, since that may lose precision, and a float tensor can be displayed already).

+

Classes are also provided and for composing transforms, and mapping them over collections. Pipeline is a transform which composes several Transform, knowing how to decode them or show an encoded item.

+
+

source

+
+

Transform

+
+
 Transform (enc=None, dec=None, split_idx=None, order=None)
+
+

Delegates (__call__,decode,setup) to (encodes,decodes,setups) if split_idx matches

+

A Transform is the main building block of the fastai data pipelines. In the most general terms a transform can be any function you want to apply to your data, however the Transform class provides several mechanisms that make the process of building them easy and flexible.

+
+
+

The main Transform features:

+
    +
  • Type dispatch - Type annotations are used to determine if a transform should be applied to the given argument. It also gives an option to provide several implementations and it choses the one to run based on the type. This is useful for example when running both independent and dependent variables through the pipeline where some transforms only make sense for one and not the other. Another usecase is designing a transform that handles different data formats. Note that if a transform takes multiple arguments only the type of the first one is used for dispatch.
  • +
  • Handling of tuples - When a tuple (or a subclass of tuple) of data is passed to a transform it will get applied to each element separately. You can opt out of this behavior by passing a list or an L, as only tuples gets this specific behavior. An alternative is to use ItemTransform defined below, which will always take the input as a whole.
  • +
  • Reversability - A transform can be made reversible by implementing the decodes method. This is mainly used to turn something like a category which is encoded as a number back into a label understandable by humans for showing purposes. Like the regular call method, the decode method that is used to decode will be applied over each element of a tuple separately.
  • +
  • Type propagation - Whenever possible a transform tries to return data of the same type it received. Mainly used to maintain semantics of things like ArrayImage which is a thin wrapper of pytorch’s Tensor. You can opt out of this behavior by adding ->None return type annotation.
  • +
  • Preprocessing - The setup method can be used to perform any one-time calculations to be later used by the transform, for example generating a vocabulary to encode categorical data.
  • +
  • Filtering based on the dataset type - By setting the split_idx flag you can make the transform be used only in a specific DataSource subset like in training, but not validation.
  • +
  • Ordering - You can set the order attribute which the Pipeline uses when it needs to merge two lists of transforms.
  • +
  • Appending new behavior with decorators - You can easily extend an existing Transform by creating encodes or decodes methods for new data types. You can put those new methods outside the original transform definition and decorate them with the class you wish them patched into. This can be used by the fastai library users to add their own behavior, or multiple modules contributing to the same transform.
  • +
+
+
+

Defining a Transform

+

There are a few ways to create a transform with different ratios of simplicity to flexibility. - Extending the Transform class - Use inheritence to implement the methods you want. - Passing methods to the constructor - Instantiate the Transform class and pass your functions as enc and dec arguments. - @Transform decorator - Turn any function into a Transform by just adding a decorator - very straightforward if all you need is a single encodes implementation. - Passing a function to fastai APIs - Same as above, but when passing a function to other transform aware classes like Pipeline or TfmdDS you don’t even need a decorator. Your function will get converted to a Transform automatically.

+

A simple way to create a Transform is to pass a function to the constructor. In the below example, we pass an anonymous function that does integer division by 2:

+
+
f = Transform(lambda o:o//2)
+
+

If you call this transform, it will apply the transformation:

+
+
test_eq_type(f(2), 1)
+
+

Another way to define a Transform is to extend the Transform class:

+
+
class A(Transform): pass
+
+

However, to enable your transform to do something, you have to define an encodes method. Note that we can use the class name as a decorator to add this method to the original class.

+
+
@A
+def encodes(self, x): return x+1
+
+f1 = A()
+test_eq(f1(1), 2) # f1(1) is the same as f1.encode(1)
+
+

In addition to adding an encodes method, we can also add a decodes method. This enables you to call the decode method (without an s). For more information about the purpose of decodes, see the discussion about Reversibility in the above section.

+

Just like with encodes, you can add a decodes method to the original class by using the class name as a decorator:

+
+
class B(A): pass
+
+@B
+def decodes(self, x): return x-1
+
+f2 = B()
+test_eq(f2.decode(2), 1)
+
+test_eq(f2(1), 2) # uses A's encode method from the parent class
+
+

If you do not define an encodes or decodes method the original value will be returned:

+
+
class _Tst(Transform): pass 
+
+f3 = _Tst() # no encodes or decodes method have been defined
+test_eq_type(f3.decode(2.0), 2.0)
+test_eq_type(f3(2), 2)
+
+

Transforms can be created from class methods too:

+
+
class A:
+    @classmethod
+    def create(cls, x:int): return x+1
+test_eq(Transform(A.create)(1), 2)
+
+
+

Defining Transforms With A Decorator

+

Transform can be used as a decorator to turn a function into a Transform.

+
+
@Transform
+def f(x): return x//2
+test_eq_type(f(2), 1)
+test_eq_type(f.decode(2.0), 2.0)
+
+@Transform
+def f(x): return x*2
+test_eq_type(f(2), 4)
+test_eq_type(f.decode(2.0), 2.0)
+
+
+
+

Typed Dispatch and Transforms

+

We can also apply different transformations depending on the type of the input passed by using TypedDispatch. TypedDispatch automatically works with Transform when using type hints:

+
+
class A(Transform): pass
+
+@A
+def encodes(self, x:int): return x//2
+
+@A
+def encodes(self, x:float): return x+1
+
+

When we pass in an int, this calls the first encodes method:

+
+
f = A()
+test_eq_type(f(3), 1)
+
+

When we pass in a float, this calls the second encodes method:

+
+
test_eq_type(f(2.), 3.)
+
+

When we pass in a type that is not specified in encodes, the original value is returned:

+
+
test_eq(f('a'), 'a')
+
+

If the type annotation is a tuple, then any type in the tuple will match:

+
+
class MyClass(int): pass
+
+class A(Transform):
+    def encodes(self, x:MyClass|float): return x/2
+    def encodes(self, x:str|list): return str(x)+'_1'
+
+f = A()
+
+

The below two examples match the first encodes, with a type of MyClass and float, respectively:

+
+
test_eq(f(MyClass(2)), 1.) # input is of type MyClass 
+test_eq(f(6.0), 3.0) # input is of type float
+
+

The next two examples match the second encodes method, with a type of str and list, respectively:

+
+
test_eq(f('a'), 'a_1') # input is of type str
+test_eq(f(['a','b','c']), "['a', 'b', 'c']_1") # input is of type list
+
+
+
+

Casting Types With Transform

+

Without any intervention it is easy for operations to change types in Python. For example, FloatSubclass (defined below) becomes a float after performing multiplication:

+
+
class FloatSubclass(float): pass
+test_eq_type(FloatSubclass(3.0) * 2, 6.0)
+
+

This behavior is often not desirable when performing transformations on data. Therefore, Transform will attempt to cast the output to be of the same type as the input by default. In the below example, the output will be cast to a FloatSubclass type to match the type of the input:

+
+
@Transform
+def f(x): return x*2
+
+test_eq_type(f(FloatSubclass(3.0)), FloatSubclass(6.0))
+
+

We can optionally turn off casting by annotating the transform function with a return type of None:

+
+
@Transform
+def f(x)-> None: return x*2 # Same transform as above, but with a -> None annotation
+
+test_eq_type(f(FloatSubclass(3.0)), 6.0)  # Casting is turned off because of -> None annotation
+
+

However, Transform will only cast output back to the input type when the input is a subclass of the output. In the below example, the input is of type FloatSubclass which is not a subclass of the output which is of type str. Therefore, the output doesn’t get cast back to FloatSubclass and stays as type str:

+
+
@Transform
+def f(x): return str(x)
+    
+test_eq_type(f(Float(2.)), '2.0')
+
+

Just like encodes, the decodes method will cast outputs to match the input type in the same way. In the below example, the output of decodes remains of type MySubclass:

+
+
class MySubclass(int): pass
+
+def enc(x): return MySubclass(x+1)
+def dec(x): return x-1
+
+
+f = Transform(enc,dec)
+t = f(1) # t is of type MySubclass
+test_eq_type(f.decode(t), MySubclass(1)) # the output of decode is cast to MySubclass to match the input type.
+
+
+
+

Apply Transforms On Subsets With split_idx

+

You can apply transformations to subsets of data by specifying a split_idx property. If a transform has a split_idx then it’s only applied if the split_idx param matches. In the below example, we set split_idx equal to 1:

+
+
def enc(x): return x+1
+def dec(x): return x-1
+f = Transform(enc,dec)
+f.split_idx = 1
+
+

The transformations are applied when a matching split_idx parameter is passed:

+
+
test_eq(f(1, split_idx=1),2)
+test_eq(f.decode(2, split_idx=1),1)
+
+

On the other hand, transformations are ignored when the split_idx parameter does not match:

+
+
test_eq(f(1, split_idx=0), 1)
+test_eq(f.decode(2, split_idx=0), 2)
+
+
+
+

Transforms on Lists

+

Transform operates on lists as a whole, not element-wise:

+
+
class A(Transform):
+    def encodes(self, x): return dict(x)
+    def decodes(self, x): return list(x.items())
+    
+f = A()
+_inp = [(1,2), (3,4)]
+t = f(_inp)
+
+test_eq(t, dict(_inp))
+test_eq(f.decodes(t), _inp)
+
+

If you want a transform to operate on a list elementwise, you must implement this appropriately in the encodes and decodes methods:

+
+
class AL(Transform): pass
+
+@AL
+def encodes(self, x): return [x_+1 for x_ in x]
+
+@AL
+def decodes(self, x): return [x_-1 for x_ in x]
+
+f = AL()
+t = f([1,2])
+
+test_eq(t, [2,3])
+test_eq(f.decode(t), [1,2])
+
+
+
+

Transforms on Tuples

+

Unlike lists, Transform operates on tuples element-wise.

+
+
def neg_int(x): return -x
+f = Transform(neg_int)
+
+test_eq(f((1,2,3)), (-1,-2,-3))
+
+

Transforms will also apply TypedDispatch element-wise on tuples when an input type annotation is specified. In the below example, the values 1.0 and 3.0 are ignored because they are of type float, not int:

+
+
def neg_int(x:int): return -x
+f = Transform(neg_int)
+
+test_eq(f((1.0, 2, 3.0)), (1.0, -2, 3.0))
+
+

Another example of how Transform can use TypedDispatch with tuples is shown below:

+
+
class B(Transform): pass
+
+@B
+def encodes(self, x:int): return x+1
+
+@B
+def encodes(self, x:str): return x+'hello'
+
+@B
+def encodes(self, x): return str(x)+'!'
+
+

If the input is not an int or str, the third encodes method will apply:

+
+
b = B()
+test_eq(b([1]), '[1]!') 
+test_eq(b([1.0]), '[1.0]!')
+
+

However, if the input is a tuple, then the appropriate method will apply according to the type of each element in the tuple:

+
+
test_eq(b(('1',)), ('1hello',))
+test_eq(b((1,2)), (2,3))
+test_eq(b(('a',1.0)), ('ahello','1.0!'))
+
+

Dispatching over tuples works recursively, by the way:

+
+
class B(Transform):
+    def encodes(self, x:int): return x+1
+    def encodes(self, x:str): return x+'_hello'
+    def decodes(self, x:int): return x-1
+    def decodes(self, x:str): return x.replace('_hello', '')
+
+f = B()
+start = (1.,(2,'3'))
+t = f(start)
+test_eq_type(t, (1.,(3,'3_hello')))
+test_eq(f.decode(t), start)
+
+

Dispatching also works with typing module type classes, like numbers.integral:

+
+
@Transform
+def f(x:numbers.Integral): return x+1
+
+t = f((1,'1',1))
+test_eq(t, (2, '1', 2))
+
+
+

source

+
+
+
+

InplaceTransform

+
+
 InplaceTransform (enc=None, dec=None, split_idx=None, order=None)
+
+

A Transform that modifies in-place and just returns whatever it’s passed

+
+
class A(InplaceTransform): pass
+
+@A
+def encodes(self, x:pd.Series): x.fillna(10, inplace=True)
+    
+f = A()
+
+test_eq_type(f(pd.Series([1,2,None])),pd.Series([1,2,10],dtype=np.float64)) #fillna fills with floats.
+
+
+

source

+
+
+

DisplayedTransform

+
+
 DisplayedTransform (enc=None, dec=None, split_idx=None, order=None)
+
+

A transform with a __repr__ that shows its attrs

+

Transforms normally are represented by just their class name and a list of encodes and decodes implementations:

+
+
class A(Transform): encodes,decodes = noop,noop
+f = A()
+f
+
+
A:
+encodes: (object,object) -> noop
+decodes: (object,object) -> noop
+
+
+

A DisplayedTransform will in addition show the contents of all attributes listed in the comma-delimited string self.store_attrs:

+
+
class A(DisplayedTransform):
+    encodes = noop
+    def __init__(self, a, b=2):
+        super().__init__()
+        store_attr()
+    
+A(a=1,b=2)
+
+
A -- {'a': 1, 'b': 2}:
+encodes: (object,object) -> noop
+decodes: 
+
+
+
+

source

+
+
+

ItemTransform

+
+
 ItemTransform (enc=None, dec=None, split_idx=None, order=None)
+
+

A transform that always take tuples as items

+

ItemTransform is the class to use to opt out of the default behavior of Transform.

+
+
class AIT(ItemTransform): 
+    def encodes(self, xy): x,y=xy; return (x+y,y)
+    def decodes(self, xy): x,y=xy; return (x-y,y)
+    
+f = AIT()
+test_eq(f((1,2)), (3,2))
+test_eq(f.decode((3,2)), (1,2))
+
+

If you pass a special tuple subclass, the usual retain type behavior of Transform will keep it:

+
+
class _T(tuple): pass
+x = _T((1,2))
+test_eq_type(f(x), _T((3,2)))
+
+
+

source

+
+
+

get_func

+
+
 get_func (t, name, *args, **kwargs)
+
+

Get the t.name (potentially partial-ized with args and kwargs) or noop if not defined

+

This works for any kind of t supporting getattr, so a class or a module.

+
+
test_eq(get_func(operator, 'neg', 2)(), -2)
+test_eq(get_func(operator.neg, '__call__')(2), -2)
+test_eq(get_func(list, 'foobar')([2]), [2])
+a = [2,1]
+get_func(list, 'sort')(a)
+test_eq(a, [1,2])
+
+

Transforms are built with multiple-dispatch: a given function can have several methods depending on the type of the object received. This is done directly with the TypeDispatch module and type-annotation in Transform, but you can also use the following class.

+
+

source

+
+
+

Func

+
+
 Func (name, *args, **kwargs)
+
+

Basic wrapper around a name with args and kwargs to call on a given type

+

You can call the Func object on any module name or type, even a list of types. It will return the corresponding function (with a default to noop if nothing is found) or list of functions.

+
+
test_eq(Func('sqrt')(math), math.sqrt)
+
+
+
+
+

Sig

+
+
 Sig (*args, **kwargs)
+
+

Sig is just sugar-syntax to create a Func object more easily with the syntax Sig.name(*args, **kwargs).

+
+
f = Sig.sqrt()
+test_eq(f(math), math.sqrt)
+
+
+

source

+
+
+

compose_tfms

+
+
 compose_tfms (x, tfms, is_enc=True, reverse=False, **kwargs)
+
+

Apply all func_nm attribute of tfms on x, maybe in reverse order

+
+
def to_int  (x):   return Int(x)
+def to_float(x):   return Float(x)
+def double  (x):   return x*2
+def half(x)->None: return x/2
+
+
+
def test_compose(a, b, *fs): test_eq_type(compose_tfms(a, tfms=map(Transform,fs)), b)
+
+test_compose(1,   Int(1),   to_int)
+test_compose(1,   Float(1), to_int,to_float)
+test_compose(1,   Float(2), to_int,to_float,double)
+test_compose(2.0, 2.0,      to_int,double,half)
+
+
+
class A(Transform):
+    def encodes(self, x:float):  return Float(x+1)
+    def decodes(self, x): return x-1
+    
+tfms = [A(), Transform(math.sqrt)]
+t = compose_tfms(3., tfms=tfms)
+test_eq_type(t, Float(2.))
+test_eq(compose_tfms(t, tfms=tfms, is_enc=False), 1.)
+test_eq(compose_tfms(4., tfms=tfms, reverse=True), 3.)
+
+
+
tfms = [A(), Transform(math.sqrt)]
+test_eq(compose_tfms((9,3.), tfms=tfms), (3,2.))
+
+
+

source

+
+
+

mk_transform

+
+
 mk_transform (f)
+
+

Convert function f to Transform if it isn’t already one

+
+

source

+
+
+

gather_attrs

+
+
 gather_attrs (o, k, nm)
+
+

Used in getattr to collect all attrs k from self.{nm}

+
+

source

+
+
+

gather_attr_names

+
+
 gather_attr_names (o, nm)
+
+

Used in dir to collect all attrs k from self.{nm}

+
+

source

+
+
+

Pipeline

+
+
 Pipeline (funcs=None, split_idx=None)
+
+

A pipeline of composed (for encode/decode) transforms, setup with types

+
+
add_docs(Pipeline,
+         __call__="Compose `__call__` of all `fs` on `o`",
+         decode="Compose `decode` of all `fs` on `o`",
+         show="Show `o`, a single item from a tuple, decoding as needed",
+         add="Add transforms `ts`",
+         setup="Call each tfm's `setup` in order")
+
+

Pipeline is a wrapper for compose_tfms. You can pass instances of Transform or regular functions in funcs, the Pipeline will wrap them all in Transform (and instantiate them if needed) during the initialization. It handles the transform setup by adding them one at a time and calling setup on each, goes through them in order in __call__ or decode and can show an object by applying decoding the transforms up until the point it gets an object that knows how to show itself.

+
+
# Empty pipeline is noop
+pipe = Pipeline()
+test_eq(pipe(1), 1)
+test_eq(pipe((1,)), (1,))
+# Check pickle works
+assert pickle.loads(pickle.dumps(pipe))
+
+
+
class IntFloatTfm(Transform):
+    def encodes(self, x):  return Int(x)
+    def decodes(self, x):  return Float(x)
+    foo=1
+
+int_tfm=IntFloatTfm()
+
+def neg(x): return -x
+neg_tfm = Transform(neg, neg)
+
+
+
pipe = Pipeline([neg_tfm, int_tfm])
+
+start = 2.0
+t = pipe(start)
+test_eq_type(t, Int(-2))
+test_eq_type(pipe.decode(t), Float(start))
+test_stdout(lambda:pipe.show(t), '-2')
+
+
+
pipe = Pipeline([neg_tfm, int_tfm])
+t = pipe(start)
+test_stdout(lambda:pipe.show(pipe((1.,2.))), '-1\n-2')
+test_eq(pipe.foo, 1)
+assert 'foo' in dir(pipe)
+assert 'int_float_tfm' in dir(pipe)
+
+

You can add a single transform or multiple transforms ts using Pipeline.add. Transforms will be ordered by Transform.order.

+
+
pipe = Pipeline([neg_tfm, int_tfm])
+class SqrtTfm(Transform):
+    order=-1
+    def encodes(self, x): 
+        return x**(.5)
+    def decodes(self, x): return x**2
+pipe.add(SqrtTfm())
+test_eq(pipe(4),-2)
+test_eq(pipe.decode(-2),4)
+pipe.add([SqrtTfm(),SqrtTfm()])
+test_eq(pipe(256),-2)
+test_eq(pipe.decode(-2),256)
+
+

Transforms are available as attributes named with the snake_case version of the names of their types. Attributes in transforms can be directly accessed as attributes of the pipeline.

+
+
test_eq(pipe.int_float_tfm, int_tfm)
+test_eq(pipe.foo, 1)
+
+pipe = Pipeline([int_tfm, int_tfm])
+pipe.int_float_tfm
+test_eq(pipe.int_float_tfm[0], int_tfm)
+test_eq(pipe.foo, [1,1])
+
+
+
# Check opposite order
+pipe = Pipeline([int_tfm,neg_tfm])
+t = pipe(start)
+test_eq(t, -2)
+test_stdout(lambda:pipe.show(t), '-2')
+
+
+
class A(Transform):
+    def encodes(self, x):  return int(x)
+    def decodes(self, x):  return Float(x)
+
+pipe = Pipeline([neg_tfm, A])
+t = pipe(start)
+test_eq_type(t, -2)
+test_eq_type(pipe.decode(t), Float(start))
+test_stdout(lambda:pipe.show(t), '-2.0')
+
+
+
s2 = (1,2)
+pipe = Pipeline([neg_tfm, A])
+t = pipe(s2)
+test_eq_type(t, (-1,-2))
+test_eq_type(pipe.decode(t), (Float(1.),Float(2.)))
+test_stdout(lambda:pipe.show(t), '-1.0\n-2.0')
+
+
+
from PIL import Image
+
+
+
class ArrayImage(ndarray):
+    _show_args = {'cmap':'viridis'}
+    def __new__(cls, x, *args, **kwargs):
+        if isinstance(x,tuple): super().__new__(cls, x, *args, **kwargs)
+        if args or kwargs: raise RuntimeError('Unknown array init args')
+        if not isinstance(x,ndarray): x = array(x)
+        return x.view(cls)
+    
+    def show(self, ctx=None, figsize=None, **kwargs):
+        if ctx is None: _,ctx = plt.subplots(figsize=figsize)
+        ctx.imshow(im, **{**self._show_args, **kwargs})
+        ctx.axis('off')
+        return ctx
+    
+im = Image.open(TEST_IMAGE)
+im_t = ArrayImage(im)
+
+
+
def f1(x:ArrayImage): return -x
+def f2(x): return Image.open(x).resize((128,128))
+def f3(x:Image.Image): return(ArrayImage(array(x)))
+
+
+
pipe = Pipeline([f2,f3,f1])
+t = pipe(TEST_IMAGE)
+test_eq(type(t), ArrayImage)
+test_eq(t, -array(f3(f2(TEST_IMAGE))))
+
+
+
pipe = Pipeline([f2,f3])
+t = pipe(TEST_IMAGE)
+ax = pipe.show(t)
+
+
+
+

+
+
+
+
+
+
#test_fig_exists(ax)
+
+
+
#Check filtering is properly applied
+add1 = B()
+add1.split_idx = 1
+pipe = Pipeline([neg_tfm, A(), add1])
+test_eq(pipe(start), -2)
+pipe.split_idx=1
+test_eq(pipe(start), -1)
+pipe.split_idx=0
+test_eq(pipe(start), -2)
+for t in [None, 0, 1]:
+    pipe.split_idx=t
+    test_eq(pipe.decode(pipe(start)), start)
+    test_stdout(lambda: pipe.show(pipe(start)), "-2.0")
+
+
+
def neg(x): return -x
+test_eq(type(mk_transform(neg)), Transform)
+test_eq(type(mk_transform(math.sqrt)), Transform)
+test_eq(type(mk_transform(lambda a:a*2)), Transform)
+test_eq(type(mk_transform(Pipeline([neg]))), Pipeline)
+
+
+
+

Methods

+
+
#TODO: method examples
+
+
+

source

+
+
+

Pipeline.__call__

+
+
 Pipeline.__call__ (o)
+
+

Call self as a function.

+
+

source

+
+
+

Pipeline.decode

+
+
 Pipeline.decode (o, full=True)
+
+
+

source

+
+
+

Pipeline.setup

+
+
 Pipeline.setup (items=None, train_setup=False)
+
+

During the setup, the Pipeline starts with no transform and adds them one at a time, so that during its setup, each transform gets the items processed up to its point and not after.

+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/transform.html.md b/transform.html.md new file mode 100644 index 00000000..be2df566 --- /dev/null +++ b/transform.html.md @@ -0,0 +1,1009 @@ +# Transforms + + + + +``` python +from __future__ import annotations +from nbdev.showdoc import * +from fastcore.test import * +from fastcore.nb_imports import * +``` + +The classes here provide functionality for creating a composition of +*partially reversible functions*. By “partially reversible” we mean that +a transform can be `decode`d, creating a form suitable for display. This +is not necessarily identical to the original form (e.g. a transform that +changes a byte tensor to a float tensor does not recreate a byte tensor +when decoded, since that may lose precision, and a float tensor can be +displayed already). + +Classes are also provided and for composing transforms, and mapping them +over collections. +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) is a +transform which composes several +[`Transform`](https://fastcore.fast.ai/transform.html#transform), +knowing how to decode them or show an encoded item. + +------------------------------------------------------------------------ + +source + +### Transform + +> Transform (enc=None, dec=None, split_idx=None, order=None) + +*Delegates (`__call__`,`decode`,`setup`) to +(encodes,decodes,setups) if +`split_idx` matches* + +A [`Transform`](https://fastcore.fast.ai/transform.html#transform) is +the main building block of the fastai data pipelines. In the most +general terms a transform can be any function you want to apply to your +data, however the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) class +provides several mechanisms that make the process of building them easy +and flexible. + +### The main [`Transform`](https://fastcore.fast.ai/transform.html#transform) features: + +- **Type dispatch** - Type annotations are used to determine if a + transform should be applied to the given argument. It also gives an + option to provide several implementations and it choses the one to run + based on the type. This is useful for example when running both + independent and dependent variables through the pipeline where some + transforms only make sense for one and not the other. Another usecase + is designing a transform that handles different data formats. Note + that if a transform takes multiple arguments only the type of the + first one is used for dispatch. +- **Handling of tuples** - When a tuple (or a subclass of tuple) of data + is passed to a transform it will get applied to each element + separately. You can opt out of this behavior by passing a list or an + [`L`](https://fastcore.fast.ai/foundation.html#l), as only tuples gets + this specific behavior. An alternative is to use + [`ItemTransform`](https://fastcore.fast.ai/transform.html#itemtransform) + defined below, which will always take the input as a whole. +- **Reversability** - A transform can be made reversible by implementing + the decodes method. This is mainly used to turn something + like a category which is encoded as a number back into a label + understandable by humans for showing purposes. Like the regular call + method, the `decode` method that is used to decode will be applied + over each element of a tuple separately. +- **Type propagation** - Whenever possible a transform tries to return + data of the same type it received. Mainly used to maintain semantics + of things like `ArrayImage` which is a thin wrapper of pytorch’s + `Tensor`. You can opt out of this behavior by adding `->None` return + type annotation. +- **Preprocessing** - The `setup` method can be used to perform any + one-time calculations to be later used by the transform, for example + generating a vocabulary to encode categorical data. +- **Filtering based on the dataset type** - By setting the `split_idx` + flag you can make the transform be used only in a specific + `DataSource` subset like in training, but not validation. +- **Ordering** - You can set the `order` attribute which the + [`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) uses + when it needs to merge two lists of transforms. +- **Appending new behavior with decorators** - You can easily extend an + existing + [`Transform`](https://fastcore.fast.ai/transform.html#transform) by + creating encodes or decodes methods for new + data types. You can put those new methods outside the original + transform definition and decorate them with the class you wish them + patched into. This can be used by the fastai library users to add + their own behavior, or multiple modules contributing to the same + transform. + +### Defining a [`Transform`](https://fastcore.fast.ai/transform.html#transform) + +There are a few ways to create a transform with different ratios of +simplicity to flexibility. - **Extending the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) +class** - Use inheritence to implement the methods you want. - **Passing +methods to the constructor** - Instantiate the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) class +and pass your functions as `enc` and `dec` arguments. - **@Transform +decorator** - Turn any function into a +[`Transform`](https://fastcore.fast.ai/transform.html#transform) by just +adding a decorator - very straightforward if all you need is a single +encodes implementation. - **Passing a function to fastai +APIs** - Same as above, but when passing a function to other transform +aware classes like +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) or +`TfmdDS` you don’t even need a decorator. Your function will get +converted to a +[`Transform`](https://fastcore.fast.ai/transform.html#transform) +automatically. + +A simple way to create a +[`Transform`](https://fastcore.fast.ai/transform.html#transform) is to +pass a function to the constructor. In the below example, we pass an +anonymous function that does integer division by 2: + +``` python +f = Transform(lambda o:o//2) +``` + +If you call this transform, it will apply the transformation: + +``` python +test_eq_type(f(2), 1) +``` + +Another way to define a Transform is to extend the +[`Transform`](https://fastcore.fast.ai/transform.html#transform) class: + +``` python +class A(Transform): pass +``` + +However, to enable your transform to do something, you have to define an +encodes method. Note that we can use the class name as a +decorator to add this method to the original class. + +``` python +@A +def encodes(self, x): return x+1 + +f1 = A() +test_eq(f1(1), 2) # f1(1) is the same as f1.encode(1) +``` + +In addition to adding an encodes method, we can also add a +decodes method. This enables you to call the `decode` +method (without an s). For more information about the purpose of +decodes, see the discussion about Reversibility in [the +above section](#The-main-Transform-features). + +Just like with encodes, you can add a decodes method to the +original class by using the class name as a decorator: + +``` python +class B(A): pass + +@B +def decodes(self, x): return x-1 + +f2 = B() +test_eq(f2.decode(2), 1) + +test_eq(f2(1), 2) # uses A's encode method from the parent class +``` + +If you do not define an encodes or decodes +method the original value will be returned: + +``` python +class _Tst(Transform): pass + +f3 = _Tst() # no encodes or decodes method have been defined +test_eq_type(f3.decode(2.0), 2.0) +test_eq_type(f3(2), 2) +``` + +Transforms can be created from class methods too: + +``` python +class A: + @classmethod + def create(cls, x:int): return x+1 +test_eq(Transform(A.create)(1), 2) +``` + +#### Defining Transforms With A Decorator + +[`Transform`](https://fastcore.fast.ai/transform.html#transform) can be +used as a decorator to turn a function into a +[`Transform`](https://fastcore.fast.ai/transform.html#transform). + +``` python +@Transform +def f(x): return x//2 +test_eq_type(f(2), 1) +test_eq_type(f.decode(2.0), 2.0) + +@Transform +def f(x): return x*2 +test_eq_type(f(2), 4) +test_eq_type(f.decode(2.0), 2.0) +``` + +#### Typed Dispatch and Transforms + +We can also apply different transformations depending on the type of the +input passed by using `TypedDispatch`. `TypedDispatch` automatically +works with +[`Transform`](https://fastcore.fast.ai/transform.html#transform) when +using type hints: + +``` python +class A(Transform): pass + +@A +def encodes(self, x:int): return x//2 + +@A +def encodes(self, x:float): return x+1 +``` + +When we pass in an `int`, this calls the first encodes method: + +``` python +f = A() +test_eq_type(f(3), 1) +``` + +When we pass in a `float`, this calls the second encodes method: + +``` python +test_eq_type(f(2.), 3.) +``` + +When we pass in a type that is not specified in encodes, +the original value is returned: + +``` python +test_eq(f('a'), 'a') +``` + +If the type annotation is a tuple, then any type in the tuple will +match: + +``` python +class MyClass(int): pass + +class A(Transform): + def encodes(self, x:MyClass|float): return x/2 + def encodes(self, x:str|list): return str(x)+'_1' + +f = A() +``` + +The below two examples match the first encodes, with a type of `MyClass` +and `float`, respectively: + +``` python +test_eq(f(MyClass(2)), 1.) # input is of type MyClass +test_eq(f(6.0), 3.0) # input is of type float +``` + +The next two examples match the second `encodes` method, with a type of +`str` and `list`, respectively: + +``` python +test_eq(f('a'), 'a_1') # input is of type str +test_eq(f(['a','b','c']), "['a', 'b', 'c']_1") # input is of type list +``` + +#### Casting Types With Transform + +Without any intervention it is easy for operations to change types in +Python. For example, `FloatSubclass` (defined below) becomes a `float` +after performing multiplication: + +``` python +class FloatSubclass(float): pass +test_eq_type(FloatSubclass(3.0) * 2, 6.0) +``` + +This behavior is often not desirable when performing transformations on +data. Therefore, +[`Transform`](https://fastcore.fast.ai/transform.html#transform) will +attempt to cast the output to be of the same type as the input by +default. In the below example, the output will be cast to a +`FloatSubclass` type to match the type of the input: + +``` python +@Transform +def f(x): return x*2 + +test_eq_type(f(FloatSubclass(3.0)), FloatSubclass(6.0)) +``` + +We can optionally turn off casting by annotating the transform function +with a return type of `None`: + +``` python +@Transform +def f(x)-> None: return x*2 # Same transform as above, but with a -> None annotation + +test_eq_type(f(FloatSubclass(3.0)), 6.0) # Casting is turned off because of -> None annotation +``` + +However, +[`Transform`](https://fastcore.fast.ai/transform.html#transform) will +only cast output back to the input type when the input is a subclass of +the output. In the below example, the input is of type `FloatSubclass` +which is not a subclass of the output which is of type `str`. Therefore, +the output doesn’t get cast back to `FloatSubclass` and stays as type +`str`: + +``` python +@Transform +def f(x): return str(x) + +test_eq_type(f(Float(2.)), '2.0') +``` + +Just like encodes, the decodes method will +cast outputs to match the input type in the same way. In the below +example, the output of decodes remains of type +`MySubclass`: + +``` python +class MySubclass(int): pass + +def enc(x): return MySubclass(x+1) +def dec(x): return x-1 + + +f = Transform(enc,dec) +t = f(1) # t is of type MySubclass +test_eq_type(f.decode(t), MySubclass(1)) # the output of decode is cast to MySubclass to match the input type. +``` + +#### Apply Transforms On Subsets With `split_idx` + +You can apply transformations to subsets of data by specifying a +`split_idx` property. If a transform has a `split_idx` then it’s only +applied if the `split_idx` param matches. In the below example, we set +`split_idx` equal to `1`: + +``` python +def enc(x): return x+1 +def dec(x): return x-1 +f = Transform(enc,dec) +f.split_idx = 1 +``` + +The transformations are applied when a matching `split_idx` parameter is +passed: + +``` python +test_eq(f(1, split_idx=1),2) +test_eq(f.decode(2, split_idx=1),1) +``` + +On the other hand, transformations are ignored when the `split_idx` +parameter does not match: + +``` python +test_eq(f(1, split_idx=0), 1) +test_eq(f.decode(2, split_idx=0), 2) +``` + +#### Transforms on Lists + +Transform operates on lists as a whole, **not element-wise**: + +``` python +class A(Transform): + def encodes(self, x): return dict(x) + def decodes(self, x): return list(x.items()) + +f = A() +_inp = [(1,2), (3,4)] +t = f(_inp) + +test_eq(t, dict(_inp)) +test_eq(f.decodes(t), _inp) +``` + +If you want a transform to operate on a list elementwise, you must +implement this appropriately in the encodes and +decodes methods: + +``` python +class AL(Transform): pass + +@AL +def encodes(self, x): return [x_+1 for x_ in x] + +@AL +def decodes(self, x): return [x_-1 for x_ in x] + +f = AL() +t = f([1,2]) + +test_eq(t, [2,3]) +test_eq(f.decode(t), [1,2]) +``` + +#### Transforms on Tuples + +Unlike lists, +[`Transform`](https://fastcore.fast.ai/transform.html#transform) +operates on tuples element-wise. + +``` python +def neg_int(x): return -x +f = Transform(neg_int) + +test_eq(f((1,2,3)), (-1,-2,-3)) +``` + +Transforms will also apply `TypedDispatch` element-wise on tuples when +an input type annotation is specified. In the below example, the values +`1.0` and `3.0` are ignored because they are of type `float`, not `int`: + +``` python +def neg_int(x:int): return -x +f = Transform(neg_int) + +test_eq(f((1.0, 2, 3.0)), (1.0, -2, 3.0)) +``` + +Another example of how +[`Transform`](https://fastcore.fast.ai/transform.html#transform) can use +`TypedDispatch` with tuples is shown below: + +``` python +class B(Transform): pass + +@B +def encodes(self, x:int): return x+1 + +@B +def encodes(self, x:str): return x+'hello' + +@B +def encodes(self, x): return str(x)+'!' +``` + +If the input is not an `int` or `str`, the third `encodes` method will +apply: + +``` python +b = B() +test_eq(b([1]), '[1]!') +test_eq(b([1.0]), '[1.0]!') +``` + +However, if the input is a tuple, then the appropriate method will apply +according to the type of each element in the tuple: + +``` python +test_eq(b(('1',)), ('1hello',)) +test_eq(b((1,2)), (2,3)) +test_eq(b(('a',1.0)), ('ahello','1.0!')) +``` + +Dispatching over tuples works recursively, by the way: + +``` python +class B(Transform): + def encodes(self, x:int): return x+1 + def encodes(self, x:str): return x+'_hello' + def decodes(self, x:int): return x-1 + def decodes(self, x:str): return x.replace('_hello', '') + +f = B() +start = (1.,(2,'3')) +t = f(start) +test_eq_type(t, (1.,(3,'3_hello'))) +test_eq(f.decode(t), start) +``` + +Dispatching also works with `typing` module type classes, like +`numbers.integral`: + +``` python +@Transform +def f(x:numbers.Integral): return x+1 + +t = f((1,'1',1)) +test_eq(t, (2, '1', 2)) +``` + +------------------------------------------------------------------------ + +source + +### InplaceTransform + +> InplaceTransform (enc=None, dec=None, split_idx=None, order=None) + +*A [`Transform`](https://fastcore.fast.ai/transform.html#transform) that +modifies in-place and just returns whatever it’s passed* + +``` python +class A(InplaceTransform): pass + +@A +def encodes(self, x:pd.Series): x.fillna(10, inplace=True) + +f = A() + +test_eq_type(f(pd.Series([1,2,None])),pd.Series([1,2,10],dtype=np.float64)) #fillna fills with floats. +``` + +------------------------------------------------------------------------ + +source + +### DisplayedTransform + +> DisplayedTransform (enc=None, dec=None, split_idx=None, order=None) + +*A transform with a `__repr__` that shows its attrs* + +Transforms normally are represented by just their class name and a list +of encodes and decodes implementations: + +``` python +class A(Transform): encodes,decodes = noop,noop +f = A() +f +``` + + A: + encodes: (object,object) -> noop + decodes: (object,object) -> noop + +A +[`DisplayedTransform`](https://fastcore.fast.ai/transform.html#displayedtransform) +will in addition show the contents of all attributes listed in the +comma-delimited string `self.store_attrs`: + +``` python +class A(DisplayedTransform): + encodes = noop + def __init__(self, a, b=2): + super().__init__() + store_attr() + +A(a=1,b=2) +``` + + A -- {'a': 1, 'b': 2}: + encodes: (object,object) -> noop + decodes: + +------------------------------------------------------------------------ + +source + +### ItemTransform + +> ItemTransform (enc=None, dec=None, split_idx=None, order=None) + +*A transform that always take tuples as items* + +[`ItemTransform`](https://fastcore.fast.ai/transform.html#itemtransform) +is the class to use to opt out of the default behavior of +[`Transform`](https://fastcore.fast.ai/transform.html#transform). + +``` python +class AIT(ItemTransform): + def encodes(self, xy): x,y=xy; return (x+y,y) + def decodes(self, xy): x,y=xy; return (x-y,y) + +f = AIT() +test_eq(f((1,2)), (3,2)) +test_eq(f.decode((3,2)), (1,2)) +``` + +If you pass a special tuple subclass, the usual retain type behavior of +[`Transform`](https://fastcore.fast.ai/transform.html#transform) will +keep it: + +``` python +class _T(tuple): pass +x = _T((1,2)) +test_eq_type(f(x), _T((3,2))) +``` + +------------------------------------------------------------------------ + +source + +### get_func + +> get_func (t, name, *args, **kwargs) + +*Get the `t.name` (potentially partial-ized with `args` and `kwargs`) or +`noop` if not defined* + +This works for any kind of `t` supporting `getattr`, so a class or a +module. + +``` python +test_eq(get_func(operator, 'neg', 2)(), -2) +test_eq(get_func(operator.neg, '__call__')(2), -2) +test_eq(get_func(list, 'foobar')([2]), [2]) +a = [2,1] +get_func(list, 'sort')(a) +test_eq(a, [1,2]) +``` + +Transforms are built with multiple-dispatch: a given function can have +several methods depending on the type of the object received. This is +done directly with the +[`TypeDispatch`](https://fastcore.fast.ai/dispatch.html#typedispatch) +module and type-annotation in +[`Transform`](https://fastcore.fast.ai/transform.html#transform), but +you can also use the following class. + +------------------------------------------------------------------------ + +source + +### Func + +> Func (name, *args, **kwargs) + +*Basic wrapper around a `name` with `args` and `kwargs` to call on a +given type* + +You can call the [`Func`](https://fastcore.fast.ai/transform.html#func) +object on any module name or type, even a list of types. It will return +the corresponding function (with a default to `noop` if nothing is +found) or list of functions. + +``` python +test_eq(Func('sqrt')(math), math.sqrt) +``` + +------------------------------------------------------------------------ + +### Sig + +> Sig (*args, **kwargs) + +`Sig` is just sugar-syntax to create a +[`Func`](https://fastcore.fast.ai/transform.html#func) object more +easily with the syntax `Sig.name(*args, **kwargs)`. + +``` python +f = Sig.sqrt() +test_eq(f(math), math.sqrt) +``` + +------------------------------------------------------------------------ + +source + +### compose_tfms + +> compose_tfms (x, tfms, is_enc=True, reverse=False, **kwargs) + +*Apply all `func_nm` attribute of `tfms` on `x`, maybe in `reverse` +order* + +``` python +def to_int (x): return Int(x) +def to_float(x): return Float(x) +def double (x): return x*2 +def half(x)->None: return x/2 +``` + +``` python +def test_compose(a, b, *fs): test_eq_type(compose_tfms(a, tfms=map(Transform,fs)), b) + +test_compose(1, Int(1), to_int) +test_compose(1, Float(1), to_int,to_float) +test_compose(1, Float(2), to_int,to_float,double) +test_compose(2.0, 2.0, to_int,double,half) +``` + +``` python +class A(Transform): + def encodes(self, x:float): return Float(x+1) + def decodes(self, x): return x-1 + +tfms = [A(), Transform(math.sqrt)] +t = compose_tfms(3., tfms=tfms) +test_eq_type(t, Float(2.)) +test_eq(compose_tfms(t, tfms=tfms, is_enc=False), 1.) +test_eq(compose_tfms(4., tfms=tfms, reverse=True), 3.) +``` + +``` python +tfms = [A(), Transform(math.sqrt)] +test_eq(compose_tfms((9,3.), tfms=tfms), (3,2.)) +``` + +------------------------------------------------------------------------ + +source + +### mk_transform + +> mk_transform (f) + +*Convert function `f` to +[`Transform`](https://fastcore.fast.ai/transform.html#transform) if it +isn’t already one* + +------------------------------------------------------------------------ + +source + +### gather_attrs + +> gather_attrs (o, k, nm) + +*Used in **getattr** to collect all attrs `k` from `self.{nm}`* + +------------------------------------------------------------------------ + +source + +### gather_attr_names + +> gather_attr_names (o, nm) + +*Used in **dir** to collect all attrs `k` from `self.{nm}`* + +------------------------------------------------------------------------ + +source + +### Pipeline + +> Pipeline (funcs=None, split_idx=None) + +*A pipeline of composed (for encode/decode) transforms, setup with +types* + +``` python +add_docs(Pipeline, + __call__="Compose `__call__` of all `fs` on `o`", + decode="Compose `decode` of all `fs` on `o`", + show="Show `o`, a single item from a tuple, decoding as needed", + add="Add transforms `ts`", + setup="Call each tfm's `setup` in order") +``` + +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) is a +wrapper for +[`compose_tfms`](https://fastcore.fast.ai/transform.html#compose_tfms). +You can pass instances of +[`Transform`](https://fastcore.fast.ai/transform.html#transform) or +regular functions in `funcs`, the +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) will wrap +them all in +[`Transform`](https://fastcore.fast.ai/transform.html#transform) (and +instantiate them if needed) during the initialization. It handles the +transform `setup` by adding them one at a time and calling setup on +each, goes through them in order in `__call__` or `decode` and can +`show` an object by applying decoding the transforms up until the point +it gets an object that knows how to show itself. + +``` python +# Empty pipeline is noop +pipe = Pipeline() +test_eq(pipe(1), 1) +test_eq(pipe((1,)), (1,)) +# Check pickle works +assert pickle.loads(pickle.dumps(pipe)) +``` + +``` python +class IntFloatTfm(Transform): + def encodes(self, x): return Int(x) + def decodes(self, x): return Float(x) + foo=1 + +int_tfm=IntFloatTfm() + +def neg(x): return -x +neg_tfm = Transform(neg, neg) +``` + +``` python +pipe = Pipeline([neg_tfm, int_tfm]) + +start = 2.0 +t = pipe(start) +test_eq_type(t, Int(-2)) +test_eq_type(pipe.decode(t), Float(start)) +test_stdout(lambda:pipe.show(t), '-2') +``` + +``` python +pipe = Pipeline([neg_tfm, int_tfm]) +t = pipe(start) +test_stdout(lambda:pipe.show(pipe((1.,2.))), '-1\n-2') +test_eq(pipe.foo, 1) +assert 'foo' in dir(pipe) +assert 'int_float_tfm' in dir(pipe) +``` + +You can add a single transform or multiple transforms `ts` using +[`Pipeline.add`](https://fastcore.fast.ai/transform.html#pipeline.add). +Transforms will be ordered by `Transform.order`. + +``` python +pipe = Pipeline([neg_tfm, int_tfm]) +class SqrtTfm(Transform): + order=-1 + def encodes(self, x): + return x**(.5) + def decodes(self, x): return x**2 +pipe.add(SqrtTfm()) +test_eq(pipe(4),-2) +test_eq(pipe.decode(-2),4) +pipe.add([SqrtTfm(),SqrtTfm()]) +test_eq(pipe(256),-2) +test_eq(pipe.decode(-2),256) +``` + +Transforms are available as attributes named with the snake_case version +of the names of their types. Attributes in transforms can be directly +accessed as attributes of the pipeline. + +``` python +test_eq(pipe.int_float_tfm, int_tfm) +test_eq(pipe.foo, 1) + +pipe = Pipeline([int_tfm, int_tfm]) +pipe.int_float_tfm +test_eq(pipe.int_float_tfm[0], int_tfm) +test_eq(pipe.foo, [1,1]) +``` + +``` python +# Check opposite order +pipe = Pipeline([int_tfm,neg_tfm]) +t = pipe(start) +test_eq(t, -2) +test_stdout(lambda:pipe.show(t), '-2') +``` + +``` python +class A(Transform): + def encodes(self, x): return int(x) + def decodes(self, x): return Float(x) + +pipe = Pipeline([neg_tfm, A]) +t = pipe(start) +test_eq_type(t, -2) +test_eq_type(pipe.decode(t), Float(start)) +test_stdout(lambda:pipe.show(t), '-2.0') +``` + +``` python +s2 = (1,2) +pipe = Pipeline([neg_tfm, A]) +t = pipe(s2) +test_eq_type(t, (-1,-2)) +test_eq_type(pipe.decode(t), (Float(1.),Float(2.))) +test_stdout(lambda:pipe.show(t), '-1.0\n-2.0') +``` + +``` python +from PIL import Image +``` + +``` python +class ArrayImage(ndarray): + _show_args = {'cmap':'viridis'} + def __new__(cls, x, *args, **kwargs): + if isinstance(x,tuple): super().__new__(cls, x, *args, **kwargs) + if args or kwargs: raise RuntimeError('Unknown array init args') + if not isinstance(x,ndarray): x = array(x) + return x.view(cls) + + def show(self, ctx=None, figsize=None, **kwargs): + if ctx is None: _,ctx = plt.subplots(figsize=figsize) + ctx.imshow(im, **{**self._show_args, **kwargs}) + ctx.axis('off') + return ctx + +im = Image.open(TEST_IMAGE) +im_t = ArrayImage(im) +``` + +``` python +def f1(x:ArrayImage): return -x +def f2(x): return Image.open(x).resize((128,128)) +def f3(x:Image.Image): return(ArrayImage(array(x))) +``` + +``` python +pipe = Pipeline([f2,f3,f1]) +t = pipe(TEST_IMAGE) +test_eq(type(t), ArrayImage) +test_eq(t, -array(f3(f2(TEST_IMAGE)))) +``` + +``` python +pipe = Pipeline([f2,f3]) +t = pipe(TEST_IMAGE) +ax = pipe.show(t) +``` + +![](05_transform_files/figure-commonmark/cell-73-output-1.png) + +``` python +#test_fig_exists(ax) +``` + +``` python +#Check filtering is properly applied +add1 = B() +add1.split_idx = 1 +pipe = Pipeline([neg_tfm, A(), add1]) +test_eq(pipe(start), -2) +pipe.split_idx=1 +test_eq(pipe(start), -1) +pipe.split_idx=0 +test_eq(pipe(start), -2) +for t in [None, 0, 1]: + pipe.split_idx=t + test_eq(pipe.decode(pipe(start)), start) + test_stdout(lambda: pipe.show(pipe(start)), "-2.0") +``` + +``` python +def neg(x): return -x +test_eq(type(mk_transform(neg)), Transform) +test_eq(type(mk_transform(math.sqrt)), Transform) +test_eq(type(mk_transform(lambda a:a*2)), Transform) +test_eq(type(mk_transform(Pipeline([neg]))), Pipeline) +``` + +### Methods + +``` python +#TODO: method examples +``` + +------------------------------------------------------------------------ + +source + +### Pipeline.\_\_call\_\_ + +> Pipeline.__call__ (o) + +*Call self as a function.* + +------------------------------------------------------------------------ + +source + +### Pipeline.decode + +> Pipeline.decode (o, full=True) + +------------------------------------------------------------------------ + +source + +### Pipeline.setup + +> Pipeline.setup (items=None, train_setup=False) + +During the setup, the +[`Pipeline`](https://fastcore.fast.ai/transform.html#pipeline) starts +with no transform and adds them one at a time, so that during its setup, +each transform gets the items processed up to its point and not after. diff --git a/xdg.html b/xdg.html new file mode 100644 index 00000000..347c7f0a --- /dev/null +++ b/xdg.html @@ -0,0 +1,867 @@ + + + + + + + + + + +XDG – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

XDG

+
+ +
+
+ XDG Base Directory Specification helpers. +
+
+ + +
+ + + + +
+ + + +
+ + + +

See the XDG Base Directory Specification for more information.

+
+

Overview

+

xdg_cache_home, xdg_config_home, xdg_data_home, and xdg_state_home return pathlib.Path objects containing the value of the environment variable named XDG_CACHE_HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME respectively, or the default defined in the specification if the environment variable is unset, empty, or contains a relative path rather than absolute path.

+

xdg_config_dirs and xdg_data_dirs return a list of pathlib.Path objects containing the value, split on colons, of the environment variable named XDG_CONFIG_DIRS and XDG_DATA_DIRS respectively, or the default defined in the specification if the environment variable is unset or empty. Relative paths are ignored, as per the specification.

+

xdg_runtime_dir returns a pathlib.Path object containing the value of the XDG_RUNTIME_DIR environment variable, or None if the environment variable is not set, or contains a relative path rather than absolute path.

+
+
+

Helpers

+

We’ll start by defining a context manager that temporarily sets an environment variable to demonstrate the behaviour of each helper function:

+
+
from contextlib import contextmanager
+
+
+
@contextmanager
+def env(variable, value):
+    old = os.environ.get(variable, None)
+    try:
+        os.environ[variable] = value
+        yield
+    finally:
+        if old is None: del os.environ[variable]
+        else: os.environ[variable] = old
+
+
+

source

+
+

xdg_cache_home

+
+
 xdg_cache_home ()
+
+

Path corresponding to XDG_CACHE_HOME

+
+
from fastcore.test import *
+
+
+
test_eq(xdg_cache_home(), Path.home()/'.cache')
+with env('XDG_CACHE_HOME', '/home/fastai/.cache'):
+    test_eq(xdg_cache_home(), Path('/home/fastai/.cache'))
+
+
+

source

+
+
+

xdg_config_dirs

+
+
 xdg_config_dirs ()
+
+

Paths corresponding to XDG_CONFIG_DIRS

+
+
test_eq(xdg_config_dirs(), [Path('/etc/xdg')])
+with env('XDG_CONFIG_DIRS', '/home/fastai/.xdg:/home/fastai/.config'):
+    test_eq(xdg_config_dirs(), [Path('/home/fastai/.xdg'), Path('/home/fastai/.config')])
+
+
+

source

+
+
+

xdg_config_home

+
+
 xdg_config_home ()
+
+

Path corresponding to XDG_CONFIG_HOME

+
+
test_eq(xdg_config_home(), Path.home()/'.config')
+with env('XDG_CONFIG_HOME', '/home/fastai/.config'):
+    test_eq(xdg_config_home(), Path('/home/fastai/.config'))
+
+
+

source

+
+
+

xdg_data_dirs

+
+
 xdg_data_dirs ()
+
+

Paths corresponding to XDG_DATA_DIRS`

+
+

source

+
+
+

xdg_data_home

+
+
 xdg_data_home ()
+
+

Path corresponding to XDG_DATA_HOME

+
+
test_eq(xdg_data_home(), Path.home()/'.local/share')
+with env('XDG_DATA_HOME', '/home/fastai/.data'):
+    test_eq(xdg_data_home(), Path('/home/fastai/.data'))
+
+
+

source

+
+
+

xdg_runtime_dir

+
+
 xdg_runtime_dir ()
+
+

Path corresponding to XDG_RUNTIME_DIR

+
+

source

+
+
+

xdg_state_home

+
+
 xdg_state_home ()
+
+

Path corresponding to XDG_STATE_HOME

+
+
test_eq(xdg_state_home(), Path.home()/'.local/state')
+with env('XDG_STATE_HOME', '/home/fastai/.state'):
+    test_eq(xdg_state_home(), Path('/home/fastai/.state'))
+
+
+

Copyright © 2016-2021 Scott Stevenson

+

Modifications copyright © 2022 onwards Jeremy Howard

+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/xdg.html.md b/xdg.html.md new file mode 100644 index 00000000..d21d25c3 --- /dev/null +++ b/xdg.html.md @@ -0,0 +1,180 @@ +# XDG + + + + +See the [XDG Base Directory +Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) +for more information. + +## Overview + +[`xdg_cache_home`](https://fastcore.fast.ai/xdg.html#xdg_cache_home), +[`xdg_config_home`](https://fastcore.fast.ai/xdg.html#xdg_config_home), +[`xdg_data_home`](https://fastcore.fast.ai/xdg.html#xdg_data_home), and +[`xdg_state_home`](https://fastcore.fast.ai/xdg.html#xdg_state_home) +return `pathlib.Path` objects containing the value of the environment +variable named `XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, and +`XDG_STATE_HOME` respectively, or the default defined in the +specification if the environment variable is unset, empty, or contains a +relative path rather than absolute path. + +[`xdg_config_dirs`](https://fastcore.fast.ai/xdg.html#xdg_config_dirs) +and [`xdg_data_dirs`](https://fastcore.fast.ai/xdg.html#xdg_data_dirs) +return a list of `pathlib.Path` objects containing the value, split on +colons, of the environment variable named `XDG_CONFIG_DIRS` and +`XDG_DATA_DIRS` respectively, or the default defined in the +specification if the environment variable is unset or empty. Relative +paths are ignored, as per the specification. + +[`xdg_runtime_dir`](https://fastcore.fast.ai/xdg.html#xdg_runtime_dir) +returns a `pathlib.Path` object containing the value of the +`XDG_RUNTIME_DIR` environment variable, or `None` if the environment +variable is not set, or contains a relative path rather than absolute +path. + +## Helpers + +We’ll start by defining a context manager that temporarily sets an +environment variable to demonstrate the behaviour of each helper +function: + +``` python +from contextlib import contextmanager +``` + +``` python +@contextmanager +def env(variable, value): + old = os.environ.get(variable, None) + try: + os.environ[variable] = value + yield + finally: + if old is None: del os.environ[variable] + else: os.environ[variable] = old +``` + +------------------------------------------------------------------------ + +source + +### xdg_cache_home + +> xdg_cache_home () + +*Path corresponding to `XDG_CACHE_HOME`* + +``` python +from fastcore.test import * +``` + +``` python +test_eq(xdg_cache_home(), Path.home()/'.cache') +with env('XDG_CACHE_HOME', '/home/fastai/.cache'): + test_eq(xdg_cache_home(), Path('/home/fastai/.cache')) +``` + +------------------------------------------------------------------------ + +source + +### xdg_config_dirs + +> xdg_config_dirs () + +*Paths corresponding to `XDG_CONFIG_DIRS`* + +``` python +test_eq(xdg_config_dirs(), [Path('/etc/xdg')]) +with env('XDG_CONFIG_DIRS', '/home/fastai/.xdg:/home/fastai/.config'): + test_eq(xdg_config_dirs(), [Path('/home/fastai/.xdg'), Path('/home/fastai/.config')]) +``` + +------------------------------------------------------------------------ + +source + +### xdg_config_home + +> xdg_config_home () + +*Path corresponding to `XDG_CONFIG_HOME`* + +``` python +test_eq(xdg_config_home(), Path.home()/'.config') +with env('XDG_CONFIG_HOME', '/home/fastai/.config'): + test_eq(xdg_config_home(), Path('/home/fastai/.config')) +``` + +------------------------------------------------------------------------ + +source + +### xdg_data_dirs + +> xdg_data_dirs () + +*Paths corresponding to XDG_DATA_DIRS\`* + +------------------------------------------------------------------------ + +source + +### xdg_data_home + +> xdg_data_home () + +*Path corresponding to `XDG_DATA_HOME`* + +``` python +test_eq(xdg_data_home(), Path.home()/'.local/share') +with env('XDG_DATA_HOME', '/home/fastai/.data'): + test_eq(xdg_data_home(), Path('/home/fastai/.data')) +``` + +------------------------------------------------------------------------ + +source + +### xdg_runtime_dir + +> xdg_runtime_dir () + +*Path corresponding to `XDG_RUNTIME_DIR`* + +------------------------------------------------------------------------ + +source + +### xdg_state_home + +> xdg_state_home () + +*Path corresponding to `XDG_STATE_HOME`* + +``` python +test_eq(xdg_state_home(), Path.home()/'.local/state') +with env('XDG_STATE_HOME', '/home/fastai/.state'): + test_eq(xdg_state_home(), Path('/home/fastai/.state')) +``` + +------------------------------------------------------------------------ + +Copyright © 2016-2021 Scott Stevenson + +Modifications copyright © 2022 onwards Jeremy Howard diff --git a/xml.html b/xml.html new file mode 100644 index 00000000..f076c0dd --- /dev/null +++ b/xml.html @@ -0,0 +1,986 @@ + + + + + + + + + + +XML – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

XML

+
+ +
+
+ Concise generation of XML. +
+
+ + +
+ + + + +
+ + + +
+ + + +
+
from IPython.display import Markdown
+from pprint import pprint
+
+from fastcore.test import test_eq
+
+
+

FT functions

+
+

source

+
+

attrmap

+
+
 attrmap (o)
+
+
+

source

+
+
+

valmap

+
+
 valmap (o)
+
+
+

source

+
+
+

FT

+
+
 FT (tag:str, cs:tuple, attrs:dict=None, void_=False, **kwargs)
+
+

A ‘Fast Tag’ structure, containing tag,children,and attrs

+
+

source

+
+
+

ft

+
+
 ft (tag:str, *c, void_:bool=False, attrmap:<built-
+     infunctioncallable>=<function attrmap>, valmap:<built-
+     infunctioncallable>=<function valmap>, ft_cls=<class '__main__.FT'>,
+     **kw)
+
+

Create an FT structure for to_xml()

+

The main HTML tags are exported as ft partials.

+

Attributes are passed as keywords. Use ‘klass’ and ‘fr’ instead of ‘class’ and ‘for’, to avoid Python reserved word clashes.

+
+

source

+
+
+

Html

+
+
 Html (*c, doctype=True, **kwargs)
+
+

An HTML tag, optionally preceeded by !DOCTYPE HTML

+
+
samp = Html(
+    Head(Title('Some page')),
+    Body(Div('Some text\nanother line', (Input(name="jph's"), Img(src="filename", data=1)),
+             cls=['myclass', 'another'],
+             style={'padding':1, 'margin':2}))
+)
+pprint(samp)
+
+
(!doctype((),{'html': True}),
+ html((head((title(('Some page',),{}),),{}), body((div(('Some text\nanother line', input((),{'name': "jph's"}), img((),{'src': 'filename', 'data': 1})),{'class': 'myclass another', 'style': 'padding:1; margin:2'}),),{})),{}))
+
+
+
+
elem = P('Some text', id="myid")
+print(elem.tag)
+print(elem.children)
+print(elem.attrs)
+
+
p
+('Some text',)
+{'id': 'myid'}
+
+
+

You can get and set attrs directly:

+
+
elem.id = 'newid'
+print(elem.id, elem.get('id'), elem.get('foo', 'missing'))
+elem
+
+
newid newid missing
+
+
+
p(('Some text',),{'id': 'newid'})
+
+
+
+

source

+
+
+

Safe

+

*str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

+

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.*

+
+
+
+

Conversion to XML/HTML

+
+

source

+
+

to_xml

+
+
 to_xml (elm, lvl=0, indent=True, do_escape=True)
+
+

Convert ft element tree into an XML string

+
+
h = to_xml(samp, do_escape=False)
+print(h)
+
+
<!doctype html>
+<html>
+  <head>
+    <title>Some page</title>
+  </head>
+  <body>
+    <div class="myclass another" style="padding:1; margin:2">
+Some text
+another line      <input name="jph's">
+<img src="filename" data="1">    </div>
+  </body>
+</html>
+
+
+
+
+
class PageTitle:
+    def __ft__(self): return H1("Hello")
+
+class HomePage:
+    def __ft__(self): return Div(PageTitle(), Div('hello'))
+
+h = to_xml(Div(HomePage()))
+expected_output = """<div>
+  <div>
+    <h1>Hello</h1>
+    <div>hello</div>
+  </div>
+</div>
+"""
+assert h == expected_output
+
+
+
print(h)
+
+
<div>
+  <div>
+    <h1>Hello</h1>
+    <div>hello</div>
+  </div>
+</div>
+
+
+
+
+
h = to_xml(samp, indent=False)
+print(h)
+
+
<!doctype html><html><head><title>Some page</title></head><body><div class="myclass another" style="padding:1; margin:2">Some text
+another line<input name="jph's"><img src="filename" data="1"></div></body></html>
+
+
+

Interoperability both directions with Django and Jinja using the html() protocol:

+
+
def _esc(s): return s.__html__() if hasattr(s, '__html__') else Safe(escape(s))
+
+r = Safe('<b>Hello from Django</b>')
+print(to_xml(Div(r)))
+print(_esc(Div(P('Hello from fastcore <3'))))
+
+
<div><b>Hello from Django</b></div>
+
+<div>
+  <p>Hello from fastcore &lt;3</p>
+</div>
+
+
+
+
+
+
+

Display

+
+

source

+
+

highlight

+
+
 highlight (s, lang='html')
+
+

Markdown to syntax-highlight s in language lang

+
+

source

+
+
+

showtags

+
+
 showtags (s)
+
+

You can also reorder the children to come after the attrs, if you use this alternative syntax for FT where the children are in a second pair of () (behind the scenes this is because FT implements __call__ to add children).

+
+
Body(klass='myclass')(
+    Div(style='padding:3px')(
+        'Some text 1<2',
+        I(spurious=True)('in italics'),
+        Input(name='me'),
+        Img(src="filename", data=1)
+    )
+)
+
+
<body class="myclass">
+  <div style="padding:3px">
+Some text 1&lt;2<i spurious>in italics</i>    <input name="me">
+<img src="filename" data="1">  </div>
+</body>
+
+
+
+

source

+
+
+

getattr

+
+
 __getattr__ (tag)
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/xml.html.md b/xml.html.md new file mode 100644 index 00000000..31dfdb18 --- /dev/null +++ b/xml.html.md @@ -0,0 +1,280 @@ +# XML + + + + +``` python +from IPython.display import Markdown +from pprint import pprint + +from fastcore.test import test_eq +``` + +## FT functions + +------------------------------------------------------------------------ + +source + +### attrmap + +> attrmap (o) + +------------------------------------------------------------------------ + +source + +### valmap + +> valmap (o) + +------------------------------------------------------------------------ + +source + +### FT + +> FT (tag:str, cs:tuple, attrs:dict=None, void_=False, **kwargs) + +*A ‘Fast Tag’ structure, containing `tag`,`children`,and `attrs`* + +------------------------------------------------------------------------ + +source + +### ft + +> ft (tag:str, *c, void_:bool=False, attrmap: infunctioncallable>=, valmap: infunctioncallable>=, ft_cls=, +> **kw) + +*Create an [`FT`](https://fastcore.fast.ai/xml.html#ft) structure for +`to_xml()`* + +The main HTML tags are exported as +[`ft`](https://fastcore.fast.ai/xml.html#ft) partials. + +Attributes are passed as keywords. Use ‘klass’ and ‘fr’ instead of +‘class’ and ‘for’, to avoid Python reserved word clashes. + +------------------------------------------------------------------------ + +source + +### Html + +> Html (*c, doctype=True, **kwargs) + +*An HTML tag, optionally preceeded by `!DOCTYPE HTML`* + +``` python +samp = Html( + Head(Title('Some page')), + Body(Div('Some text\nanother line', (Input(name="jph's"), Img(src="filename", data=1)), + cls=['myclass', 'another'], + style={'padding':1, 'margin':2})) +) +pprint(samp) +``` + + (!doctype((),{'html': True}), + html((head((title(('Some page',),{}),),{}), body((div(('Some text\nanother line', input((),{'name': "jph's"}), img((),{'src': 'filename', 'data': 1})),{'class': 'myclass another', 'style': 'padding:1; margin:2'}),),{})),{})) + +``` python +elem = P('Some text', id="myid") +print(elem.tag) +print(elem.children) +print(elem.attrs) +``` + + p + ('Some text',) + {'id': 'myid'} + +You can get and set attrs directly: + +``` python +elem.id = 'newid' +print(elem.id, elem.get('id'), elem.get('foo', 'missing')) +elem +``` + + newid newid missing + + p(('Some text',),{'id': 'newid'}) + +------------------------------------------------------------------------ + +source + +### Safe + +\*str(object=’’) -\> str str(bytes_or_buffer\[, encoding\[, errors\]\]) +-\> str + +Create a new string object from the given object. If encoding or errors +is specified, then the object must expose a data buffer that will be +decoded using the given encoding and error handler. Otherwise, returns +the result of object.\_\_str\_\_() (if defined) or repr(object). +encoding defaults to sys.getdefaultencoding(). errors defaults to +‘strict’.\* + +## Conversion to XML/HTML + +------------------------------------------------------------------------ + +source + +### to_xml + +> to_xml (elm, lvl=0, indent=True, do_escape=True) + +*Convert [`ft`](https://fastcore.fast.ai/xml.html#ft) element tree into +an XML string* + +``` python +h = to_xml(samp, do_escape=False) +print(h) +``` + + + + + Some page + + +
+ Some text + another line +
+ + + +``` python +class PageTitle: + def __ft__(self): return H1("Hello") + +class HomePage: + def __ft__(self): return Div(PageTitle(), Div('hello')) + +h = to_xml(Div(HomePage())) +expected_output = """
+
+

Hello

+
hello
+
+
+""" +assert h == expected_output +``` + +``` python +print(h) +``` + +
+
+

Hello

+
hello
+
+
+ +``` python +h = to_xml(samp, indent=False) +print(h) +``` + + Some page
Some text + another line
+ +Interoperability both directions with Django and Jinja using the +[**html**() +protocol](https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.escape): + +``` python +def _esc(s): return s.__html__() if hasattr(s, '__html__') else Safe(escape(s)) + +r = Safe('Hello from Django') +print(to_xml(Div(r))) +print(_esc(Div(P('Hello from fastcore <3')))) +``` + +
Hello from Django
+ +
+

Hello from fastcore <3

+
+ +## Display + +------------------------------------------------------------------------ + +source + +### highlight + +> highlight (s, lang='html') + +*Markdown to syntax-highlight `s` in language `lang`* + +------------------------------------------------------------------------ + +source + +### showtags + +> showtags (s) + +You can also reorder the children to come *after* the attrs, if you use +this alternative syntax for [`FT`](https://fastcore.fast.ai/xml.html#ft) +where the children are in a second pair of `()` (behind the scenes this +is because [`FT`](https://fastcore.fast.ai/xml.html#ft) implements +`__call__` to add children). + +``` python +Body(klass='myclass')( + Div(style='padding:3px')( + 'Some text 1<2', + I(spurious=True)('in italics'), + Input(name='me'), + Img(src="filename", data=1) + ) +) +``` + +``` html + +
+Some text 1<2in italics +
+ +``` + +------------------------------------------------------------------------ + +source + +### **getattr** + +> __getattr__ (tag) diff --git a/xtras.html b/xtras.html new file mode 100644 index 00000000..750dff30 --- /dev/null +++ b/xtras.html @@ -0,0 +1,2293 @@ + + + + + + + + + + +Utility functions – fastcore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Utility functions

+
+ +
+
+ Utility functions used in the fastai library +
+
+ + +
+ + + + +
+ + + +
+ + + +
+

File Functions

+

Utilities (other than extensions to Pathlib.Path) for dealing with IO.

+
+

source

+
+

walk

+
+
 walk (path:pathlib.Path|str, symlinks:bool=True, keep_file:<built-
+       infunctioncallable>=<function ret_true>, keep_folder:<built-
+       infunctioncallable>=<function ret_true>, skip_folder:<built-
+       infunctioncallable>=<function ret_false>, func:<built-
+       infunctioncallable>=<function join>, ret_folders:bool=False)
+
+

Generator version of os.walk, using functions to filter files and folders

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
pathpathlib.Path | strpath to start searching
symlinksboolTruefollow symlinks?
keep_filecallableret_truefunction that returns True for wanted files
keep_foldercallableret_truefunction that returns True for folders to enter
skip_foldercallableret_falsefunction that returns True for folders to skip
funccallablejoinfunction to apply to each matched file
ret_foldersboolFalsereturn folders, not just files
+
+

source

+
+
+

globtastic

+
+
 globtastic (path:pathlib.Path|str, recursive:bool=True,
+             symlinks:bool=True, file_glob:str=None, file_re:str=None,
+             folder_re:str=None, skip_file_glob:str=None,
+             skip_file_re:str=None, skip_folder_re:str=None, func:<built-
+             infunctioncallable>=<function join>, ret_folders:bool=False)
+
+

A more powerful glob, including regex matches, symlink handling, and skip parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
pathpathlib.Path | strpath to start searching
recursiveboolTruesearch subfolders
symlinksboolTruefollow symlinks?
file_globstrNoneOnly include files matching glob
file_restrNoneOnly include files matching regex
folder_restrNoneOnly enter folders matching regex
skip_file_globstrNoneSkip files matching glob
skip_file_restrNoneSkip files matching regex
skip_folder_restrNoneSkip folders matching regex,
funccallablejoinfunction to apply to each matched file
ret_foldersboolFalsereturn folders, not just files
ReturnsLPaths to matched files
+
+
globtastic('.', skip_folder_re='^[_.]', folder_re='core', file_glob='*.*py*', file_re='c')
+
+
(#5) ['./fastcore/docments.py','./fastcore/dispatch.py','./fastcore/basics.py','./fastcore/docscrape.py','./fastcore/script.py']
+
+
+
+

source

+
+
+

maybe_open

+
+
 maybe_open (f, mode='r', **kwargs)
+
+

Context manager: open f if it is a path (and close on exit)

+

This is useful for functions where you want to accept a path or file. maybe_open will not close your file handle if you pass one in.

+
+
def _f(fn):
+    with maybe_open(fn) as f: return f.encoding
+
+fname = '00_test.ipynb'
+sys_encoding = 'cp1252' if sys.platform == 'win32' else 'UTF-8'
+test_eq(_f(fname), sys_encoding)
+with open(fname) as fh: test_eq(_f(fh), sys_encoding)
+
+

For example, we can use this to reimplement imghdr.what from the Python standard library, which is written in Python 3.9 as:

+
+
from fastcore import imghdr
+
+
+
def what(file, h=None):
+    f = None
+    try:
+        if h is None:
+            if isinstance(file, (str,os.PathLike)):
+                f = open(file, 'rb')
+                h = f.read(32)
+            else:
+                location = file.tell()
+                h = file.read(32)
+                file.seek(location)
+        for tf in imghdr.tests:
+            res = tf(h, f)
+            if res: return res
+    finally:
+        if f: f.close()
+    return None
+
+

Here’s an example of the use of this function:

+
+
fname = 'images/puppy.jpg'
+what(fname)
+
+
'jpeg'
+
+
+

With maybe_open, Self, and L.map_first, we can rewrite this in a much more concise and (in our opinion) clear way:

+
+
def what(file, h=None):
+    if h is None:
+        with maybe_open(file, 'rb') as f: h = f.peek(32)
+    return L(imghdr.tests).map_first(Self(h,file))
+
+

…and we can check that it still works:

+
+
test_eq(what(fname), 'jpeg')
+
+

…along with the version passing a file handle:

+
+
with open(fname,'rb') as f: test_eq(what(f), 'jpeg')
+
+

…along with the h parameter version:

+
+
with open(fname,'rb') as f: test_eq(what(None, h=f.read(32)), 'jpeg')
+
+
+

source

+
+
+

mkdir

+
+
 mkdir (path, exist_ok=False, parents=False, overwrite=False, **kwargs)
+
+

Creates and returns a directory defined by path, optionally removing previous existing directory if overwrite is True

+
+
with tempfile.TemporaryDirectory() as d:
+    path = Path(os.path.join(d, 'new_dir'))
+    new_dir = mkdir(path)
+    assert new_dir.exists()
+    test_eq(new_dir, path)
+        
+    # test overwrite
+    with open(new_dir/'test.txt', 'w') as f: f.writelines('test')
+    test_eq(len(list(walk(new_dir))), 1) # assert file is present
+    new_dir = mkdir(new_dir, overwrite=True)
+    test_eq(len(list(walk(new_dir))), 0) # assert file was deleted
+
+
+

source

+
+
+

image_size

+
+
 image_size (fn)
+
+

Tuple of (w,h) for png, gif, or jpg; None otherwise

+
+
test_eq(image_size(fname), (1200,803))
+
+
+

source

+
+
+

bunzip

+
+
 bunzip (fn)
+
+

bunzip fn, raising exception if output already exists

+
+
f = Path('files/test.txt')
+if f.exists(): f.unlink()
+bunzip('files/test.txt.bz2')
+t = f.open().readlines()
+test_eq(len(t),1)
+test_eq(t[0], 'test\n')
+f.unlink()
+
+
+

source

+
+
+

loads

+
+
 loads (s, **kw)
+
+

Same as json.loads, but handles None

+
+

source

+
+
+

loads_multi

+
+
 loads_multi (s:str)
+
+

Generator of >=0 decoded json dicts, possibly with non-json ignored text at start and end

+
+
tst = """
+# ignored
+{ "a":1 }
+hello
+{
+"b":2
+}
+"""
+
+test_eq(list(loads_multi(tst)), [{'a': 1}, {'b': 2}])
+
+
+

source

+
+
+

dumps

+
+
 dumps (obj, **kw)
+
+

Same as json.dumps, but uses ujson if available

+
+

source

+
+
+

untar_dir

+
+
 untar_dir (fname, dest, rename=False, overwrite=False)
+
+

untar file into dest, creating a directory if the root contains more than one item

+
+
def test_untar(foldername, rename=False, **kwargs):
+    with tempfile.TemporaryDirectory() as d:
+        nm = os.path.join(d, 'a')
+        shutil.make_archive(nm, 'gztar', **kwargs)
+        with tempfile.TemporaryDirectory() as d2:
+            d2 = Path(d2)
+            untar_dir(nm+'.tar.gz', d2, rename=rename)
+            test_eq(d2.ls(), [d2/foldername])
+
+

If the contents of fname contain just one file or directory, it is placed directly in dest:

+
+
# using `base_dir` in `make_archive` results in `images` directory included in file names
+test_untar('images', base_dir='images')
+
+

If rename then the directory created is named based on the archive, without extension:

+
+
test_untar('a', base_dir='images', rename=True)
+
+

If the contents of fname contain multiple files and directories, a new folder in dest is created with the same name as fname (but without extension):

+
+
# using `root_dir` in `make_archive` results in `images` directory *not* included in file names
+test_untar('a', root_dir='images')
+
+
+

source

+
+
+

repo_details

+
+
 repo_details (url)
+
+

Tuple of owner,name from ssh or https git repo url

+
+
test_eq(repo_details('https://github.com/fastai/fastai.git'), ['fastai', 'fastai'])
+test_eq(repo_details('git@github.com:fastai/nbdev.git\n'), ['fastai', 'nbdev'])
+
+
+

source

+
+
+

run

+
+
 run (cmd, *rest, same_in_win=False, ignore_ex=False, as_bytes=False,
+      stderr=False)
+
+

Pass cmd (splitting with shlex if string) to subprocess.run; return stdout; raise IOError if fails

+

You can pass a string (which will be split based on standard shell rules), a list, or pass args directly:

+
+
run('echo', same_in_win=True)
+run('pip', '--version', same_in_win=True)
+run(['pip', '--version'], same_in_win=True)
+
+
'pip 23.3.1 from /Users/jhoward/miniconda3/lib/python3.11/site-packages/pip (python 3.11)'
+
+
+
+
if sys.platform == 'win32':
+    assert 'ipynb' in run('cmd /c dir /p')
+    assert 'ipynb' in run(['cmd', '/c', 'dir', '/p'])
+    assert 'ipynb' in run('cmd', '/c', 'dir',  '/p')
+else:
+    assert 'ipynb' in run('ls -ls')
+    assert 'ipynb' in run(['ls', '-l'])
+    assert 'ipynb' in run('ls', '-l')
+
+

Some commands fail in non-error situations, like grep. Use ignore_ex in those cases, which will return a tuple of stdout and returncode:

+
+
if sys.platform == 'win32':
+    test_eq(run('cmd /c findstr asdfds 00_test.ipynb', ignore_ex=True)[0], 1)
+else:
+    test_eq(run('grep asdfds 00_test.ipynb', ignore_ex=True)[0], 1)
+
+

run automatically decodes returned bytes to a str. Use as_bytes to skip that:

+
+
if sys.platform == 'win32':
+    test_eq(run('cmd /c echo hi'), 'hi')
+else:
+    test_eq(run('echo hi', as_bytes=True), b'hi\n')
+
+
+

source

+
+
+

open_file

+
+
 open_file (fn, mode='r', **kwargs)
+
+

Open a file, with optional compression if gz or bz2 suffix

+
+

source

+
+
+

save_pickle

+
+
 save_pickle (fn, o)
+
+

Save a pickle file, to a file name or opened file

+
+

source

+
+
+

load_pickle

+
+
 load_pickle (fn)
+
+

Load a pickle file from a file name or opened file

+
+
for suf in '.pkl','.bz2','.gz':
+    # delete=False is added for Windows
+    # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file
+    with tempfile.NamedTemporaryFile(suffix=suf, delete=False) as f:
+        fn = Path(f.name)
+        save_pickle(fn, 't')
+        t = load_pickle(fn)
+    f.close()
+    test_eq(t,'t')
+
+
+

source

+
+
+

parse_env

+
+
 parse_env (s:str=None, fn:Union[str,pathlib.Path]=None)
+
+

Parse a shell-style environment string or file

+
+
testf = """# comment
+   # another comment
+ export FOO="bar#baz"
+BAR=thing # comment "ok"
+  baz='thong'
+QUX=quux
+export ZAP = "zip" # more comments
+   FOOBAR = 42   # trailing space and comment"""
+
+exp = dict(FOO='bar#baz', BAR='thing', baz='thong', QUX='quux', ZAP='zip', FOOBAR='42')
+
+test_eq(parse_env(testf),  exp)
+
+
+

source

+
+
+

expand_wildcards

+
+
 expand_wildcards (code)
+
+

Expand all wildcard imports in the given code string.

+
+
inp = """from math import *
+from os import *
+from random import *
+def func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)"""
+
+exp = """from math import pi, sin
+from os import path
+from random import randint
+def func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)"""
+
+test_eq(expand_wildcards(inp), exp)
+
+inp = """from itertools import *
+def func(): pass"""
+test_eq(expand_wildcards(inp), inp)
+
+inp = """def outer():
+    from math import *
+    def inner():
+        from os import *
+        return sin(pi) + path.join('a', 'b')"""
+
+exp = """def outer():
+    from math import pi, sin
+    def inner():
+        from os import path
+        return sin(pi) + path.join('a', 'b')"""
+
+test_eq(expand_wildcards(inp), exp)
+
+
+
+
+

Collections

+
+

source

+
+

dict2obj

+
+
 dict2obj (d, list_func=<class 'fastcore.foundation.L'>, dict_func=<class
+           'fastcore.basics.AttrDict'>)
+
+

Convert (possibly nested) dicts (or lists of dicts) to AttrDict

+

This is a convenience to give you “dotted” access to (possibly nested) dictionaries, e.g:

+
+
d1 = dict(a=1, b=dict(c=2,d=3))
+d2 = dict2obj(d1)
+test_eq(d2.b.c, 2)
+test_eq(d2.b['c'], 2)
+
+

It can also be used on lists of dicts.

+
+
_list_of_dicts = [d1, d1]
+ds = dict2obj(_list_of_dicts)
+test_eq(ds[0].b.c, 2)
+
+
+

source

+
+
+

obj2dict

+
+
 obj2dict (d)
+
+

Convert (possibly nested) AttrDicts (or lists of AttrDicts) to dict

+

obj2dict can be used to reverse what is done by dict2obj:

+
+
test_eq(obj2dict(d2), d1)
+test_eq(obj2dict(ds), _list_of_dicts)
+
+
+

source

+
+
+

repr_dict

+
+
 repr_dict (d)
+
+

Print nested dicts and lists, such as returned by dict2obj

+
+
print(repr_dict(d2))
+
+
- a: 1
+- b: 
+  - c: 2
+  - d: 3
+
+
+
+

source

+
+
+

is_listy

+
+
 is_listy (x)
+
+

isinstance(x, (tuple,list,L,slice,Generator))

+
+
assert is_listy((1,))
+assert is_listy([1])
+assert is_listy(L([1]))
+assert is_listy(slice(2))
+assert not is_listy(array([1]))
+
+
+

source

+
+
+

mapped

+
+
 mapped (f, it)
+
+

map f over it, unless it’s not listy, in which case return f(it)

+
+
def _f(x,a=1): return x-a
+
+test_eq(mapped(_f,1),0)
+test_eq(mapped(_f,[1,2]),[0,1])
+test_eq(mapped(_f,(1,)),(0,))
+
+
+
+
+

Extensions to Pathlib.Path

+

The following methods are added to the standard python libary Pathlib.Path.

+
+

source

+
+

Path.readlines

+
+
 Path.readlines (hint=-1, encoding='utf8')
+
+

Read the content of self

+
+

source

+
+
+

Path.read_json

+
+
 Path.read_json (encoding=None, errors=None)
+
+

Same as read_text followed by loads

+
+

source

+
+
+

Path.mk_write

+
+
 Path.mk_write (data, encoding=None, errors=None, mode=511)
+
+

Make all parent dirs of self, and write data

+
+

source

+
+
+

Path.relpath

+
+
 Path.relpath (start=None)
+
+

Same as os.path.relpath, but returns a Path, and resolves symlinks

+
+
p = Path('../fastcore/').resolve()
+p
+
+
Path('/Users/daniel.roy.greenfeld/fh/fastcore/fastcore')
+
+
+
+
p.relpath(Path.cwd())
+
+
Path('../fastcore')
+
+
+
+

source

+
+
+

Path.ls

+
+
 Path.ls (n_max=None, file_type=None, file_exts=None)
+
+

Contents of path as a list

+

We add an ls() method to pathlib.Path which is simply defined as list(Path.iterdir()), mainly for convenience in REPL environments such as notebooks.

+
+
path = Path()
+t = path.ls()
+assert len(t)>0
+t1 = path.ls(10)
+test_eq(len(t1), 10)
+t2 = path.ls(file_exts='.ipynb')
+assert len(t)>len(t2)
+t[0]
+
+
Path('000_tour.ipynb')
+
+
+

You can also pass an optional file_type MIME prefix and/or a list of file extensions.

+
+
lib_path = (path/'../fastcore')
+txt_files=lib_path.ls(file_type='text')
+assert len(txt_files) > 0 and txt_files[0].suffix=='.py'
+ipy_files=path.ls(file_exts=['.ipynb'])
+assert len(ipy_files) > 0 and ipy_files[0].suffix=='.ipynb'
+txt_files[0],ipy_files[0]
+
+
(Path('../fastcore/shutil.py'), Path('000_tour.ipynb'))
+
+
+
+

source

+
+
+

Path.__repr__

+
+
 Path.__repr__ ()
+
+

Return repr(self).

+

fastai also updates the repr of Path such that, if Path.BASE_PATH is defined, all paths are printed relative to that path (as long as they are contained in Path.BASE_PATH:

+
+
t = ipy_files[0].absolute()
+try:
+    Path.BASE_PATH = t.parent.parent
+    test_eq(repr(t), f"Path('nbs/{t.name}')")
+finally: Path.BASE_PATH = None
+
+
+

source

+
+
+

Path.delete

+
+
 Path.delete ()
+
+

Delete a file, symlink, or directory tree

+
+
+
+

Reindexing Collections

+
+

source

+
+

ReindexCollection

+
+
 ReindexCollection (coll, idxs=None, cache=None, tfm=<function noop>)
+
+

Reindexes collection coll with indices idxs and optional LRU cache of size cache

+

This is useful when constructing batches or organizing data in a particular manner (i.e. for deep learning). This class is primarly used in organizing data for language models in fastai.

+

You can supply a custom index upon instantiation with the idxs argument, or you can call the reindex method to supply a new index for your collection.

+

Here is how you can reindex a list such that the elements are reversed:

+
+
rc=ReindexCollection(['a', 'b', 'c', 'd', 'e'], idxs=[4,3,2,1,0])
+list(rc)
+
+
['e', 'd', 'c', 'b', 'a']
+
+
+

Alternatively, you can use the reindex method:

+
+

source

+
+
ReindexCollection.reindex
+
+
 ReindexCollection.reindex (idxs)
+
+

Replace self.idxs with idxs

+
+
rc=ReindexCollection(['a', 'b', 'c', 'd', 'e'])
+rc.reindex([4,3,2,1,0])
+list(rc)
+
+
['e', 'd', 'c', 'b', 'a']
+
+
+

You can optionally specify a LRU cache, which uses functools.lru_cache upon instantiation:

+
+
sz = 50
+t = ReindexCollection(L.range(sz), cache=2)
+
+#trigger a cache hit by indexing into the same element multiple times
+t[0], t[0]
+t._get.cache_info()
+
+
CacheInfo(hits=1, misses=1, maxsize=2, currsize=1)
+
+
+

You can optionally clear the LRU cache by calling the cache_clear method:

+
+

source

+
+
+
ReindexCollection.cache_clear
+
+
 ReindexCollection.cache_clear ()
+
+

Clear LRU cache

+
+
sz = 50
+t = ReindexCollection(L.range(sz), cache=2)
+
+#trigger a cache hit by indexing into the same element multiple times
+t[0], t[0]
+t.cache_clear()
+t._get.cache_info()
+
+
CacheInfo(hits=0, misses=0, maxsize=2, currsize=0)
+
+
+
+

source

+
+
+
ReindexCollection.shuffle
+
+
 ReindexCollection.shuffle ()
+
+

Randomly shuffle indices

+

Note that an ordered index is automatically constructed for the data structure even if one is not supplied.

+
+
rc=ReindexCollection(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
+rc.shuffle()
+list(rc)
+
+
['a', 'd', 'h', 'c', 'e', 'b', 'f', 'g']
+
+
+
+
sz = 50
+t = ReindexCollection(L.range(sz), cache=2)
+test_eq(list(t), range(sz))
+test_eq(t[sz-1], sz-1)
+test_eq(t._get.cache_info().hits, 1)
+t.shuffle()
+test_eq(t._get.cache_info().hits, 1)
+test_ne(list(t), range(sz))
+test_eq(set(t), set(range(sz)))
+t.cache_clear()
+test_eq(t._get.cache_info().hits, 0)
+test_eq(t.count(0), 1)
+
+
+
+
+
+

Other Helpers

+
+

source

+ +
+

truncstr

+
+
 truncstr (s:str, maxlen:int, suf:str='…', space='')
+
+

Truncate s to length maxlen, adding suffix suf if truncated

+
+
w = 'abacadabra'
+test_eq(truncstr(w, 10), w)
+test_eq(truncstr(w, 5), 'abac…')
+test_eq(truncstr(w, 5, suf=''), 'abaca')
+test_eq(truncstr(w, 11, space='_'), w+"_")
+test_eq(truncstr(w, 10, space='_'), w[:-1]+'…')
+test_eq(truncstr(w, 5, suf='!!'), 'aba!!')
+
+
+

source

+
+
+

sparkline

+
+
 sparkline (data, mn=None, mx=None, empty_zero=False)
+
+

Sparkline for data, with Nones (and zero, if empty_zero) shown as empty column

+
+
data = [9,6,None,1,4,0,8,15,10]
+print(f'without "empty_zero": {sparkline(data, empty_zero=False)}')
+print(f'   with "empty_zero": {sparkline(data, empty_zero=True )}')
+
+
without "empty_zero": ▅▂ ▁▂▁▃▇▅
+   with "empty_zero": ▅▂ ▁▂ ▃▇▅
+
+
+

You can set a maximum and minimum for the y-axis of the sparkline with the arguments mn and mx respectively:

+
+
sparkline([1,2,3,400], mn=0, mx=3)
+
+
'▂▅▇▇'
+
+
+
+

source

+
+
+

modify_exception

+
+
 modify_exception (e:Exception, msg:str=None, replace:bool=False)
+
+

Modifies e with a custom message attached

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
eExceptionAn exception
msgstrNoneA custom message
replaceboolFalseWhether to replace e.args with [msg]
ReturnsException
+
+
msg = "This is my custom message!"
+
+test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), None)), contains='')
+test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), msg)), contains=msg)
+test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception("The first message"), msg)), contains="The first message This is my custom message!")
+test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception("The first message"), msg, True)), contains="This is my custom message!")
+
+
+

source

+
+
+

round_multiple

+
+
 round_multiple (x, mult, round_down=False)
+
+

Round x to nearest multiple of mult

+
+
test_eq(round_multiple(63,32), 64)
+test_eq(round_multiple(50,32), 64)
+test_eq(round_multiple(40,32), 32)
+test_eq(round_multiple( 0,32),  0)
+test_eq(round_multiple(63,32, round_down=True), 32)
+test_eq(round_multiple((63,40),32), (64,32))
+
+
+

source

+
+
+

set_num_threads

+
+
 set_num_threads (nt)
+
+

Get numpy (and others) to use nt threads

+

This sets the number of threads consistently for many tools, by:

+
    +
  1. Set the following environment variables equal to nt: OPENBLAS_NUM_THREADS,NUMEXPR_NUM_THREADS,OMP_NUM_THREADS,MKL_NUM_THREADS
  2. +
  3. Sets nt threads for numpy and pytorch.
  4. +
+
+

source

+
+
+

join_path_file

+
+
 join_path_file (file, path, ext='')
+
+

Return path/file if file is a string or a Path, file otherwise

+
+
path = Path.cwd()/'_tmp'/'tst'
+f = join_path_file('tst.txt', path)
+assert path.exists()
+test_eq(f, path/'tst.txt')
+with open(f, 'w') as f_: assert join_path_file(f_, path) == f_
+shutil.rmtree(Path.cwd()/'_tmp')
+
+
+

source

+
+
+

autostart

+
+
 autostart (g)
+
+

Decorator that automatically starts a generator

+
+

source

+
+

EventTimer

+
+
 EventTimer (store=5, span=60)
+
+

An event timer with history of store items of time span

+

Add events with add, and get number of events and their frequency (freq).

+
+
# Random wait function for testing
+def _randwait(): yield from (sleep(random.random()/200) for _ in range(100))
+
+c = EventTimer(store=5, span=0.03)
+for o in _randwait(): c.add(1)
+print(f'Num Events: {c.events}, Freq/sec: {c.freq:.01f}')
+print('Most recent: ', sparkline(c.hist), *L(c.hist).map('{:.01f}'))
+
+
Num Events: 3, Freq/sec: 205.6
+Most recent:  ▁▁▃▁▇ 254.1 263.2 284.5 259.9 315.7
+
+
+
+

source

+
+
+
+

stringfmt_names

+
+
 stringfmt_names (s:str)
+
+

Unique brace-delimited names in s

+
+
s = '/pulls/{pull_number}/reviews/{review_id}'
+test_eq(stringfmt_names(s), ['pull_number','review_id'])
+
+
+

source

+
+

PartialFormatter

+
+
 PartialFormatter ()
+
+

A string.Formatter that doesn’t error on missing fields, and tracks missing fields and unused args

+
+

source

+
+
+
+

partial_format

+
+
 partial_format (s:str, **kwargs)
+
+

string format s, ignoring missing field errors, returning missing and extra fields

+

The result is a tuple of (formatted_string,missing_fields,extra_fields), e.g:

+
+
res,missing,xtra = partial_format(s, pull_number=1, foo=2)
+test_eq(res, '/pulls/1/reviews/{review_id}')
+test_eq(missing, ['review_id'])
+test_eq(xtra, {'foo':2})
+
+
+

source

+
+
+

utc2local

+
+
 utc2local (dt:datetime.datetime)
+
+

Convert dt from UTC to local time

+
+
dt = datetime(2000,1,1,12)
+print(f'{dt} UTC is {utc2local(dt)} local time')
+
+
2000-01-01 12:00:00 UTC is 2000-01-01 22:00:00+10:00 local time
+
+
+
+

source

+
+
+

local2utc

+
+
 local2utc (dt:datetime.datetime)
+
+

Convert dt from local to UTC time

+
+
print(f'{dt} local is {local2utc(dt)} UTC time')
+
+
2000-01-01 12:00:00 local is 2000-01-01 02:00:00+00:00 UTC time
+
+
+
+

source

+
+
+

trace

+
+
 trace (f)
+
+

Add set_trace to an existing function f

+

You can add a breakpoint to an existing function, e.g:

+
Path.cwd = trace(Path.cwd)
+Path.cwd()
+

Now, when the function is called it will drop you into the debugger. Note, you must issue the s command when you begin to step into the function that is being traced.

+
+

source

+
+
+

modified_env

+
+
 modified_env (*delete, **replace)
+
+

Context manager temporarily modifying os.environ by deleting delete and replacing replace

+
+
# USER isn't in Cloud Linux Environments
+env_test = 'USERNAME' if sys.platform == "win32" else 'SHELL'
+oldusr = os.environ[env_test]
+
+replace_param = {env_test: 'a'}
+with modified_env('PATH', **replace_param):
+    test_eq(os.environ[env_test], 'a')
+    assert 'PATH' not in os.environ
+
+assert 'PATH' in os.environ
+test_eq(os.environ[env_test], oldusr)
+
+
+

source

+
+

ContextManagers

+
+
 ContextManagers (mgrs)
+
+

Wrapper for contextlib.ExitStack which enters a collection of context managers

+
+

source

+
+
+
+

shufflish

+
+
 shufflish (x, pct=0.04)
+
+

Randomly relocate items of x up to pct of len(x) from their starting location

+
+

source

+
+
+

console_help

+
+
 console_help (libname:str)
+
+

Show help for all console scripts from libname

+ + + + + + + + + + + + + + + +
TypeDetails
libnamestrname of library for console script listing
+
+

source

+
+
+

hl_md

+
+
 hl_md (s, lang='xml', show=True)
+
+

Syntax highlight s using lang.

+

When we display code in a notebook, it’s nice to highlight it, so we create a function to simplify that:

+
+
hl_md('<test><xml foo="bar">a child</xml></test>')
+
+
<test><xml foo="bar">a child</xml></test>
+
+
+
+

source

+
+
+

type2str

+
+
 type2str (typ:type)
+
+

Stringify typ

+
+
test_eq(type2str(Optional[float]), 'Union[float, None]')
+
+
+

source

+
+
+

dataclass_src

+
+
 dataclass_src (cls)
+
+
+
DC = make_dataclass('DC', [('x', int), ('y', Optional[float], None), ('z', float, None)])
+print(dataclass_src(DC))
+
+
@dataclass
+class DC:
+    x: int
+    y: Union[float, None] = None
+    z: float = None
+
+
+
+
+

source

+
+
+

Unset

+
+
 Unset (value, names=None, module=None, qualname=None, type=None, start=1)
+
+

An enumeration.

+
+

source

+
+
+

nullable_dc

+
+
 nullable_dc (cls)
+
+

Like dataclass, but default of UNSET added to fields without defaults

+
+
@nullable_dc
+class Person: name: str; age: int; city: str = "Unknown"
+Person(name="Bob")
+
+
Person(name='Bob', age=UNSET, city='Unknown')
+
+
+
+

source

+
+
+

make_nullable

+
+
 make_nullable (clas)
+
+
+
@dataclass
+class Person: name: str; age: int; city: str = "Unknown"
+
+make_nullable(Person)
+Person("Bob", city='NY')
+
+
Person(name='Bob', age=UNSET, city='NY')
+
+
+
+
Person(name="Bob")
+
+
Person(name='Bob', age=UNSET, city='Unknown')
+
+
+
+
Person("Bob", 34)
+
+
Person(name='Bob', age=34, city='Unknown')
+
+
+
+

source

+
+
+

flexiclass

+
+
 flexiclass (cls)
+
+

Convert cls into a dataclass like make_nullable. Converts in place and also returns the result.

+ + + + + + + + + + + + + + + + + + + + +
TypeDetails
clsThe class to convert
Returnsdataclass
+

This can be used as a decorator…

+
+
@flexiclass
+class Person: name: str; age: int; city: str = "Unknown"
+
+bob = Person(name="Bob")
+bob
+
+
Person(name='Bob', age=UNSET, city='Unknown')
+
+
+

…or can update the behavior of an existing class (or dataclass):

+
+
class Person: name: str; age: int; city: str = "Unknown"
+
+flexiclass(Person)
+bob = Person(name="Bob")
+bob
+
+
Person(name='Bob', age=UNSET, city='Unknown')
+
+
+

Action occurs in-place:

+
+
class Person: name: str; age: int; city: str = "Unknown"
+
+flexiclass(Person)
+is_dataclass(Person)
+
+
True
+
+
+
+

source

+
+
+

asdict

+
+
 asdict (o)
+
+

Convert o to a dict, supporting dataclasses, namedtuples, iterables, and __dict__ attrs.

+

Any UNSET values are not included.

+
+
asdict(bob)
+
+
{'name': 'Bob', 'city': 'Unknown'}
+
+
+

To customise dict conversion behavior for a class, implement the _asdict method (this is used in the Python stdlib for named tuples).

+
+

source

+
+
+

is_typeddict

+
+
 is_typeddict (cls:type)
+
+

Check if cls is a TypedDict

+
+
class MyDict(TypedDict): name:str
+
+assert is_typeddict(MyDict)
+assert not is_typeddict({'a':1})
+
+
+

source

+
+
+

is_namedtuple

+
+
 is_namedtuple (cls)
+
+

True if cls is a namedtuple type

+
+
assert is_namedtuple(namedtuple('tst', ['a']))
+assert not is_namedtuple(tuple)
+
+
+

source

+
+
+

flexicache

+
+
 flexicache (*funcs, maxsize=128)
+
+

Like lru_cache, but customisable with policy funcs

+

This is a flexible lru cache function that you can pass a list of functions to. Those functions define the cache eviction policy. For instance, time_policy is provided for time-based cache eviction, and mtime_policy evicts based on a file’s modified-time changing. The policy functions are passed the last value that function returned was (initially None), and return a new value to indicate the cache has expired. When the cache expires, all functions are called with None to force getting new values.

+
+

source

+
+
+

time_policy

+
+
 time_policy (seconds)
+
+

A flexicache policy that expires cached items after seconds have passed

+
+

source

+
+
+

mtime_policy

+
+
 mtime_policy (filepath)
+
+

A flexicache policy that expires cached items after filepath modified-time changes

+
+
@flexicache(time_policy(10), mtime_policy('000_tour.ipynb'))
+def cached_func(x, y): return x+y
+
+cached_func(1,2)
+
+
3
+
+
+
+
@flexicache(time_policy(10), mtime_policy('000_tour.ipynb'))
+async def cached_func(x, y): return x+y
+
+await cached_func(1,2)
+await cached_func(1,2)
+
+
3
+
+
+
+

source

+
+
+

timed_cache

+
+
 timed_cache (seconds=60, maxsize=128)
+
+

Like lru_cache, but also with time-based eviction

+

This function is a small convenience wrapper for using flexicache with time_policy.

+
+
@timed_cache(seconds=0.05, maxsize=2)
+def cached_func(x): return x * 2, time()
+
+# basic caching
+result1, time1 = cached_func(2)
+test_eq(result1, 4)
+sleep(0.001)
+result2, time2 = cached_func(2)
+test_eq(result2, 4)
+test_eq(time1, time2)
+
+# caching different values
+result3, _ = cached_func(3)
+test_eq(result3, 6)
+
+# maxsize
+_, time4 = cached_func(4)
+_, time2_new = cached_func(2)
+test_close(time2, time2_new, eps=0.1)
+_, time3_new = cached_func(3)
+test_ne(time3_new, time())
+
+# time expiration
+sleep(0.05)
+_, time4_new = cached_func(4)
+test_ne(time4_new, time())
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/xtras.html.md b/xtras.html.md new file mode 100644 index 00000000..15c27e43 --- /dev/null +++ b/xtras.html.md @@ -0,0 +1,1854 @@ +# Utility functions + + + + +## File Functions + +Utilities (other than extensions to Pathlib.Path) for dealing with IO. + +------------------------------------------------------------------------ + +source + +### walk + +> walk (path:pathlib.Path|str, symlinks:bool=True, keep_file: infunctioncallable>=, keep_folder: infunctioncallable>=, skip_folder: infunctioncallable>=, func: infunctioncallable>=, ret_folders:bool=False) + +*Generator version of `os.walk`, using functions to filter files and +folders* + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
pathpathlib.Path | strpath to start searching
symlinksboolTruefollow symlinks?
keep_filecallableret_truefunction that returns True for wanted files
keep_foldercallableret_truefunction that returns True for folders to enter
skip_foldercallableret_falsefunction that returns True for folders to skip
funccallablejoinfunction to apply to each matched file
ret_foldersboolFalsereturn folders, not just files
+ +------------------------------------------------------------------------ + +source + +### globtastic + +> globtastic (path:pathlib.Path|str, recursive:bool=True, +> symlinks:bool=True, file_glob:str=None, file_re:str=None, +> folder_re:str=None, skip_file_glob:str=None, +> skip_file_re:str=None, skip_folder_re:str=None, func: infunctioncallable>=, ret_folders:bool=False) + +*A more powerful `glob`, including regex matches, symlink handling, and +skip parameters* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
pathpathlib.Path | strpath to start searching
recursiveboolTruesearch subfolders
symlinksboolTruefollow symlinks?
file_globstrNoneOnly include files matching glob
file_restrNoneOnly include files matching regex
folder_restrNoneOnly enter folders matching regex
skip_file_globstrNoneSkip files matching glob
skip_file_restrNoneSkip files matching regex
skip_folder_restrNoneSkip folders matching regex,
funccallablejoinfunction to apply to each matched file
ret_foldersboolFalsereturn folders, not just files
ReturnsLPaths to matched files
+ +``` python +globtastic('.', skip_folder_re='^[_.]', folder_re='core', file_glob='*.*py*', file_re='c') +``` + + (#5) ['./fastcore/docments.py','./fastcore/dispatch.py','./fastcore/basics.py','./fastcore/docscrape.py','./fastcore/script.py'] + +------------------------------------------------------------------------ + +source + +### maybe_open + +> maybe_open (f, mode='r', **kwargs) + +*Context manager: open `f` if it is a path (and close on exit)* + +This is useful for functions where you want to accept a path *or* file. +[`maybe_open`](https://fastcore.fast.ai/xtras.html#maybe_open) will not +close your file handle if you pass one in. + +``` python +def _f(fn): + with maybe_open(fn) as f: return f.encoding + +fname = '00_test.ipynb' +sys_encoding = 'cp1252' if sys.platform == 'win32' else 'UTF-8' +test_eq(_f(fname), sys_encoding) +with open(fname) as fh: test_eq(_f(fh), sys_encoding) +``` + +For example, we can use this to reimplement +[`imghdr.what`](https://docs.python.org/3/library/imghdr.html#imghdr.what) +from the Python standard library, which is [written in Python +3.9](https://github.com/python/cpython/blob/3.9/Lib/imghdr.py#L11) as: + +``` python +from fastcore import imghdr +``` + +``` python +def what(file, h=None): + f = None + try: + if h is None: + if isinstance(file, (str,os.PathLike)): + f = open(file, 'rb') + h = f.read(32) + else: + location = file.tell() + h = file.read(32) + file.seek(location) + for tf in imghdr.tests: + res = tf(h, f) + if res: return res + finally: + if f: f.close() + return None +``` + +Here’s an example of the use of this function: + +``` python +fname = 'images/puppy.jpg' +what(fname) +``` + + 'jpeg' + +With [`maybe_open`](https://fastcore.fast.ai/xtras.html#maybe_open), +`Self`, and +[`L.map_first`](https://fastcore.fast.ai/foundation.html#l.map_first), +we can rewrite this in a much more concise and (in our opinion) clear +way: + +``` python +def what(file, h=None): + if h is None: + with maybe_open(file, 'rb') as f: h = f.peek(32) + return L(imghdr.tests).map_first(Self(h,file)) +``` + +…and we can check that it still works: + +``` python +test_eq(what(fname), 'jpeg') +``` + +…along with the version passing a file handle: + +``` python +with open(fname,'rb') as f: test_eq(what(f), 'jpeg') +``` + +…along with the `h` parameter version: + +``` python +with open(fname,'rb') as f: test_eq(what(None, h=f.read(32)), 'jpeg') +``` + +------------------------------------------------------------------------ + +source + +### mkdir + +> mkdir (path, exist_ok=False, parents=False, overwrite=False, **kwargs) + +*Creates and returns a directory defined by `path`, optionally removing +previous existing directory if `overwrite` is `True`* + +``` python +with tempfile.TemporaryDirectory() as d: + path = Path(os.path.join(d, 'new_dir')) + new_dir = mkdir(path) + assert new_dir.exists() + test_eq(new_dir, path) + + # test overwrite + with open(new_dir/'test.txt', 'w') as f: f.writelines('test') + test_eq(len(list(walk(new_dir))), 1) # assert file is present + new_dir = mkdir(new_dir, overwrite=True) + test_eq(len(list(walk(new_dir))), 0) # assert file was deleted +``` + +------------------------------------------------------------------------ + +source + +### image_size + +> image_size (fn) + +*Tuple of (w,h) for png, gif, or jpg; `None` otherwise* + +``` python +test_eq(image_size(fname), (1200,803)) +``` + +------------------------------------------------------------------------ + +source + +### bunzip + +> bunzip (fn) + +*bunzip `fn`, raising exception if output already exists* + +``` python +f = Path('files/test.txt') +if f.exists(): f.unlink() +bunzip('files/test.txt.bz2') +t = f.open().readlines() +test_eq(len(t),1) +test_eq(t[0], 'test\n') +f.unlink() +``` + +------------------------------------------------------------------------ + +source + +### loads + +> loads (s, **kw) + +*Same as `json.loads`, but handles `None`* + +------------------------------------------------------------------------ + +source + +### loads_multi + +> loads_multi (s:str) + +*Generator of \>=0 decoded json dicts, possibly with non-json ignored +text at start and end* + +``` python +tst = """ +# ignored +{ "a":1 } +hello +{ +"b":2 +} +""" + +test_eq(list(loads_multi(tst)), [{'a': 1}, {'b': 2}]) +``` + +------------------------------------------------------------------------ + +source + +### dumps + +> dumps (obj, **kw) + +*Same as `json.dumps`, but uses `ujson` if available* + +------------------------------------------------------------------------ + +source + +### untar_dir + +> untar_dir (fname, dest, rename=False, overwrite=False) + +*untar `file` into `dest`, creating a directory if the root contains +more than one item* + +``` python +def test_untar(foldername, rename=False, **kwargs): + with tempfile.TemporaryDirectory() as d: + nm = os.path.join(d, 'a') + shutil.make_archive(nm, 'gztar', **kwargs) + with tempfile.TemporaryDirectory() as d2: + d2 = Path(d2) + untar_dir(nm+'.tar.gz', d2, rename=rename) + test_eq(d2.ls(), [d2/foldername]) +``` + +If the contents of `fname` contain just one file or directory, it is +placed directly in `dest`: + +``` python +# using `base_dir` in `make_archive` results in `images` directory included in file names +test_untar('images', base_dir='images') +``` + +If `rename` then the directory created is named based on the archive, +without extension: + +``` python +test_untar('a', base_dir='images', rename=True) +``` + +If the contents of `fname` contain multiple files and directories, a new +folder in `dest` is created with the same name as `fname` (but without +extension): + +``` python +# using `root_dir` in `make_archive` results in `images` directory *not* included in file names +test_untar('a', root_dir='images') +``` + +------------------------------------------------------------------------ + +source + +### repo_details + +> repo_details (url) + +*Tuple of `owner,name` from ssh or https git repo `url`* + +``` python +test_eq(repo_details('https://github.com/fastai/fastai.git'), ['fastai', 'fastai']) +test_eq(repo_details('git@github.com:fastai/nbdev.git\n'), ['fastai', 'nbdev']) +``` + +------------------------------------------------------------------------ + +source + +### run + +> run (cmd, *rest, same_in_win=False, ignore_ex=False, as_bytes=False, +> stderr=False) + +*Pass `cmd` (splitting with `shlex` if string) to `subprocess.run`; +return `stdout`; raise `IOError` if fails* + +You can pass a string (which will be split based on standard shell +rules), a list, or pass args directly: + +``` python +run('echo', same_in_win=True) +run('pip', '--version', same_in_win=True) +run(['pip', '--version'], same_in_win=True) +``` + + 'pip 23.3.1 from /Users/jhoward/miniconda3/lib/python3.11/site-packages/pip (python 3.11)' + +``` python +if sys.platform == 'win32': + assert 'ipynb' in run('cmd /c dir /p') + assert 'ipynb' in run(['cmd', '/c', 'dir', '/p']) + assert 'ipynb' in run('cmd', '/c', 'dir', '/p') +else: + assert 'ipynb' in run('ls -ls') + assert 'ipynb' in run(['ls', '-l']) + assert 'ipynb' in run('ls', '-l') +``` + +Some commands fail in non-error situations, like `grep`. Use `ignore_ex` +in those cases, which will return a tuple of stdout and returncode: + +``` python +if sys.platform == 'win32': + test_eq(run('cmd /c findstr asdfds 00_test.ipynb', ignore_ex=True)[0], 1) +else: + test_eq(run('grep asdfds 00_test.ipynb', ignore_ex=True)[0], 1) +``` + +[`run`](https://fastcore.fast.ai/xtras.html#run) automatically decodes +returned bytes to a `str`. Use `as_bytes` to skip that: + +``` python +if sys.platform == 'win32': + test_eq(run('cmd /c echo hi'), 'hi') +else: + test_eq(run('echo hi', as_bytes=True), b'hi\n') +``` + +------------------------------------------------------------------------ + +source + +### open_file + +> open_file (fn, mode='r', **kwargs) + +*Open a file, with optional compression if gz or bz2 suffix* + +------------------------------------------------------------------------ + +source + +### save_pickle + +> save_pickle (fn, o) + +*Save a pickle file, to a file name or opened file* + +------------------------------------------------------------------------ + +source + +### load_pickle + +> load_pickle (fn) + +*Load a pickle file from a file name or opened file* + +``` python +for suf in '.pkl','.bz2','.gz': + # delete=False is added for Windows + # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file + with tempfile.NamedTemporaryFile(suffix=suf, delete=False) as f: + fn = Path(f.name) + save_pickle(fn, 't') + t = load_pickle(fn) + f.close() + test_eq(t,'t') +``` + +------------------------------------------------------------------------ + +source + +### parse_env + +> parse_env (s:str=None, fn:Union[str,pathlib.Path]=None) + +*Parse a shell-style environment string or file* + +``` python +testf = """# comment + # another comment + export FOO="bar#baz" +BAR=thing # comment "ok" + baz='thong' +QUX=quux +export ZAP = "zip" # more comments + FOOBAR = 42 # trailing space and comment""" + +exp = dict(FOO='bar#baz', BAR='thing', baz='thong', QUX='quux', ZAP='zip', FOOBAR='42') + +test_eq(parse_env(testf), exp) +``` + +------------------------------------------------------------------------ + +source + +### expand_wildcards + +> expand_wildcards (code) + +*Expand all wildcard imports in the given code string.* + +``` python +inp = """from math import * +from os import * +from random import * +def func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)""" + +exp = """from math import pi, sin +from os import path +from random import randint +def func(): return sin(pi) + path.join('a', 'b') + randint(1, 10)""" + +test_eq(expand_wildcards(inp), exp) + +inp = """from itertools import * +def func(): pass""" +test_eq(expand_wildcards(inp), inp) + +inp = """def outer(): + from math import * + def inner(): + from os import * + return sin(pi) + path.join('a', 'b')""" + +exp = """def outer(): + from math import pi, sin + def inner(): + from os import path + return sin(pi) + path.join('a', 'b')""" + +test_eq(expand_wildcards(inp), exp) +``` + +## Collections + +------------------------------------------------------------------------ + +source + +### dict2obj + +> dict2obj (d, list_func=, dict_func= 'fastcore.basics.AttrDict'>) + +*Convert (possibly nested) dicts (or lists of dicts) to +[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict)* + +This is a convenience to give you “dotted” access to (possibly nested) +dictionaries, e.g: + +``` python +d1 = dict(a=1, b=dict(c=2,d=3)) +d2 = dict2obj(d1) +test_eq(d2.b.c, 2) +test_eq(d2.b['c'], 2) +``` + +It can also be used on lists of dicts. + +``` python +_list_of_dicts = [d1, d1] +ds = dict2obj(_list_of_dicts) +test_eq(ds[0].b.c, 2) +``` + +------------------------------------------------------------------------ + +source + +### obj2dict + +> obj2dict (d) + +*Convert (possibly nested) AttrDicts (or lists of AttrDicts) to `dict`* + +[`obj2dict`](https://fastcore.fast.ai/xtras.html#obj2dict) can be used +to reverse what is done by +[`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj): + +``` python +test_eq(obj2dict(d2), d1) +test_eq(obj2dict(ds), _list_of_dicts) +``` + +------------------------------------------------------------------------ + +source + +### repr_dict + +> repr_dict (d) + +*Print nested dicts and lists, such as returned by +[`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj)* + +``` python +print(repr_dict(d2)) +``` + + - a: 1 + - b: + - c: 2 + - d: 3 + +------------------------------------------------------------------------ + +source + +### is_listy + +> is_listy (x) + +*`isinstance(x, (tuple,list,L,slice,Generator))`* + +``` python +assert is_listy((1,)) +assert is_listy([1]) +assert is_listy(L([1])) +assert is_listy(slice(2)) +assert not is_listy(array([1])) +``` + +------------------------------------------------------------------------ + +source + +### mapped + +> mapped (f, it) + +*map `f` over `it`, unless it’s not listy, in which case return `f(it)`* + +``` python +def _f(x,a=1): return x-a + +test_eq(mapped(_f,1),0) +test_eq(mapped(_f,[1,2]),[0,1]) +test_eq(mapped(_f,(1,)),(0,)) +``` + +## Extensions to Pathlib.Path + +The following methods are added to the standard python libary +[Pathlib.Path](https://docs.python.org/3/library/pathlib.html#basic-use). + +------------------------------------------------------------------------ + +source + +### Path.readlines + +> Path.readlines (hint=-1, encoding='utf8') + +*Read the content of `self`* + +------------------------------------------------------------------------ + +source + +### Path.read_json + +> Path.read_json (encoding=None, errors=None) + +*Same as `read_text` followed by +[`loads`](https://fastcore.fast.ai/xtras.html#loads)* + +------------------------------------------------------------------------ + +source + +### Path.mk_write + +> Path.mk_write (data, encoding=None, errors=None, mode=511) + +*Make all parent dirs of `self`, and write `data`* + +------------------------------------------------------------------------ + +source + +### Path.relpath + +> Path.relpath (start=None) + +*Same as `os.path.relpath`, but returns a `Path`, and resolves symlinks* + +``` python +p = Path('../fastcore/').resolve() +p +``` + + Path('/Users/daniel.roy.greenfeld/fh/fastcore/fastcore') + +``` python +p.relpath(Path.cwd()) +``` + + Path('../fastcore') + +------------------------------------------------------------------------ + +source + +### Path.ls + +> Path.ls (n_max=None, file_type=None, file_exts=None) + +*Contents of path as a list* + +We add an `ls()` method to `pathlib.Path` which is simply defined as +`list(Path.iterdir())`, mainly for convenience in REPL environments such +as notebooks. + +``` python +path = Path() +t = path.ls() +assert len(t)>0 +t1 = path.ls(10) +test_eq(len(t1), 10) +t2 = path.ls(file_exts='.ipynb') +assert len(t)>len(t2) +t[0] +``` + + Path('000_tour.ipynb') + +You can also pass an optional `file_type` MIME prefix and/or a list of +file extensions. + +``` python +lib_path = (path/'../fastcore') +txt_files=lib_path.ls(file_type='text') +assert len(txt_files) > 0 and txt_files[0].suffix=='.py' +ipy_files=path.ls(file_exts=['.ipynb']) +assert len(ipy_files) > 0 and ipy_files[0].suffix=='.ipynb' +txt_files[0],ipy_files[0] +``` + + (Path('../fastcore/shutil.py'), Path('000_tour.ipynb')) + +------------------------------------------------------------------------ + +source + +### Path.\_\_repr\_\_ + +> Path.__repr__ () + +*Return repr(self).* + +fastai also updates the `repr` of `Path` such that, if `Path.BASE_PATH` +is defined, all paths are printed relative to that path (as long as they +are contained in `Path.BASE_PATH`: + +``` python +t = ipy_files[0].absolute() +try: + Path.BASE_PATH = t.parent.parent + test_eq(repr(t), f"Path('nbs/{t.name}')") +finally: Path.BASE_PATH = None +``` + +------------------------------------------------------------------------ + +source + +### Path.delete + +> Path.delete () + +*Delete a file, symlink, or directory tree* + +## Reindexing Collections + +------------------------------------------------------------------------ + +source + +#### ReindexCollection + +> ReindexCollection (coll, idxs=None, cache=None, tfm=) + +*Reindexes collection `coll` with indices `idxs` and optional LRU cache +of size `cache`* + +This is useful when constructing batches or organizing data in a +particular manner (i.e. for deep learning). This class is primarly used +in organizing data for language models in fastai. + +You can supply a custom index upon instantiation with the `idxs` +argument, or you can call the `reindex` method to supply a new index for +your collection. + +Here is how you can reindex a list such that the elements are reversed: + +``` python +rc=ReindexCollection(['a', 'b', 'c', 'd', 'e'], idxs=[4,3,2,1,0]) +list(rc) +``` + + ['e', 'd', 'c', 'b', 'a'] + +Alternatively, you can use the `reindex` method: + +------------------------------------------------------------------------ + +source + +###### ReindexCollection.reindex + +> ReindexCollection.reindex (idxs) + +*Replace `self.idxs` with idxs* + +``` python +rc=ReindexCollection(['a', 'b', 'c', 'd', 'e']) +rc.reindex([4,3,2,1,0]) +list(rc) +``` + + ['e', 'd', 'c', 'b', 'a'] + +You can optionally specify a LRU cache, which uses +[functools.lru_cache](https://docs.python.org/3/library/functools.html#functools.lru_cache) +upon instantiation: + +``` python +sz = 50 +t = ReindexCollection(L.range(sz), cache=2) + +#trigger a cache hit by indexing into the same element multiple times +t[0], t[0] +t._get.cache_info() +``` + + CacheInfo(hits=1, misses=1, maxsize=2, currsize=1) + +You can optionally clear the LRU cache by calling the `cache_clear` +method: + +------------------------------------------------------------------------ + +source + +##### ReindexCollection.cache_clear + +> ReindexCollection.cache_clear () + +*Clear LRU cache* + +``` python +sz = 50 +t = ReindexCollection(L.range(sz), cache=2) + +#trigger a cache hit by indexing into the same element multiple times +t[0], t[0] +t.cache_clear() +t._get.cache_info() +``` + + CacheInfo(hits=0, misses=0, maxsize=2, currsize=0) + +------------------------------------------------------------------------ + +source + +##### ReindexCollection.shuffle + +> ReindexCollection.shuffle () + +*Randomly shuffle indices* + +Note that an ordered index is automatically constructed for the data +structure even if one is not supplied. + +``` python +rc=ReindexCollection(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']) +rc.shuffle() +list(rc) +``` + + ['a', 'd', 'h', 'c', 'e', 'b', 'f', 'g'] + +``` python +sz = 50 +t = ReindexCollection(L.range(sz), cache=2) +test_eq(list(t), range(sz)) +test_eq(t[sz-1], sz-1) +test_eq(t._get.cache_info().hits, 1) +t.shuffle() +test_eq(t._get.cache_info().hits, 1) +test_ne(list(t), range(sz)) +test_eq(set(t), set(range(sz))) +t.cache_clear() +test_eq(t._get.cache_info().hits, 0) +test_eq(t.count(0), 1) +``` + +## Other Helpers + +------------------------------------------------------------------------ + +source + +### get_source_link + +> get_source_link (func) + +*Return link to `func` in source code* + +[`get_source_link`](https://fastcore.fast.ai/xtras.html#get_source_link) +allows you get a link to source code related to an object. For +[nbdev](https://github.com/fastai/nbdev) related projects such as +fastcore, we can get the full link to a GitHub repo. For `nbdev` +projects, be sure to properly set the `git_url` in `settings.ini` +(derived from `lib_name` and `branch` on top of the prefix you will need +to adapt) so that those links are correct. + +For example, below we get the link to +[`fastcore.test.test_eq`](https://fastcore.fast.ai/test.html#test_eq): + +``` python +from fastcore.test import test_eq +``` + +``` python +assert 'fastcore/test.py' in get_source_link(test_eq) +assert get_source_link(test_eq).startswith('https://github.com/fastai/fastcore') +get_source_link(test_eq) +``` + + 'https://github.com/fastai/fastcore/tree/master/fastcore/test.py#L35' + +------------------------------------------------------------------------ + +source + +### truncstr + +> truncstr (s:str, maxlen:int, suf:str='…', space='') + +*Truncate `s` to length `maxlen`, adding suffix `suf` if truncated* + +``` python +w = 'abacadabra' +test_eq(truncstr(w, 10), w) +test_eq(truncstr(w, 5), 'abac…') +test_eq(truncstr(w, 5, suf=''), 'abaca') +test_eq(truncstr(w, 11, space='_'), w+"_") +test_eq(truncstr(w, 10, space='_'), w[:-1]+'…') +test_eq(truncstr(w, 5, suf='!!'), 'aba!!') +``` + +------------------------------------------------------------------------ + +source + +### sparkline + +> sparkline (data, mn=None, mx=None, empty_zero=False) + +*Sparkline for `data`, with `None`s (and zero, if `empty_zero`) shown as +empty column* + +``` python +data = [9,6,None,1,4,0,8,15,10] +print(f'without "empty_zero": {sparkline(data, empty_zero=False)}') +print(f' with "empty_zero": {sparkline(data, empty_zero=True )}') +``` + + without "empty_zero": ▅▂ ▁▂▁▃▇▅ + with "empty_zero": ▅▂ ▁▂ ▃▇▅ + +You can set a maximum and minimum for the y-axis of the sparkline with +the arguments `mn` and `mx` respectively: + +``` python +sparkline([1,2,3,400], mn=0, mx=3) +``` + + '▂▅▇▇' + +------------------------------------------------------------------------ + +source + +### modify_exception + +> modify_exception (e:Exception, msg:str=None, replace:bool=False) + +*Modifies `e` with a custom message attached* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDefaultDetails
eExceptionAn exception
msgstrNoneA custom message
replaceboolFalseWhether to replace e.args with [msg]
ReturnsException
+ +``` python +msg = "This is my custom message!" + +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), None)), contains='') +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception(), msg)), contains=msg) +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception("The first message"), msg)), contains="The first message This is my custom message!") +test_fail(lambda: (_ for _ in ()).throw(modify_exception(Exception("The first message"), msg, True)), contains="This is my custom message!") +``` + +------------------------------------------------------------------------ + +source + +### round_multiple + +> round_multiple (x, mult, round_down=False) + +*Round `x` to nearest multiple of `mult`* + +``` python +test_eq(round_multiple(63,32), 64) +test_eq(round_multiple(50,32), 64) +test_eq(round_multiple(40,32), 32) +test_eq(round_multiple( 0,32), 0) +test_eq(round_multiple(63,32, round_down=True), 32) +test_eq(round_multiple((63,40),32), (64,32)) +``` + +------------------------------------------------------------------------ + +source + +### set_num_threads + +> set_num_threads (nt) + +*Get numpy (and others) to use `nt` threads* + +This sets the number of threads consistently for many tools, by: + +1. Set the following environment variables equal to `nt`: + `OPENBLAS_NUM_THREADS`,`NUMEXPR_NUM_THREADS`,`OMP_NUM_THREADS`,`MKL_NUM_THREADS` +2. Sets `nt` threads for numpy and pytorch. + +------------------------------------------------------------------------ + +source + +### join_path_file + +> join_path_file (file, path, ext='') + +*Return `path/file` if file is a string or a `Path`, file otherwise* + +``` python +path = Path.cwd()/'_tmp'/'tst' +f = join_path_file('tst.txt', path) +assert path.exists() +test_eq(f, path/'tst.txt') +with open(f, 'w') as f_: assert join_path_file(f_, path) == f_ +shutil.rmtree(Path.cwd()/'_tmp') +``` + +------------------------------------------------------------------------ + +source + +### autostart + +> autostart (g) + +*Decorator that automatically starts a generator* + +------------------------------------------------------------------------ + +source + +#### EventTimer + +> EventTimer (store=5, span=60) + +*An event timer with history of `store` items of time `span`* + +Add events with `add`, and get number of `events` and their frequency +(`freq`). + +``` python +# Random wait function for testing +def _randwait(): yield from (sleep(random.random()/200) for _ in range(100)) + +c = EventTimer(store=5, span=0.03) +for o in _randwait(): c.add(1) +print(f'Num Events: {c.events}, Freq/sec: {c.freq:.01f}') +print('Most recent: ', sparkline(c.hist), *L(c.hist).map('{:.01f}')) +``` + + Num Events: 3, Freq/sec: 205.6 + Most recent: ▁▁▃▁▇ 254.1 263.2 284.5 259.9 315.7 + +------------------------------------------------------------------------ + +source + +### stringfmt_names + +> stringfmt_names (s:str) + +*Unique brace-delimited names in `s`* + +``` python +s = '/pulls/{pull_number}/reviews/{review_id}' +test_eq(stringfmt_names(s), ['pull_number','review_id']) +``` + +------------------------------------------------------------------------ + +source + +#### PartialFormatter + +> PartialFormatter () + +*A `string.Formatter` that doesn’t error on missing fields, and tracks +missing fields and unused args* + +------------------------------------------------------------------------ + +source + +### partial_format + +> partial_format (s:str, **kwargs) + +*string format `s`, ignoring missing field errors, returning missing and +extra fields* + +The result is a tuple of +`(formatted_string,missing_fields,extra_fields)`, e.g: + +``` python +res,missing,xtra = partial_format(s, pull_number=1, foo=2) +test_eq(res, '/pulls/1/reviews/{review_id}') +test_eq(missing, ['review_id']) +test_eq(xtra, {'foo':2}) +``` + +------------------------------------------------------------------------ + +source + +### utc2local + +> utc2local (dt:datetime.datetime) + +*Convert `dt` from UTC to local time* + +``` python +dt = datetime(2000,1,1,12) +print(f'{dt} UTC is {utc2local(dt)} local time') +``` + + 2000-01-01 12:00:00 UTC is 2000-01-01 22:00:00+10:00 local time + +------------------------------------------------------------------------ + +source + +### local2utc + +> local2utc (dt:datetime.datetime) + +*Convert `dt` from local to UTC time* + +``` python +print(f'{dt} local is {local2utc(dt)} UTC time') +``` + + 2000-01-01 12:00:00 local is 2000-01-01 02:00:00+00:00 UTC time + +------------------------------------------------------------------------ + +source + +### trace + +> trace (f) + +*Add `set_trace` to an existing function `f`* + +You can add a breakpoint to an existing function, e.g: + +``` python +Path.cwd = trace(Path.cwd) +Path.cwd() +``` + +Now, when the function is called it will drop you into the debugger. +Note, you must issue the `s` command when you begin to step into the +function that is being traced. + +------------------------------------------------------------------------ + +source + +### modified_env + +> modified_env (*delete, **replace) + +*Context manager temporarily modifying `os.environ` by deleting `delete` +and replacing `replace`* + +``` python +# USER isn't in Cloud Linux Environments +env_test = 'USERNAME' if sys.platform == "win32" else 'SHELL' +oldusr = os.environ[env_test] + +replace_param = {env_test: 'a'} +with modified_env('PATH', **replace_param): + test_eq(os.environ[env_test], 'a') + assert 'PATH' not in os.environ + +assert 'PATH' in os.environ +test_eq(os.environ[env_test], oldusr) +``` + +------------------------------------------------------------------------ + +source + +#### ContextManagers + +> ContextManagers (mgrs) + +*Wrapper for `contextlib.ExitStack` which enters a collection of context +managers* + +------------------------------------------------------------------------ + +source + +### shufflish + +> shufflish (x, pct=0.04) + +*Randomly relocate items of `x` up to `pct` of `len(x)` from their +starting location* + +------------------------------------------------------------------------ + +source + +### console_help + +> console_help (libname:str) + +*Show help for all console scripts from `libname`* + + + + + + + + + + + + + + + + +
TypeDetails
libnamestrname of library for console script listing
+ +------------------------------------------------------------------------ + +source + +### hl_md + +> hl_md (s, lang='xml', show=True) + +*Syntax highlight `s` using `lang`.* + +When we display code in a notebook, it’s nice to highlight it, so we +create a function to simplify that: + +``` python +hl_md('a child') +``` + +``` xml +a child +``` + +------------------------------------------------------------------------ + +source + +### type2str + +> type2str (typ:type) + +*Stringify `typ`* + +``` python +test_eq(type2str(Optional[float]), 'Union[float, None]') +``` + +------------------------------------------------------------------------ + +source + +### dataclass_src + +> dataclass_src (cls) + +``` python +DC = make_dataclass('DC', [('x', int), ('y', Optional[float], None), ('z', float, None)]) +print(dataclass_src(DC)) +``` + + @dataclass + class DC: + x: int + y: Union[float, None] = None + z: float = None + +------------------------------------------------------------------------ + +source + +### Unset + +> Unset (value, names=None, module=None, qualname=None, type=None, start=1) + +*An enumeration.* + +------------------------------------------------------------------------ + +source + +### nullable_dc + +> nullable_dc (cls) + +*Like `dataclass`, but default of `UNSET` added to fields without +defaults* + +``` python +@nullable_dc +class Person: name: str; age: int; city: str = "Unknown" +Person(name="Bob") +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +------------------------------------------------------------------------ + +source + +### make_nullable + +> make_nullable (clas) + +``` python +@dataclass +class Person: name: str; age: int; city: str = "Unknown" + +make_nullable(Person) +Person("Bob", city='NY') +``` + + Person(name='Bob', age=UNSET, city='NY') + +``` python +Person(name="Bob") +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +``` python +Person("Bob", 34) +``` + + Person(name='Bob', age=34, city='Unknown') + +------------------------------------------------------------------------ + +source + +### flexiclass + +> flexiclass (cls) + +*Convert `cls` into a `dataclass` like +[`make_nullable`](https://fastcore.fast.ai/xtras.html#make_nullable). +Converts in place and also returns the result.* + + + + + + + + + + + + + + + + + + + + + +
TypeDetails
clsThe class to convert
Returnsdataclass
+ +This can be used as a decorator… + +``` python +@flexiclass +class Person: name: str; age: int; city: str = "Unknown" + +bob = Person(name="Bob") +bob +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +…or can update the behavior of an existing class (or dataclass): + +``` python +class Person: name: str; age: int; city: str = "Unknown" + +flexiclass(Person) +bob = Person(name="Bob") +bob +``` + + Person(name='Bob', age=UNSET, city='Unknown') + +Action occurs in-place: + +``` python +class Person: name: str; age: int; city: str = "Unknown" + +flexiclass(Person) +is_dataclass(Person) +``` + + True + +------------------------------------------------------------------------ + +source + +### asdict + +> asdict (o) + +*Convert `o` to a `dict`, supporting dataclasses, namedtuples, +iterables, and `__dict__` attrs.* + +Any `UNSET` values are not included. + +``` python +asdict(bob) +``` + + {'name': 'Bob', 'city': 'Unknown'} + +To customise dict conversion behavior for a class, implement the +`_asdict` method (this is used in the Python stdlib for named tuples). + +------------------------------------------------------------------------ + +source + +### is_typeddict + +> is_typeddict (cls:type) + +*Check if `cls` is a `TypedDict`* + +``` python +class MyDict(TypedDict): name:str + +assert is_typeddict(MyDict) +assert not is_typeddict({'a':1}) +``` + +------------------------------------------------------------------------ + +source + +### is_namedtuple + +> is_namedtuple (cls) + +*`True` if `cls` is a namedtuple type* + +``` python +assert is_namedtuple(namedtuple('tst', ['a'])) +assert not is_namedtuple(tuple) +``` + +------------------------------------------------------------------------ + +source + +### flexicache + +> flexicache (*funcs, maxsize=128) + +*Like `lru_cache`, but customisable with policy `funcs`* + +This is a flexible lru cache function that you can pass a list of +functions to. Those functions define the cache eviction policy. For +instance, +[`time_policy`](https://fastcore.fast.ai/xtras.html#time_policy) is +provided for time-based cache eviction, and +[`mtime_policy`](https://fastcore.fast.ai/xtras.html#mtime_policy) +evicts based on a file’s modified-time changing. The policy functions +are passed the last value that function returned was (initially `None`), +and return a new value to indicate the cache has expired. When the cache +expires, all functions are called with `None` to force getting new +values. + +------------------------------------------------------------------------ + +source + +### time_policy + +> time_policy (seconds) + +*A [`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) policy +that expires cached items after `seconds` have passed* + +------------------------------------------------------------------------ + +source + +### mtime_policy + +> mtime_policy (filepath) + +*A [`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) policy +that expires cached items after `filepath` modified-time changes* + +``` python +@flexicache(time_policy(10), mtime_policy('000_tour.ipynb')) +def cached_func(x, y): return x+y + +cached_func(1,2) +``` + + 3 + +``` python +@flexicache(time_policy(10), mtime_policy('000_tour.ipynb')) +async def cached_func(x, y): return x+y + +await cached_func(1,2) +await cached_func(1,2) +``` + + 3 + +------------------------------------------------------------------------ + +source + +### timed_cache + +> timed_cache (seconds=60, maxsize=128) + +*Like `lru_cache`, but also with time-based eviction* + +This function is a small convenience wrapper for using +[`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) with +[`time_policy`](https://fastcore.fast.ai/xtras.html#time_policy). + +``` python +@timed_cache(seconds=0.05, maxsize=2) +def cached_func(x): return x * 2, time() + +# basic caching +result1, time1 = cached_func(2) +test_eq(result1, 4) +sleep(0.001) +result2, time2 = cached_func(2) +test_eq(result2, 4) +test_eq(time1, time2) + +# caching different values +result3, _ = cached_func(3) +test_eq(result3, 6) + +# maxsize +_, time4 = cached_func(4) +_, time2_new = cached_func(2) +test_close(time2, time2_new, eps=0.1) +_, time3_new = cached_func(3) +test_ne(time3_new, time()) + +# time expiration +sleep(0.05) +_, time4_new = cached_func(4) +test_ne(time4_new, time()) +```
+ + +