dictlens


Namedictlens JSON
Version 0.1.1 PyPI version JSON
download
home_pageNone
SummaryA lightweight library to compare JSON, dicts, dataclasses, and Pydantic models with tolerance and ignore rules.
upload_time2025-10-14 22:40:45
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseApache-2.0
keywords json diff compare dict dataclass pydantic tolerance ignore
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # dictlens

Deep structural comparison for Python dicts with per-field numeric
tolerance and JSONPath-like targeting.

## Overview

`dictlens` is a lightweight Python library for comparing two nested
`dict` structures (or any dict-like objects) with **fine-grained
tolerance control**.

It supports:

- ✅ Global absolute (`abs_tol`) and relative (`rel_tol`) numeric
  tolerances
- ✅ Per-field tolerance overrides via JSONPath-like expressions
- ✅ Ignoring volatile or irrelevant fields
- ✅ Detailed debug logs that explain *why* two structures differ

It’s ideal for comparing API payloads, serialized models, ML metrics, or
configurations where small numeric drifts are expected.

## Installation

    pip install dictlens

## Quick Examples

``` python
from dictlens import compare_dicts

a = {"sensor": {"temp": 21.5, "humidity": 48.0}}
b = {"sensor": {"temp": 21.7, "humidity": 48.5}}

# Default tolerances
res = compare_dicts(a, b, abs_tol=0.05, rel_tol=0.01, show_debug=True)
print(res)  # False
```

```console
### Output (debug)

[NUMERIC COMPARE] $.sensor.temp: 21.5 vs 21.7 | diff=0.200000 | abs_tol=0.05 | rel_tol=0.01 | threshold=0.217000
[MATCH NUMERIC] $.sensor.temp: within tolerance
[NUMERIC COMPARE] $.sensor.humidity: 48.0 vs 48.5 | diff=0.500000 | abs_tol=0.05 | rel_tol=0.01 | threshold=0.485000
[FAIL NUMERIC] $.sensor.humidity → diff=0.500000 > threshold=0.485000
[FAIL IN DICT] $.sensor.humidity
[FAIL IN DICT] $..sensor
```

#### Ignore Fields

```python
from dictlens import compare_dicts

def test_ignore_path_root_field():
    a = {"id": 1, "timestamp": "now"}
    b = {"id": 1, "timestamp": "later"}
    
    # Ignore only the root timestamp field
    ignore_paths = ["$.timestamp"]
    
    result = compare_dicts(a, b, ignore_paths=ignore_paths)
    print(result) # True


def test_ignore_paths_complex():
    """
    Ignore multiple paths with different patterns:
      - Exact path:            $.user.profile.updated_at
      - Array wildcard:        $.devices[*].debug
      - Recursive descent key: $..trace
    """
    a = {
        "user": {
            "id": 7,
            "profile": {"updated_at": "2025-10-14T10:00:00Z", "age": 30}
        },
        "devices": [
            {"id": "d1", "debug": "alpha", "temp": 20.0},
            {"id": "d2", "debug": "beta", "temp": 20.1}
        ],
        "sessions": [
            {"events": [{"meta": {"trace": "abc"}, "value": 10.0}]},
            {"events": [{"meta": {"trace": "def"}, "value": 10.5}]}
        ]
    }

    b = {
        "user": {
            "id": 7,
            "profile": {"updated_at": "2025-10-15T10:00:05Z", "age": 30}
        },
        "devices": [
            {"id": "d1", "debug": "changed", "temp": 20.05},
            {"id": "d2", "debug": "changed", "temp": 20.18}
        ],
        "sessions": [
            {"events": [{"meta": {"trace": "xyz"}, "value": 10.01}]},
            {"events": [{"meta": {"trace": "uvw"}, "value": 10.52}]}
        ]
    }

    # Ignore updated_at (exact), all device.debug (wildcard), any 'trace' anywhere (recursive)
    ignore_paths = [
        "$.user.profile.updated_at",
        "$.devices[*].debug",
        "$..trace",
    ]

    # Small global tolerance to allow minor sensor/value drift
    result =  compare_dicts(
                    a, b,
                    ignore_paths=ignore_paths,
                    abs_tol=0.05,
                    rel_tol=0.02
                )
    print(result) # True
```

#### Per-field Tolerances

You can override tolerances for specific paths using JSONPath-like
expressions.

