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.
- vcti_lookup-1.0.1/LICENSE +8 -0
- vcti_lookup-1.0.1/PKG-INFO +251 -0
- vcti_lookup-1.0.1/README.md +233 -0
- vcti_lookup-1.0.1/pyproject.toml +38 -0
- vcti_lookup-1.0.1/setup.cfg +4 -0
- vcti_lookup-1.0.1/src/vcti/lookup/__init__.py +35 -0
- vcti_lookup-1.0.1/src/vcti/lookup/getter.py +156 -0
- vcti_lookup-1.0.1/src/vcti/lookup/lookup.py +312 -0
- vcti_lookup-1.0.1/src/vcti/lookup/py.typed +0 -0
- vcti_lookup-1.0.1/src/vcti/lookup/rule.py +39 -0
- vcti_lookup-1.0.1/src/vcti_lookup.egg-info/PKG-INFO +251 -0
- vcti_lookup-1.0.1/src/vcti_lookup.egg-info/SOURCES.txt +17 -0
- vcti_lookup-1.0.1/src/vcti_lookup.egg-info/dependency_links.txt +1 -0
- vcti_lookup-1.0.1/src/vcti_lookup.egg-info/requires.txt +11 -0
- vcti_lookup-1.0.1/src/vcti_lookup.egg-info/top_level.txt +1 -0
- vcti_lookup-1.0.1/src/vcti_lookup.egg-info/zip-safe +1 -0
- vcti_lookup-1.0.1/tests/test_getter.py +199 -0
- vcti_lookup-1.0.1/tests/test_lookup.py +831 -0
- vcti_lookup-1.0.1/tests/test_rule.py +85 -0
|
@@ -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,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
|
+
]
|