# Basic


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

<!-- # Prologue -->

------------------------------------------------------------------------

# Type Utilities

## empty

A sentinel value for distinguishing “no value provided” from `None`.
Useful in function signatures where `None` is a valid argument.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L51"
target="_blank" style="float:right; font-size:smaller">source</a>

### is_empty

``` python

def is_empty(
    x
)->bool:

```

``` python
test_is(str(empty), 'empty')
empty
```

    empty

``` python
def test_empty() -> Empty: return empty
test_eq_type(test_empty(), empty)
```

# Data Structures

## AD

Attribute Dict - a dictionary that also allows attribute-style access.
Perfect for configuration objects or API responses where you want both
`config['key']` and `config.key` syntax.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L56"
target="_blank" style="float:right; font-size:smaller">source</a>

### AD

``` python

def AD(
    args:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):

```

*`dict` subclass that also provides access to keys as attrs*

``` python
# Basic usage: dict and attribute access
ad = AD(a=1, b=2)
test_eq(ad.a, 1)
test_eq(ad['a'], 1)

ad.b = 3
test_eq(ad.b, 3)
ad.update(a=4, b=5)
test_eq(ad.b, 5)
```

``` python
# Edge cases: empty dict, missing keys
ad = AD()
test_fail(lambda: ad.a)
test_fail(lambda: ad['b'])
```

## is_listy

Test whether `x` is iterable but not a string or bytes. Useful for
duck-typing checks where you want to handle collections but not strings.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L68"
target="_blank" style="float:right; font-size:smaller">source</a>

### is_listy_type

``` python

def is_listy_type(
    x
):

```

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L65"
target="_blank" style="float:right; font-size:smaller">source</a>

### is_listy

``` python

def is_listy(
    x
):

```

``` python
# Common usage: distinguish collections from strings
test_is(is_listy([1, 2, 3]), True)
test_is(is_listy((1, 2, 3)), True)
test_is(is_listy({'a': 1}), True)
test_is(is_listy('hello'), False)  # strings are NOT listy
test_is(is_listy(b'bytes'), False)  # bytes are NOT listy
test_is(is_listy(None), False)
```

``` python
# Coverage: ranges, generators, and type checking
test_is(is_listy(range(3)), True)
test_is(is_listy((i for i in range(3))), True)
test_is(is_listy(FC.L(1, 2, 3)), True)
test_is(is_listy({1, 2, 3}), True)
test_is(is_listy(r'raw string'), False)

test_is(is_listy_type(list), True)
test_is(is_listy_type(tuple), True)
test_is(is_listy_type(dict), True)
test_is(is_listy_type(str), False)
test_is(is_listy_type(NoneType), False)
```

## flatten

Recursively flatten nested collections into a single generator. Unlike
`itertools.chain`, handles arbitrarily nested structures.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L72"
target="_blank" style="float:right; font-size:smaller">source</a>

### flatten

``` python

def flatten(
    o
):

```

*Concatenate all collections and items as a generator*

``` python
# Basic usage: flatten arbitrarily nested structures
test_eq(list(flatten([1, 2, 3])), [1, 2, 3])  # already flat
test_eq(list(flatten([1, [2, 3]])), [1, 2, 3])
test_eq(list(flatten([[1], [2], [3]])), [1, 2, 3])
test_eq(list(flatten((1, (2, 3)))), [1, 2, 3])  # works with tuples
test_eq(list(flatten([('a', (2,)), ('c', (4,))])), ['a', 2, 'c', 4])
```

``` python
# Coverage: empty list
test_eq(list(flatten([])), [])
```

## Almost dataclass

A dataclass that allows you to specify fields with default values.

``` python
def _flds(o, *args): return tuple(_ for _ in inspect.get_annotations(o).keys())+args
```

``` python
class A:
    a: str
    b: str

_flds(A), _flds(A, 'c')
```

    (('a', 'b'), ('a', 'b', 'c'))

``` python
_flds(A)
```

    ()

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L84"
target="_blank" style="float:right; font-size:smaller">source</a>

### Fields

``` python

def Fields(
    args:VAR_POSITIONAL, # type: ignore
    but:str='', cast:bool=False, store_args:NoneType=None
):

```

*Set annotated fields of `self` extracted from caller’s locals; `*args`
-\> optional (None), `**kwargs` -\> defaults*

``` python
class A:
    a: str
    b: str
    def __init__(self, a:str,  b:str, c=None, d='ff'): Fields('e', f=7.3)#FC.store_attr(_flds(self), c=None, d=7.3)

o = A('aa', 'bb')
# test_eq(vars(o)['__stored_args__'], {'e': None, 'a': 'aa', 'b': 'bb', 'c': None, 'd': 'ff', 'f': 7.3})
test_eq(A.__fields__, ('a', 'b', 'c', 'd', 'e', 'f'))  # type: ignore
print(inspect.signature(A))
vars(o)
```

    (a: 'str', b: 'str', c=None, d='ff')

    {'a': 'aa', 'b': 'bb', 'c': None, 'd': 'ff', 'e': None, 'f': 7.3}

``` python
class Chapter(FC.AttrDict):
    "A chapter of a book"
    def __init__(self, num:int, lines:slice, title:str, pov:str, content:str): Fields(ids={})
    @property
    def meta(self): return [self[_] for _ in type(self).__fields__ if _ not in ('ids', 'content')]  # type: ignore
    @property
    def text(self):
        r = self.lines
        return '/n'.join(lines[r.start:r.stop])  # type: ignore

_ch = Chapter(1, slice(0, 10), 'title', 'pov', 'contents')
_ch
```

``` python
{ 'content': 'contents',
  'ids': {},
  'lines': slice(0, 10, None),
  'num': 1,
  'pov': 'pov',
  'title': 'title'}
```

``` python
inspect.signature(Chapter)
```

    <Signature (num: 'int', lines: 'slice', title: 'str', pov: 'str', content: 'str')>

``` python
_ch.meta
```

    [1, slice(0, 10, None), 'title', 'pov']

``` python
class Book(list):
    "A book"
    title: str
    author: str
    def __init__(self, title,  author): Fields()
    @property
    def front_matter(self): return FC.AttrDict({k:getattr(self, k) for k in ('title', 'author')})
    @property
    def body(self) -> Sequence[Chapter]: return tuple(*self)

bk = Book('Tiempo Atrás', 'Rafael Marín')
bk, bk.front_matter, bk.body
```

    ([], {'title': 'Tiempo Atrás', 'author': 'Rafael Marín'}, ())

# String Utilities

## shorten

Truncate strings intelligently from left, right, or center. Useful for
displaying long identifiers, paths, or data in logs and UIs.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L107"
target="_blank" style="float:right; font-size:smaller">source</a>

### shortens

``` python

def shortens(
    xs:Iterable[Any], mode:Literal['l', 'r', 'c']='l', limit:int=40, trunc:str='…', empty:str=''
):

```

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L97"
target="_blank" style="float:right; font-size:smaller">source</a>