```python
from dictlens import compare_dicts

a = {"a": 1.0, "b": 2.0}
b = {"a": 1.5, "b": 2.5}
abs_tol_fields = {"$.b": 1.0}
result = compare_dicts(a, b,abs_tol=0.5, abs_tol_fields=abs_tol_fields) 
print(result)  # True
```

#### array specific index tolerance

```python
from dictlens import compare_dicts

a = {"sensors": [{"temp": 20.0}, {"temp": 21.0}]}
b = {"sensors": [{"temp": 20.05}, {"temp": 21.5}]}
abs_tol_fields = {"$.sensors[0].temp": 0.1, "$.sensors[1].temp": 1.0} 
result = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)
print(result) # True
```

#### array wildcard tolerance

```python
from dictlens import compare_dicts

a = {"sensors": [{"temp": 20.0}, {"temp": 21.0}]}
b = {"sensors": [{"temp": 20.2}, {"temp": 21.1}]}
abs_tol_fields = {"$.sensors[*].temp": 0.5}
result = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)
print(result) # True
```

#### property wildcard tolerance

```python
from dictlens import compare_dicts

a = {"network": {"n1": {"v": 10}, "n2": {"v": 10}}}
b = {"network": {"n1": {"v": 10.5}, "n2": {"v": 9.8}}}
abs_tol_fields = {"$.network.*.v": 1.0}
result = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)
print(result) # True
```

#### recursive wildcard tolerance

```python
from dictlens import compare_dicts

a  = {"meta": {"deep": {"very": {"x": 100}}}}
b = {"meta": {"deep": {"very": {"x": 101}}}}
abs_tol_fields = {"$..x": 2.0}
result = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)
print(result)
```

#### combined global and field tolerances

```python 
from dictlens import compare_dicts

# Original reading (e.g., baseline snapshot)
a = {
    "station": {
        "id": "ST-42",
        "location": "Paris",
        "version": 1.0
    },
    "metrics": {
        "temperature": 21.5,
        "humidity": 48.0,
        "pressure": 1013.2,
        "wind_speed": 5.4
    },
    "status": {
        "battery_level": 96.0,
        "signal_strength": -72
    },
    "timestamp": "2025-10-14T10:00:00Z"
}

# New reading (e.g., after transmission)
b = {
    "station": {
        "id": "ST-42",
        "location": "Paris",
        "version": 1.03   # version drift allowed (custom abs_tol)
    },
    "metrics": {
        "temperature": 21.6,   # tiny drift (global rel_tol ok)
        "humidity": 49.3,      # bigger drift (custom abs_tol ok)
        "pressure": 1013.5,    # tiny drift (global ok)
        "wind_speed": 5.6      # small drift (global ok)
    },
    "status": {
        "battery_level": 94.8,    # within abs_tol
        "signal_strength": -69    # within rel_tol (5%)
    },
    "timestamp": "2025-10-14T10:00:02Z"  # ignored
}

abs_tol_fields = {
    "$.metrics.humidity": 2.0,     # humidity sensors are noisy
    "$.station.version": 0.1       # small version drift allowed
}

rel_tol_fields = {
    "$.status.signal_strength": 0.05,
    "$.metrics.wind_speed": 0.05,
    "$.status.battery_level": 0.02  # allow ±2% battery drift
}

ignore_paths = ["$.meta.id"] 

result = compare_dicts(
    a,
    b,
    abs_tol=0.05,
    rel_tol=0.01,
    abs_tol_fields=abs_tol_fields,
    rel_tol_fields=rel_tol_fields,
    ignore_paths=ignore_paths,
    show_debug=True
)

print(result) # True
```

## Supported Path Patterns

`dictlens` implements a simplified subset of JSONPath syntax:

<table>
<thead>
<tr>
<th>Pattern</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$.a.b</code></td>
<td>Exact field path</td>
</tr>
<tr>
<td><code>$.items[0].price</code></td>
<td>Specific array index</td>
</tr>
<tr>
<td><code>$.items[*].price</code></td>
<td>Any array element</td>
</tr>
<tr>
<td><code>$.data.*.value</code></td>
<td>Any property name</td>
</tr>
<tr>
<td><code>$..x</code></td>
<td>Recursive descent for key <code>x</code></td>
</tr>
</tbody>
</table>

🔍 Supported and Future JSONPath Features

dictlens currently supports a focused subset of JSONPath syntax designed for simplicity and performance in numeric
comparisons.

