vcti-lookup 1.0.1__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,8 @@
1
+ Copyright (c) 2018-2026 Visual Collaboration Technologies Inc.
2
+ All Rights Reserved.
3
+
4
+ This software is proprietary and confidential. Unauthorized copying,
5
+ distribution, or use of this software, via any medium, is strictly
6
+ prohibited. Access is granted only to authorized VCollab developers
7
+ and individuals explicitly authorized by Visual Collaboration
8
+ Technologies Inc.
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: vcti-lookup
3
+ Version: 1.0.1
4
+ Summary: Attribute-based item lookup and filtering for Python collections
5
+ Author: Visual Collaboration Technologies Inc.
6
+ Requires-Python: <3.15,>=3.12
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: vcti-predicate>=1.0.7
10
+ Provides-Extra: test
11
+ Requires-Dist: pytest; extra == "test"
12
+ Requires-Dist: pytest-cov; extra == "test"
13
+ Provides-Extra: lint
14
+ Requires-Dist: ruff; extra == "lint"
15
+ Provides-Extra: numpy
16
+ Requires-Dist: numpy>=1.24; extra == "numpy"
17
+ Dynamic: license-file
18
+
19
+ # Lookup
20
+
21
+ ## Purpose
22
+
23
+ `vcti-lookup` provides `Lookup`, an attribute-based filtering engine
24
+ for Python sequences. It works at two levels:
25
+
26
+ **Level 1 — Filtering.** You have a sequence of items, each with an
27
+ ID and attributes. Lookup lets you filter by rules and get matching
28
+ items back. The items can be dicts, dataclasses, Pydantic models,
29
+ numpy structured-array rows, or any object — pluggable getters
30
+ control how attributes are extracted.
31
+
32
+ **Level 2 — Data mapping.** You have external data (a list of file
33
+ paths, a numpy array, a tree of nodes) and a separate set of
34
+ attributes describing each item. You build an attribute set where
35
+ `id = index`, filter with Lookup, and use the matching IDs to index
36
+ back into your data. Lookup doesn't wrap or copy your data — you
37
+ keep full control.
38
+
39
+ ```
40
+ Level 1 — Filter directly Level 2 — Map to external data
41
+
42
+ ┌──────────────┐ ┌───────────────┐
43
+ │ items │ │ your data │
44
+ │ (id + attrs) │ │ (list, array) │
45
+ └──────┬───────┘ └───────▲───────┘
46
+ │ │ indices
47
+ ▼ │
48
+ ┌──────────────┐ Rules ┌──────────────┐ Rules
49
+ │ Lookup │◄────── │ Lookup │◄──────
50
+ └──────┬───────┘ └──────┬───────┘
51
+ │ │
52
+ ▼ ▼
53
+ matched items matching_ids()
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Installation
59
+
60
+ ### From GitHub (recommended for development)
61
+
62
+ ```bash
63
+ # Latest main branch
64
+ pip install vcti-lookup
65
+
66
+
67
+ ### In `requirements.txt`
68
+
69
+ ```
70
+ vcti-lookup>=1.0.1
71
+ ```
72
+
73
+ ### In `pyproject.toml` dependencies
74
+
75
+ ```toml
76
+ dependencies = [
77
+ "vcti-lookup>=1.0.1",
78
+ ]
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Quick Start
84
+
85
+ ### Level 1 — Filter items directly
86
+
87
+ ```python
88
+ from vcti.lookup import Lookup, Rule
89
+
90
+ items = [
91
+ {"id": 1, "type": "pdf", "size": 1000},
92
+ {"id": 2, "type": "txt", "size": 200},
93
+ {"id": 3, "type": "pdf", "size": 1500},
94
+ ]
95
+
96
+ lk = Lookup(items)
97
+
98
+ # Single rule
99
+ pdfs = lk.filter([Rule("type", "==", "pdf")])
100
+ # [{"id": 1, ...}, {"id": 3, ...}]
101
+
102
+ # Multiple rules (AND)
103
+ large_pdfs = lk.filter([Rule("type", "==", "pdf"), Rule("size", ">", 1200)])
104
+ # [{"id": 3, ...}]
105
+
106
+ # OR logic
107
+ result = lk.filter_any([Rule("type", "==", "pdf"), Rule("type", "==", "jpg")])
108
+
109
+ # Exclusion (complement of filter)
110
+ non_pdfs = lk.exclude([Rule("type", "==", "pdf")])
111
+
112
+ # Lookup by ID
113
+ item = lk.get(2)
114
+
115
+ # Unary operators
116
+ lk.filter([Rule("name", "is_empty")])
117
+ ```
118
+
119
+ ### Level 2 — Map attributes to external data
120
+
121
+ ```python
122
+ from vcti.lookup import Lookup, Rule
123
+
124
+ # Your data — any shape, Lookup doesn't touch it
125
+ data = ["report.pdf", "notes.txt", "analysis.pdf"]
126
+
127
+ # Build attribute set — id = index into your data
128
+ attrs = [
129
+ {"id": 0, "type": "pdf", "size": 1000},
130
+ {"id": 1, "type": "txt", "size": 200},
131
+ {"id": 2, "type": "pdf", "size": 1500},
132
+ ]
133
+
134
+ lk = Lookup(attrs)
135
+ indices = lk.matching_ids([Rule("type", "==", "pdf")])
136
+ # [0, 2]
137
+
138
+ # Map back to your data
139
+ results = [data[i] for i in indices]
140
+ # ["report.pdf", "analysis.pdf"]
141
+ ```
142
+
143
+ ### Numpy structured arrays (direct, no copy)
144
+
145
+ ```python
146
+ import numpy as np
147
+ from vcti.lookup import Lookup, Rule
148
+ from vcti.lookup.getter import numpy_getter
149
+
150
+ dt = np.dtype([("id", "i4"), ("type", "U10"), ("value", "f8")])
151
+ arr = np.array([(0, "sensor", 3.14), (1, "actuator", 2.71)], dtype=dt)
152
+
153
+ # Pass the array directly — no conversion
154
+ lk = Lookup(arr, getter=numpy_getter, id_key="id")
155
+ indices = lk.matching_ids([Rule("type", "==", "sensor")])
156
+ # [0]
157
+
158
+ # Index back into the array
159
+ results = arr[indices]
160
+ ```
161
+
162
+ ### Dataclasses
163
+
164
+ ```python
165
+ from dataclasses import dataclass
166
+
167
+ @dataclass
168
+ class File:
169
+ id: int
170
+ type: str
171
+ size: int
172
+
173
+ files = [File(1, "pdf", 1000), File(2, "txt", 200), File(3, "pdf", 1500)]
174
+ lk = Lookup(files)
175
+ result = lk.filter([Rule("type", "==", "pdf")])
176
+ # [File(1, "pdf", 1000), File(3, "pdf", 1500)]
177
+ ```
178
+
179
+ ### Modifiers
180
+
181
+ Modifiers are keyword arguments forwarded to vcti-predicate. They
182
+ control comparison behaviour. Pass them as the fourth argument to
183
+ ``Rule``.
184
+
185
+ ```python
186
+ items = [{"id": 1, "name": "Report_Q1"}, {"id": 2, "name": "report_Q2"}]
187
+ lk = Lookup(items)
188
+
189
+ # Default: case-insensitive (both match)
190
+ lk.filter([Rule("name", "^=", "report")])
191
+
192
+ # Case-sensitive via modifier (only item 1)
193
+ lk.filter([Rule("name", "^=", "Report", {"case_sensitive": True})])
194
+
195
+ # Float tolerance
196
+ lk.filter([Rule("value", "==", 3.14, {"tolerance": 0.01})])
197
+
198
+ # Regex with multiline
199
+ lk.filter([Rule("content", "~=", r"^error:", {"multiline": True})])
200
+ ```
201
+
202
+ See [patterns.md](docs/patterns.md) for more modifier examples.
203
+
204
+ ---
205
+
206
+ ## Public API
207
+
208
+ | Name | Type | Description |
209
+ |------|------|-------------|
210
+ | `Lookup[T]` | Class | Attribute-based filtering for any sequence |
211
+ | `Rule` | Frozen dataclass | Filter condition (attribute, operator, value, modifiers) |
212
+ | `ValueGetter` | Type alias | `Callable[[Any, str], Any]` |
213
+ | `MISSING` | Sentinel | Returned by getters when attribute is absent |
214
+ | `auto_getter` | Function | Handles dicts and objects transparently |
215
+ | `dict_getter` | Function | Extracts values from dicts |
216
+ | `object_getter` | Function | Extracts values via `getattr` |
217
+ | `attributes_getter` | Function | Extracts values from `.attributes` dict |
218
+ | `numpy_getter` | Function | Extracts fields from numpy structured-array rows |
219
+
220
+ ### Lookup methods
221
+
222
+ | Method | Returns | Description |
223
+ |--------|---------|-------------|
224
+ | `filter(rules)` | `list[T]` | Items matching ALL rules (AND) |
225
+ | `filter_any(rules)` | `list[T]` | Items matching ANY rule (OR) |
226
+ | `exclude(rules)` | `list[T]` | Items NOT matching all rules |
227
+ | `first(rules)` | `T \| None` | First matching item (short-circuits) |
228
+ | `first_any(rules)` | `T \| None` | First item matching any rule (short-circuits) |
229
+ | `first_id(rules)` | `Any \| None` | ID of first matching item |
230
+ | `matching_ids(rules)` | `list[Any]` | IDs of all matching items |
231
+ | `count(rules)` | `int` | Number of matching items (no list allocation) |
232
+ | `get(item_id)` | `T \| None` | Item by ID (O(1)) |
233
+ | `items` | `Sequence[T]` | The underlying sequence (same reference) |
234
+ | `lk[i]` / `lk[i:j]` | `T` / `Sequence[T]` | Index or slice access |
235
+
236
+ ---
237
+
238
+ ## Dependencies
239
+
240
+ - [vcti-predicate](https://github.com/vcollab/vcti-python-predicate) — condition evaluation engine
241
+ - [numpy](https://numpy.org/) — optional, for `numpy_getter`
242
+
243
+ ---
244
+
245
+ ## Documentation
246
+
247
+ - [Design](docs/design.md) — Concepts, architecture, and design decisions
248
+ - [Patterns](docs/patterns.md) — Real-world recipes and usage patterns
249
+ - [Source Guide](docs/source-guide.md) — Implementation walkthrough
250
+ - [Extending](docs/extending.md) — Creating custom value getters
251
+ - [API Reference](docs/api.md) — Autodoc for all modules
@@ -0,0 +1,233 @@
1
+ # Lookup
2
+
3
+ ## Purpose
4
+
5
+ `vcti-lookup` provides `Lookup`, an attribute-based filtering engine
6
+ for Python sequences. It works at two levels:
7
+
8
+ **Level 1 — Filtering.** You have a sequence of items, each with an
9
+ ID and attributes. Lookup lets you filter by rules and get matching
10
+ items back. The items can be dicts, dataclasses, Pydantic models,
11
+ numpy structured-array rows, or any object — pluggable getters
12
+ control how attributes are extracted.
13
+
14
+ **Level 2 — Data mapping.** You have external data (a list of file
15
+ paths, a numpy array, a tree of nodes) and a separate set of
16
+ attributes describing each item. You build an attribute set where
17
+ `id = index`, filter with Lookup, and use the matching IDs to index
18
+ back into your data. Lookup doesn't wrap or copy your data — you
19
+ keep full control.
20
+
21
+ ```
22
+ Level 1 — Filter directly Level 2 — Map to external data
23
+
24
+ ┌──────────────┐ ┌───────────────┐
25
+ │ items │ │ your data │
26
+ │ (id + attrs) │ │ (list, array) │
27
+ └──────┬───────┘ └───────▲───────┘
28
+ │ │ indices
29
+ ▼ │
30
+ ┌──────────────┐ Rules ┌──────────────┐ Rules
31
+ │ Lookup │◄────── │ Lookup │◄──────
32
+ └──────┬───────┘ └──────┬───────┘
33
+ │ │
34
+ ▼ ▼
35
+ matched items matching_ids()
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ ### From GitHub (recommended for development)
43
+
44
+ ```bash
45
+ # Latest main branch
46
+ pip install vcti-lookup
47
+
48
+
49
+ ### In `requirements.txt`
50
+
51
+ ```
52
+ vcti-lookup>=1.0.1
53
+ ```
54
+
55
+ ### In `pyproject.toml` dependencies
56
+
57
+ ```toml
58
+ dependencies = [
59
+ "vcti-lookup>=1.0.1",
60
+ ]
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Quick Start
66
+
67
+ ### Level 1 — Filter items directly
68
+
69
+ ```python
70
+ from vcti.lookup import Lookup, Rule
71
+
72
+ items = [
73
+ {"id": 1, "type": "pdf", "size": 1000},
74
+ {"id": 2, "type": "txt", "size": 200},
75
+ {"id": 3, "type": "pdf", "size": 1500},
76
+ ]
77
+
78
+ lk = Lookup(items)
79
+
80
+ # Single rule
81
+ pdfs = lk.filter([Rule("type", "==", "pdf")])
82
+ # [{"id": 1, ...}, {"id": 3, ...}]
83
+
84
+ # Multiple rules (AND)
85
+ large_pdfs = lk.filter([Rule("type", "==", "pdf"), Rule("size", ">", 1200)])
86
+ # [{"id": 3, ...}]
87
+
88
+ # OR logic
89
+ result = lk.filter_any([Rule("type", "==", "pdf"), Rule("type", "==", "jpg")])
90
+
91
+ # Exclusion (complement of filter)
92
+ non_pdfs = lk.exclude([Rule("type", "==", "pdf")])
93
+
94
+ # Lookup by ID
95
+ item = lk.get(2)
96
+
97
+ # Unary operators
98
+ lk.filter([Rule("name", "is_empty")])
99
+ ```
100
+
101
+ ### Level 2 — Map attributes to external data
102
+
103
+ ```python
104
+ from vcti.lookup import Lookup, Rule
105
+
106
+ # Your data — any shape, Lookup doesn't touch it
107
+ data = ["report.pdf", "notes.txt", "analysis.pdf"]
108
+
109
+ # Build attribute set — id = index into your data
110
+ attrs = [
111
+ {"id": 0, "type": "pdf", "size": 1000},
112
+ {"id": 1, "type": "txt", "size": 200},
113
+ {"id": 2, "type": "pdf", "size": 1500},
114
+ ]
115
+
116
+ lk = Lookup(attrs)
117
+ indices = lk.matching_ids([Rule("type", "==", "pdf")])
118
+ # [0, 2]
119
+
120
+ # Map back to your data
121
+ results = [data[i] for i in indices]
122
+ # ["report.pdf", "analysis.pdf"]
123
+ ```
124
+
125
+ ### Numpy structured arrays (direct, no copy)
126
+
127
+ ```python
128
+ import numpy as np
129
+ from vcti.lookup import Lookup, Rule
130
+ from vcti.lookup.getter import numpy_getter
131
+
132
+ dt = np.dtype([("id", "i4"), ("type", "U10"), ("value", "f8")])
133
+ arr = np.array([(0, "sensor", 3.14), (1, "actuator", 2.71)], dtype=dt)
134
+
135
+ # Pass the array directly — no conversion
136
+ lk = Lookup(arr, getter=numpy_getter, id_key="id")
137
+ indices = lk.matching_ids([Rule("type", "==", "sensor")])
138
+ # [0]
139
+
140
+ # Index back into the array
141
+ results = arr[indices]
142
+ ```
143
+
144
+ ### Dataclasses
145
+
146
+ ```python
147
+ from dataclasses import dataclass
148
+
149
+ @dataclass
150
+ class File:
151
+ id: int
152
+ type: str
153
+ size: int
154
+
155
+ files = [File(1, "pdf", 1000), File(2, "txt", 200), File(3, "pdf", 1500)]
156
+ lk = Lookup(files)
157
+ result = lk.filter([Rule("type", "==", "pdf")])
158
+ # [File(1, "pdf", 1000), File(3, "pdf", 1500)]
159
+ ```
160
+
161
+ ### Modifiers
162
+
163
+ Modifiers are keyword arguments forwarded to vcti-predicate. They
164
+ control comparison behaviour. Pass them as the fourth argument to
165
+ ``Rule``.
166
+
167
+ ```python
168
+ items = [{"id": 1, "name": "Report_Q1"}, {"id": 2, "name": "report_Q2"}]
169
+ lk = Lookup(items)
170
+
171
+ # Default: case-insensitive (both match)
172
+ lk.filter([Rule("name", "^=", "report")])
173
+
174
+ # Case-sensitive via modifier (only item 1)
175
+ lk.filter([Rule("name", "^=", "Report", {"case_sensitive": True})])
176
+
177
+ # Float tolerance
178
+ lk.filter([Rule("value", "==", 3.14, {"tolerance": 0.01})])
179
+
180
+ # Regex with multiline
181
+ lk.filter([Rule("content", "~=", r"^error:", {"multiline": True})])
182
+ ```
183
+
184
+ See [patterns.md](docs/patterns.md) for more modifier examples.
185
+
186
+ ---
187
+
188
+ ## Public API
189
+
190
+ | Name | Type | Description |
191
+ |------|------|-------------|
192
+ | `Lookup[T]` | Class | Attribute-based filtering for any sequence |
193
+ | `Rule` | Frozen dataclass | Filter condition (attribute, operator, value, modifiers) |
194
+ | `ValueGetter` | Type alias | `Callable[[Any, str], Any]` |
195
+ | `MISSING` | Sentinel | Returned by getters when attribute is absent |
196
+ | `auto_getter` | Function | Handles dicts and objects transparently |
197
+ | `dict_getter` | Function | Extracts values from dicts |
198
+ | `object_getter` | Function | Extracts values via `getattr` |
199
+ | `attributes_getter` | Function | Extracts values from `.attributes` dict |
200
+ | `numpy_getter` | Function | Extracts fields from numpy structured-array rows |
201
+
202
+ ### Lookup methods
203
+
204
+ | Method | Returns | Description |
205
+ |--------|---------|-------------|
206
+ | `filter(rules)` | `list[T]` | Items matching ALL rules (AND) |
207
+ | `filter_any(rules)` | `list[T]` | Items matching ANY rule (OR) |
208
+ | `exclude(rules)` | `list[T]` | Items NOT matching all rules |
209
+ | `first(rules)` | `T \| None` | First matching item (short-circuits) |
210
+ | `first_any(rules)` | `T \| None` | First item matching any rule (short-circuits) |
211
+ | `first_id(rules)` | `Any \| None` | ID of first matching item |
212
+ | `matching_ids(rules)` | `list[Any]` | IDs of all matching items |
213
+ | `count(rules)` | `int` | Number of matching items (no list allocation) |
214
+ | `get(item_id)` | `T \| None` | Item by ID (O(1)) |
215
+ | `items` | `Sequence[T]` | The underlying sequence (same reference) |
216
+ | `lk[i]` / `lk[i:j]` | `T` / `Sequence[T]` | Index or slice access |
217
+
218
+ ---
219
+
220
+ ## Dependencies
221
+
222
+ - [vcti-predicate](https://github.com/vcollab/vcti-python-predicate) — condition evaluation engine
223
+ - [numpy](https://numpy.org/) — optional, for `numpy_getter`
224
+
225
+ ---
226
+
227
+ ## Documentation
228
+
229
+ - [Design](docs/design.md) — Concepts, architecture, and design decisions
230
+ - [Patterns](docs/patterns.md) — Real-world recipes and usage patterns
231
+ - [Source Guide](docs/source-guide.md) — Implementation walkthrough
232
+ - [Extending](docs/extending.md) — Creating custom value getters
233
+ - [API Reference](docs/api.md) — Autodoc for all modules
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vcti-lookup"
7
+ version = "1.0.1"
8
+ description = "Attribute-based item lookup and filtering for Python collections"
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "Visual Collaboration Technologies Inc."}
12
+ ]
13
+ requires-python = ">=3.12,<3.15"
14
+ dependencies = [
15
+ "vcti-predicate>=1.0.7",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ test = ["pytest", "pytest-cov"]
20
+ lint = ["ruff"]
21
+ numpy = ["numpy>=1.24"]
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
25
+ include = ["vcti.lookup", "vcti.lookup.*"]
26
+
27
+ [tool.setuptools]
28
+ zip-safe = true
29
+
30
+ [tool.pytest.ini_options]
31
+ addopts = "--cov=vcti.lookup --cov-report=term-missing --cov-fail-under=95"
32
+
33
+ [tool.ruff]
34
+ target-version = "py312"
35
+ line-length = 99
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E", "F", "W", "I", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ # Copyright Visual Collaboration Technologies Inc. All Rights Reserved.
2
+ # See LICENSE for details.
3
+ """vcti-lookup — attribute-based item lookup and filtering.
4
+
5
+ Public API re-exports for convenient access.
6
+ """
7
+
8
+ from importlib.metadata import version
9
+
10
+ from .getter import (
11
+ MISSING,
12
+ ValueGetter,
13
+ attributes_getter,
14
+ auto_getter,
15
+ dict_getter,
16
+ numpy_getter,
17
+ object_getter,
18
+ )
19
+ from .lookup import Lookup
20
+ from .rule import Rule
21
+
22
+ __version__ = version("vcti-lookup")
23
+
24
+ __all__ = [
25
+ "__version__",
26
+ "MISSING",
27
+ "ValueGetter",
28
+ "auto_getter",
29
+ "attributes_getter",
30
+ "dict_getter",
31
+ "numpy_getter",
32
+ "object_getter",
33
+ "Lookup",
34
+ "Rule",
35
+ ]