jsonology 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .coverage
11
+ .hypothesis/
12
+ uv.lock
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The jsonology authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.5
2
+ Name: jsonology
3
+ Version: 0.1.0
4
+ Summary: Evaluate a strict, tiny JSON DSL (comparisons, if, and/or/not) against a $-rooted context object
5
+ Author: The jsonology authors
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: dsl,evaluator,expression,json,jsonlogic,rules
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Provides-Extra: dev
22
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
23
+ Requires-Dist: mypy>=1.11; extra == 'dev'
24
+ Requires-Dist: pytest>=8; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # jsonology
29
+
30
+ Evaluate a strict, tiny JSON DSL against a context object.
31
+
32
+ ```python
33
+ >>> from jsonology import evaluate
34
+ >>> evaluate({"==": ["$.operation", "edit"]}, {"operation": "edit"})
35
+ True
36
+ >>> evaluate({"if": [{"==": ["$.operation", "edit"]}, "an edit!", "not an edit"]},
37
+ ... {"operation": "delete"})
38
+ 'not an edit'
39
+ ```
40
+
41
+ Expressions are JSON values, authored by trusted teammates, stored in rules or
42
+ config. `jsonology` interprets them strictly and errors loudly. Zero runtime
43
+ dependencies. Python ≥ 3.10, fully typed.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install jsonology
49
+ ```
50
+
51
+ ## The language
52
+
53
+ An **expression** is one of:
54
+
55
+ - a JSON scalar — a literal (`5`, `5.5`, `"edit"`, `true`, `false`, `null`)
56
+ - a string starting with `$.` — a path into the context
57
+ - an operator object `{op: [args...]}` — where every argument is itself an expression
58
+
59
+ ### Operators
60
+
61
+ | Operator | Arity | Semantics |
62
+ | --- | --- | --- |
63
+ | `==`, `!=` | exactly 2 | strict equality, no coercion |
64
+ | `<`, `<=`, `>`, `>=` | exactly 2 | ordering: two numbers or two strings only |
65
+ | `if` | exactly 3 | `[condition, if_true, if_false]`, **lazy** |
66
+ | `and`, `or` | 1 or more | short-circuit, return strict booleans |
67
+ | `not` | exactly 1 | negation of truthiness |
68
+
69
+ Operator objects must have exactly one key. Bare lists are not expressions.
70
+ Unknown operators are rejected with an error naming them.
71
+
72
+ ### Paths
73
+
74
+ - `$.user.address.city` — dot-nested object access
75
+ - `$.items.0` — numeric segments index lists (no negative indices)
76
+ - a missing path evaluates to `null`, so `{"==": ["$.nickname", null]}` is the "is unset" idiom
77
+ - `$` alone is the entire context (which may itself be a list)
78
+
79
+ ### Truthiness
80
+
81
+ `if` conditions and `and`/`or`/`not` operands are interpreted Python-style:
82
+
83
+ | Value | Truthy? |
84
+ | --- | --- |
85
+ | `false`, `null` | no |
86
+ | `0` | no |
87
+ | `""` | no |
88
+ | `[]`, `{}` | no |
89
+ | everything else | yes |
90
+
91
+ ### Strict typing
92
+
93
+ - No coercion: `{"==": ["5", 5]}` is `false`.
94
+ - `int` and `float` unify: `5 == 5.0` is `true`.
95
+ - `bool` is its own type: `true == 1` is `false`, and ordering booleans is an error.
96
+ - Cross-type equality is always decided, never an error: `{"==": [1, "1"]}` → `false`.
97
+ - Ordering is only defined for two numbers or two strings; anything else —
98
+ cross-type, `null` on either side, booleans, lists — raises `EvaluationError`.
99
+ Note `{"<": ["$.missing", 5]}` raises, because a missing path is `null`.
100
+ - Strings order lexicographically: `{"<": ["10", "9"]}` is `true`.
101
+ - Equality is deep and structural over arrays and objects.
102
+
103
+ ### Laziness and short-circuiting
104
+
105
+ `if` evaluates only the taken branch. `and` stops at the first falsy operand,
106
+ `or` at the first truthy one — later operands are never evaluated (or errored):
107
+
108
+ ```python
109
+ evaluate({"and": [False, {"<": [None, 1]}]}, {}) # False — the broken branch never runs
110
+ ```
111
+
112
+ Because of this laziness, syntax errors inside dead branches stay silent —
113
+ `evaluate({"if": [True, 1, {"frobnicate": [1]}]}, {})` is `1`.
114
+
115
+ ## API
116
+
117
+ ```python
118
+ evaluate(expression, data) -> Any
119
+ ```
120
+
121
+ - `expression`: an already-parsed Python object **or** a raw JSON string.
122
+ A string that is `"$"` or starts with `"$."` is treated as a path, not JSON
123
+ text; any other string is parsed as JSON and failures raise
124
+ `ExpressionSyntaxError`. There is **no escape hatch** for literal strings
125
+ that start with `$.` — they are always paths.
126
+ - `data`: the context object (a mapping, or a list if your paths start `$.0` …).
127
+ - Pure, stateless, thread-safe: neither `expression` nor `data` is mutated.
128
+
129
+ Two exceptions, and only two:
130
+
131
+ | Exception | Raised for |
132
+ | --- | --- |
133
+ | `ExpressionSyntaxError` | malformed DSL: structure, arity, unknown operator, invalid JSON text, nesting too deep |
134
+ | `EvaluationError` | undefined operation during evaluation (ordering rules above) |
135
+
136
+ Error messages name the offending operator and locate the failing
137
+ subexpression within the expression tree, e.g. `$.if[0].==[1]` — "argument 1
138
+ of the `==` at argument 0 of the `if`".
139
+
140
+ ```python
141
+ >>> from jsonology import evaluate, ExpressionSyntaxError
142
+ >>> try:
143
+ ... evaluate('{"if": [{"==": ["$.a", {"x": 1, "y": 2}]}, 1, 2]}', {})
144
+ ... except ExpressionSyntaxError as e:
145
+ ... print(e)
146
+ operator object must have exactly one key, got 2: {"x": 1, "y": 2} (at $.if[0].==[1])
147
+ ```
148
+
149
+ ## Limitations (deliberate, v1)
150
+
151
+ - No literal escape hatch for strings starting with `$.`.
152
+ - No array or object literals: every operand is a scalar, a path, or a nested
153
+ operator. `{"==": ["$.tags", ["a", "b"]]}` is a syntax error (compare two
154
+ paths instead: `{"==": ["$.tags", "$.want"]}`), and an object literal like
155
+ `{"x": 1}` in operand position fails as an unknown operator `'x'`.
156
+ - Dead `if` branches and short-circuited `and`/`or` operands are never
157
+ evaluated, so their syntax is not validated.
158
+ - Context keys containing dots cannot be addressed by paths.
159
+ - The contract is JSON types. Non-JSON Python values in `data` (datetime,
160
+ Decimal, …) are out of contract — same-type pairs fall through to Python
161
+ semantics, everything else is unspecified. For time comparisons store
162
+ ISO-8601 strings (lexicographic `<` does the right thing) or epoch integers.
163
+ - No custom operators, no depth or size limits (trusted-input model).
164
+ - No `compile()` step — expressions are interpreted directly; reach for one
165
+ only if profiling demands it.
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ uv venv && uv pip install -e '.[dev]'
171
+ python -m pytest # 149 cases: table-driven goldens + property-based invariants
172
+ python -m ruff check .
173
+ python -m mypy
174
+ ```
175
+
176
+ The property suite (Hypothesis) checks the contract as invariants: only the
177
+ two documented exceptions ever escape, `==`/ordering obey the algebraic laws
178
+ (reflexivity, symmetry, transitivity, connexity), truthiness matches the
179
+ reference table, path resolution matches an independent reference walk, dead
180
+ branches never evaluate, inputs are never mutated, and JSON-text input agrees
181
+ with object input.
182
+
183
+ ## License
184
+
185
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,158 @@
1
+ # jsonology
2
+
3
+ Evaluate a strict, tiny JSON DSL against a context object.
4
+
5
+ ```python
6
+ >>> from jsonology import evaluate
7
+ >>> evaluate({"==": ["$.operation", "edit"]}, {"operation": "edit"})
8
+ True
9
+ >>> evaluate({"if": [{"==": ["$.operation", "edit"]}, "an edit!", "not an edit"]},
10
+ ... {"operation": "delete"})
11
+ 'not an edit'
12
+ ```
13
+
14
+ Expressions are JSON values, authored by trusted teammates, stored in rules or
15
+ config. `jsonology` interprets them strictly and errors loudly. Zero runtime
16
+ dependencies. Python ≥ 3.10, fully typed.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install jsonology
22
+ ```
23
+
24
+ ## The language
25
+
26
+ An **expression** is one of:
27
+
28
+ - a JSON scalar — a literal (`5`, `5.5`, `"edit"`, `true`, `false`, `null`)
29
+ - a string starting with `$.` — a path into the context
30
+ - an operator object `{op: [args...]}` — where every argument is itself an expression
31
+
32
+ ### Operators
33
+
34
+ | Operator | Arity | Semantics |
35
+ | --- | --- | --- |
36
+ | `==`, `!=` | exactly 2 | strict equality, no coercion |
37
+ | `<`, `<=`, `>`, `>=` | exactly 2 | ordering: two numbers or two strings only |
38
+ | `if` | exactly 3 | `[condition, if_true, if_false]`, **lazy** |
39
+ | `and`, `or` | 1 or more | short-circuit, return strict booleans |
40
+ | `not` | exactly 1 | negation of truthiness |
41
+
42
+ Operator objects must have exactly one key. Bare lists are not expressions.
43
+ Unknown operators are rejected with an error naming them.
44
+
45
+ ### Paths
46
+
47
+ - `$.user.address.city` — dot-nested object access
48
+ - `$.items.0` — numeric segments index lists (no negative indices)
49
+ - a missing path evaluates to `null`, so `{"==": ["$.nickname", null]}` is the "is unset" idiom
50
+ - `$` alone is the entire context (which may itself be a list)
51
+
52
+ ### Truthiness
53
+
54
+ `if` conditions and `and`/`or`/`not` operands are interpreted Python-style:
55
+
56
+ | Value | Truthy? |
57
+ | --- | --- |
58
+ | `false`, `null` | no |
59
+ | `0` | no |
60
+ | `""` | no |
61
+ | `[]`, `{}` | no |
62
+ | everything else | yes |
63
+
64
+ ### Strict typing
65
+
66
+ - No coercion: `{"==": ["5", 5]}` is `false`.
67
+ - `int` and `float` unify: `5 == 5.0` is `true`.
68
+ - `bool` is its own type: `true == 1` is `false`, and ordering booleans is an error.
69
+ - Cross-type equality is always decided, never an error: `{"==": [1, "1"]}` → `false`.
70
+ - Ordering is only defined for two numbers or two strings; anything else —
71
+ cross-type, `null` on either side, booleans, lists — raises `EvaluationError`.
72
+ Note `{"<": ["$.missing", 5]}` raises, because a missing path is `null`.
73
+ - Strings order lexicographically: `{"<": ["10", "9"]}` is `true`.
74
+ - Equality is deep and structural over arrays and objects.
75
+
76
+ ### Laziness and short-circuiting
77
+
78
+ `if` evaluates only the taken branch. `and` stops at the first falsy operand,
79
+ `or` at the first truthy one — later operands are never evaluated (or errored):
80
+
81
+ ```python
82
+ evaluate({"and": [False, {"<": [None, 1]}]}, {}) # False — the broken branch never runs
83
+ ```
84
+
85
+ Because of this laziness, syntax errors inside dead branches stay silent —
86
+ `evaluate({"if": [True, 1, {"frobnicate": [1]}]}, {})` is `1`.
87
+
88
+ ## API
89
+
90
+ ```python
91
+ evaluate(expression, data) -> Any
92
+ ```
93
+
94
+ - `expression`: an already-parsed Python object **or** a raw JSON string.
95
+ A string that is `"$"` or starts with `"$."` is treated as a path, not JSON
96
+ text; any other string is parsed as JSON and failures raise
97
+ `ExpressionSyntaxError`. There is **no escape hatch** for literal strings
98
+ that start with `$.` — they are always paths.
99
+ - `data`: the context object (a mapping, or a list if your paths start `$.0` …).
100
+ - Pure, stateless, thread-safe: neither `expression` nor `data` is mutated.
101
+
102
+ Two exceptions, and only two:
103
+
104
+ | Exception | Raised for |
105
+ | --- | --- |
106
+ | `ExpressionSyntaxError` | malformed DSL: structure, arity, unknown operator, invalid JSON text, nesting too deep |
107
+ | `EvaluationError` | undefined operation during evaluation (ordering rules above) |
108
+
109
+ Error messages name the offending operator and locate the failing
110
+ subexpression within the expression tree, e.g. `$.if[0].==[1]` — "argument 1
111
+ of the `==` at argument 0 of the `if`".
112
+
113
+ ```python
114
+ >>> from jsonology import evaluate, ExpressionSyntaxError
115
+ >>> try:
116
+ ... evaluate('{"if": [{"==": ["$.a", {"x": 1, "y": 2}]}, 1, 2]}', {})
117
+ ... except ExpressionSyntaxError as e:
118
+ ... print(e)
119
+ operator object must have exactly one key, got 2: {"x": 1, "y": 2} (at $.if[0].==[1])
120
+ ```
121
+
122
+ ## Limitations (deliberate, v1)
123
+
124
+ - No literal escape hatch for strings starting with `$.`.
125
+ - No array or object literals: every operand is a scalar, a path, or a nested
126
+ operator. `{"==": ["$.tags", ["a", "b"]]}` is a syntax error (compare two
127
+ paths instead: `{"==": ["$.tags", "$.want"]}`), and an object literal like
128
+ `{"x": 1}` in operand position fails as an unknown operator `'x'`.
129
+ - Dead `if` branches and short-circuited `and`/`or` operands are never
130
+ evaluated, so their syntax is not validated.
131
+ - Context keys containing dots cannot be addressed by paths.
132
+ - The contract is JSON types. Non-JSON Python values in `data` (datetime,
133
+ Decimal, …) are out of contract — same-type pairs fall through to Python
134
+ semantics, everything else is unspecified. For time comparisons store
135
+ ISO-8601 strings (lexicographic `<` does the right thing) or epoch integers.
136
+ - No custom operators, no depth or size limits (trusted-input model).
137
+ - No `compile()` step — expressions are interpreted directly; reach for one
138
+ only if profiling demands it.
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ uv venv && uv pip install -e '.[dev]'
144
+ python -m pytest # 149 cases: table-driven goldens + property-based invariants
145
+ python -m ruff check .
146
+ python -m mypy
147
+ ```
148
+
149
+ The property suite (Hypothesis) checks the contract as invariants: only the
150
+ two documented exceptions ever escape, `==`/ordering obey the algebraic laws
151
+ (reflexivity, symmetry, transitivity, connexity), truthiness matches the
152
+ reference table, path resolution matches an independent reference walk, dead
153
+ branches never evaluate, inputs are never mutated, and JSON-text input agrees
154
+ with object input.
155
+
156
+ ## License
157
+
158
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,151 @@
1
+ # jsonology — Specification (v1)
2
+
3
+ Status: confirmed by owner after design review (3 grilling rounds, 2026).
4
+
5
+ ## Purpose
6
+
7
+ A from-scratch, zero-dependency Python library that evaluates a JSON DSL
8
+ against a context object. Expressions are authored by trusted teammates and
9
+ stored in rules/config; the library errors loudly and applies no artificial
10
+ input limits.
11
+
12
+ Package name `jsonology` (verified free on PyPI). Originally `jsonlogi`,
13
+ renamed before first release: the old name was one typo away from
14
+ `jsonlogic` / `jsonlogic-py`, which are different tools.
15
+
16
+ ## Grammar
17
+
18
+ An **expression** is one of:
19
+
20
+ - a JSON scalar (number, string, `true`, `false`, `null`) — a literal
21
+ - a string starting with `$.` — a path into the context
22
+ - an operator object: `{op: [args...]}` where every arg is itself an expression
23
+ - any of the above nested recursively
24
+
25
+ Rules:
26
+
27
+ - Operator objects have **exactly one key**; otherwise `ExpressionSyntaxError`.
28
+ - Bare lists are not expressions → `ExpressionSyntaxError`.
29
+ - Consequently there are **no array or object literals**: a bare list operand
30
+ is a syntax error, and a single-key dict in operand position is always an
31
+ operator object (`{"x": 1}` there fails as an unknown operator). Values of
32
+ any shape enter expressions only through paths.
33
+ - Unknown operator → `ExpressionSyntaxError` naming the operator.
34
+ - **Strict arity**: comparisons exactly 2 args · `if` exactly 3 · `and`/`or`
35
+ ≥ 1 · `not` exactly 1.
36
+ - No escape hatch for literal strings that start with `$.` (documented
37
+ limitation). Context keys containing dots are unsupported (documented
38
+ limitation).
39
+ - No custom-operator registration in v1.
40
+
41
+ ## Paths
42
+
43
+ - `$.user.address.city` — dot-nested object access.
44
+ - `$.items.0` — numeric segments index lists.
45
+ - Missing path evaluates to `null`, so `{"==": ["$.nickname", null]}` is the
46
+ "is unset" idiom.
47
+ - Bare `$` evaluates to the entire context; the context may itself be a list
48
+ (`evaluate("$.0", ["a", "b"])` → `"a"`).
49
+
50
+ ## Operators (10)
51
+
52
+ `==` `!=` `>` `<` `>=` `<=` `if` `and` `or` `not`
53
+
54
+ ### Comparisons — strict, no coercion
55
+
56
+ - `"5" == 5` → `false` (and `!=` → `true`); cross-type equality is always
57
+ `false`/`true`, never raises.
58
+ - `5 == 5.0` → `true` (int/float unify).
59
+ - `null == null` → `true`.
60
+ - `true == 1` → `false`: `bool` is a **distinct type** from numbers.
61
+ - Ordering (`>` `<` `>=` `<=`):
62
+ - numbers with numbers, strings with strings (lexicographic) — defined.
63
+ - any cross-type pair, or any operand `null`, or two booleans →
64
+ `EvaluationError`.
65
+ - `!=` is exactly the negation of `==` in every case.
66
+
67
+ ### `if` — `[condition, if_true, if_false]`
68
+
69
+ Lazy: only the taken branch is evaluated. Strict 3-arity. Corollary: dead
70
+ branches (and short-circuited `and`/`or` operands) are never validated, so
71
+ syntax errors inside them stay silent.
72
+
73
+ ### `and` / `or` / `not`
74
+
75
+ - `and`/`or`: n-ary (≥ 1), **short-circuit** (evaluation stops at the first
76
+ decisive operand), return strict `true`/`false`.
77
+ - `not`: unary, returns strict `true`/`false`.
78
+
79
+ ### Truthiness (Python-style)
80
+
81
+ Applies to `if` conditions and `and`/`or`/`not` operands:
82
+
83
+ | value | truthy? |
84
+ | ---------------- | ------- |
85
+ | `false`, `null` | no |
86
+ | `0` | no |
87
+ | `""` | no |
88
+ | `[]`, `{}` | no |
89
+ | everything else | yes |
90
+
91
+ ## Errors
92
+
93
+ Exactly two exception types:
94
+
95
+ - `ExpressionSyntaxError` — malformed DSL (structure, arity, unknown operator,
96
+ unparseable JSON string input, nesting too deep).
97
+ - `EvaluationError` — type trouble during evaluation (undefined ordering).
98
+
99
+ Error messages carry the offending operator, a snippet of its arguments, and
100
+ the path of the failing subexpression within the expression tree
101
+ (e.g. `$.if[0].==[1]`). Python `RecursionError` is wrapped as
102
+ `ExpressionSyntaxError("expression too deeply nested")`.
103
+
104
+ ## API
105
+
106
+ ```python
107
+ evaluate(expression, data: Mapping) -> Any
108
+ ```
109
+
110
+ - Accepts an already-parsed Python object **or** a raw JSON string; string
111
+ parse failures raise `ExpressionSyntaxError`.
112
+ - Pure, stateless, thread-safe; never mutates `expression` or `data`.
113
+ - Returns any JSON value.
114
+
115
+ Non-JSON Python types in `data` (datetime, Decimal, …) are out of contract.
116
+ README recommends ISO-8601 strings for time comparisons (lexicographic `<`
117
+ works); NaN and other non-JSON floats are likewise out of contract.
118
+
119
+ ## Build
120
+
121
+ - Direct interpretation — walk the JSON each call; no compile step. Add
122
+ `compile()` only if profiling demands.
123
+ - MIT · Python ≥ 3.10 · `src/jsonology/` layout · `py.typed` · hatchling ·
124
+ zero runtime dependencies · publish to PyPI once tests + README exist.
125
+
126
+ ## Tests & docs
127
+
128
+ - pytest, table-driven golden tests (~90 cases): every operator × every edge
129
+ (missing paths, cross-type, nesting, laziness, short-circuit) plus
130
+ error-message assertions.
131
+ - Property-based tests (Hypothesis): totality of the two-exception contract
132
+ over fuzzed expressions; equality and ordering algebraic laws; truthiness
133
+ against a reference table; differential path resolution; laziness; purity
134
+ and determinism; JSON-text vs object input agreement.
135
+ - Docs: a thorough README only (operator table, truthiness table, missing-path
136
+ rules, the `$.` literal caveat). No doc site until a second consumer exists.
137
+
138
+ ## Decision log (rejections worth remembering)
139
+
140
+ - **Reuse**: rejected — JsonLogic's Python ecosystem is alpha-grade and the
141
+ syntax differs (`$.x` vs `{"var": "x"}`).
142
+ - **No coercion anywhere**: `"5" != 5`; strict typing chosen over JS-style
143
+ coercion.
144
+ - **No variadic `if`**: else-chains expressed by nesting; strict arity
145
+ throughout.
146
+ - **`and`/`or` return strict booleans**, not the decisive operand — the
147
+ default-value idiom gets a dedicated `coalesce` operator someday, if wanted.
148
+ - **Python-style truthiness** (`[]`/`{}` falsy), not JS-style.
149
+ - **`bool` distinct from `int`**: guards against `$.count` matching `true`.
150
+ - **No operator extensibility, no depth limits, no input size limits** in v1
151
+ (trusted-input model).
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jsonology"
7
+ version = "0.1.0"
8
+ description = "Evaluate a strict, tiny JSON DSL (comparisons, if, and/or/not) against a $-rooted context object"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "The jsonology authors" }]
12
+ requires-python = ">=3.10"
13
+ keywords = ["json", "dsl", "expression", "rules", "evaluator", "jsonlogic"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Software Development :: Libraries",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11", "hypothesis>=6.100"]
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/jsonology"]
33
+
34
+ [tool.pytest.ini_options]
35
+ testpaths = ["tests"]
36
+ addopts = "-q"
37
+
38
+ [tool.ruff]
39
+ line-length = 100
40
+ target-version = "py310"
41
+ src = ["src", "tests"]
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
45
+
46
+ [tool.mypy]
47
+ python_version = "3.10"
48
+ strict = true
49
+ files = ["src"]
@@ -0,0 +1,6 @@
1
+ """jsonology — evaluate a strict, tiny JSON DSL against a ``$``-rooted context object."""
2
+
3
+ from jsonology.core import EvaluationError, ExpressionSyntaxError, evaluate
4
+
5
+ __all__ = ["EvaluationError", "ExpressionSyntaxError", "evaluate"]
6
+ __version__ = "0.1.0"