These patterns cover the vast majority of practical comparison use cases.

At the moment, advanced JSONPath features such as filters ([?()]), unions ([0,1,2]), slices ([0:2]), or expressions are
not yet supported.
Future versions of dictlens may expand support for these features once performance and readability trade-offs are fully
evaluated.

## Full API

    compare_dicts(
        left: dict,
        right: dict,
        *,
        ignore_fields: list[str] = None,
        abs_tol: float = 0.0,
        rel_tol: float = 0.0,
        abs_tol_fields: dict[str, float] = None,
        rel_tol_fields: dict[str, float] = None,
        epsilon: float = 1e-12,
        show_debug: bool = False,
    ) -> bool

### Arguments

<table>
<thead>
<tr>
<th>Parameter D</th>
<th>escription</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>left, right</code></td>
<td>The two structures to compare.</td>
</tr>
<tr>
<td><code>ignore_fields</code></td>
<td>Keys to ignore during comparison.</td>
</tr>
<tr>
<td><code>abs_tol</code></td>
<td>Global absolute tolerance for numeric drift.</td>
</tr>
<tr>
<td><code>rel_tol</code></td>
<td>Global relative tolerance.</td>
</tr>
<tr>
<td><code>abs_tol_fields</code></td>
<td>Dict of per-field absolute tolerances.</td>
</tr>
<tr>
<td><code>rel_tol_fields</code></td>
<td>Dict of per-field relative tolerances.</td>
</tr>
<tr>
<td><code>epsilon</code></td>
<td>Small float to absorb FP rounding errors.</td>
</tr>
<tr>
<td><code>show_debug</code></td>
<td>If True, prints detailed comparison logs.</td>
</tr>
</tbody>
</table>


## Tips

- Both dicts must have the same keys — missing keys fail the comparison.
- Lists are compared in order.
- For dataclasses or Pydantic models, call `.dict()` first:
  `compare_dicts(model_a.dict(), model_b.dict())`
- Numeric strings (`"1.0"`) are **not treated as numbers**. Only real
  numeric types (`int`/`float`) are compared numerically.

## Use Cases

- Snapshot testing of API responses or data models.
- Machine-learning drift and metric comparison.
- CI/CD pipelines to verify payload consistency.
- Configuration or schema diffing.

## License

