Metadata-Version: 2.5
Name: dashloom
Version: 1.1.1
Summary: Functional essentials woven on pydash: records, paths, diffs, validation, and a lazy chain.
Author-email: Michael Beeks <michael.beeks23@proton.me>
License-Expression: MIT
License-File: LICENSE
Keywords: functional,lodash,pydash,records,utilities,validation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: pydash==5.1.2
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# dashloom

Functional essentials woven on [pydash](https://pypi.org/project/pydash/). dashloom keeps pydash as the engine for map, filter, deep get, grouping, case conversion, chunking, debounce, and the rest of its API, and adds the record, path, diff, and validation tools that show up in everyday Python.

Requires Python 3.10+ and pydash.

## Install

After the package is published on PyPI:

```bash
pip install dashloom
```

That installs dashloom and its dependency, pydash. It does not install the test tools.

## A chain

`loom()` (also available as `chain()`) queues steps and runs them at `value()`. A chain with no seed can be called like a function. Unknown methods are pydash functions, so `sum` reaches `pydash.sum_` and `without` reaches `pydash.without`.

```python
from dashloom import loom, pydash

people = [
    {"name": "Ada", "city": "London", "score": 91},
    {"name": "Grace", "city": "New York", "score": 88},
    {"name": "Lin", "city": "London", "score": 95},
]

leaders = (
    loom(people)
    .where(city="London")
    .order_by("score", "desc")
    .select("name", "score")
    .value()
)
# [{"name": "Lin", "score": 95}, {"name": "Ada", "score": 91}]

assert loom().map(lambda n: n * 2).sum()([1, 2, 3]) == 12
assert pydash.debounce is not None
```

`set` and `set_` on a chain copy the container. Pass `inplace=True` to mutate. Direct pydash writers are still available on `dashloom.pydash`.

## Records

```python
from dashloom import join, paginate, pivot, where

rows = [
    {"id": 1, "region": "us", "amount": 10},
    {"id": 2, "region": "eu", "amount": 5},
    {"id": 1, "region": "us", "amount": 2},
]
page = paginate(where(rows, region="us"), page=1, per_page=1)
table = pivot(rows, "region", "id", "amount", agg="sum")
paired = join(
    [{"id": 1, "name": "Ada"}],
    [{"id": 1, "name": "Admin", "role": "owner"}],
    on="id",
)
```

`where`, `order_by`, `group`, `index`, and `unique_by` keep the original row objects. `select`, `rename`, `join`, and `pivot` return new dicts. List order is stable. `None` sort keys stay last. `order_by` accepts `asc`/`desc` and pydash-style booleans (`True` ascending, `False` descending). A leading `-` on a key means descending when orders are omitted.

`join` supports `inner`, `left`, `right`, and `full`. Overlapping non-key fields get the suffixes `_x` and `_y`.

## Paths, keys, diff

```python
from dashloom import coalesce, deep_defaults, diff, flatten_keys, patch, set_, snake_keys

profile = {"user": {"name": "Ada"}}
renamed = set_(profile, "user.roles[0]", "admin")  # profile is unchanged
city = coalesce({"address": {"city": None}, "city": "London"}, "address.city", "city")
config = deep_defaults({"db": {"host": "local"}}, {"db": {"port": 5432}})
flat = flatten_keys({"user": {"name": "Ada"}, "tags": ["a"]})
# {"user.name": "Ada", "tags[0]": "a"}

before = {"service": {"retries": 1}}
after = {"service": {"retries": 3}, "region": "us"}
assert patch(before, diff(before, after)) == after

snake_keys({"userName": {"cityName": "Paris"}})
```

Paths use dots, list indexes, and quoted keys: `user.name`, `items[0].id`, `["a.b"]`. Dict keys in diff and flatten are strings. Lists are compared by index, so a reorder is a series of replacements. `patch(before, diff(before, after))` rebuilds `after` for dicts, lists, and scalars.

`get` returns a stored `None`. The default is used only when the path is missing. `dig` raises `PathError` when the path is missing.

## Validation

```python
from dashloom import Schema, between, email, integer, required, string

account = (
    Schema()
    .field("email", required(), string(), email())
    .field("age", required(), integer(), between(0, 120))
    .field("address.city", string(), required())
)
account.assert_valid({"email": "ada@example.com", "age": 36, "address": {"city": "London"}})
```

Field paths are pydash paths. Every failed rule is reported. `required` rejects a missing path and `None`.

## Text, numbers, calls, sequences

```python
from dashloom import human_size, mask, parse_duration, retry, slug, window

slug("Crème brûlée")          # creme-brulee
mask("secret", keep_start=1, keep_end=1)
human_size(1536)              # 1.5 KiB
parse_duration("1h30m")       # 5400.0
window([1, 2, 3, 4], 2)       # [[1, 2], [2, 3], [3, 4]]

@retry(attempts=3, delay=0)
def load():
    return "ok"
```

`pluralize` is a regular-English heuristic. `compact` follows pydash and removes every falsey value, including `0` and `""`. `parse_number` accepts currency marks, thousands separators, and parentheses for negatives.

## pydash

```python
from dashloom import pydash

pydash.map_([1, 2, 3], lambda n: n + 1)
pydash.chain([1, 2, 3, 4]).without(2).value()
```

`from dashloom import pydash` is the installed pydash package. Use it for debounce, throttle, curry, merge, clone, and the rest of the pydash 8 API. The main pydash module is the supported surface; dashloom calls that, not pydash's private submodules.

## Develop

From this folder, install the local source plus pytest and ruff:

```bash
python -m pip install -e ".[dev]"
python -m ruff check src tests examples
python -m pytest
python examples/quickstart.py
```

`-e` means editable: Python uses this folder, so edits apply without reinstalling. `.[dev]` is the optional `dev` dependency group in `pyproject.toml`.

Build the files that get uploaded to PyPI:

```bash
python -m pip install build twine
python -m build
python -m twine upload dist/*
```

`python -m build` writes `dist/dashloom-1.1.1.tar.gz` and a wheel. `twine upload` publishes them. After that, other people can run `pip install dashloom`. Uploading needs a PyPI account and an API token.

## License

MIT. pydash is a separate project under its own MIT license.
