kodexa 7.0.12396563598__tar.gz → 7.0.12399073274__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.
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/PKG-INFO +1 -1
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/model.py +11 -16
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/persistence.py +40 -13
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/ast.py +24 -18
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/testing/test_utils.py +18 -18
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/pyproject.toml +1 -1
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/LICENSE +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/README.md +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/assistant/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/assistant/assistant.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/connectors/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/connectors/connectors.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/dataclasses/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/dataclasses/templates/llm_data_class.j2 +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/base.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/entities/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/entities/check_response.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/entities/product.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/entities/product_subscription.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/objects.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/utils.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/pipeline/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/pipeline/pipeline.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/platform/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/platform/client.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/platform/interaction.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/platform/kodexa.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/core.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/lexrules.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/lextab.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/lextab.pyi +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/parserules.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/parserules.pyi +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/parsetab.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/selectors/parsetab.pyi +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/spatial/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/spatial/azure_models.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/spatial/bbox_common.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/spatial/table_form_common.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/steps/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/steps/common.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/testing/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/testing/test_components.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/training/__init__.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/training/train_utils.py +0 -0
- {kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/utils/__init__.py +0 -0
@@ -293,8 +293,6 @@ class ContentNode(object):
|
|
293
293
|
if content is not None and len(self.get_content_parts()) == 0:
|
294
294
|
self.set_content_parts([content])
|
295
295
|
|
296
|
-
self.cached_all_content = None
|
297
|
-
|
298
296
|
def get_content_parts(self):
|
299
297
|
return self.document.get_persistence().get_content_parts(self)
|
300
298
|
|
@@ -793,12 +791,13 @@ class ContentNode(object):
|
|
793
791
|
result = self.select(selector, variables)
|
794
792
|
return result[0] if len(result) > 0 else None
|
795
793
|
|
796
|
-
def select(self, selector, variables=None):
|
794
|
+
def select(self, selector, variables=None, first_only=False):
|
797
795
|
"""Select and return the child nodes of this node that match the selector value.
|
798
796
|
|
799
797
|
Args:
|
800
798
|
selector (str): The selector (ie. //*)
|
801
799
|
variables (dict, optional): A dictionary of variable name/value to use in substituion; defaults to None. Dictionary keys should match a variable specified in the selector.
|
800
|
+
first_only (bool, optional): If True, only the first matching node will be returned; defaults to False.
|
802
801
|
|
803
802
|
Returns:
|
804
803
|
list[ContentNode]: A list of the matching content nodes. If no matches are found, the list will be empty.
|
@@ -815,18 +814,17 @@ class ContentNode(object):
|
|
815
814
|
from kodexa.selectors import parse
|
816
815
|
from kodexa.selectors.ast import SelectorContext
|
817
816
|
|
818
|
-
context = SelectorContext(self.document)
|
819
|
-
parsed_selector = parse(selector)
|
817
|
+
context = SelectorContext(self.document, first_only=first_only)
|
820
818
|
self.document.get_persistence().flush_cache()
|
819
|
+
parsed_selector = parse(selector)
|
821
820
|
return parsed_selector.resolve(self, variables, context)
|
822
821
|
|
823
|
-
def get_all_content(self, separator=" ", strip=True
|
822
|
+
def get_all_content(self, separator=" ", strip=True):
|
824
823
|
"""Get this node's content, concatenated with all of its children's content.
|
825
824
|
|
826
825
|
Args:
|
827
826
|
separator(str, optional): The separator to use in joining content together; defaults to " ".
|
828
827
|
strip(boolean, optional): Strip the result
|
829
|
-
use_cache(boolean, optional): Use the cache
|
830
828
|
|
831
829
|
Returns:
|
832
830
|
str: The complete content for this node concatenated with the content of all child nodes.
|
@@ -836,9 +834,6 @@ class ContentNode(object):
|
|
836
834
|
"This string is made up of multiple nodes"
|
837
835
|
"""
|
838
836
|
s = ""
|
839
|
-
if self.cached_all_content is not None and use_cache:
|
840
|
-
return self.cached_all_content
|
841
|
-
|
842
837
|
children = self.get_content_parts()
|
843
838
|
for part in children:
|
844
839
|
if isinstance(part, str):
|
@@ -861,14 +856,13 @@ class ContentNode(object):
|
|
861
856
|
s += separator
|
862
857
|
s += child.get_all_content(separator, strip=strip)
|
863
858
|
|
864
|
-
|
865
|
-
return self.cached_all_content
|
859
|
+
return s.strip() if strip else s
|
866
860
|
|
867
861
|
def adopt_children(self, nodes_to_adopt, replace=False):
|
868
862
|
"""This will take a list of content nodes and adopt them under this node, ensuring they are re-parented.
|
869
863
|
|
870
864
|
Args:
|
871
|
-
|
865
|
+
nodes_to_adopt (List[ContentNode]): A list of ContentNodes that will be added to the end of this node's children collection
|
872
866
|
replace (bool): If True, will remove all current children and replace them with the new list; defaults to True
|
873
867
|
|
874
868
|
>>> # select all nodes of type 'line', then the root node 'adopts' them
|
@@ -3109,17 +3103,18 @@ class Document(object):
|
|
3109
3103
|
>>> document.get_root().select_first('//*[hasTag($tagName)]', {"tagName": "div"})
|
3110
3104
|
ContentNode
|
3111
3105
|
"""
|
3112
|
-
result = self.select(selector, variables)
|
3106
|
+
result = self.select(selector, variables, first_only=True)
|
3113
3107
|
return result[0] if len(result) > 0 else None
|
3114
3108
|
|
3115
3109
|
def select(
|
3116
|
-
self, selector: str, variables: Optional[dict] = None
|
3110
|
+
self, selector: str, variables: Optional[dict] = None, first_only=False
|
3117
3111
|
) -> List[ContentNode]:
|
3118
3112
|
"""Execute a selector on the root node and then return a list of the matching nodes.
|
3119
3113
|
|
3120
3114
|
Args:
|
3121
3115
|
selector (str): The selector (ie. //*)
|
3122
3116
|
variables (Optional[dict): A dictionary of variable name/value to use in substituion; defaults to an empty
|
3117
|
+
first_only (bool): If True, only the first matching node is returned; defaults to False.
|
3123
3118
|
dictionary. Dictionary keys should match a variable specified in the selector.
|
3124
3119
|
|
3125
3120
|
Returns:
|
@@ -3131,7 +3126,7 @@ class Document(object):
|
|
3131
3126
|
if variables is None:
|
3132
3127
|
variables = {}
|
3133
3128
|
if self.content_node:
|
3134
|
-
result = self.content_node.select(selector, variables)
|
3129
|
+
result = self.content_node.select(selector, variables, first_only)
|
3135
3130
|
if isinstance(result, list):
|
3136
3131
|
return result
|
3137
3132
|
|
@@ -3,6 +3,7 @@ import logging
|
|
3
3
|
import pathlib
|
4
4
|
import sqlite3
|
5
5
|
import tempfile
|
6
|
+
import time
|
6
7
|
import uuid
|
7
8
|
from typing import List, Optional
|
8
9
|
|
@@ -42,6 +43,22 @@ FEATURE_TYPE_LOOKUP = "select id from f_type where name = ?"
|
|
42
43
|
METADATA_INSERT = "insert into metadata(id,metadata) values (1,?)"
|
43
44
|
METADATA_DELETE = "delete from metadata where id=1"
|
44
45
|
|
46
|
+
# Configuration constants
|
47
|
+
CACHE_SIZE = 10000 # Number of nodes to cache
|
48
|
+
BATCH_SIZE = 1000 # Size of batches for bulk operations
|
49
|
+
SLOW_QUERY_THRESHOLD = 1.0 # Seconds
|
50
|
+
MAX_CONNECTIONS = 5 # Maximum number of database connections
|
51
|
+
|
52
|
+
def monitor_performance(func):
|
53
|
+
"""Performance monitoring decorator"""
|
54
|
+
def wrapper(*args, **kwargs):
|
55
|
+
start_time = time.time()
|
56
|
+
result = func(*args, **kwargs)
|
57
|
+
duration = time.time() - start_time
|
58
|
+
if duration > SLOW_QUERY_THRESHOLD:
|
59
|
+
logger.warning(f"Slow operation detected: {func.__name__}, duration: {duration}s")
|
60
|
+
return result
|
61
|
+
return wrapper
|
45
62
|
|
46
63
|
class SqliteDocumentPersistence(object):
|
47
64
|
"""
|
@@ -57,7 +74,7 @@ class SqliteDocumentPersistence(object):
|
|
57
74
|
The Sqlite persistence engine to support large scale documents (part of the V4 Kodexa Document Architecture)
|
58
75
|
"""
|
59
76
|
|
60
|
-
def __init__(self, document: Document, filename: str = None, delete_on_close=False, inmemory=False):
|
77
|
+
def __init__(self, document: Document, filename: str = None, delete_on_close=False, inmemory=False, persistence_manager=None):
|
61
78
|
self.document = document
|
62
79
|
|
63
80
|
self.node_types = {}
|
@@ -138,6 +155,7 @@ class SqliteDocumentPersistence(object):
|
|
138
155
|
|
139
156
|
return mem_conn
|
140
157
|
|
158
|
+
@monitor_performance
|
141
159
|
def get_all_tags(self):
|
142
160
|
"""
|
143
161
|
Retrieves all tags from the document.
|
@@ -153,6 +171,7 @@ class SqliteDocumentPersistence(object):
|
|
153
171
|
|
154
172
|
return features
|
155
173
|
|
174
|
+
@monitor_performance
|
156
175
|
def update_features(self, node):
|
157
176
|
"""
|
158
177
|
Updates the features of a given node in the document.
|
@@ -188,6 +207,7 @@ class SqliteDocumentPersistence(object):
|
|
188
207
|
self.cursor.execute("DELETE FROM ft where cn_id=?", [node.uuid])
|
189
208
|
self.cursor.executemany(FEATURE_INSERT, all_features)
|
190
209
|
|
210
|
+
@monitor_performance
|
191
211
|
def update_node(self, node):
|
192
212
|
"""
|
193
213
|
Updates a given node in the document.
|
@@ -200,6 +220,7 @@ class SqliteDocumentPersistence(object):
|
|
200
220
|
[node.index, node._parent_uuid, node.uuid],
|
201
221
|
)
|
202
222
|
|
223
|
+
@monitor_performance
|
203
224
|
def get_content_nodes(self, node_type, parent_node: ContentNode, include_children):
|
204
225
|
"""
|
205
226
|
Retrieves content nodes from the document based on the given parameters.
|
@@ -213,8 +234,8 @@ class SqliteDocumentPersistence(object):
|
|
213
234
|
list: A list of content nodes that match the given parameters.
|
214
235
|
"""
|
215
236
|
nodes = []
|
216
|
-
|
217
|
-
|
237
|
+
if not self.connection.in_transaction:
|
238
|
+
self.cursor.execute("BEGIN TRANSACTION")
|
218
239
|
if include_children:
|
219
240
|
if node_type == "*":
|
220
241
|
query = """
|
@@ -284,6 +305,7 @@ class SqliteDocumentPersistence(object):
|
|
284
305
|
],
|
285
306
|
).fetchall()
|
286
307
|
except StopIteration:
|
308
|
+
self.connection.commit()
|
287
309
|
return []
|
288
310
|
else:
|
289
311
|
query = "select id, pid, nt, idx from cn where pid=? and nt=? order by idx"
|
@@ -300,11 +322,14 @@ class SqliteDocumentPersistence(object):
|
|
300
322
|
],
|
301
323
|
).fetchall()
|
302
324
|
except StopIteration:
|
325
|
+
self.connection.commit()
|
303
326
|
return []
|
304
327
|
|
305
328
|
for raw_node in list(results):
|
306
329
|
nodes.append(self.__build_node(raw_node))
|
307
330
|
|
331
|
+
self.connection.commit()
|
332
|
+
|
308
333
|
return nodes
|
309
334
|
|
310
335
|
def initialize(self):
|
@@ -326,6 +351,7 @@ class SqliteDocumentPersistence(object):
|
|
326
351
|
self.cursor.close()
|
327
352
|
self.connection.close()
|
328
353
|
|
354
|
+
@monitor_performance
|
329
355
|
def get_max_feature_id(self):
|
330
356
|
"""
|
331
357
|
Retrieves the maximum feature id from the document.
|
@@ -396,6 +422,7 @@ class SqliteDocumentPersistence(object):
|
|
396
422
|
|
397
423
|
self.__update_metadata()
|
398
424
|
|
425
|
+
@monitor_performance
|
399
426
|
def content_node_count(self):
|
400
427
|
"""
|
401
428
|
Counts the number of content nodes in the document.
|
@@ -405,6 +432,7 @@ class SqliteDocumentPersistence(object):
|
|
405
432
|
"""
|
406
433
|
self.cursor.execute("select * from cn").fetchall()
|
407
434
|
|
435
|
+
@monitor_performance
|
408
436
|
def get_feature_type_id(self, feature):
|
409
437
|
"""
|
410
438
|
Retrieves the id of a given feature.
|
@@ -466,6 +494,7 @@ class SqliteDocumentPersistence(object):
|
|
466
494
|
|
467
495
|
return result[0]
|
468
496
|
|
497
|
+
@monitor_performance
|
469
498
|
def __insert_node(self, node: ContentNode, parent, execute=True):
|
470
499
|
"""
|
471
500
|
Inserts a node into the document.
|
@@ -1385,7 +1414,7 @@ class PersistenceManager(object):
|
|
1385
1414
|
self.node_parent_cache = {}
|
1386
1415
|
|
1387
1416
|
self._underlying_persistence = SqliteDocumentPersistence(
|
1388
|
-
document, filename, delete_on_close, inmemory=inmemory
|
1417
|
+
document, filename, delete_on_close, inmemory=inmemory, persistence_manager=self
|
1389
1418
|
)
|
1390
1419
|
|
1391
1420
|
def get_steps(self) -> list[ProcessingStep]:
|
@@ -1523,7 +1552,6 @@ class PersistenceManager(object):
|
|
1523
1552
|
Returns:
|
1524
1553
|
List[Node]: A list of nodes tagged with the specified tag.
|
1525
1554
|
"""
|
1526
|
-
self.flush_cache()
|
1527
1555
|
return self._underlying_persistence.get_tagged_nodes(tag, tag_uuid)
|
1528
1556
|
|
1529
1557
|
def get_all_tagged_nodes(self):
|
@@ -1533,7 +1561,6 @@ class PersistenceManager(object):
|
|
1533
1561
|
Returns:
|
1534
1562
|
List[Node]: A list of all tagged nodes.
|
1535
1563
|
"""
|
1536
|
-
self.flush_cache()
|
1537
1564
|
return self._underlying_persistence.get_all_tagged_nodes()
|
1538
1565
|
|
1539
1566
|
def initialize(self):
|
@@ -1565,6 +1592,7 @@ class PersistenceManager(object):
|
|
1565
1592
|
"""
|
1566
1593
|
self._underlying_persistence.close()
|
1567
1594
|
|
1595
|
+
@monitor_performance
|
1568
1596
|
def flush_cache(self):
|
1569
1597
|
"""
|
1570
1598
|
Flushes the cache by merging it with the underlying persistence layer.
|
@@ -1574,11 +1602,14 @@ class PersistenceManager(object):
|
|
1574
1602
|
all_content_parts = []
|
1575
1603
|
all_features = []
|
1576
1604
|
node_id_with_features = []
|
1577
|
-
|
1578
|
-
logger.debug("Merging cache to persistence")
|
1579
1605
|
dirty_nodes = self.node_cache.get_dirty_objs()
|
1580
1606
|
|
1607
|
+
if len(dirty_nodes) == 0:
|
1608
|
+
return
|
1609
|
+
|
1581
1610
|
logger.debug(f"Identified {len(dirty_nodes)} nodes to update")
|
1611
|
+
if not self._underlying_persistence.connection.in_transaction:
|
1612
|
+
self._underlying_persistence.connection.execute("BEGIN TRANSACTION")
|
1582
1613
|
|
1583
1614
|
next_feature_id = self._underlying_persistence.get_max_feature_id()
|
1584
1615
|
for node in dirty_nodes:
|
@@ -1618,7 +1649,6 @@ class PersistenceManager(object):
|
|
1618
1649
|
|
1619
1650
|
self.node_cache.undirty(node)
|
1620
1651
|
|
1621
|
-
logger.debug(f"Writing {len(all_node_ids)} nodes")
|
1622
1652
|
self._underlying_persistence.cursor.executemany(
|
1623
1653
|
"DELETE FROM cn where id=?", all_node_ids
|
1624
1654
|
)
|
@@ -1631,14 +1661,11 @@ class PersistenceManager(object):
|
|
1631
1661
|
self._underlying_persistence.cursor.executemany(
|
1632
1662
|
"DELETE FROM cnp where cn_id=?", all_node_ids
|
1633
1663
|
)
|
1634
|
-
logger.debug(f"Writing {len(all_content_parts)} content parts")
|
1635
|
-
|
1636
1664
|
self._underlying_persistence.cursor.executemany(
|
1637
1665
|
CONTENT_NODE_PART_INSERT, all_content_parts
|
1638
1666
|
)
|
1639
|
-
|
1640
|
-
logger.debug(f"Writing {len(all_features)} features")
|
1641
1667
|
self._underlying_persistence.cursor.executemany(FEATURE_INSERT, all_features)
|
1668
|
+
self._underlying_persistence.connection.commit()
|
1642
1669
|
|
1643
1670
|
def get_content_nodes(self, node_type, parent_node, include_children):
|
1644
1671
|
"""
|
@@ -36,11 +36,12 @@ __all__ = [
|
|
36
36
|
|
37
37
|
|
38
38
|
class SelectorContext:
|
39
|
-
def __init__(self, document: Document):
|
39
|
+
def __init__(self, document: Document, first_only=False):
|
40
40
|
self.pattern_cache = {}
|
41
41
|
self.last_op = None
|
42
42
|
self.document: Document = document
|
43
43
|
self.stream = 0
|
44
|
+
self.first_only = first_only
|
44
45
|
|
45
46
|
def cache_pattern(self, pattern):
|
46
47
|
if pattern not in self.pattern_cache:
|
@@ -53,20 +54,26 @@ class PipelineExpression(object):
|
|
53
54
|
|
54
55
|
def __init__(self, left, op, right):
|
55
56
|
self.left = left
|
56
|
-
"""the left side of the pipeline expression"""
|
57
57
|
self.op = op
|
58
|
-
"""the operator of the pipeline expression"""
|
59
58
|
self.right = right
|
60
|
-
"""the right side of the pipeline expression"""
|
61
59
|
|
62
60
|
def resolve(self, content_node: ContentNode, variables, context: SelectorContext):
|
63
61
|
left_nodes = self.left.resolve(content_node, variables, context)
|
64
62
|
result_nodes: List[ContentNode] = []
|
65
63
|
context.stream = context.stream + 1
|
66
|
-
|
67
|
-
|
64
|
+
|
65
|
+
# If first_only is True and we already have left nodes, only process the first one
|
66
|
+
nodes_to_process = left_nodes[:1] if context.first_only and left_nodes else left_nodes
|
67
|
+
|
68
|
+
for node in nodes_to_process:
|
69
|
+
right_results = self.right.resolve(node, variables, context)
|
70
|
+
result_nodes.extend(right_results)
|
71
|
+
# If first_only is True and we found a match, return immediately
|
72
|
+
if context.first_only and result_nodes:
|
73
|
+
break
|
74
|
+
|
68
75
|
context.stream = context.stream - 1
|
69
|
-
return result_nodes
|
76
|
+
return result_nodes[:1] if context.first_only else result_nodes
|
70
77
|
|
71
78
|
|
72
79
|
class UnaryExpression(object):
|
@@ -181,17 +188,12 @@ class AbsolutePath(object):
|
|
181
188
|
|
182
189
|
|
183
190
|
class Step(object):
|
184
|
-
"""
|
185
|
-
A single step in a relative path. a; @b; text(); parent::foo:bar[5].
|
186
|
-
"""
|
191
|
+
"""A single step in a relative path."""
|
187
192
|
|
188
193
|
def __init__(self, axis, node_test, predicates):
|
189
194
|
self.axis = axis
|
190
|
-
"""the step's axis, or @ or None if abbreviated or undefined"""
|
191
195
|
self.node_test = node_test
|
192
|
-
"""a NameTest or NodeType object describing the test represented"""
|
193
196
|
self.predicates = predicates
|
194
|
-
"""a list of predicates filtering the step"""
|
195
197
|
|
196
198
|
def resolve(self, obj, variables, context: SelectorContext):
|
197
199
|
match = True
|
@@ -217,8 +219,9 @@ class Step(object):
|
|
217
219
|
return []
|
218
220
|
|
219
221
|
nodes = self.node_test.test(axis_node, variables, context)
|
220
|
-
|
221
222
|
final_nodes = []
|
223
|
+
|
224
|
+
# If first_only is True, only process until we find the first match
|
222
225
|
for node in nodes:
|
223
226
|
match = True
|
224
227
|
for predicate in self.predicates:
|
@@ -230,6 +233,8 @@ class Step(object):
|
|
230
233
|
|
231
234
|
if match:
|
232
235
|
final_nodes.append(node)
|
236
|
+
if context.first_only:
|
237
|
+
break
|
233
238
|
|
234
239
|
return final_nodes
|
235
240
|
|
@@ -247,9 +252,7 @@ class NameTest(object):
|
|
247
252
|
|
248
253
|
def __init__(self, prefix, name):
|
249
254
|
self.prefix = prefix
|
250
|
-
"""the namespace prefix used for the test, or None if unset"""
|
251
255
|
self.name = name
|
252
|
-
"""the node name used for the test, or *"""
|
253
256
|
|
254
257
|
def test(self, obj, variables, context: SelectorContext):
|
255
258
|
if isinstance(obj, ContentNode):
|
@@ -257,12 +260,15 @@ class NameTest(object):
|
|
257
260
|
if self.name == "*" or self.name == obj.node_type:
|
258
261
|
return [obj]
|
259
262
|
else:
|
260
|
-
|
263
|
+
nodes = context.document.get_persistence().get_content_nodes(
|
261
264
|
self.name, obj, context.last_op != "/"
|
262
265
|
)
|
266
|
+
# If first_only is True, return only the first matching node
|
267
|
+
return nodes[:1] if context.first_only else nodes
|
268
|
+
|
263
269
|
if isinstance(obj, ContentFeature):
|
264
270
|
return self.name == "*" or (
|
265
|
-
|
271
|
+
obj.feature_type == self.prefix and obj.name == self.name
|
266
272
|
)
|
267
273
|
return False
|
268
274
|
|
@@ -333,27 +333,27 @@ class ExtensionPackUtil:
|
|
333
333
|
if options is None:
|
334
334
|
options = {}
|
335
335
|
|
336
|
-
for service in self.kodexa_metadata
|
337
|
-
if service
|
336
|
+
for service in self.kodexa_metadata["services"]:
|
337
|
+
if service["type"] == "action" and service["slug"] == action_slug:
|
338
338
|
# TODO We need to validate all the options
|
339
339
|
|
340
|
-
if len(service
|
340
|
+
if len(service["metadata"]["options"]) > 0:
|
341
341
|
option_names = []
|
342
|
-
for option in service
|
343
|
-
option_names.append(option
|
344
|
-
if option
|
345
|
-
options[option
|
346
|
-
if option
|
342
|
+
for option in service["metadata"]["options"]:
|
343
|
+
option_names.append(option["name"])
|
344
|
+
if option["name"] not in options and "default" in option and option["default"] is not None:
|
345
|
+
options[option["name"]] = option["default"]
|
346
|
+
if option["required"] and option["name"] not in options:
|
347
347
|
raise OptionException(
|
348
|
-
f"Missing required option {option
|
348
|
+
f"Missing required option {option['name']}"
|
349
349
|
)
|
350
350
|
|
351
351
|
for option_name in options.keys():
|
352
352
|
if option_name not in option_names:
|
353
353
|
# We need to determine if this is actually a group
|
354
354
|
is_group = False
|
355
|
-
for check_option in service
|
356
|
-
if check_option["group"] is not None:
|
355
|
+
for check_option in service["metadata"]["options"]:
|
356
|
+
if "group" in check_option and check_option["group"] is not None:
|
357
357
|
if check_option["group"]["name"] == option_name:
|
358
358
|
is_group = True
|
359
359
|
|
@@ -363,8 +363,8 @@ class ExtensionPackUtil:
|
|
363
363
|
)
|
364
364
|
|
365
365
|
# We need to create and return our action
|
366
|
-
module = importlib.import_module(service
|
367
|
-
klass = getattr(module, service
|
366
|
+
module = importlib.import_module(service["step"]["package"])
|
367
|
+
klass = getattr(module, service["step"]["class"])
|
368
368
|
new_instance = klass(**options)
|
369
369
|
|
370
370
|
# Since we will be using to access metadata we will need to
|
@@ -421,15 +421,15 @@ class ExtensionPackUtil:
|
|
421
421
|
if options is None:
|
422
422
|
options = {}
|
423
423
|
|
424
|
-
for service in self.kodexa_metadata
|
425
|
-
if service
|
424
|
+
for service in self.kodexa_metadata["services"]:
|
425
|
+
if service["type"] == "assistant" and service["slug"] == assistant_slug:
|
426
426
|
# TODO We need to validate all the options
|
427
427
|
|
428
428
|
# We need to create and return our action
|
429
429
|
|
430
|
-
logger.info(f"Creating new assistant {service
|
431
|
-
module = importlib.import_module(service
|
432
|
-
klass = getattr(module, service
|
430
|
+
logger.info(f"Creating new assistant {service['assistant']}")
|
431
|
+
module = importlib.import_module(service["assistant"]["package"])
|
432
|
+
klass = getattr(module, service["assistant"]["class"])
|
433
433
|
return klass(**options)
|
434
434
|
|
435
435
|
raise Exception("Unable to find the assistant " + assistant_slug)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "kodexa"
|
3
|
-
version = "7.0.
|
3
|
+
version = "7.0.012399073274"
|
4
4
|
description = "Python SDK for the Kodexa Platform"
|
5
5
|
authors = ["Austin Redenbaugh <austin@kodexa.com>", "Philip Dodds <philip@kodexa.com>", "Romar Cablao <rcablao@kodexa.com>", "Amadea Paula Dodds <amadeapaula@kodexa.com>"]
|
6
6
|
readme = "README.md"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/dataclasses/templates/llm_data_class.j2
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{kodexa-7.0.12396563598 → kodexa-7.0.12399073274}/kodexa/model/entities/product_subscription.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|