Apache License 2.0 — © 2025 Mohamed Tahri Contributions welcome 🤝

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "dictlens",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "json, diff, compare, dict, dataclass, pydantic, tolerance, ignore",
    "author": null,
    "author_email": "Mohamed Tahri <simotahri1@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/81/7b/9659291258d7968665a6c3500fbfa465dc2c5fd06c8af91a54aa60123574/dictlens-0.1.1.tar.gz",
    "platform": null,
    "description": "# dictlens\n\nDeep structural comparison for Python dicts with per-field numeric\ntolerance and JSONPath-like targeting.\n\n## Overview\n\n`dictlens` is a lightweight Python library for comparing two nested\n`dict` structures (or any dict-like objects) with **fine-grained\ntolerance control**.\n\nIt supports:\n\n- \u2705 Global absolute (`abs_tol`) and relative (`rel_tol`) numeric\n  tolerances\n- \u2705 Per-field tolerance overrides via JSONPath-like expressions\n- \u2705 Ignoring volatile or irrelevant fields\n- \u2705 Detailed debug logs that explain *why* two structures differ\n\nIt\u2019s ideal for comparing API payloads, serialized models, ML metrics, or\nconfigurations where small numeric drifts are expected.\n\n## Installation\n\n    pip install dictlens\n\n## Quick Examples\n\n``` python\nfrom dictlens import compare_dicts\n\na = {\"sensor\": {\"temp\": 21.5, \"humidity\": 48.0}}\nb = {\"sensor\": {\"temp\": 21.7, \"humidity\": 48.5}}\n\n# Default tolerances\nres = compare_dicts(a, b, abs_tol=0.05, rel_tol=0.01, show_debug=True)\nprint(res)  # False\n```\n\n```console\n### Output (debug)\n\n[NUMERIC COMPARE] $.sensor.temp: 21.5 vs 21.7 | diff=0.200000 | abs_tol=0.05 | rel_tol=0.01 | threshold=0.217000\n[MATCH NUMERIC] $.sensor.temp: within tolerance\n[NUMERIC COMPARE] $.sensor.humidity: 48.0 vs 48.5 | diff=0.500000 | abs_tol=0.05 | rel_tol=0.01 | threshold=0.485000\n[FAIL NUMERIC] $.sensor.humidity \u2192 diff=0.500000 > threshold=0.485000\n[FAIL IN DICT] $.sensor.humidity\n[FAIL IN DICT] $..sensor\n```\n\n#### Ignore Fields\n\n```python\nfrom dictlens import compare_dicts\n\ndef test_ignore_path_root_field():\n    a = {\"id\": 1, \"timestamp\": \"now\"}\n    b = {\"id\": 1, \"timestamp\": \"later\"}\n    \n    # Ignore only the root timestamp field\n    ignore_paths = [\"$.timestamp\"]\n    \n    result = compare_dicts(a, b, ignore_paths=ignore_paths)\n    print(result) # True\n\n\ndef test_ignore_paths_complex():\n    \"\"\"\n    Ignore multiple paths with different patterns:\n      - Exact path:            $.user.profile.updated_at\n      - Array wildcard:        $.devices[*].debug\n      - Recursive descent key: $..trace\n    \"\"\"\n    a = {\n        \"user\": {\n            \"id\": 7,\n            \"profile\": {\"updated_at\": \"2025-10-14T10:00:00Z\", \"age\": 30}\n        },\n        \"devices\": [\n            {\"id\": \"d1\", \"debug\": \"alpha\", \"temp\": 20.0},\n            {\"id\": \"d2\", \"debug\": \"beta\", \"temp\": 20.1}\n        ],\n        \"sessions\": [\n            {\"events\": [{\"meta\": {\"trace\": \"abc\"}, \"value\": 10.0}]},\n            {\"events\": [{\"meta\": {\"trace\": \"def\"}, \"value\": 10.5}]}\n        ]\n    }\n\n    b = {\n        \"user\": {\n            \"id\": 7,\n            \"profile\": {\"updated_at\": \"2025-10-15T10:00:05Z\", \"age\": 30}\n        },\n        \"devices\": [\n            {\"id\": \"d1\", \"debug\": \"changed\", \"temp\": 20.05},\n            {\"id\": \"d2\", \"debug\": \"changed\", \"temp\": 20.18}\n        ],\n        \"sessions\": [\n            {\"events\": [{\"meta\": {\"trace\": \"xyz\"}, \"value\": 10.01}]},\n            {\"events\": [{\"meta\": {\"trace\": \"uvw\"}, \"value\": 10.52}]}\n        ]\n    }\n\n    # Ignore updated_at (exact), all device.debug (wildcard), any 'trace' anywhere (recursive)\n    ignore_paths = [\n        \"$.user.profile.updated_at\",\n        \"$.devices[*].debug\",\n        \"$..trace\",\n    ]\n\n    # Small global tolerance to allow minor sensor/value drift\n    result =  compare_dicts(\n                    a, b,\n                    ignore_paths=ignore_paths,\n                    abs_tol=0.05,\n                    rel_tol=0.02\n                )\n    print(result) # True\n```\n\n#### Per-field Tolerances\n\nYou can override tolerances for specific paths using JSONPath-like\nexpressions.\n\n```python\nfrom dictlens import compare_dicts\n\na = {\"a\": 1.0, \"b\": 2.0}\nb = {\"a\": 1.5, \"b\": 2.5}\nabs_tol_fields = {\"$.b\": 1.0}\nresult = compare_dicts(a, b,abs_tol=0.5, abs_tol_fields=abs_tol_fields) \nprint(result)  # True\n```\n\n#### array specific index tolerance\n\n```python\nfrom dictlens import compare_dicts\n\na = {\"sensors\": [{\"temp\": 20.0}, {\"temp\": 21.0}]}\nb = {\"sensors\": [{\"temp\": 20.05}, {\"temp\": 21.5}]}\nabs_tol_fields = {\"$.sensors[0].temp\": 0.1, \"$.sensors[1].temp\": 1.0} \nresult = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)\nprint(result) # True\n```\n\n#### array wildcard tolerance\n\n```python\nfrom dictlens import compare_dicts\n\na = {\"sensors\": [{\"temp\": 20.0}, {\"temp\": 21.0}]}\nb = {\"sensors\": [{\"temp\": 20.2}, {\"temp\": 21.1}]}\nabs_tol_fields = {\"$.sensors[*].temp\": 0.5}\nresult = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)\nprint(result) # True\n```\n\n#### property wildcard tolerance\n\n```python\nfrom dictlens import compare_dicts\n\na = {\"network\": {\"n1\": {\"v\": 10}, \"n2\": {\"v\": 10}}}\nb = {\"network\": {\"n1\": {\"v\": 10.5}, \"n2\": {\"v\": 9.8}}}\nabs_tol_fields = {\"$.network.*.v\": 1.0}\nresult = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)\nprint(result) # True\n```\n\n#### recursive wildcard tolerance\n\n```python\nfrom dictlens import compare_dicts\n\na  = {\"meta\": {\"deep\": {\"very\": {\"x\": 100}}}}\nb = {\"meta\": {\"deep\": {\"very\": {\"x\": 101}}}}\nabs_tol_fields = {\"$..x\": 2.0}\nresult = compare_dicts(a, b, abs_tol_fields=abs_tol_fields)\nprint(result)\n```\n\n#### combined global and field tolerances\n\n```python \nfrom dictlens import compare_dicts\n\n# Original reading (e.g., baseline snapshot)\na = {\n    \"station\": {\n        \"id\": \"ST-42\",\n        \"location\": \"Paris\",\n        \"version\": 1.0\n    },\n    \"metrics\": {\n        \"temperature\": 21.5,\n        \"humidity\": 48.0,\n        \"pressure\": 1013.2,\n        \"wind_speed\": 5.4\n    },\n    \"status\": {\n        \"battery_level\": 96.0,\n        \"signal_strength\": -72\n    },\n    \"timestamp\": \"2025-10-14T10:00:00Z\"\n}\n\n# New reading (e.g., after transmission)\nb = {\n    \"station\": {\n        \"id\": \"ST-42\",\n        \"location\": \"Paris\",\n        \"version\": 1.03   # version drift allowed (custom abs_tol)\n    },\n    \"metrics\": {\n        \"temperature\": 21.6,   # tiny drift (global rel_tol ok)\n        \"humidity\": 49.3,      # bigger drift (custom abs_tol ok)\n        \"pressure\": 1013.5,    # tiny drift (global ok)\n        \"wind_speed\": 5.6      # small drift (global ok)\n    },\n    \"status\": {\n        \"battery_level\": 94.8,    # within abs_tol\n        \"signal_strength\": -69    # within rel_tol (5%)\n    },\n    \"timestamp\": \"2025-10-14T10:00:02Z\"  # ignored\n}\n\nabs_tol_fields = {\n    \"$.metrics.humidity\": 2.0,     # humidity sensors are noisy\n    \"$.station.version\": 0.1       # small version drift allowed\n}\n\nrel_tol_fields = {\n    \"$.status.signal_strength\": 0.05,\n    \"$.metrics.wind_speed\": 0.05,\n    \"$.status.battery_level\": 0.02  # allow \u00b12% battery drift\n}\n\nignore_paths = [\"$.meta.id\"] \n\nresult = compare_dicts(\n    a,\n    b,\n    abs_tol=0.05,\n    rel_tol=0.01,\n    abs_tol_fields=abs_tol_fields,\n    rel_tol_fields=rel_tol_fields,\n    ignore_paths=ignore_paths,\n    show_debug=True\n)\n\nprint(result) # True\n```\n\n## Supported Path Patterns\n\n`dictlens` implements a simplified subset of JSONPath syntax:\n\n<table>\n<thead>\n<tr>\n<th>Pattern</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>$.a.b</code></td>\n<td>Exact field path</td>\n</tr>\n<tr>\n<td><code>$.items[0].price</code></td>\n<td>Specific array index</td>\n</tr>\n<tr>\n<td><code>$.items[*].price</code></td>\n<td>Any array element</td>\n</tr>\n<tr>\n<td><code>$.data.*.value</code></td>\n<td>Any property name</td>\n</tr>\n<tr>\n<td><code>$..x</code></td>\n<td>Recursive descent for key <code>x</code></td>\n</tr>\n</tbody>\n</table>\n\n\ud83d\udd0d Supported and Future JSONPath Features\n\ndictlens currently supports a focused subset of JSONPath syntax designed for simplicity and performance in numeric\ncomparisons.\n\nThese patterns cover the vast majority of practical comparison use cases.\n\nAt the moment, advanced JSONPath features such as filters ([?()]), unions ([0,1,2]), slices ([0:2]), or expressions are\nnot yet supported.\nFuture versions of dictlens may expand support for these features once performance and readability trade-offs are fully\nevaluated.\n\n## Full API\n\n    compare_dicts(\n        left: dict,\n        right: dict,\n        *,\n        ignore_fields: list[str] = None,\n        abs_tol: float = 0.0,\n        rel_tol: float = 0.0,\n        abs_tol_fields: dict[str, float] = None,\n        rel_tol_fields: dict[str, float] = None,\n        epsilon: float = 1e-12,\n        show_debug: bool = False,\n    ) -> bool\n\n### Arguments\n\n<table>\n<thead>\n<tr>\n<th>Parameter D</th>\n<th>escription</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>left, right</code></td>\n<td>The two structures to compare.</td>\n</tr>\n<tr>\n<td><code>ignore_fields</code></td>\n<td>Keys to ignore during comparison.</td>\n</tr>\n<tr>\n<td><code>abs_tol</code></td>\n<td>Global absolute tolerance for numeric drift.</td>\n</tr>\n<tr>\n<td><code>rel_tol</code></td>\n<td>Global relative tolerance.</td>\n</tr>\n<tr>\n<td><code>abs_tol_fields</code></td>\n<td>Dict of per-field absolute tolerances.</td>\n</tr>\n<tr>\n<td><code>rel_tol_fields</code></td>\n<td>Dict of per-field relative tolerances.</td>\n</tr>\n<tr>\n<td><code>epsilon</code></td>\n<td>Small float to absorb FP rounding errors.</td>\n</tr>\n<tr>\n<td><code>show_debug</code></td>\n<td>If True, prints detailed comparison logs.</td>\n</tr>\n</tbody>\n</table>\n\n\n## Tips\n\n- Both dicts must have the same keys \u2014 missing keys fail the comparison.\n- Lists are compared in order.\n- For dataclasses or Pydantic models, call `.dict()` first:\n  `compare_dicts(model_a.dict(), model_b.dict())`\n- Numeric strings (`\"1.0\"`) are **not treated as numbers**. Only real\n  numeric types (`int`/`float`) are compared numerically.\n\n## Use Cases\n\n- Snapshot testing of API responses or data models.\n- Machine-learning drift and metric comparison.\n- CI/CD pipelines to verify payload consistency.\n- Configuration or schema diffing.\n\n## License\n\nApache License 2.0 \u2014 \u00a9 2025 Mohamed Tahri Contributions welcome \ud83e\udd1d\n",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "A lightweight library to compare JSON, dicts, dataclasses, and Pydantic models with tolerance and ignore rules.",
    "version": "0.1.1",
    "project_urls": null,
    "split_keywords": [
        "json",
        " diff",
        " compare",
        " dict",
        " dataclass",
        " pydantic",
        " tolerance",
        " ignore"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c78056b147acd3679c86008bc6d6cb765222dd2ae71200700e6b5d94ed2c85ff",
                "md5": "78e7abc2c343e7581011eb83b2df240b",
                "sha256": "e856ad59dccaa79b4e3af1caeb03b97876b22f7f495f92f77b400a1d706947d1"
            },
            "downloads": -1,
            "filename": "dictlens-0.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "78e7abc2c343e7581011eb83b2df240b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 11977,
            "upload_time": "2025-10-14T22:40:44",
            "upload_time_iso_8601": "2025-10-14T22:40:44.818994Z",
            "url": "https://files.pythonhosted.org/packages/c7/80/56b147acd3679c86008bc6d6cb765222dd2ae71200700e6b5d94ed2c85ff/dictlens-0.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "817b9659291258d7968665a6c3500fbfa465dc2c5fd06c8af91a54aa60123574",
                "md5": "01e8913bbe45f894831e7980c9972253",
                "sha256": "aebae575b17b71ecee6d3945b5a628b5edc3a91b4c34b53888081c288d96095d"
            },
            "downloads": -1,
            "filename": "dictlens-0.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "01e8913bbe45f894831e7980c9972253",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 13254,
            "upload_time": "2025-10-14T22:40:45",
            "upload_time_iso_8601": "2025-10-14T22:40:45.665677Z",
            "url": "https://files.pythonhosted.org/packages/81/7b/9659291258d7968665a6c3500fbfa465dc2c5fd06c8af91a54aa60123574/dictlens-0.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-14 22:40:45",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "dictlens"
}
        
Elapsed time: 0.62925s