### shorten

``` python

def shorten(
    x:Any, mode:Literal['l', 'r', 'c']='l', limit:int=40, trunc:str='…', empty:str=''
)->str:

```

``` python
# Basic usage: truncate from left (default), right, or center
uuid = 'ad2663b4-5ff5-40e3-a6ed-cc35f5627f8d'
test_eq(shorten(uuid, limit=17), '…a6ed-cc35f5627f8d')  # left truncation (default)
test_eq(shorten(uuid, 'r', limit=17), 'ad2663b4-5ff5-40e…')  # right truncation
test_eq(shorten(uuid, 'c', limit=17), 'ad2663b … 5627f8d')  # center truncation

test_eq(shorten('It was the best of times', limit=12), '…est of times')
```

``` python
# Coverage: short strings (no truncation), numbers, empty
test_eq(shorten(0), '0')
test_eq(shorten(234), '234')
test_eq(shorten('asdfgh'), 'asdfgh')  # under limit, no change
```

``` python
test_eq(list(shortens(['ad2663b4-5ff5-40e3-a6ed-cc35f5627f8d'], 'l', 10)), ['…35f5627f8d'])
test_eq(list(shortens(('abcdef', 'wertyu', 'sd'), 'r', 4)), ['abcd…', 'wert…', 'sd'])
test_eq(''.join(shortens('It was the best of times...'.split(), 'r', 1, '')), 'Iwtbot')
```

# Function Composition

## Runner

Create a function that runs multiple callables in sequence with the same
arguments. Useful for side-effects like logging, validation, and updates
without explicit chaining.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L114"
target="_blank" style="float:right; font-size:smaller">source</a>

### Runner

``` python

def Runner(
    fns:_FuncItem
)->Callable:

```

*Return a function that runs callables `fns` in sequence with same
arguments. * Only side-effects, no composition.

``` python
def f1(o,x,y=0): o.r += x+y
def f2(o,x,y=0): o.r += x*y
f3 = lambda o, x,y: print(o.r)

o = SimpleNamespace(r=0)
runner = Runner(f1, f2, f3)
runner(o, 2, 3)  # 11

Runner()(o, 2, 3)
test_eq(o.r, 11)
Runner([f1, f2], f3)(o, 2, 3)
test_eq(o.r, 22)
Runner([f1, [f2, f3]])(o, 2, 3)
test_eq(o.r, 33)
```

    11
    22
    33

# Object Utilities

Helpers for manipulating objects and their attributes.

## setattrs

Copy attributes from one object/dict to another. Works bidirectionally:
can copy from dict→object, object→dict, object→object, or dict→dict.

**Common use cases:** - Initialize objects from configuration dicts -
Copy specific fields between instances - Hydrate objects from API
responses

**Parameters:** - `dest`: Target object or dict to set attributes on -
`src`: Source object or dict to read from - `flds`: Comma-separated
field names (optional; defaults to all keys/public attrs)

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L125"
target="_blank" style="float:right; font-size:smaller">source</a>

### setattrs

``` python

def setattrs(
    dest, src, flds:str=''
):

```

*Set `flds` or keys() or dir() attributes from `src` into `dest`*

``` python
class A: a = 1; b = 2; c = 3
a = A()
setattrs(a, {'b': 22, 'd': 44})
test_eq(a.a, 1)
test_eq(a.b, 22)
test_eq(a.c, 3)
test_eq(a.d, 44)  # type: ignore
```

## val_at - Implementation Evolution

The following cells document the development process of `val_at`,
showing how the implementation evolved to handle increasingly complex
cases (dicts → lists → mixed → JSON strings). This section is preserved
for design rationale but can be skipped by most users.

