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)')
```
------------------------------------------------------------------------
source
### splice_sig
``` python
def splice_sig(
wrapper, fn, skips:VAR_POSITIONAL
):
```
*Replace `*args`/`**kwargs` in wrapper’s sig with fn’s params (minus
skips)*
[`splice_sig`](https://fastcore.fast.ai/meta.html#splice_sig) is a
utility for building decorators that need to “wrap” a function while
adding their own parameters. It takes a wrapper function with
`*args`/`**kwargs` placeholders and splices in the wrapped function’s
parameters at that position.
#### The Problem
When writing decorators that add context parameters (like `id`, `dname`,
`log_changed`), you face awkward choices:
1. **Hardcode the signature** — brittle, doesn’t adapt to decorated
functions
2. **Manual signature surgery** — verbose, error-prone, requires
bookkeeping
3. **Separate “template” functions** — extra indirection, easy to
desync
#### The Solution
Define your wrapper with `*args`/`**kwargs` at the splice point:
``` python
def wrapper(id, *args, dname='', **kw):
# ↑ pre ↑ splice ↑ post
```
Then call `splice_sig(wrapper, fn, skips)` to: 1. Take params **before**
`*args` from the wrapper (“pre”) 2. Take params from `fn`, excluding
`skips` (“middle”) 3. Take keyword-only params **after** `*args` from
the wrapper (“post”) 4. Apply `wraps(fn)` to preserve metadata
Key Points:
- The wrapper’s `*args` marks where the inner function’s params get
inserted
- Params before `*args` become positional; keyword-only params after
stay keyword-only
- `skips` removes params from `fn` (typically the “data” param your
wrapper provides)
- `wraps(fn)` is applied automatically, preserving `__name__`,
`__doc__`, etc.
#### Examples
*Simple logging decorator:*
``` python
def logged(fn):
"Add verbose flag that logs input/output"
def wrapper(text, *args, verbose=False, **kw):
result = fn(text, *args, **kw)
if verbose: print(f'{fn.__name__}({text!r}) → {result!r}')
return result
return splice_sig(wrapper, fn, 'text')
@logged
def shout(text, times=1):
"Uppercase and repeat"
return text.upper() * times
```
``` python
shout('hi', 2, verbose=True)
```
shout('hi') → 'HIHI'
'HIHI'
The signature combines the function and the wrapper:
``` python
shout?
```
``` python
def shout(
text, times:int=1, verbose:bool=False
):
```
Uppercase and repeat
**File:**
`/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_65408/3741654590.py`
**Type:** function
*API client wrapper:*
``` python
def api_call(fn):
"Decorator adding auth and retry params to API transform functions"
def wrapper(endpoint, *args, auth_token=None, retries=3, **kw):
for attempt in range(retries):
resp = httpx.get(endpoint, headers={'Authorization': auth_token})
if resp.ok: break
return fn(resp.json(), *args, **kw)
return splice_sig(wrapper, fn, 'data',)
@api_call
def extract_ids(data, key='id'):
"Get `key` value from items in `data['results']`"
return [item[key] for item in data.get('results', [])]
```
``` python
extract_ids?
```
``` python
def extract_ids(
endpoint, key:str='id', auth_token:NoneType=None, retries:int=3
):
```
Get `key` value from items in `data['results']`
**File:**
`/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_65408/3604814096.py`
**Type:** function
---
# Source: https://fastcore.fast.ai/net.html.md
# Network functionality
``` python
from fastcore.test import *
from nbdev.showdoc import *
from fastcore.nb_imports import *
```
## URLs
------------------------------------------------------------------------
source
### urlquote
``` python
def 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
``` python
def urlwrap(
url, data:NoneType=None, headers:NoneType=None
):
```
*Wrap `url` in a urllib `Request` with
[`urlquote`](https://fastcore.fast.ai/net.html#urlquote)*
------------------------------------------------------------------------
source
#### HTTP4xxClientError
``` python
def HTTP4xxClientError(
url, code, msg, hdrs, fp
):
```
*Base class for client exceptions (code 4xx) from `url*` functions*
------------------------------------------------------------------------
source
#### HTTP5xxServerError
``` python
def HTTP5xxServerError(
url, code, msg, hdrs, fp
):
```
*Base class for server exceptions (code 5xx) from `url*` functions*
------------------------------------------------------------------------
source
### urlopener
``` python
def urlopener(
):
```
------------------------------------------------------------------------
source
### urlopen
``` python
def urlopen(
url, data:NoneType=None, headers:NoneType=None, timeout:NoneType=None, kwargs:VAR_KEYWORD
):
```
*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",
"status": "404"
}
------------------------------------------------------------------------
source
### urlread
``` python
def urlread(
url, data:NoneType=None, headers:NoneType=None, decode:bool=True, return_json:bool=False,
return_headers:bool=False, timeout:NoneType=None, kwargs:VAR_KEYWORD
):
```
*Retrieve `url`, using `data` dict or `kwargs` to `POST` if present*
------------------------------------------------------------------------
source
### urljson
``` python
def urljson(
url, data:NoneType=None, headers:NoneType=None, timeout:NoneType=None
):
```
*Retrieve `url` and decode json*
``` python
test_eq(urljson('https://httpbin.org/get')['headers']['User-Agent'], url_default_headers['User-Agent'])
```
------------------------------------------------------------------------
source
### urlcheck
``` python
def urlcheck(
url, headers:NoneType=None, timeout:int=10
):
```
------------------------------------------------------------------------
source
### urlclean
``` python
def 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
``` python
def urlretrieve(
url, filename:NoneType=None, reporthook:NoneType=None, data:NoneType=None, headers:NoneType=None,
timeout:NoneType=None
):
```
*Same as `urllib.request.urlretrieve` but also works with `Request`
objects*
------------------------------------------------------------------------
source
### urldest
``` python
def urldest(
url, dest:NoneType=None
):
```
------------------------------------------------------------------------
source
### urlsave
``` python
def urlsave(
url, dest:NoneType=None, reporthook:NoneType=None, headers:NoneType=None, timeout:NoneType=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
``` python
def 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
``` python
def urlrequest(
url, verb, headers:NoneType=None, route:NoneType=None, query:NoneType=None, data:NoneType=None,
json_data:bool=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
``` python
def summary(
skip:NoneType=None
)->dict:
```
*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
``` python
def urlsend(
url, verb, headers:NoneType=None, decode:bool=True, route:NoneType=None, query:NoneType=None, data:NoneType=None,
json_data:bool=True, return_json:bool=True, return_headers:bool=False, debug:NoneType=None,
timeout:NoneType=None
):
```
*Send request with
[`urlrequest`](https://fastcore.fast.ai/net.html#urlrequest), converting
result to json if `return_json`*
------------------------------------------------------------------------
source
### do_request
``` python
def do_request(
url, post:bool=False, headers:NoneType=None, data:VAR_KEYWORD
):
```
*Call GET or json-encoded POST on `url`, depending on `post`*
## Basic client/server
------------------------------------------------------------------------
source
### start_server
``` python
def start_server(
port, host:NoneType=None, dgram:bool=False, reuse_addr:bool=True, n_queue:NoneType=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
``` python
def start_client(
port, host:NoneType=None, dgram:bool=False
):
```
*Create a `socket` client on `port`, with optional `host`, of type
`dgram`*
------------------------------------------------------------------------
source
### tobytes
``` python
def tobytes(
s:str
)->bytes:
```
*Convert `s` into HTTP-ready bytes format*
``` python
test_eq(tobytes('foo\nbar'), b'foo\r\nbar')
```
------------------------------------------------------------------------
source
### http_response
``` python
def http_response(
body:NoneType=None, status:int=200, hdrs:NoneType=None, kwargs:VAR_KEYWORD
):
```
*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
``` python
def recv_once(
host:str='localhost', port:int=8000
):
```
*Spawn a thread to receive a single HTTP request and store in `d['r']`*
---
# Source: https://fastcore.fast.ai/parallel.html.md
# Parallel
``` python
from fastcore.test import *
from nbdev.showdoc import *
from fastcore.nb_imports import *
```
------------------------------------------------------------------------
source
### threaded
``` python
def threaded(
process:bool=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
``` python
def 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
``` python
def 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
``` python
def parallelable(
param_name, num_workers, f:NoneType=None
):
```
------------------------------------------------------------------------
source
#### ThreadPoolExecutor
``` python
def ThreadPoolExecutor(
max_workers:int=4, on_exc:builtin_function_or_method=print, pause:int=0, kwargs:VAR_KEYWORD
):
```
*Same as Python’s ThreadPoolExecutor, except can pass `max_workers==0`
for serial execution*
------------------------------------------------------------------------
source
#### ProcessPoolExecutor
``` python
def ProcessPoolExecutor(
max_workers:int=4, on_exc:builtin_function_or_method=print, pause:int=0, mp_context:NoneType=None,
initializer:NoneType=None, initargs:tuple=(), max_tasks_per_child:NoneType=None
):
```
*Same as Python’s ProcessPoolExecutor, except can pass `max_workers==0`
for serial execution*
------------------------------------------------------------------------
source
### parallel
``` python
def parallel(
f, items, args:VAR_POSITIONAL, n_workers:int=4, total:NoneType=None, progress:NoneType=None, pause:int=0,
method:NoneType=None, threadpool:bool=False, timeout:NoneType=None, chunksize:int=1, kwargs:VAR_KEYWORD
):
```
*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 2026-01-29 10:59:46.704165
1 2026-01-29 10:59:46.954594
2 2026-01-29 10:59:47.204557
3 2026-01-29 10:59:47.455629
4 2026-01-29 10:59:47.707717
------------------------------------------------------------------------
source
### parallel_async
``` python
def parallel_async(
f, items, args:VAR_POSITIONAL, n_workers:int=16, timeout:NoneType=None, chunksize:int=1,
on_exc:builtin_function_or_method=print, kwargs:VAR_KEYWORD
):
```
*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 2026-01-29 10:59:48.007998 0.21615721710303581
3 2026-01-29 10:59:48.027639 0.018363443750668673
2 2026-01-29 10:59:48.487320 0.6952425451756079
1 2026-01-29 10:59:48.550808 0.7519131063729974
5 2026-01-29 10:59:48.703458 0.21434694863260173
4 2026-01-29 10:59:48.914600 0.8860713333269471
------------------------------------------------------------------------
source
### run_procs
``` python
def run_procs(
f, f_done, args
):
```
*Call `f` for each item in `args` in parallel, yielding `f_done`*
------------------------------------------------------------------------
source
### parallel_gen
``` python
def parallel_gen(
cls, items, n_workers:int=4, kwargs:VAR_KEYWORD
):
```
*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)
```
---
# Source: https://fastcore.fast.ai/script.html.md
# 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.
> **Boolean Arguments Default to False**
>
> Arguments of type `bool` or
> [`store_true`](https://fastcore.fast.ai/script.html#store_true)
> default to `False` regardless of whether you provide a default or not.
> Use [`bool_arg`](https://fastcore.fast.ai/script.html#bool_arg) as the
> type instead of `bool` if you want to set a default value of True. For
> example:
>
> ``` python
> @call_parse
> def main(msg:str="Hi", # The message
> upper:bool_arg=True): # Convert to uppercase?
> ```
## 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
``` python
def 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
``` python
def 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
``` python
def bool_arg(
v
):
```
*Use as `type` for [`Param`](https://fastcore.fast.ai/script.html#param)
to get `bool` behavior*
------------------------------------------------------------------------
source
### clean_type_str
``` python
def 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
``` python
def Param(
help:str='', type:NoneType=None, opt:bool=True, action:NoneType=None, nargs:NoneType=None, const:NoneType=None,
choices:NoneType=None, required:NoneType=None, default:NoneType=None, version:NoneType=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`,
`version` (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
``` python
def 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`*
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),
v:Param("Print version", action='version', version='%(prog)s 2.0.0'),
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] [--v] [--b B] [--c {aa,bb,cc}] required a
my docs
positional arguments:
required Required param
a param 1
options:
-h, --help show this help message and exit
--v Print version
--b B param 2 (default: test)
--c {aa,bb,cc} param 3 (default: aa)
We can also check the version and help flags are working.
``` python
try: p.parse_args(['--v'])
except: pass
```
progname 2.0.0
``` python
try: p.parse_args(['-h'])
except: pass
```
usage: progname [-h] [--v] [--b B] [--c {aa,bb,cc}] required a
my docs
positional arguments:
required Required param
a param 1
options:
-h, --help show this help message and exit
--v Print version
--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
options:
-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 Union types:
``` python
def h(n:int|str, exts:str|list=None):
"Test union types"
p = anno_parser(h, 'test')
p.print_help()
```
usage: test [-h] [--exts EXTS] n
Test union types
positional arguments:
n
options:
-h, --help show this help message and exit
--exts EXTS
``` python
test_eq(p.parse_args(['42', '--exts', 'py']).n, 42)
test_eq(p.parse_args(['hello']).n, 'hello')
```
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).
------------------------------------------------------------------------
source
### args_from_prog
``` python
def args_from_prog(
func, prog
):
```
*Extract args from `prog`*
``` 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
### set_ctx
``` python
def set_ctx(
cv, val:bool=True
):
```
------------------------------------------------------------------------
source
### call_parse
``` python
def call_parse(
func:NoneType=None, nested:bool=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.
---
# Source: https://fastcore.fast.ai/test.html.md
# 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
``` python
def test_fail(
f, msg:str='', contains:str='', exc:type=Exception, args:NoneType=None, kwargs:NoneType=None
):
```
*Fails with `msg` unless `f()` raises an exception of type `exc` 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)
def _fail(): raise ValueError()
test_fail(_fail, exc=ValueError)
test_fail(lambda: test_fail(_fail, exc=IndexError), exc=AssertionError)
```
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
``` python
def test(
a, b, cmp, cname:NoneType=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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def is_close(
a, b, eps:float=1e-05
):
```
*Is `a` within `eps` of `b`*
------------------------------------------------------------------------
source
### test_close
``` python
def test_close(
a, b, eps:float=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
``` python
def 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
``` python
def 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
``` python
def test_stdout(
f, exp, regex:bool=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
``` python
def test_warns(
f, show:bool=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
```

``` python
im = Image.open(TEST_IMAGE_BW).resize((128,128)); im
```

------------------------------------------------------------------------
source
### test_fig_exists
``` python
def test_fig_exists(
ax
):
```
*Test there is a figure displayed in `ax`*
``` python
fig,ax = plt.subplots()
ax.imshow(array(im));
```

``` python
test_fig_exists(ax)
```
------------------------------------------------------------------------
source
### ExceptionExpected
``` python
def ExceptionExpected(
ex:type=Exception, regex:str=''
):
```
*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()`](https://fastcore.fast.ai/test.html#exceptionexpected).
``` python
with exception: _tst_1()
```
---
# Source: https://fastcore.fast.ai/tour.html.md
# 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('000_tour')
```
[Open `000_tour` in
Colab](https://colab.research.google.com/github/AnswerDotAI/fastcore/blob/main/nbs/000_tour.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()
```
[Path('images/mnist3.png'), Path('images/att_00000.png'), Path('images/att_00005.png'), Path('images/att_00007.png'), Path('images/att_00006.png'), Path('images/puppy.jpg')]
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)
```
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)
```
[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
```
[14, 19, 13, 11, 8, 9, 0, 6, 4, 15, 1, 17, 16, 5, 3, 10, 2, 7, 12, 18]
[`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]
```
[13, 8, 0]
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))
```
[1, 9, 11, 12, 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)
```
[1, 2, 3, 4]
---
# Source: https://fastcore.fast.ai/xdg.html.md
# 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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def xdg_data_dirs(
):
```
*Paths corresponding to XDG_DATA_DIRS\`*
------------------------------------------------------------------------
source
### xdg_data_home
``` python
def 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
``` python
def xdg_runtime_dir(
):
```
*Path corresponding to `XDG_RUNTIME_DIR`*
------------------------------------------------------------------------
source
### xdg_state_home
``` python
def 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
---
# Source: https://fastcore.fast.ai/xml.html.md
# XML
``` python
from IPython.display import Markdown
from pprint import pprint
from fastcore.test import test_eq, test_ne
```
## FT functions
------------------------------------------------------------------------
source
### attrmap
``` python
def attrmap(
o
):
```
------------------------------------------------------------------------
source
### valmap
``` python
def valmap(
o
):
```
------------------------------------------------------------------------
source
### FT
``` python
def FT(
tag:str, cs:tuple, attrs:dict=None, void_:bool=False, kwargs:VAR_KEYWORD
):
```
*A ‘Fast Tag’ structure, containing `tag`,`children`,and `attrs`*
------------------------------------------------------------------------
source
### ft
``` python
def ft(
tag:str, c:VAR_POSITIONAL, void_:bool=False, attrmap:callable=attrmap, valmap:callable=valmap, ft_cls:type=FT,
kw:VAR_KEYWORD
):
```
*Create an [`FT`](https://fastcore.fast.ai/xml.html#ft) structure for
[`to_xml()`](https://fastcore.fast.ai/xml.html#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
``` python
def Html(
c:VAR_POSITIONAL, doctype:bool=True, kwargs:VAR_KEYWORD
)->FT:
```
*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
``` python
def Safe(
args:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):
```
*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
``` python
def to_xml(
elm, lvl:int=0, indent:bool=True, do_escape:bool=True
):
```
*Convert [`ft`](https://fastcore.fast.ai/xml.html#ft) element tree into
an XML string*
------------------------------------------------------------------------
source
### FT.\_\_html\_\_
``` python
def __html__(
):
```
``` python
to_xml(Div(""), do_escape=True)
```
"<script>alert('XSS')</script>
\n"
``` python
h = to_xml(samp, do_escape=False)
print(h)
```
Some page
``` python
c = I('hello')
print(c)
```
hello
``` python
c
```
hello
``` 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 = """
"""
assert h == expected_output
```
``` python
print(h)
```
``` python
h = to_xml(samp, indent=False)
print(h)
```
Some page
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
FT attributes are rendered with
[`to_xml`](https://fastcore.fast.ai/xml.html#to_xml):
``` python
print(to_xml(P('hi', value=Div('ho'))))
```
hi
FT components also stringify with
[`to_xml`](https://fastcore.fast.ai/xml.html#to_xml):
``` python
print(Div('ho'))
```
ho
------------------------------------------------------------------------
source
### FT.\_\_hash\_\_
``` python
def __hash__(
):
```
*Return hash(self).*
------------------------------------------------------------------------
source
### FT.\_\_eq\_\_
``` python
def __eq__(
other
):
```
*Return self==value.*
[`FT`](https://fastcore.fast.ai/xml.html#ft) object equality and hashing
is based on tag, attrs, and children.
``` python
test_eq(Div('hello', id='x'), Div('hello', id='x'))
test_ne(Div('hello'), Div('goodbye'))
test_ne(Div('hello', id='a'), Div('hello', id='b'))
test_ne(P('hello'), Div('hello'))
test_eq(hash(Div('hello', id='x')), hash(Div('hello', id='x')))
assert hash(Div('hello')), hash(Div('goodbye'))
```
## Display
------------------------------------------------------------------------
source
### highlight
``` python
def highlight(
s, lang:str='html'
):
```
*Markdown to syntax-highlight `s` in language `lang`*
------------------------------------------------------------------------
source
### showtags
``` python
def 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
hl_md(
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
```
------------------------------------------------------------------------
source
### **getattr**
``` python
def __getattr__(
tag
):
```
---
# Source: https://fastcore.fast.ai/xtras.html.md
# Utility functions
## File Functions
Utilities (other than extensions to Pathlib.Path) for dealing with IO.
------------------------------------------------------------------------
source
### walk
``` python
def walk(
path:Path | str, # path to start searching
symlinks:bool=True, # follow symlinks?
keep_file:callable=ret_true, # function that returns True for wanted files
keep_folder:callable=ret_true, # function that returns True for folders to enter
skip_folder:callable=ret_false, # function that returns True for folders to skip
func:callable=join, # function to apply to each matched file
ret_folders:bool=False, # return folders, not just files
sort:bool=True, # sort files by name within each folder
):
```
*Generator version of `os.walk`, using functions to filter files and
folders*
------------------------------------------------------------------------
source
### exttypes
``` python
def exttypes(
types
):
```
*Get exts for comma-separated or list `typ`; if not found in list,
return list with just `types`.* Supported: py, js, java, c, cpp, rb, r,
ex, sh, web, doc, cfg
``` python
print(exttypes('py,doc'))
print(exttypes('zig,txt'))
```
['ipynb', 'py', 'md', 'rst']
['zig', 'txt']
------------------------------------------------------------------------
source
### globtastic
``` python
def globtastic(
path:Path | str='.', # path to start searching
recursive:bool=True, # search subfolders
symlinks:bool=True, # follow symlinks?
file_glob:str=None, # Only include files matching glob
file_re:str=None, # Only include files matching regex
folder_re:str=None, # Only enter folders matching regex
skip_file_glob:str=None, # Skip files matching glob
skip_file_re:str=None, # Skip files matching regex
skip_folder_re:str=None, # Skip folders matching regex,
func:callable=join, # function to apply to each matched file
ret_folders:bool=False, # return folders, not just files
sort:bool=True, # sort files by name within each folder
types:str | list=None, # list or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
exts:str | list=None, # list or comma-separated str of exts to include
)->L: # Paths to matched files
```
*A more powerful `glob`, including regex matches, symlink handling, and
skip parameters*
``` python
globtastic('.', skip_folder_re='^[_.]', folder_re='core', file_glob='*.*py*', file_re='c')
```
['./fastcore/basics.py', './fastcore/dispatch.py', './fastcore/docments.py', './fastcore/docscrape.py', './fastcore/script.py']
``` python
globtastic(skip_folder_re='^[_.]', folder_re='core', types='py', file_re='c', skip_file_re='^_', sort=True)
```
['./fastcore/all.py', './fastcore/ansi.py', './fastcore/basics.py', './fastcore/dispatch.py', './fastcore/docments.py', './fastcore/docscrape.py', './fastcore/foundation.py', './fastcore/imghdr.py', './fastcore/imports.py', './fastcore/meta.py', './fastcore/nb_imports.py', './fastcore/net.py', './fastcore/parallel.py', './fastcore/py2pyi.py', './fastcore/script.py', './fastcore/shutil.py', './fastcore/style.py', './fastcore/test.py', './fastcore/tools.py', './fastcore/transform.py', './fastcore/utils.py', './fastcore/xdg.py', './fastcore/xml.py', './fastcore/xtras.py']
------------------------------------------------------------------------
source
### pglob
``` python
def pglob(
path:Path | str='.', # path to start searching
func:callable=Path, # function to apply to each matched file
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, ret_folders:bool=False, sort:bool=True,
types:str | list=None, exts:str | list=None
)->L: # Paths to matched files
```
*Shortcut for `globtastic(..., call=Path)`*
``` python
pglob('..', skip_folder_re='^[_.]', types='doc', skip_file_re='^_')[:6]
```
[Path('../CHANGELOG.md'), Path('../CODE_OF_CONDUCT.md'), Path('../CONTRIBUTING.md'), Path('../README.md')]
------------------------------------------------------------------------
source
### maybe_open
``` python
def maybe_open(
f, mode:str='r', kwargs:VAR_KEYWORD
):
```
*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).lower(), sys_encoding)
with open(fname) as fh: test_eq(_f(fh).lower(), 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
``` python
def mkdir(
path, exist_ok:bool=False, parents:bool=False, overwrite:bool=False, kwargs:VAR_KEYWORD
):
```
*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
``` python
def image_size(
fn
):
```
*Tuple of (w,h) for png, gif, or jpg; `None` otherwise*
``` python
test_eq(image_size(fname), (1200,803))
```
``` python
from PIL import Image
from IPython.display import Image as IPImage
```
``` python
img = Image.new('RGB', (50, 50), color='red')
img
```

------------------------------------------------------------------------
source
### img_bytes
``` python
def img_bytes(
img, fmt:str='PNG'
):
```
``` python
ib = img_bytes(img)
IPImage(ib)
```

------------------------------------------------------------------------
source
### detect_mime
``` python
def detect_mime(
data
):
```
*Get the MIME type for bytes `data`, covering common PDF, audio, video,
and image types*
``` python
detect_mime(ib)
```
'image/png'
``` python
test_is(detect_mime('this is a non-byte string'),None)
```
------------------------------------------------------------------------
source
### bunzip
``` python
def 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
``` python
def loads(
s, kw:VAR_KEYWORD
):
```
*Same as `json.loads`, but handles `None`*
------------------------------------------------------------------------
source
### loads_multi
``` python
def 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
``` python
def dumps(
obj, kw:VAR_KEYWORD
):
```
*Same as `json.dumps`, but uses `ujson` if available*
------------------------------------------------------------------------
source
### untar_dir
``` python
def untar_dir(
fname, dest, rename:bool=False, overwrite:bool=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
``` python
def 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
### shell
``` python
def shell(
args:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):
```
*Shortcut for `subprocess.run(shell=True)`*
------------------------------------------------------------------------
source
### ssh
``` python
def ssh(
host, args:str='', user:str='ubuntu', sock:NoneType=None
):
```
*Run SSH command with given arguments*
------------------------------------------------------------------------
source
### rsync_multi
``` python
def rsync_multi(
ip, files, user:str='ubuntu', persist:str='5m'
):
```
*Transfer multiple files with rename using persistent SSH connection*
------------------------------------------------------------------------
source
### run
``` python
def run(
cmd, rest:VAR_POSITIONAL, same_in_win:bool=False, ignore_ex:bool=False, as_bytes:bool=False, stderr:bool=True
):
```
*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 25.3 from /Users/rensdimmendaal/git/.venv/lib/python3.12/site-packages/pip (python 3.12)'
``` 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
``` python
def open_file(
fn, mode:str='r', kwargs:VAR_KEYWORD
):
```
*Open a file, with optional compression if gz or bz2 suffix*
------------------------------------------------------------------------
source
### save_pickle
``` python
def save_pickle(
fn, o
):
```
*Save a pickle file, to a file name or opened file*
------------------------------------------------------------------------
source
### load_pickle
``` python
def 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
``` python
def parse_env(
s:str=None, fn:Union[str, Path]=None
)->dict:
```
*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
``` python
def 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
``` python
def dict2obj(
d:NoneType=None, list_func:_L_Meta=L, dict_func:type=AttrDict, kwargs:VAR_KEYWORD
):
```
*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)
```
kwargs can also be used:
``` python
d3 = dict2obj(a=1, b=dict(c=2,d=3))
test_eq(d3.b.c, 2)
test_eq(d3.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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def 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
``` python
def readlines(
hint:int=-1, encoding:str='utf8'
):
```
*Read the content of `self`*
------------------------------------------------------------------------
source
### Path.read_json
``` python
def read_json(
encoding:NoneType=None, errors:NoneType=None
):
```
*Same as `read_text` followed by
[`loads`](https://fastcore.fast.ai/xtras.html#loads)*
------------------------------------------------------------------------
source
### Path.mk_write
``` python
def mk_write(
data, encoding:NoneType=None, errors:NoneType=None, mode:int=511, uid:int=-1, gid:int=-1
):
```
*Make all parent dirs of `self`, and write `data`*
------------------------------------------------------------------------
source
### Path.write_json
``` python
def write_json(
data, encoding:NoneType=None, errors:NoneType=None, mode:int=511, uid:int=-1, gid:int=-1, kw:VAR_KEYWORD
):
```
*Same as [`dumps`](https://fastcore.fast.ai/xtras.html#dumps)followed by
`mk_write`*
------------------------------------------------------------------------
source
### Path.relpath
``` python
def relpath(
start:NoneType=None
):
```
*Same as `os.path.relpath`, but returns a `Path`, and resolves symlinks*
``` python
p = Path('../fastcore/').resolve()
p
```
Path('/Users/rensdimmendaal/git/repos/fastcore/fastcore')
``` python
p.relpath(Path.cwd())
```
Path('../fastcore')
------------------------------------------------------------------------
source
### Path.ls
``` python
def ls(
n_max:NoneType=None, file_type:NoneType=None, file_exts:NoneType=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('llms.txt')
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.normpath
``` python
def normpath(
):
```
*Normalize path, eliminating double slashes, etc.*
`normpath` normalizes a path by collapsing redundant separators and
up-level references (e.g., `..`).
``` python
p = Path('foo//bar/../baz')
p.normpath()
```
Path('foo/baz')
------------------------------------------------------------------------
source
### Path.\_\_repr\_\_
``` python
def __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
``` python
def delete(
):
```
*Delete a file, symlink, or directory tree*
## Reindexing Collections
------------------------------------------------------------------------
source
#### ReindexCollection
``` python
def ReindexCollection(
coll, idxs:NoneType=None, cache:NoneType=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:
``` 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
``` python
def 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
``` python
def 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
``` python
def 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', 'g', 'h', 'c', 'd', 'b', 'f', 'e']
``` 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)
```
## [`SaveReturn`](https://fastcore.fast.ai/xtras.html#savereturn) and [`save_iter`](https://fastcore.fast.ai/xtras.html#save_iter) Variants
These utilities solve a common problem in Python: how to extract
additional information from generator functions beyond just the yielded
values.
In Python, generator functions can `yield` values and also `return` a
final value, but the return value is normally lost when you iterate over
the generator:
``` python
def example_generator():
total = 0
for i in range(3):
total += i
yield i
return total # This gets lost!
# The return value (3) is lost
values = list(example_generator()) # [0, 1, 2]
```
------------------------------------------------------------------------
source
### SaveReturn
``` python
def SaveReturn(
its
):
```
*Wrap an iterator such that the generator function’s return value is
stored in `.value`*
Exported source
``` python
class SaveReturn:
"Wrap an iterator such that the generator function's return value is stored in `.value`"
def __init__(self, its): self.its = its
def __iter__(self):
self.value = yield from self.its
return self.value
```
[`SaveReturn`](https://fastcore.fast.ai/xtras.html#savereturn) is the
simplest approach to solving this problem - it wraps any existing
(non-async) generator and captures its return value. This works because
`yield from` (used internally in
[`SaveReturn`](https://fastcore.fast.ai/xtras.html#savereturn)) returns
the value from the `return` of the generator function.
``` python
def sum_range(n):
total = 0
for i in range(n):
total += i
yield i
return total # This value is returned by yield from
sr = SaveReturn(sum_range(5))
values = list(sr) # This will consume the generator and get the return value
print(f"Values: {values}")
sr.value
```
Values: [0, 1, 2, 3, 4]
10
In order to provide an accurate signature for
[`save_iter`](https://fastcore.fast.ai/xtras.html#save_iter), we need a
version of `wraps` that removes leading parameters:
------------------------------------------------------------------------
source
### trim_wraps
``` python
def trim_wraps(
f, n:int=1
):
```
*Like wraps, but removes the first n parameters from the signature*
[`trim_wraps`](https://fastcore.fast.ai/xtras.html#trim_wraps) is a
decorator factory that works like `functools.wraps`, but removes the
first `n` parameters from the wrapped function’s signature. This is
useful when creating wrapper functions that consume some parameters
internally and shouldn’t expose them in the public API.
``` python
def adder(base, x, y): return base + x + y
def make_adder(base_value):
@trim_wraps(adder)
def _(x, y): return adder(base_value, x, y)
return _
add_10 = make_adder(10)
print(f"{add_10.__name__}{inspect.signature(add_10)}")
```
adder(x, y)
------------------------------------------------------------------------
source
### save_iter
``` python
def save_iter(
g
):
```
*Decorator that allows a generator function to store values in the
returned iterator object*
[`save_iter`](https://fastcore.fast.ai/xtras.html#save_iter) modifies
generator functions to store state in the iterator object itself. The
generator receives an object as its first parameter, which it can use to
store attributes. You can store values during iteration, not just at the
end, and you can store multiple attributes if needed.
``` python
@save_iter
def sum_range(o, n): # Note: 'o' parameter added
total = 0
for i in range(n):
total += i
yield i
o.value = total # Store directly on the iterator object
```
Because iternally
[`save_iter`](https://fastcore.fast.ai/xtras.html#save_iter) uses
[`trim_wraps`](https://fastcore.fast.ai/xtras.html#trim_wraps), the
signature of `sum_range` correctly shows that you should *not* pass `o`
to it; it’s injected by the decorating function.
``` python
print(sum_range.__signature__)
```
(n)
``` python
sr = sum_range(5)
print(f"Values: {list(sr)}")
print(f"Sum stored: {sr.value}")
```
Values: [0, 1, 2, 3, 4]
Sum stored: 10
------------------------------------------------------------------------
source
### asave_iter
``` python
def asave_iter(
g
):
```
*Like [`save_iter`](https://fastcore.fast.ai/xtras.html#save_iter), but
for async iterators*
[`asave_iter`](https://fastcore.fast.ai/xtras.html#asave_iter) provides
the same functionality as
[`save_iter`](https://fastcore.fast.ai/xtras.html#save_iter), but for
async generator functions. `yield from` and `return` can not be used
with async generator functions, so
[`SaveReturn`](https://fastcore.fast.ai/xtras.html#savereturn) can’t be
used here.
``` python
@asave_iter
async def asum_range(self, n):
total = 0
for i in range(n):
total += i
yield i
self.value = total
asr = asum_range(5)
print(f"Values: {[o async for o in asr]}")
print(f"Sum stored: {asr.value}")
```
Values: [0, 1, 2, 3, 4]
Sum stored: 10
## Other Helpers
------------------------------------------------------------------------
source
### unqid
``` python
def unqid(
seeded:bool=False
):
```
*Generate a unique id suitable for use as a Python identifier*
[`unqid`](https://fastcore.fast.ai/xtras.html#unqid) generates a random
unique identifier that is safe to use as a Python variable name (starts
with `_`, uses only alphanumeric characters and underscores). It’s based
on UUID4, encoded in URL-safe base64.
If `seeded=True`, uses `random.getrandbits` which respects
`random.seed()`, making it reproducible. Otherwise uses `uuid4()` which
is always random.
``` python
unqid()
```
'_xNpj56ekTqyk9MsVDYRnnA'
With seeding for reproducibility:
``` python
random.seed(42)
a = unqid(seeded=True)
random.seed(42)
b = unqid(seeded=True)
test_eq(a, b)
```
Without seeding - always unique:
``` python
test_ne(unqid(), unqid())
```
------------------------------------------------------------------------
source
### rtoken_hex
``` python
def rtoken_hex(
nbytes:int=16, # Number of bytes to generate
)->str: # hex string of length nbytes*2
```
*Generate a random hex string using Python’s random module.*
This is the same as `secrets.token_hex`, but is reproducible/seedable.
``` python
import secrets
```
``` python
secrets.token_hex(4),rtoken_hex(4)
```
('0100dd53', '8c7d7247')
------------------------------------------------------------------------
source
### friendly_name
``` python
def friendly_name(
levels:int=3, suffix:int=4
):
```
*Generate a random human-readable name with customizable word levels and
suffix length*
[`friendly_name`](https://fastcore.fast.ai/xtras.html#friendly_name)
generates random, human-readable names by combining adjectives, nouns,
verbs, and adverbs with a random alphanumeric suffix. This is useful for
creating memorable identifiers for temporary files, test data, or
user-friendly resource names.
``` python
friendly_name() # Default: 3 word levels + 4-char suffix
```
'objective-forest-builds-0y6d'
Names are hyphen-separated and follow the pattern
`adjective-noun-verb-adverb`, randomly chosen from lists of size 102,
116, 110, and 30, respectively. The `levels` param selects how many of
the names to include:
``` python
friendly_name(2) # 2 words + 4-char suffix
```
'lavender-hummingbird-divu'
`suffix` sets the length of the random alphanumeric ending. Each suffix
item is taken from the 36 options of lowercase letters plus digits.
``` python
friendly_name(4, 6) # All 4 word types + 6-char suffix
```
'elated-koala-begins-softly-zpqk51'
------------------------------------------------------------------------
source
### n_friendly_names
``` python
def n_friendly_names(
levels:int=3, suffix:int=4
):
```
*Number of possible combos for \`friendly_names*
The number of combinations if all levels are included is:
``` python
print(f'{n_friendly_names(4):,}')
```
65,581,614,489,600
The default settings give:
``` python
print(f'{n_friendly_names():,}')
```
2,186,053,816,320
------------------------------------------------------------------------
source
### exec_eval
``` python
def exec_eval(
code, # Code to exec/eval
g:NoneType=None, # Globals namespace dict
l:NoneType=None, # Locals namespace dict
):
```
*Evaluate `code` in `g` (defaults to `globals()`) and `l` (defaults to
`locals()`)*
This is a combination of `eval` and `exec`, which behaves like ipython
and Jupyter. If the last line is an expression, it is evaluated and the
result is returned:
``` python
exec_eval('''
def f(x): return x+1
f(1)
''')
```
2
By default, the code uses the caller’s globals and locals. For instance,
here `f` is available since it’s been added to our symbol table:
``` python
exec_eval('print(f(2))')
```
3
Pass a dict as the `g` param in order to use an arbitrary namespace:
``` python
exec_eval('print(f)', {'f': 'Hi I am f.'})
```
Hi I am f.
This function helps us identify the first declared raw function of a
dispatched function:
``` python
from plum import Function
```
``` python
def f1(x): return "Any"
def f2(x:int): return "Int"
df = Function(f1).dispatch(f1).dispatch(f2)
test_eq(_unwrapped_type_dispatch_func(df), f1)
```
------------------------------------------------------------------------
source
### get_source_link
``` python
def 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/AnswerDotAI/fastcore')
get_source_link(test_eq)
```
'https://github.com/AnswerDotAI/fastcore/fastcore/test.py#L38'
------------------------------------------------------------------------
source
### sparkline
``` python
def sparkline(
data, mn:NoneType=None, mx:NoneType=None, empty_zero:bool=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
``` python
def modify_exception(
e:Exception, # An exception
msg:str=None, # A custom message
replace:bool=False, # Whether to replace e.args with [msg]
)->Exception:
```
*Modifies `e` with a custom message attached*
``` 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
``` python
def round_multiple(
x, mult, round_down:bool=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
``` python
def 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
``` python
def join_path_file(
file, path, ext:str=''
):
```
*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
``` python
def autostart(
g
):
```
*Decorator that automatically starts a generator*
------------------------------------------------------------------------
source
#### EventTimer
``` python
def EventTimer(
store:int=5, span:int=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: 9, Freq/sec: 346.2
Most recent: ▅▃▇▁▇ 263.7 256.6 278.3 235.1 276.9
------------------------------------------------------------------------
source
### stringfmt_names
``` python
def stringfmt_names(
s:str
)->list:
```
*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
``` python
def PartialFormatter(
):
```
*A `string.Formatter` that doesn’t error on missing fields, and tracks
missing fields and unused args*
------------------------------------------------------------------------
source
### partial_format
``` python
def partial_format(
s:str, kwargs:VAR_KEYWORD
):
```
*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
### truncstr
``` python
def truncstr(
s:str, maxlen:int, suf:str='…', space:str='', sizevar:str=None
)->str:
```
*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!!')
```
`sizevar` lets you include the original string length in your suffix.
E.g when you set `sizevar='_n_'`, any `{_n_}` in your suffix gets
replaced with the actual length of the string before truncation. For
instance, here the `(11)` tells you the original string was 11
characters long:
``` python
test_eq(truncstr('hello world', 8, suf='…({_n_})', sizevar='_n_'), 'hel…(11)')
```
------------------------------------------------------------------------
source
### utc2local
``` python
def 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 13:00:00+01:00 local time
------------------------------------------------------------------------
source
### local2utc
``` python
def 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 11:00:00+00:00 UTC time
------------------------------------------------------------------------
source
### trace
``` python
def 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
``` python
def modified_env(
delete:VAR_POSITIONAL, replace:VAR_KEYWORD
):
```
*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
``` python
def ContextManagers(
mgrs
):
```
*Wrapper for `contextlib.ExitStack` which enters a collection of context
managers*
------------------------------------------------------------------------
source
### shufflish
``` python
def shufflish(
x, pct:float=0.04
):
```
*Randomly relocate items of `x` up to `pct` of `len(x)` from their
starting location*
------------------------------------------------------------------------
source
### console_help
``` python
def console_help(
libname:str
):
```
*Show help for all console scripts from `libname`*
------------------------------------------------------------------------
source
### hl_md
``` python
def hl_md(
s, lang:str='html', show:bool=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')
```
``` html
a child
```
------------------------------------------------------------------------
source
### type2str
``` python
def type2str(
typ:type
)->str:
```
*Stringify `typ`*
``` python
test_eq(type2str(Optional[float]), 'Union[float, None]')
```
------------------------------------------------------------------------
source
### dataclass_src
``` python
def 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
``` python
def Unset(
args:VAR_POSITIONAL, kwds:VAR_KEYWORD
):
```
*Create a collection of name/value pairs.*
Example enumeration:
> > > class Color(Enum): … RED = 1 … BLUE = 2 … GREEN = 3
Access them by:
- attribute access:
> > > Color.RED \
- value lookup:
> > > Color(1) \
- name lookup:
> > > Color\[‘RED’\] \
Enumerations can be iterated over, and know how many members they have:
> > > len(Color) 3
> > > list(Color) \[\, \, \ > > 3\>\]
Methods can be added to enumerations, and members can have their own
attributes – see the documentation for details.
------------------------------------------------------------------------
source
### nullable_dc
``` python
def 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
``` python
def 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
``` python
def flexiclass(
cls, # The class to convert
)->dataclass:
```
*Convert `cls` into a `dataclass` like
[`make_nullable`](https://fastcore.fast.ai/xtras.html#make_nullable).
Converts in place and also returns the result.*
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
``` python
def asdict(
o
)->dict:
```
*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'}
Set the optional `__flds__` parameter to customise the field list, and
the optional `__skip__` parameter to skip some names.
``` python
class CustomObj:
def __init__(self): self.a,self.b,self.c,self.d = 1,2,3,4
__flds__ = ['a','b','c','d']
__skip__ = ['b']
obj = CustomObj()
test_eq(asdict(obj), {'a': 1, 'c': 3, 'd': 4})
```
To customise dict conversion behavior for a class, implement the
`_asdict` method (this is used in the Python stdlib for named tuples).
------------------------------------------------------------------------
source
### vars_pub
``` python
def vars_pub(
x
):
```
*Get public non-skipped vars*
The [`vars_pub`](https://fastcore.fast.ai/xtras.html#vars_pub) function
returns a list of public (non-underscore-prefixed) variable names from
an object, excluding any names listed in the object’s optional
`__skip__` attribute.
``` python
class TestObj:
def __init__(self): self.pub_attr,self._priv_attr,self.another_pub,self.skip_me = 1,2,3,4
__skip__ = ['skip_me']
obj = TestObj()
test_eq(vars_pub(obj), ['pub_attr', 'another_pub'])
```
Without `__skip__`, all pub vars are returned
``` python
class SimpleObj:
def __init__(self): self.a,self._b,self.c = 1,2,3
simple = SimpleObj()
test_eq(vars_pub(simple), ['a', 'c'])
```
------------------------------------------------------------------------
source
### is_typeddict
``` python
def is_typeddict(
cls:type
)->bool:
```
*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
``` python
def is_namedtuple(
cls
):
```
*`True` if `cls` is a namedtuple type*
``` python
assert is_namedtuple(namedtuple('tst', ['a']))
assert not is_namedtuple(tuple)
```
------------------------------------------------------------------------
source
### CachedIter
``` python
def CachedIter(
o
):
```
*Cache the result returned by an iterator*
``` python
def f():
yield 1
return 2
r = CachedIter(f())
for o in r: print(o)
r.value
```
1
2
------------------------------------------------------------------------
source
### CachedAwaitable
``` python
def CachedAwaitable(
o
):
```
*Cache the result from an awaitable*
------------------------------------------------------------------------
source
### reawaitable
``` python
def reawaitable(
func:callable
):
```
*Wraps the result of an asynchronous function into an object which can
be awaited more than once*
`CachedCoro` and
[`reawaitable`](https://fastcore.fast.ai/xtras.html#reawaitable) are
partly based on [python issue
tracker](https://bugs.python.org/issue46622) code from Serhiy Storchaka.
They allow an awaitable to be called multiple times.
``` python
@reawaitable
async def fetch_data():
await asyncio.sleep(0.1)
return "data"
r = fetch_data()
print(await r) # "data"
print(await r) # "data" (no delay)
```
data
data
------------------------------------------------------------------------
source
### flexicache
``` python
def flexicache(
funcs:VAR_POSITIONAL, maxsize:int=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
``` python
def time_policy(
seconds
):
```
*A [`flexicache`](https://fastcore.fast.ai/xtras.html#flexicache) policy
that expires cached items after `seconds` have passed*
------------------------------------------------------------------------
source
### mtime_policy
``` python
def 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
print(await cached_func(1,2))
await cached_func(1,2)
```
3
3
------------------------------------------------------------------------
source
### timed_cache
``` python
def timed_cache(
seconds:int=60, maxsize:int=128
):
```
*Like `lru_cache`, but also with time-based eviction*
``` python
# demonstrate that flexicache is LRU
@flexicache(maxsize=2)
def cached_func(x): return time()
time_1 = cached_func(1)
test_eq(time_1, cached_func(1))
time_2 = cached_func(2)
test_eq(time_1, cached_func(1))
test_eq(time_2, cached_func(2))
time_3 = cached_func(3) # Removes 1
test_eq(time_2, cached_func(2)) # cache remains
test_eq(time_3, cached_func(3)) # cache remains
test_ne(time_1, cached_func(1)) # NEQ, removes 2
test_ne(time_2, cached_func(2)) # NEQ, removes 3
test_eq(cached_func(1), cached_func(1))
```
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())
```