docent-python 0.1.0a1__py3-none-any.whl → 0.1.0a3__py3-none-any.whl
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.
Potentially problematic release.
This version of docent-python might be problematic. Click here for more details.
- docent/data_models/__init__.py +0 -10
- docent/sdk/client.py +5 -49
- {docent_python-0.1.0a1.dist-info → docent_python-0.1.0a3.dist-info}/METADATA +2 -4
- {docent_python-0.1.0a1.dist-info → docent_python-0.1.0a3.dist-info}/RECORD +6 -7
- {docent_python-0.1.0a1.dist-info → docent_python-0.1.0a3.dist-info}/licenses/LICENSE.md +1 -1
- docent/data_models/filters.py +0 -205
- {docent_python-0.1.0a1.dist-info → docent_python-0.1.0a3.dist-info}/WHEEL +0 -0
docent/data_models/__init__.py
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
from docent.data_models.agent_run import AgentRun
|
|
2
2
|
from docent.data_models.citation import Citation
|
|
3
|
-
from docent.data_models.filters import (
|
|
4
|
-
AgentRunIdFilter,
|
|
5
|
-
BaseFrameFilter,
|
|
6
|
-
ComplexFilter,
|
|
7
|
-
SearchResultPredicateFilter,
|
|
8
|
-
)
|
|
9
3
|
from docent.data_models.metadata import BaseAgentRunMetadata, BaseMetadata, FrameDimension
|
|
10
4
|
from docent.data_models.regex import RegexSnippet
|
|
11
5
|
from docent.data_models.transcript import Transcript
|
|
@@ -14,11 +8,7 @@ __all__ = [
|
|
|
14
8
|
"AgentRun",
|
|
15
9
|
"Citation",
|
|
16
10
|
"RegexSnippet",
|
|
17
|
-
"AgentRunIdFilter",
|
|
18
11
|
"FrameDimension",
|
|
19
|
-
"BaseFrameFilter",
|
|
20
|
-
"SearchResultPredicateFilter",
|
|
21
|
-
"ComplexFilter",
|
|
22
12
|
"BaseAgentRunMetadata",
|
|
23
13
|
"BaseMetadata",
|
|
24
14
|
"Transcript",
|
docent/sdk/client.py
CHANGED
|
@@ -5,7 +5,6 @@ import requests
|
|
|
5
5
|
|
|
6
6
|
from docent._log_util.logger import get_logger
|
|
7
7
|
from docent.data_models.agent_run import AgentRun
|
|
8
|
-
from docent.data_models.filters import FrameFilter
|
|
9
8
|
|
|
10
9
|
logger = get_logger(__name__)
|
|
11
10
|
|
|
@@ -24,7 +23,11 @@ class Docent:
|
|
|
24
23
|
"""
|
|
25
24
|
|
|
26
25
|
def __init__(
|
|
27
|
-
self,
|
|
26
|
+
self,
|
|
27
|
+
server_url: str = "https://aws-docent-backend.transluce.org",
|
|
28
|
+
web_url: str = "https://aws-docent.transluce.org",
|
|
29
|
+
email: str | None = None,
|
|
30
|
+
password: str | None = None,
|
|
28
31
|
):
|
|
29
32
|
self._server_url = server_url.rstrip("/") + "/rest"
|
|
30
33
|
self._web_url = web_url.rstrip("/")
|
|
@@ -177,53 +180,6 @@ class Docent:
|
|
|
177
180
|
logger.info(f"Successfully added {total_runs} agent runs to FrameGrid '{fg_id}'")
|
|
178
181
|
return {"status": "success", "total_runs_added": total_runs}
|
|
179
182
|
|
|
180
|
-
def get_base_filter(self, fg_id: str) -> dict[str, Any] | None:
|
|
181
|
-
"""Retrieves the base filter for a FrameGrid.
|
|
182
|
-
|
|
183
|
-
The base filter defines default filtering applied to all views.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
fg_id: ID of the FrameGrid.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
dict or None: Filter data if a filter exists, None otherwise.
|
|
190
|
-
|
|
191
|
-
Raises:
|
|
192
|
-
requests.exceptions.HTTPError: If the API request fails.
|
|
193
|
-
"""
|
|
194
|
-
url = f"{self._server_url}/{fg_id}/base_filter"
|
|
195
|
-
response = self._session.get(url)
|
|
196
|
-
response.raise_for_status()
|
|
197
|
-
# The endpoint returns the filter model directly or null
|
|
198
|
-
filter_data = response.json()
|
|
199
|
-
return filter_data
|
|
200
|
-
|
|
201
|
-
def set_base_filter(self, fg_id: str, filter: FrameFilter | None) -> dict[str, Any]:
|
|
202
|
-
"""Sets the base filter for a FrameGrid.
|
|
203
|
-
|
|
204
|
-
The base filter defines default filtering applied to all views.
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
fg_id: ID of the FrameGrid.
|
|
208
|
-
filter: FrameFilter object defining the filter, or None to clear the filter.
|
|
209
|
-
|
|
210
|
-
Returns:
|
|
211
|
-
dict: API response data.
|
|
212
|
-
|
|
213
|
-
Raises:
|
|
214
|
-
requests.exceptions.HTTPError: If the API request fails.
|
|
215
|
-
"""
|
|
216
|
-
url = f"{self._server_url}/{fg_id}/base_filter"
|
|
217
|
-
payload = {
|
|
218
|
-
"filter": filter.model_dump() if filter else None,
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
response = self._session.post(url, json=payload)
|
|
222
|
-
response.raise_for_status()
|
|
223
|
-
|
|
224
|
-
logger.info(f"Successfully set base filter for FrameGrid '{fg_id}'")
|
|
225
|
-
return response.json()
|
|
226
|
-
|
|
227
183
|
def list_framegrids(self) -> list[dict[str, Any]]:
|
|
228
184
|
"""Lists all available FrameGrids.
|
|
229
185
|
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docent-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a3
|
|
4
4
|
Summary: Docent SDK
|
|
5
5
|
Project-URL: Homepage, https://github.com/TransluceAI/docent
|
|
6
6
|
Project-URL: Issues, https://github.com/TransluceAI/docent/issues
|
|
7
7
|
Project-URL: Docs, https://transluce-docent.readthedocs-hosted.com/en/latest
|
|
8
|
-
Author-email: Transluce
|
|
8
|
+
Author-email: Transluce <info@transluce.org>
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE.md
|
|
11
11
|
Requires-Python: >=3.11
|
|
12
|
-
Requires-Dist: logging>=0.4.9.6
|
|
13
12
|
Requires-Dist: pydantic>=2.11.7
|
|
14
13
|
Requires-Dist: pyyaml>=6.0.2
|
|
15
|
-
Requires-Dist: sqlalchemy>=2.0.41
|
|
16
14
|
Requires-Dist: tiktoken>=0.7.0
|
|
@@ -2,11 +2,10 @@ docent/__init__.py,sha256=J2BbO6rzilfw9WXRUeolr439EGFezqbMU_kCpCCryRA,59
|
|
|
2
2
|
docent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
docent/_log_util/__init__.py,sha256=3HXXrxrSm8PxwG4llotrCnSnp7GuroK1FNHsdg6f7aE,73
|
|
4
4
|
docent/_log_util/logger.py,sha256=kwM0yRW1IJd6-XTorjWn48B4l8qvD2ZM6VDjY5eskQI,4422
|
|
5
|
-
docent/data_models/__init__.py,sha256=
|
|
5
|
+
docent/data_models/__init__.py,sha256=SbXBXpI2HLI2qeGoKD-6Qp0gY3igQAw6qOsolc_rj7Q,448
|
|
6
6
|
docent/data_models/_tiktoken_util.py,sha256=hC0EDDWItv5-0cONBnHWgZtQOflDU7ZNEhXPFo4DvPc,3057
|
|
7
7
|
docent/data_models/agent_run.py,sha256=sdvoUUpOhQAHqJHNR5KoHthCXrpJajdIREMacoR1ODk,9516
|
|
8
8
|
docent/data_models/citation.py,sha256=WsVQZcBT2EJD24ysyeVOC5Xfo165RI7P5_cOnJBgHj0,10015
|
|
9
|
-
docent/data_models/filters.py,sha256=nZquRQji_xZwea6nBxM_SRECaYECDMEQ8Zt1TXl-_jI,7484
|
|
10
9
|
docent/data_models/metadata.py,sha256=yBEm5M_gSNuoG---Fezsjm0YDGUQoJeM_BL9rwbBt-U,8035
|
|
11
10
|
docent/data_models/regex.py,sha256=0ciIerkrNwb91bY5mTcyO5nDWH67xx2tZYObV52fmBo,1684
|
|
12
11
|
docent/data_models/shared_types.py,sha256=jjm-Dh5S6v7UKInW7SEqoziOsx6Z7Uu4e3VzgCbTWvc,225
|
|
@@ -16,8 +15,8 @@ docent/data_models/chat/content.py,sha256=Co-jO8frQa_DSP11wJuhPX0s-GpJk8yqtKqPei
|
|
|
16
15
|
docent/data_models/chat/message.py,sha256=iAo38kbV6wYbFh8S23cxLy6HY4C_i3PzQ6RpSQG5dxM,3861
|
|
17
16
|
docent/data_models/chat/tool.py,sha256=x7NKINswPe0Kqvcx4ubjHzB-n0-i4DbFodvaBb2vitk,3042
|
|
18
17
|
docent/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
docent/sdk/client.py,sha256=
|
|
20
|
-
docent_python-0.1.
|
|
21
|
-
docent_python-0.1.
|
|
22
|
-
docent_python-0.1.
|
|
23
|
-
docent_python-0.1.
|
|
18
|
+
docent/sdk/client.py,sha256=J6xd100KWym-jOrXjaEyagi85ddklymF9QEOJmYxJP8,8588
|
|
19
|
+
docent_python-0.1.0a3.dist-info/METADATA,sha256=v_-KmR9urNIEdjduRZ_wzZXpplPvImgT--y_QKgGHAI,493
|
|
20
|
+
docent_python-0.1.0a3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
21
|
+
docent_python-0.1.0a3.dist-info/licenses/LICENSE.md,sha256=vOHzq3K4Ndu0UV9hPrtXvlD7pHOjyDQmGjHuLSIkRQY,1087
|
|
22
|
+
docent_python-0.1.0a3.dist-info/RECORD,,
|
|
@@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
4
4
|
|
|
5
5
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
6
|
|
|
7
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
docent/data_models/filters.py
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, Annotated, Any, Literal, Type, Union
|
|
4
|
-
from uuid import uuid4
|
|
5
|
-
|
|
6
|
-
from pydantic import BaseModel, Discriminator, Field, field_validator
|
|
7
|
-
from sqlalchemy import ColumnElement, and_, or_
|
|
8
|
-
|
|
9
|
-
from docent._log_util import get_logger
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from docent_core._db_service.schemas.tables import SQLAAgentRun
|
|
13
|
-
|
|
14
|
-
logger = get_logger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class BaseFrameFilter(BaseModel):
|
|
18
|
-
"""Base class for all frame filters."""
|
|
19
|
-
|
|
20
|
-
id: str = Field(default_factory=lambda: str(uuid4()))
|
|
21
|
-
name: str | None = None
|
|
22
|
-
supports_sql: bool = True # All filters must support SQL
|
|
23
|
-
|
|
24
|
-
def to_sqla_where_clause(self, table: Type["SQLAAgentRun"]) -> ColumnElement[bool] | None:
|
|
25
|
-
"""Convert this filter to a SQLAlchemy WHERE clause.
|
|
26
|
-
|
|
27
|
-
All filters must implement this method to support SQL execution.
|
|
28
|
-
"""
|
|
29
|
-
raise NotImplementedError(
|
|
30
|
-
f"Filter {self.__class__.__name__} must implement to_sqla_where_clause"
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class PrimitiveFilter(BaseFrameFilter):
|
|
35
|
-
"""Filter that applies a primitive operation to a metadata field."""
|
|
36
|
-
|
|
37
|
-
type: Literal["primitive"] = "primitive"
|
|
38
|
-
key_path: list[str]
|
|
39
|
-
value: Any
|
|
40
|
-
op: Literal[">", ">=", "<", "<=", "==", "!=", "~*", "!~*"]
|
|
41
|
-
|
|
42
|
-
def to_sqla_where_clause(self, table: Type["SQLAAgentRun"]) -> ColumnElement[bool] | None:
|
|
43
|
-
"""Convert this filter to a SQLAlchemy WHERE clause."""
|
|
44
|
-
|
|
45
|
-
mode = self.key_path[0]
|
|
46
|
-
|
|
47
|
-
# Extract value from JSONB using the table parameter
|
|
48
|
-
if mode == "text":
|
|
49
|
-
sqla_value = table.text_for_search # type: ignore
|
|
50
|
-
elif mode == "metadata":
|
|
51
|
-
sqla_value = table.metadata_json # type: ignore
|
|
52
|
-
else:
|
|
53
|
-
raise ValueError(f"Unsupported mode: {mode}")
|
|
54
|
-
|
|
55
|
-
for key in self.key_path[1:]:
|
|
56
|
-
sqla_value = sqla_value[key]
|
|
57
|
-
|
|
58
|
-
# Cast the extracted value to the correct type
|
|
59
|
-
# This is only necessary for metadata which is JSONB
|
|
60
|
-
if mode == "metadata":
|
|
61
|
-
if isinstance(self.value, str):
|
|
62
|
-
sqla_value = sqla_value.as_string()
|
|
63
|
-
elif isinstance(self.value, bool):
|
|
64
|
-
sqla_value = sqla_value.as_boolean()
|
|
65
|
-
elif isinstance(self.value, float) or isinstance(self.value, int): # type: ignore warning about unnecessary comparison
|
|
66
|
-
# if self.value is an int, we may still need to do sql comparisons with floats
|
|
67
|
-
sqla_value = sqla_value.as_float()
|
|
68
|
-
else:
|
|
69
|
-
raise ValueError(f"Unsupported value type: {type(self.value)}")
|
|
70
|
-
|
|
71
|
-
# Handle different operations using SQLAlchemy expressions
|
|
72
|
-
if self.op == "==":
|
|
73
|
-
return sqla_value == self.value
|
|
74
|
-
elif self.op == "!=":
|
|
75
|
-
return sqla_value != self.value
|
|
76
|
-
elif self.op == ">":
|
|
77
|
-
return sqla_value > self.value
|
|
78
|
-
elif self.op == ">=":
|
|
79
|
-
return sqla_value >= self.value
|
|
80
|
-
elif self.op == "<":
|
|
81
|
-
return sqla_value < self.value
|
|
82
|
-
elif self.op == "<=":
|
|
83
|
-
return sqla_value <= self.value
|
|
84
|
-
elif self.op == "~*":
|
|
85
|
-
return sqla_value.op("~*")(self.value)
|
|
86
|
-
else:
|
|
87
|
-
raise ValueError(f"Unsupported operation: {self.op}")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class ComplexFilter(BaseFrameFilter):
|
|
91
|
-
"""Filter that combines multiple filters with AND/OR/NOT logic."""
|
|
92
|
-
|
|
93
|
-
type: Literal["complex"] = "complex"
|
|
94
|
-
filters: list[FrameFilter]
|
|
95
|
-
op: Literal["and", "or"] = "and"
|
|
96
|
-
|
|
97
|
-
@field_validator("filters")
|
|
98
|
-
@classmethod
|
|
99
|
-
def validate_filters(cls, v: list[FrameFilter]) -> list[FrameFilter]:
|
|
100
|
-
if not v:
|
|
101
|
-
raise ValueError("ComplexFilter must have at least one filter")
|
|
102
|
-
return v
|
|
103
|
-
|
|
104
|
-
def to_sqla_where_clause(self, table: Type["SQLAAgentRun"]) -> ColumnElement[bool] | None:
|
|
105
|
-
"""Convert this filter to a SQLAlchemy WHERE clause."""
|
|
106
|
-
|
|
107
|
-
# Get WHERE clauses for all sub-filters
|
|
108
|
-
where_clauses: list[ColumnElement[bool]] = []
|
|
109
|
-
for filter_obj in self.filters:
|
|
110
|
-
where_clause = filter_obj.to_sqla_where_clause(table)
|
|
111
|
-
if where_clause is not None:
|
|
112
|
-
where_clauses.append(where_clause)
|
|
113
|
-
|
|
114
|
-
if not where_clauses:
|
|
115
|
-
return None
|
|
116
|
-
|
|
117
|
-
# Apply the operation
|
|
118
|
-
if self.op == "and":
|
|
119
|
-
result = and_(*where_clauses)
|
|
120
|
-
elif self.op == "or":
|
|
121
|
-
result = or_(*where_clauses)
|
|
122
|
-
else:
|
|
123
|
-
raise ValueError(f"Unsupported operation: {self.op}")
|
|
124
|
-
|
|
125
|
-
return result
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class AgentRunIdFilter(BaseFrameFilter):
|
|
129
|
-
"""Filter that matches specific agent run IDs."""
|
|
130
|
-
|
|
131
|
-
type: Literal["agent_run_id"] = "agent_run_id"
|
|
132
|
-
agent_run_ids: list[str]
|
|
133
|
-
|
|
134
|
-
@field_validator("agent_run_ids")
|
|
135
|
-
@classmethod
|
|
136
|
-
def validate_agent_run_ids(cls, v: list[str]) -> list[str]:
|
|
137
|
-
if not v:
|
|
138
|
-
raise ValueError("AgentRunIdFilter must have at least one agent run ID")
|
|
139
|
-
return v
|
|
140
|
-
|
|
141
|
-
def to_sqla_where_clause(self, table: Type["SQLAAgentRun"]) -> ColumnElement[bool] | None:
|
|
142
|
-
"""Convert to SQLAlchemy WHERE clause for agent run ID filtering."""
|
|
143
|
-
return table.id.in_(self.agent_run_ids)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class SearchResultPredicateFilter(BaseFrameFilter):
|
|
147
|
-
"""Filter that applies a predicate to search results."""
|
|
148
|
-
|
|
149
|
-
type: Literal["search_result_predicate"] = "search_result_predicate"
|
|
150
|
-
predicate: str
|
|
151
|
-
search_query: str
|
|
152
|
-
|
|
153
|
-
def to_sqla_where_clause(self, table: Type["SQLAAgentRun"]) -> ColumnElement[bool] | None:
|
|
154
|
-
"""Convert to SQLAlchemy WHERE clause for search result filtering."""
|
|
155
|
-
# This filter requires joining with search results table
|
|
156
|
-
# For now, we'll return None to indicate it needs special handling
|
|
157
|
-
# In practice, this would join with the search_results table
|
|
158
|
-
return None
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class SearchResultExistsFilter(BaseFrameFilter):
|
|
162
|
-
"""Filter that checks if search results exist."""
|
|
163
|
-
|
|
164
|
-
type: Literal["search_result_exists"] = "search_result_exists"
|
|
165
|
-
search_query: str
|
|
166
|
-
|
|
167
|
-
def to_sqla_where_clause(self, table: Type["SQLAAgentRun"]) -> ColumnElement[bool] | None:
|
|
168
|
-
"""Convert to SQLAlchemy WHERE clause for search result existence filtering."""
|
|
169
|
-
# This filter requires joining with search results table
|
|
170
|
-
# For now, we'll return None to indicate it needs special handling
|
|
171
|
-
# In practice, this would join with the search_results table
|
|
172
|
-
return None
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
FrameFilter = Annotated[
|
|
176
|
-
Union[
|
|
177
|
-
PrimitiveFilter,
|
|
178
|
-
ComplexFilter,
|
|
179
|
-
AgentRunIdFilter,
|
|
180
|
-
SearchResultPredicateFilter,
|
|
181
|
-
SearchResultExistsFilter,
|
|
182
|
-
],
|
|
183
|
-
Discriminator("type"),
|
|
184
|
-
]
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def parse_filter_dict(filter_dict: dict[str, Any]) -> FrameFilter:
|
|
188
|
-
"""Parse a filter dictionary into a FrameFilter object."""
|
|
189
|
-
filter_type = filter_dict.get("type")
|
|
190
|
-
|
|
191
|
-
if filter_type == "primitive":
|
|
192
|
-
return PrimitiveFilter(**filter_dict)
|
|
193
|
-
elif filter_type == "complex":
|
|
194
|
-
# Recursively parse nested filters
|
|
195
|
-
nested_filters = [parse_filter_dict(f) for f in filter_dict.get("filters", [])]
|
|
196
|
-
filter_dict["filters"] = nested_filters
|
|
197
|
-
return ComplexFilter(**filter_dict)
|
|
198
|
-
elif filter_type == "agent_run_id":
|
|
199
|
-
return AgentRunIdFilter(**filter_dict)
|
|
200
|
-
elif filter_type == "search_result_predicate":
|
|
201
|
-
return SearchResultPredicateFilter(**filter_dict)
|
|
202
|
-
elif filter_type == "search_result_exists":
|
|
203
|
-
return SearchResultExistsFilter(**filter_dict)
|
|
204
|
-
else:
|
|
205
|
-
raise ValueError(f"Unknown filter type: {filter_type}")
|
|
File without changes
|