**Skip to [Nested Data Access](#nested-data-access) if you just want to
use the function.**

### Initial attempt: Simple dict-only version

``` python
def val_at(element, json):
    return reduce(operator.getitem, element.split('.'), json)

j = {"app": {
    "Garden": {
        "Flowers": {
            "Red flower": "Rose",
            "White Flower": "Jasmine",
            "Yellow Flower": "Marigold"
        }
    },
    "Fruits": {
        "Yellow fruit": "Mango",
        "Green fruit": "Guava",
        "White Flower": "groovy"
    },
    "Trees": {
        "label": {
            "Yellow fruit": "Pumpkin",
            "White Flower": "Bogan"
        }
    }
}}

test_eq(val_at('app.Garden.Flowers.White Flower', j), 'Jasmine')
```

``` python
print((dp := 'app.Garden.Flowers'), reduce(operator.getitem, dp.split('.'), j))
```

    app.Garden.Flowers {'Red flower': 'Rose', 'White Flower': 'Jasmine', 'Yellow Flower': 'Marigold'}

``` python
def val_at(element, j:str):
    d = json.loads(j)
    return reduce(lambda d, k: d[k] if isinstance(d, Mapping) else d[int(k)], element.split('.'), d)

j2 = {"app": {
    "Garden": {
        "Flowers": {
            "Red flower": "Rose",
            "White Flower": "Jasmine",
            "Yellow Flower": "Marigold"
        }
    },
    "Fruits": {
        "Yellow fruit": ["Mango", {"Banana": ["Canary Island", "Puerto Rico"]}],
        "Green fruit": "Guava",
        "White Flower": "groovy"
    },
    "Trees": {
        "label": {
            "Yellow fruit": "Pumpkin",
            "White Flower": "Bogan"
        }
    }
}}
test_eq(val_at('app.Fruits.Yellow fruit.1.Banana.0', json.dumps(j2)), 'Canary Island')
```

``` python
apollo_astronauts = json.loads(Path('static/apollo_astronauts.json').read_text())
print((dp := 'Apollo 11.Michael Collins'), reduce(operator.getitem, dp.split('.'), apollo_astronauts))
```

    Apollo 11.Michael Collins {'Experience': 'Pilot on Gemini 10 and Command Module pilot on Apollo 11.', 'Place in history': 'Collins was the first person to perform two EVAs in one mission.', 'Fast fact': 'Collins says his "secret terror" was returning to Earth alone if the surface mission failed.', 'Lunar wisdom': 'I really believe that if the political leaders of the world could see their planet from a distance of 100,000 miles their outlook could be fundamentally changed. That all-important border would be invisible, that noisy argument silenced.'}

``` python
_T = TypeVar('_T')
_II = isinstance
def _at(d: Mapping|Sequence, k: str) -> Any: return (
    d[k] if _II(d, Mapping) else 
    d[int(k)] if _II(d, Sequence) and not _II(d, (str, bytes)) else 
    FC.stop(KeyError))  # type: ignore

def val_at(key_path: str, j: Mapping|Sequence|str|bytes|bytearray, default:_T=empty, sep:str='.') -> _T:
    "Return nested value at `key_path` from `j`. Raise if not found or `default` if not `empty`."
    try: return reduce(_at, key_path.split(sep), json.loads(j) if _II(j, (str, bytes, bytearray)) else j)
    except (KeyError, IndexError, ValueError) as e:
        if default is not empty: return default
        raise e

def key_at(key_path: str, j: Mapping|Sequence|str|bytes|bytearray, sep:str='.') -> bool:
    "Return `True` if nested `key_path` exists in `j`."
    try:
        reduce(_at, key_path.split(sep), json.loads(j) if _II(j, (str, bytes, bytearray)) else j)
        return True
    except (KeyError, IndexError):
        return False
```

``` python
FC.test_fail(lambda: val_at(object, {}))  # type: ignore
FC.test_fail(lambda: val_at(object, []))  # type: ignore
FC.test_fail(lambda: val_at(object, object))  # type: ignore
FC.test_fail(lambda: val_at('', {}))
FC.test_fail(lambda: val_at('', []))
FC.test_fail(lambda: val_at('', object))   # type: ignore

FC.test_fail(lambda: val_at('a.b', {'a': 1}))
test_eq(val_at('a.b', {'a': 1}, None), None)
```

``` python
d = [
    {'a': 1, 'b': [2,  3], 'c': {'d':   4}}, 
    {'a': 5, 'b': [6,  7],                 'd': [{'e': 81}, {'e': 82}]}, 
    {'a': 9, 'b': [10, 11], 'c': {'d': 12}}
]

test_fail(lambda: val_at('', d))
test_fail(lambda: val_at(object, d))  # type: ignore

test_fail(lambda: val_at('', object))  # type: ignore

test_eq(val_at('0.a', d), 1)
test_eq(val_at('1.b', d), [6, 7])
test_eq(val_at('2.c', d), {'d': 12})
```

``` python
j2 = {
    "app": {
        "Garden": {
            "Flowers": {
                "Red flower": "Rose",
                "White Flower": "Jasmine",
                "Yellow Flower": "Marigold"
            }
        },
        "Fruits": {
            "Yellow fruit": ["Mango", {"Banana": ["Canary Island", "Puerto Rico"]}],
            "Green fruit": "Guava",
            "White Flower": "groovy"
        },
        "Trees": {
            "label": {
                "Yellow fruit": "Pumpkin",
                "White Flower": "Bogan"
            }
        },
        "Numbers": [1, 2, 3, 4, 5],
        "Boolean": True,
        "Null": None
    }
}

j2_str = json.dumps(j2)

test_eq(val_at('app.Fruits.Yellow fruit.1.Banana.0', j2_str), 'Canary Island')
test_eq(val_at('app.Garden.Flowers.Red flower', j2_str), 'Rose')
test_eq(val_at('app.Numbers.2', j2_str), 3)
test_eq(val_at('app.Boolean', j2_str), True)
test_eq(val_at('app.Null', j2_str), None)
test_fail(lambda: val_at('app.NonExistent', j2_str))
test_fail(lambda: val_at('app.Fruits.Yellow fruit.3', j2_str))
test_is(val_at('app.Fruits.Yellow fruit.3', j2_str, None), None)

test_eq(key_at('app.Garden.Flowers.Red flower', j2_str), True)
test_eq(key_at('app.Numbers.2', j2_str), True)
test_eq(key_at('app.Fruits.Yellow fruit.1.Banana.0', j2_str), True)
test_eq(key_at('app.NonExistent', j2_str), False)
test_eq(key_at('app.Fruits.Yellow fruit.3', j2_str), False)
```

## vals_at - Implementation Evolution

Similar to `val_at`, this section shows the iterative development of
wildcard path traversal. The implementation needed to handle returning
empty sentinels for missing paths while still extracting values from
partial matches.

**Skip to [vals_at, vals_atpath](#vals_at-vals_atpath) for the final
implementation and usage examples.**

### Early wildcard implementation

``` python
_E = object()

def vals_at(path, d) -> empty | tuple[empty | object, ...] | object:
    "Return nested values-- or empty|(empty, ...)-- at `path` with wildcards '*' from `d`."
    curr, wc, rest = str(path).partition('*')
    if not wc and not rest:  return o if (o := val_at(curr, d, _E)) is not _E else empty
    o = val_at(curr.rstrip('.'), d, _E) if curr else d
    try: return (tuple(map(lambda x: empty if (res := vals_at(rest.lstrip('.'), x)) is _E else res, o))  # type: ignore
        if rest and o is not _E else o)
    except TypeError: return empty

test_eq(vals_at('a', object), empty)
```

``` python
d = [
    {'a': 1, 'b': [2,  3],  'c': {'d':   4}}, 
    {'a': 5, 'b': [6,  7],                  'd': [{'e': 81}, {'e': 82}]}, 
    {'a': 9, 'b': [10, 11], 'c': {'d': 12}}
]

test_eq(vals_at('', d), empty)
test_eq(vals_at(object, d), empty)
test_eq(vals_at('*', d), (*d,))
test_eq(vals_at(2, d), d[2])
test_eq(vals_at('a', d), empty)
test_eq(vals_at('*.a', []), ())
test_eq(vals_at('*.a.*.b', [{'a': object}]), (empty,))

test_eq(vals_at('*,a', d), (empty, empty, empty))
test_eq(vals_at('*.a', d), (1, 5, 9))
test_eq(vals_at('*.a.*', d), (1,5,9))
test_eq(vals_at('*.b.1', d), (3, 7, 11))
test_eq(vals_at('*.c.d', d), (4, empty, 12))

test_eq(vals_at('*.d.*.e', d), (empty, (81, 82), empty))
test_eq(vals_at('*.d.*.f', d), (empty, (empty, empty), empty))
test_eq(vals_at('1.d.*.e', d), (81, 82))
```

``` python
vals_at('*', d)
```

    [{'a': 1, 'b': [2, 3], 'c': {'d': 4}},
     {'a': 5, 'b': [6, 7], 'd': [{'e': 81}, {'e': 82}]},
     {'a': 9, 'b': [10, 11], 'c': {'d': 12}}]

# Nested Data Access

Working with deeply nested data (JSON, API responses, config files)
often requires verbose, error-prone access patterns. These utilities
provide clean, safe access using dot notation or path sequences.

## val_at, val_atpath

Lookup values in nested structures using dot notation (`val_at`) or path
sequences
([`val_atpath`](https://civvic.github.io/pote/basic.html#val_atpath)).

**Why use these?** - Cleaner than chained `['key1']['key2'][0]` access -
Safe default values instead of try/except blocks - Works with dicts,
lists, objects, and JSON strings - Handles mixed nesting (lists within
dicts, etc.)

**Use `val_at`** when you have a dot-separated path string:
`'user.addresses.0.city'`  
**Use
[`val_atpath`](https://civvic.github.io/pote/basic.html#val_atpath)**
when you have individual path components:
`'user', 'addresses', 0, 'city'`

**Related utilities:** - `has_key(o, 'dot.path')` /
`has_path(o, *path)` - Check if path exists without retrieving value -
`vals_at(o, 'path.*.with.wildcards')` - Extract multiple values using
wildcards (see below)

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L197"
target="_blank" style="float:right; font-size:smaller">source</a>

### has_path

``` python

def has_path(
    o, path:str | int
)->bool:

```

*Return `True` if nested `path` exists.*

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L193"
target="_blank" style="float:right; font-size:smaller">source</a>

### has_key

``` python

def has_key(
    o, attr:str, sep:str='.'
)->bool:

```

*Return `True` if nested dot-separated `attr` exists.*

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L180"
target="_blank" style="float:right; font-size:smaller">source</a>

### val_atpath

``` python

def val_atpath(
    o, path:str | int, default:Any=EmptyT
):

```

*Traverse nested `o` looking for attributes/items specified in `path`.*

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L156"
target="_blank" style="float:right; font-size:smaller">source</a>

### at\_

``` python

def at_(
    o, # Object to traverse (dict, list, object, or nested combination)
    sym:str, # Path using dots and/or brackets (e.g., 'a.b[0].c' or 'a[b][c]')",
    default:Any, # Value to return if path not found (raises exception if not provided)
    sep:str='.', # Separator for path segments
)->Any: # Value at the specified path

```

*Traverse nested `o` using path `sym` with dot notation and/or bracket
indexing*

[`at_`](https://civvic.github.io/pote/basic.html#at_) provides flexible
path-based access to nested data structures:

**Supported types:** `o` can be/contains any combination of `Sequence`,
`Mapping` (dicts, lists, tuples, L, etc), objects with \_\_getitem\_\_,
and/or dataclasses, objects with attributes accesible by `getattr`.

**Path syntax:** - Dot notation: `'a.b.c'` accesses `o['a']['b']['c']`
or `o.a.b.c` - Bracket notation: `'a[b][c]'` is equivalent to `'a.b.c'`:
`[x]` is a shorthand for `.x` - Mixed: `'a.b[0].c'` combines both
styles - Numeric indices: `'items.2'` or `'items[2]'` for list/array
access - Empty path: `''` returns the object itself

**Access priority:** Item access (`[]`) is tried before attribute access
(`.`)

**Error handling:** Raises exception if path not found, unless `default`
is provided

``` python
test_eq(at_({'a': 13}, 'a'), 13)

test_eq(at_({'a': {'b': 13}}, 'a.b'), 13)
test_eq(at_({'a': dict2obj({'b': 13})}, 'a.b'), 13)

test_eq(at_({'a': {'3': 7}}, 'a.3'), 7)

test_fail(lambda: at_(o, 'app.NonExistent'))
test_fail(lambda: at_(o, '[0'))
test_fail(lambda: at_(None, 'a'))
test_eq(at_(None, 'a', None), None)
test_fail(lambda: at_(None, 'a'))

test_eq(at_(o := {'meta': [1,2,3]}, ''), o)
test_eq(at_(o, 'meta[2]'), 3)
test_eq(at_(o, 'meta.2'), 3)

test_eq(at_('xyz', 'a', None), None)
test_eq(at_((s := 'xyz'), 'split'), s.split)

test_eq(at_([{'a':1}], '[0][a]'), 1)
```

``` python
# Basic usage: access nested data with dot notation
data = {'user': {'name': 'Alice', 'scores': [10, 20, 30]}}
test_eq(val_at(data, 'user.name'), 'Alice')
test_eq(val_at(data, 'user.scores.1'), 20)  # list index

j2_list = [{'a':1}, {'b':2}]
test_eq(val_at(j2_list, '0'), {'a':1})
test_eq(val_at(j2_list, '0.a'), 1)

# Safe default instead of exception
test_eq(val_at(data, 'user.age', default=25), 25)
```

``` python
test_eq(at_(j2_list, '0'), {'a':1})
test_eq(at_(j2_list, '1'), {'b':2})
test_eq(at_(j2_list, '[0][a]'), 1)
test_eq(at_(j2_list, '0.a'), 1)
test_eq(at_(j2_list, '[1].b'), 2)
```

``` python
# Basic usage: access nested data with dot notation
data = {'user': {'name': 'Alice', 'scores': [10, 20, 30]}}
test_eq(val_at(data, 'user.name'), 'Alice')
test_eq(val_at(data, 'user.scores.1'), 20)  # list index

# Safe default instead of exception
test_eq(val_at(data, 'user.age', default=25), 25)
```

``` python
# Works with lists of dicts (common in API responses)
records = [
    {'id': 1, 'items': [2, 3], 'meta': {'count': 4}}, 
    {'id': 5, 'items': [6, 7], 'nested': [{'val': 81}, {'val': 82}]}, 
    {'id': 9, 'items': [10, 11], 'meta': {'count': 12}}
]

test_eq(val_at(records, '0.id'), 1)
test_eq(val_at(records, '1.items'), [6, 7])
test_eq(val_at(records, '2.meta'), {'count': 12})
```

``` python
test_eq(at_(records, '[2].meta'), {'count': 12})
test_eq(at_(records, '2[meta]'), {'count': 12})
```

``` python
# Error handling: raises when path not found (unless default provided)
test_fail(lambda: val_at({}, 'a.b'))
test_fail(lambda: val_at([], 'a.b'))
test_fail(lambda: val_at({'a': 1}, 'a.b'))
test_fail(lambda: val_atpath({'a': 1}, 'a', 'b'))

# With default, no error
test_eq(val_at({'a': 1}, 'a.b', None), None)
```

``` python
j2 = {
    "app": {
        "Garden": {
            "Flowers": {
                "Red flower": "Rose",
                "White Flower": "Jasmine",
                "Yellow Flower": "Marigold"
            }
        },
        "Fruits": {
            "Yellow fruit": ["Mango", {"Banana": ["Canary Island", "Puerto Rico"]}],
            "Green fruit": "Guava",
            "White Flower": "groovy"
        },
        "Trees": {
            "label": {
                "Yellow fruit": "Pumpkin",
                "White Flower": "Bogan"
            }
        },
        "Numbers": [1, 2, 3, 4, 5],
        "Boolean": True,
        "Null": None
    }
}

j2_str = j2#json.dumps(j2)

test_eq(val_at(j2_str, 'app.Fruits.Yellow fruit.1.Banana.0'), 'Canary Island')
test_eq(val_at(j2_str, 'app.Garden.Flowers.Red flower'), 'Rose')
test_eq(val_at(j2_str, 'app.Numbers.2'), 3)
test_eq(val_at(j2_str, 'app.Boolean'), True)
test_eq(val_at(j2_str, 'app.Null'), None)
test_fail(lambda: val_at(j2_str, 'app.NonExistent'))
test_fail(lambda: val_at(j2_str, 'app.Fruits.Yellow fruit.3'))
test_is(val_at(j2_str, 'app.Fruits.Yellow fruit.3', None), None)
```

``` python
j2_obj = dict2obj(j2)

val_at(j2_obj, 'app.Fruits.Yellow fruit.1.Banana.0')
test_eq(val_at(j2_str, 'app.Null'), None)
test_eq(val_at(j2_str, 'app.Boolean'), True)
test_fail(lambda: val_at(j2_str, 'app.NonExistent'))
```

``` python
test_eq(has_key(j2_str, 'app.Garden.Flowers.Red flower'), True)
test_eq(has_key(j2_str, 'app.Numbers.2'), True)
test_eq(has_key(j2_str, 'app.Fruits.Yellow fruit.1.Banana.0'), True)
test_eq(has_key(j2_str, 'app.NonExistent'), False)
test_eq(has_key(j2_str, 'app.Fruits.Yellow fruit.3'), False)
```

``` python
test_eq(at_(j2, 'app[Numbers][2]'), 3)
test_eq(at_(j2, 'app[Fruits][Yellow fruit][1][Banana][0]'), 'Canary Island')
test_eq(at_(j2, 'app.Fruits[Yellow fruit].1.Banana[0]'), 'Canary Island')

test_eq(at_(dict2obj(j2), 'app[Fruits][Yellow fruit][1][Banana][0]'), 'Canary Island')
test_eq(at_(dict2obj(j2), 'app.Fruits[Yellow fruit].1.Banana[0]'), 'Canary Island')

test_is(at_(j2, '[bad][path]', 'default'), 'default')
test_is(at_(j2, '[missing]', None), None)
test_is(at_(j2, 'app[missing]', 'default'), 'default')
test_is(at_(j2, 'app[NonExistent]', None), None)
```

``` python
test_fail(lambda:val_at(None, '0'))
test_eq(val_at(None, '0', None), None)
test_fail(lambda:val_atpath(None, 2))
test_eq(val_atpath(None, 2, default=None), None)
test_fail(lambda:val_atpath(None, -1))
test_eq(val_atpath(None, -1, default=None), None)
test_fail(lambda:val_at(None, '0.1'))
test_eq(val_at(None, '0.1', None), None)
test_fail(lambda:val_atpath(None, 4, 'b'))
test_eq(val_atpath(None, 4, 'b', default=None), None)

test_fail(lambda:val_atpath([], 1))
test_eq(val_atpath([], 1, default=None), None)
test_fail(lambda:val_atpath([], 'a', 2))
test_eq(val_atpath([], ('a', 2), default=None), None)  # type: ignore

test_fail(lambda:val_atpath({}, 1))
test_eq(val_atpath({}, 1, default=None), None)
test_fail(lambda:val_atpath(object(), '2'))
test_eq(val_at(object(), '0', None), None)
test_fail(lambda:val_at(object(), 'a.1'))
test_eq(val_at(object(), 'a.1', None), None)
```

``` python
o = [1, 2, 3, [4, 5, 6], 7]
test_eq(val_at(o, '0'), 1)
test_eq(val_at(o, '-1'), 7)
test_eq(val_atpath(o, -1), 7)
test_eq(val_atpath(o, 2), 3)
test_eq(val_atpath(o, 3, 2), 6)
test_eq(val_atpath(o, 5, default=None), None)
test_eq(val_at(o, '3.4', None), None)

o = dict(a=1, b=2, c=3, d=dict(e=4, f=5), g=6)
test_eq(val_atpath(o, 'b'), 2)
test_eq(val_atpath(o, 'd', 'f'), 5)
test_eq(val_at(o, 'd.f'), 5)
test_eq(val_at(o, 'd.g', None), None)

(s := json.dumps(['foo', (1,2,3), {'bar': ('baz', None, 1.0, 2)}]))
o = json.loads(s)
test_eq(val_at(o, '0'), 'foo')
test_eq(val_atpath(o, 0), 'foo')
test_eq(val_at(o, '2.bar'), ['baz', None, 1.0, 2])
test_eq(val_atpath(o, 2, 'bar'), ['baz', None, 1.0, 2])
test_eq(val_at(o, '2.bar.3'), 2)
test_eq(val_at(o, '3.bar', None), None)
test_eq(val_atpath(o, 2, 'foo', default=None), None)

@dataclasses.dataclass
class _D:
    a: int
    b: str
    c: float
    d: dict[str, Any]
_d = _D(1, '2', 3.0, {'e': 4, 'f': '5', 'g': 6.0})

o = [0, 1, _d, 'a']
test_eq(val_atpath(o, 1), 1)
test_eq(val_at(o, '3'), 'a')
test_eq(val_atpath(o, 2), _d)
test_eq(val_at(o, '2.c'), 3.0)
test_eq(val_at(o, '2.d.f'), '5')
```

## vals_at, vals_atpath

Lookup **multiple** values using wildcards in paths. Think of it as a
lightweight JSONPath for common cases.

**When to use:** Extract values from all items in a collection without
explicit loops.

``` python
data = [
    {'user': {'name': 'Alice', 'age': 30}},
    {'user': {'name': 'Bob', 'age': 25}},
]
vals_at(data, '*.user.name')  # ('Alice', 'Bob')
```

**Related utilities:** - `val_at` /
[`val_atpath`](https://civvic.github.io/pote/basic.html#val_atpath) -
Single value extraction (see above) - `filter_empty=True` parameter -
Remove `empty` sentinels from results for cleaner output

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L234"
target="_blank" style="float:right; font-size:smaller">source</a>

### vals_at

``` python

def vals_at(
    o, path:str, filter_empty:bool=False
)->tuple[Any, ...]:

```

*Return nested values– or empty|(empty, …)– at `path` with wildcards ’*’
from `o`.\*

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L216"
target="_blank" style="float:right; font-size:smaller">source</a>

### vals_atpath

``` python

def vals_atpath(
    o, path:Any, filter_empty:bool=False
)->tuple[Any, ...]:

```

*Return nested values– or empty|(empty, …)– at `path` with wildcards ’*’
from `d`.\*

``` python
test_eq(vals_atpath(object, ''), ())
test_eq(vals_atpath(object, 'a'), ())
test_eq(vals_atpath(object, '*', 'a'), ())
test_eq(vals_atpath(object, '*', 'a', '*'), ())

test_eq(vals_atpath(['a', 'b'], 'a'), ())
test_eq(vals_atpath(['a', 'b'], '*'), ('a', 'b'))
test_eq(vals_atpath(['a', 'b'], '*', 'a'), ())

test_eq(vals_atpath(['a', 'b'], 0), ('a',))
test_eq(vals_atpath(['a', 'b'], 2), ())

test_eq(vals_atpath([{'a':1}], 'a'), ())
test_eq(vals_atpath([{'a':1}], '*'), [{'a':1}])
test_eq(vals_atpath([{'a':1}, {'a':2}], '*', 'a'), (1, 2))
```

``` python
test_eq(vals_at(object, ''), (object,))
test_eq(vals_at(object, 'a'), ())
test_eq(vals_at(object, '*.a'), ())
test_eq(vals_at(object, '*.a.*'), ())

test_eq(vals_at(['a', 'b'], 'a'), ())
test_eq(vals_at(['a', 'b'], '*'), ('a', 'b'))
test_eq(vals_at(['a', 'b'], '*.a'), ())

test_eq(vals_at(['a', 'b'], '0'), ('a',))
test_eq(vals_at(['a', 'b'], '2'), ())

test_eq(vals_at([{'a':1}], 'a'), ())
test_eq(vals_at([{'a':1}], '*'), [{'a':1}])
test_eq(vals_at([{'a':1}, {'a':2}], '*.a'), (1, 2))
```

``` python
d = [
    {'a': 1, 'b': [2,  3],  'c': {'d':   4}}, 
    {'a': 5, 'b': [6,  7],                  'd': [{'e': 81}, {'e': 82}]}, 
    {'a': 9, 'b': [10, 11], 'c': {'d': 12}}
]

test_eq(vals_atpath(d, ''), ())
test_eq(vals_atpath(d, object), ())  # type: ignore
test_eq(vals_atpath(d, '*'), (*d,))
test_eq(vals_atpath(d, 2), (d[2],))
test_eq(vals_atpath(d, '2'), ())
test_eq(vals_atpath(d, 'a'), ())
test_eq(vals_atpath([], '*', 'a'), ())#())
test_eq(vals_atpath([{'a': object}], '*', 'a', '*', 'b'), ())

test_eq(vals_atpath(d, '*', 'a'), (1, 5, 9))
test_eq(vals_atpath(d, '*', 'a', '*'), (1,5,9))
test_eq(vals_atpath(d, '*', 'b', 1), (3, 7, 11))
test_eq(vals_atpath(d, '*', 'c', 'd'), (4, empty, 12))
test_eq(vals_atpath(d, '*', 'c', 'd', filter_empty=True), (4, 12))

test_eq(vals_atpath(d, '*', 'd', '*', 'e'), (empty, (81, 82), empty))
test_eq(vals_atpath(d, '*', 'd', '*', 'e', filter_empty=True), ((81, 82),))
test_eq(vals_atpath(d, '*', 'd', '*', 'f'), ())
test_eq(vals_atpath(d, 1, 'd', '*', 'e'), (81, 82))
```

``` python
d = [
    {'a': 1, 'b': [2,  3],  'c': {'d':   4}}, 
    {'a': 5, 'b': [6,  7],                  'd': [{'e': 81}, {'e': 82}]}, 
    {'a': 9, 'b': [10, 11], 'c': {'d': 12}}
]

test_eq(vals_at(d, ''), (d,))
test_fail(lambda: vals_at(d, object))  # type: ignore
test_eq(vals_at(d, '*'), (*d,))
test_eq(vals_at(d, '2'), (d[2],))
test_eq(vals_at(d, 'a'), ())
test_eq(vals_at([], '*.a'), ())#())
test_eq(vals_at([{'a': object}], '*.a.*.b'), ())

test_eq(vals_at(d, '*,a'), ())
test_eq(vals_at(d, '*.a'), (1, 5, 9))
test_eq(vals_at(d, '*.a.*'), (1,5,9))
test_eq(vals_at(d, '*.b.1'), (3, 7, 11))
test_eq(vals_at(d, '*.c.d'), (4, empty, 12))
test_eq(vals_at(d, '*.c.d', filter_empty=True), (4, 12))

test_eq(vals_at(d, '*.d.*.e'), (empty, (81, 82), empty))
test_eq(vals_at(d, '*.d.*.e', filter_empty=True), ((81, 82),))
test_eq(vals_at(d, '*.d.*.f'), ())
test_eq(vals_at(d, '1.d.*.e'), (81, 82))
```

## deep_in

Check if a value exists anywhere in a nested structure (recursively
searches dicts and iterables).

``` python
def deep_in(o:Mapping|Iterable, val):
    "return True if val is in nested collections"
    if isinstance(o, Mapping):
        return val in o.values() or any(deep_in(v, val) for v in o.values() if isinstance(v, (Mapping, Iterable)))
    elif isinstance(o, Iterable):
        return val in o or any(deep_in(v, val) for v in o if isinstance(v, (Mapping, Iterable)))
    raise ValueError(f"deep_in: o must be a Mapping or Iterable, got {type(o)}")
```

``` python
test_fail(lambda:deep_in(None, 1))  # type: ignore
test_eq(deep_in((), 1), False)
test_eq(deep_in((1,), 1), True)
test_eq(deep_in((1,), 2), False)
test_eq(deep_in({'a': 1}, 1), True)
test_eq(deep_in({'a': 1}, 2), False)
test_eq(deep_in({'a': (1,)}, 1), True)
test_eq(deep_in({'a': (1,)}, 2), False)
test_eq(deep_in({'a': {'b': 1}}, 1), True)
test_eq(deep_in({'a': {'b': 1}}, 2), False)
test_eq(deep_in({'a': {'b': (1,)}}, 1), True)
test_eq(deep_in({'a': {'b': (1,)}}, 2), False)
```

``` python
def deep_in_v1(o:Mapping|Iterable, val):
    "return True if val is in o"
    if isinstance(o, Mapping):
        for v in o.values():
            if v == val or (is_listy(v) and deep_in_v1(v, val)): return True
        return False
    elif isinstance(o, Iterable):
        if val in o: return True
        return any(deep_in_v1(v, val) for v in o if is_listy(v))
    raise ValueError(f"deep_in: o must be a Mapping or Iterable, got {type(o)}")
```

``` python
def deep_in_v2(o:Mapping|Iterable, val):
    stack = deque([o])
    while stack:
        curr = stack.popleft()
        if isinstance(curr, Mapping):
            if val in curr.values(): return True
            stack.extend(v for v in curr.values() if is_listy(v))
        elif isinstance(curr, Iterable):
            if val in curr: return True  
            stack.extend(v for v in curr if is_listy(v))
        else:
            raise ValueError(f"deep_in: o must be a Mapping or Iterable, got {type(curr)}")
    return False
```

``` python
def deep_in_v3(o:Mapping|Iterable, val):
    def _search(obj):
        if isinstance(obj, Mapping):
            yield from obj.values()
            for v in obj.values():
                if is_listy(v): yield from _search(v)
        elif isinstance(obj, Iterable):
            yield from obj
            for v in obj:
                if is_listy(v): yield from _search(v)
        else:
            raise ValueError(f"deep_in: o must be a Mapping or Iterable, got {type(obj)}")
    
    return val in _search(o)
```

``` python
test_data = [
    ((), 1), ((1,), 1), ((1,), 2), ({'a': 1}, 1), ({'a': 1}, 2),  
    ({'a': (1,)}, 1), ({'a': (1,)}, 2), ({'a': {'b': 1}}, 1), 
    ({'a': {'b': 1}}, 2), ({'a': {'b': (1,)}}, 1), ({'a': {'b': (1,)}}, 2)
]

for o, val in test_data:
    orig = deep_in(o, val)
    test_eq(deep_in_v1(o, val), orig)
    test_eq(deep_in_v2(o, val), orig) 
    test_eq(deep_in_v3(o, val), orig)
```

### Performance exploration

The following cells test alternative implementations of
[`deep_in`](https://civvic.github.io/pote/basic.html#deep_in) to find
the best balance between clarity and speed. The chosen implementation
(exported above) uses recursive traversal with early termination.

``` python
deep_data = {'level1': {'level2': {'level3': {'level4': [1, 2, 3, {'deep': 'target'}]}}}}

def time_function(func, data, target, iterations=10000):
    start = time.time()
    for _ in range(iterations):
        func(data, target)
    return time.time() - start

print("\nPerformance comparison (10000 iterations):")
target = 'target'
times = {}
times['original'] = time_function(deep_in, deep_data, target)
times['v1_single_iter'] = time_function(deep_in_v1, deep_data, target)
times['v2_stack_based'] = time_function(deep_in_v2, deep_data, target)
times['v3_generator'] = time_function(deep_in_v3, deep_data, target)

for name, t in times.items():
    speedup = times['original'] / t if t > 0 else float('inf')
    print(f"{name:15}: {t:.4f}s (speedup: {speedup:.2f}x)")
```


    Performance comparison (10000 iterations):
    original       : 0.0899s (speedup: 1.00x)
    v1_single_iter : 0.0554s (speedup: 1.62x)
    v2_stack_based : 0.0628s (speedup: 1.43x)
    v3_generator   : 0.0764s (speedup: 1.18x)

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L242"
target="_blank" style="float:right; font-size:smaller">source</a>

### deep_in

``` python

def deep_in(
    o:Mapping | Iterable, val
):

```

*return True if val is in nested collections*

# Dictionary Helpers

Utilities for common dictionary operations: bulk popping, getting
multiple keys, conditional updates.

## pops\_

Pop multiple keys from a dict into a new dict. Useful for extracting
specific kwargs or splitting a config dict.

**Related:**
[`pops_values_`](https://civvic.github.io/pote/basic.html#pops_values_)
returns a tuple of values instead of a dict;
[`gets`](https://civvic.github.io/pote/basic.html#gets) for
non-destructive retrieval.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L254"
target="_blank" style="float:right; font-size:smaller">source</a>

### pops\_

``` python

def pops_(
    d:dict, ks:Hashable
)->dict:

```

*Pop existing `ks` items from `d` in-place into a dictionary.*

``` python
# Usage: extract specific keys from a dict
d = {'a': 1, 'b': 2, 'c': 3}
extracted = pops_(d, 'a', 'b')
test_eq(extracted, {'a': 1, 'b': 2})
test_eq(d, {'c': 3})  # original dict modified
```

``` python
# Coverage: missing keys, empty dict, duplicates
test_eq(pops_({'a': 1, 'b': 2, 'c': 3}, 'd'), {})  # missing keys ignored
test_eq(pops_({'a': 1, 'b': 2, 'c': 3}, 'a', 'c', 'd'), {'a': 1, 'c': 3})  # some missing
test_eq(pops_({}, 'a'), {})  # empty dict
test_eq(pops_({'a': 1}, 'a', 'a'), {'a': 1})  # duplicate keys
```

## pops_values\_

Like [`pops_`](https://civvic.github.io/pote/basic.html#pops_) but
returns a tuple of values instead of a dict. Missing keys get
`Parameter.empty` as placeholder. Useful for unpacking:
`a, b, c = pops_values_(d, 'a', 'b', 'c')`

**Related:** [`pops_`](https://civvic.github.io/pote/basic.html#pops_)
returns a dict; [`gets`](https://civvic.github.io/pote/basic.html#gets)
for non-destructive retrieval.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L259"
target="_blank" style="float:right; font-size:smaller">source</a>

### pops_values\_

``` python

def pops_values_(
    d:dict, ks:Hashable
)->tuple:

```

*Pop existing `ks` items from `d` in-place into a tuple of values or
`Parameter.empty` for missing keys.*

``` python
test_eq(pops_values_({'a': 1, 'b': 2, 'c': 3}, 'a', 'b'), (1, 2))
test_eq(pops_values_({'a': 1, 'b': 2, 'c': 3}, 'd'), (Parameter.empty,))
test_eq(pops_values_({'a': 1, 'b': 2, 'c': 3}, 'a', 'c', 'd'), (1, 3, Parameter.empty))
test_eq(pops_values_({}, 'a'), (Parameter.empty,))
test_eq(pops_values_({'a': 1}, 'a', 'a'), (1, Parameter.empty))
```

## gets

Fetch multiple values from a dict as a tuple. Missing keys return
`Parameter.empty`. Useful for unpacking multiple config values at once.

**Related:** [`pops_`](https://civvic.github.io/pote/basic.html#pops_) /
[`pops_values_`](https://civvic.github.io/pote/basic.html#pops_values_)
for destructive (modifying) retrieval.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L264"
target="_blank" style="float:right; font-size:smaller">source</a>

### gets

``` python

def gets(
    d:Mapping, ks:Hashable
):

```

*Fetches `ks` values, or `Parameter.empty` for missing keys, from `d`
into a tuple.*

``` python
# Usage: unpack multiple config values at once
config = {'host': 'localhost', 'port': 8080, 'debug': True}
host, port = gets(config, 'host', 'port')
test_eq((host, port), ('localhost', 8080))

# Missing keys return Parameter.empty
test_eq(gets({'a': 1, 'b': 2}, 'a', 'c', 'b'), (1, Parameter.empty, 2))
```

``` python
# Coverage: empty args, Path keys, reordering
test_eq(gets({'a': 1, 'b': 2}), ())  # no keys requested
a, b = gets({'a': 1, 'b': 2}, 'b', 'a')  # reordered
test_eq((a, b), (2, 1))

d = {Path('a'): 1, Path('b'): 2}  # non-string keys
test_eq(gets(d, Path('a'), Path('c'), Path('b')), (1, Parameter.empty, 2))
```

## update\_

Update dict/object with kwargs, skipping values equal to `empty_value`.
Useful for conditional updates where you don’t want to set `None` or
other sentinel values.

``` python
# def update_(d:dict|None=None, /, empty_value=None, **kwargs):
#     "Update `d` in-place with `kwargs` whose values aren't `empty_value`"
#     d = d if d is not None else {}
#     for k, v in kwargs.items():
#         if v is not empty_value: d[k] = v
#     return d
```

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L269"
target="_blank" style="float:right; font-size:smaller">source</a>

### update\_

``` python

def update_(
    dest:NoneType=None, empty_value:NoneType=None, kwargs:VAR_KEYWORD
)->Any:

```

*Update `dest` in-place with `kwargs` whose values aren’t `empty_value`*

Helper for conditionally updating dictionaries/namespaces.

``` python
# Usage: conditionally update dict (skip None by default)
d = {'a': 1}
update_(d, b=2, c=None)
test_eq(d, {'a': 1, 'b': 2})  # c=None was skipped

# Custom empty_value sentinel
d = {}
update_(d, a=True, b=Parameter.empty, empty_value=Parameter.empty)
test_eq(d, {'a': True})  # b was skipped
```

``` python
# Works with objects too (uses setattr)
class Config: host = 'localhost'; port = 8000
cfg = update_(Config(), host='0.0.0.0', timeout=None)
test_eq(cfg.host, '0.0.0.0')
test_eq(cfg.port, 8000)  # unchanged
test_is(hasattr(cfg, 'timeout'), False)  # None was skipped
```

``` python
# Coverage: create new dict, AD dict
d = update_(a=1, b=None)  # dest=None creates new dict
test_eq(d, {'a': 1})

ad = AD(a=1, b=2)
update_(ad, a=3, b=4, c=5)
test_eq(ad.a, 3)
test_eq(ad.c, 5)
```

``` python
ad = AD(a=1, b=2)
update_(ad, a=3, b=4, c=5)
test_eq(ad.a, 3)
test_eq(ad.b, 4)
test_eq(ad.c, 5)
```

# \_get_globals

Internal utility for accessing the caller’s global namespace. This
enables functions to dynamically resolve names from the calling module’s
context, useful for metaprogramming and dynamic imports.

**Note:** This is an internal helper (marked `#| exporti`). Most users
won’t need it directly.

``` python
def _gtest(): return _get_globals(__name__)
g1 = _gtest()
g2 = globals()
test_eq(g1, g2)
```

# Path Utilities

## bundle_path

Get the directory containing a module. Useful for finding resources
bundled with your package.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L285"
target="_blank" style="float:right; font-size:smaller">source</a>

### bundle_path

``` python

def bundle_path(
    mod:str | ModuleType
):

```

*Return the path to the module’s directory or current directory.*

``` python
import pote
```

``` python
test_eq(bundle_path(__name__), Path('.'))
test_eq(bundle_path('pote').resolve(), Path(pote.__file__).parent)
```

# ID Generation

Utilities for generating unique identifiers in the current session.

## Kounter

A callable counter that increments and returns the count for each key.
Useful for generating sequential IDs grouped by type.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L291"
target="_blank" style="float:right; font-size:smaller">source</a>

### Kounter

``` python

def Kounter(
    
):

```

*Initialize self. See help(type(self)) for accurate signature.*

``` python
kounter = Kounter()
cntr = Kounter()
cntr('a')
cntr('b')
cntr('a')
cntr('a')
cntr('b')
cntr('b')
cntr('b')
test_eq(cntr.d, {'a': 3, 'b': 4})
test_eq(cntr('int'), 1)
```

## id_gen

Create a function that generates unique IDs for objects. Without
arguments returns a random ID, with an object returns `TypeName_N` where
N increments per type.

``` python
lines = Path("static/wordlist.txt").read_text().splitlines()
words = [line.strip() for line in lines if line.isalpha()]
```

``` python
def modify_word(word):
    # Randomly capitalize the first or second letter
    if len(word) > 1:
        idx_to_capitalize = random.choice([0, 1])
        word = word[:idx_to_capitalize] + word[idx_to_capitalize].upper() + word[idx_to_capitalize + 1:]
    else:
        word = word.upper()  # If single letter, capitalize it
    
    # Randomly add a number (0–99) at the start or end
    if random.choice([True, False]):
        number = random.randint(0, 99)
        # if random.choice([True, False]):
        #     word = f"{number}{word}"  # Number at the start
        # else:
        word = f"{word}{number}"  # Number at the end
    
    return word

def generate_readable_id(num_words=3):
    words_part = [modify_word(random.choice(words)) for _ in range(num_words)]
    id_candidate = '-'.join(words_part)

    # Ensure it's a valid CSS identifier
    if not re.match(r"^[a-zA-Z_][\w\-]*$", id_candidate):  # Add '_' if invalid
        id_candidate = f"_{id_candidate}"
    
    return f"{id_candidate}-{random.randint(0, 9999)}"
```

``` python
generate_readable_id(), generate_readable_id()
```

    ('sEssions-Proportion4-rEminds-1249', 'rApidly37-Not-mEeting-8905')

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L299"
target="_blank" style="float:right; font-size:smaller">source</a>

### id_gen

``` python

def id_gen(
    
):

```

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L296"
target="_blank" style="float:right; font-size:smaller">source</a>

### simple_id

``` python

def simple_id(
    
):

```

The [`id_gen`](https://civvic.github.io/pote/basic.html#id_gen) function
creates a function that takes any object and generates an unique Id
valid during the current session. Useful for creating unique element IDs
in dynamic HTML content.

``` python
new_id = id_gen()
new_id(), new_id()
```

    ('b0b32310b-f0774158-a0c54127-c5617c81',
     'b9e8bcb6f-9d75594a-dfc71bee-4edd51bc')

``` python
int_id = id_gen()
int_id(7), int_id(8)
```

    ('int_1', 'int_2')

``` python
obj_id = id_gen()
o1, o2 = object(), object()
print(obj_id(o1), obj_id(o2))

dict_id = id_gen()
print(dict_id(d1 := {'a': 1}), dict_id(d2 := {'a': 1}))

pth_id = id_gen()
print(pth_id(Path('.')), pth_id(Path()), pth_id(Path('./bin')))
```

    object_1 object_2
    dict_1 dict_2
    PosixPath_1 PosixPath_2 PosixPath_3

# Metaclasses

## WithCounterMeta

A metaclass that adds automatic instance counting. Each instance gets a
`_cnt_` attribute with its creation order number.

------------------------------------------------------------------------

<a href="https://github.com/civvic/pote/blob/main/pote/basic.py#L308"
target="_blank" style="float:right; font-size:smaller">source</a>

### WithCounterMeta

``` python

def WithCounterMeta(
    args:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):

```

*Adds a `_cnt_` attribute to its classes and increments it for each new
instance.*

``` python
class _T1(metaclass=WithCounterMeta): pass
class _T2(metaclass=WithCounterMeta): pass

test_is(_T1._cnt_, 0)
test_is(_T2._cnt_, 0)

test_is(_T1()._cnt_, 0)
test_is(_T1._cnt_, 1)
test_is(_T2._cnt_, 0)

test_is(_T1()._cnt_, 1)
test_is(_T2()._cnt_, 0)
test_eq(_T1._cnt_, 2)
test_eq(_T2._cnt_, 1)
```

------------------------------------------------------------------------

<!-- # Colophon -->
