contentgrid-application-client 0.2.6__tar.gz → 0.2.7__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.
- {contentgrid_application_client-0.2.6/src/contentgrid_application_client.egg-info → contentgrid_application_client-0.2.7}/PKG-INFO +2 -2
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/src/contentgrid_application_client/application.py +86 -16
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7/src/contentgrid_application_client.egg-info}/PKG-INFO +2 -2
- contentgrid_application_client-0.2.7/src/contentgrid_application_client.egg-info/requires.txt +1 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_openapi.py +2 -3
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_search.py +85 -1
- contentgrid_application_client-0.2.6/src/contentgrid_application_client.egg-info/requires.txt +0 -1
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/LICENSE +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/README.md +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/pyproject.toml +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/pytest.ini +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/requirements.txt +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/setup.cfg +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/setup.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/src/contentgrid_application_client/__init__.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/src/contentgrid_application_client/py.typed +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/src/contentgrid_application_client.egg-info/SOURCES.txt +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/src/contentgrid_application_client.egg-info/dependency_links.txt +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/src/contentgrid_application_client.egg-info/top_level.txt +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/example-docs/document.jpg +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/example-docs/resume.pdf +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/fixtures.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_collection.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_content.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_curies.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_entity_creation.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_hal_form_type_check.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_profile.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_prompting.py +0 -0
- {contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_relations.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contentgrid-application-client
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Python Client for interacting with ContentGrid Applications
|
|
5
5
|
Author-email: Ranec Belpaire <ranec.belpaire@xenit.eu>
|
|
6
6
|
License: Copyright 2024 Xenit Solutions
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
22
22
|
Requires-Python: >=3.5
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: contentgrid-hal-client==0.2.
|
|
25
|
+
Requires-Dist: contentgrid-hal-client==0.2.7
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
Dynamic: requires-dist
|
|
28
28
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
1
2
|
from enum import Enum
|
|
2
3
|
from typing import Any, Generator, List, Optional, Self, Sequence, Union, cast
|
|
3
4
|
from contentgrid_hal_client.hal_forms import HALFormsPropertyType, HALFormsTemplate
|
|
@@ -58,6 +59,7 @@ class InteractiveApplicationResponse(InteractiveHALResponse):
|
|
|
58
59
|
class SearchType(Enum):
|
|
59
60
|
EXACT_MATCH = "exact-match"
|
|
60
61
|
PREFIX_MATCH = "prefix-match"
|
|
62
|
+
FULL_TEXT_MATCH = "full-text"
|
|
61
63
|
LESS_THAN = "less-than"
|
|
62
64
|
LESS_THAN_OR_EQUAL = "less-than-or-equal"
|
|
63
65
|
GREATER_THAN = "greater-than"
|
|
@@ -478,6 +480,7 @@ class AttributeProfile(InteractiveApplicationResponse):
|
|
|
478
480
|
# Initialize private attributes for search options
|
|
479
481
|
self.__prefix_search_option: Optional[SearchOption] = None
|
|
480
482
|
self.__exact_search_option: Optional[SearchOption] = None
|
|
483
|
+
self.__full_text_search_option: Optional[SearchOption] = None
|
|
481
484
|
self.__less_than_search_option: Optional[SearchOption] = None
|
|
482
485
|
self.__less_than_or_equal_search_option: Optional[SearchOption] = None
|
|
483
486
|
self.__greater_than_search_option: Optional[SearchOption] = None
|
|
@@ -554,6 +557,12 @@ class AttributeProfile(InteractiveApplicationResponse):
|
|
|
554
557
|
if self.__prefix_search_option is None:
|
|
555
558
|
self.__prefix_search_option = self.get_search_option(search_type=SearchType.PREFIX_MATCH)
|
|
556
559
|
return self.__prefix_search_option
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def _full_text_search_option(self) -> Optional[SearchOption]:
|
|
563
|
+
if self.__full_text_search_option is None:
|
|
564
|
+
self.__full_text_search_option = self.get_search_option(search_type=SearchType.FULL_TEXT_MATCH)
|
|
565
|
+
return self.__full_text_search_option
|
|
557
566
|
|
|
558
567
|
@property
|
|
559
568
|
def _exact_search_option(self) -> Optional[SearchOption]:
|
|
@@ -699,32 +708,94 @@ class PageInfo:
|
|
|
699
708
|
_search_form_relation_delimiter = "."
|
|
700
709
|
|
|
701
710
|
|
|
702
|
-
class
|
|
703
|
-
|
|
711
|
+
class AttributeSearch(ABC):
|
|
712
|
+
"""Abstract base class for attribute search implementations."""
|
|
713
|
+
|
|
714
|
+
def __init__(self, attribute_name: str, ignore_errors: bool = False) -> None:
|
|
704
715
|
self.attribute_name: str = attribute_name
|
|
705
|
-
self.value: Union[float | int | date | datetime | bool | str] = value
|
|
706
|
-
self.prefer_prefix: bool = prefer_prefix
|
|
707
716
|
self.ignore_errors: bool = ignore_errors
|
|
717
|
+
|
|
718
|
+
@abstractmethod
|
|
719
|
+
def get_search_params(self, attribute_profile: AttributeProfile) -> List[SearchParam]:
|
|
720
|
+
"""Convert this search specification into search parameters.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
attribute_profile: The attribute profile to validate against and extract search options from.
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
List of SearchParam objects representing the search criteria.
|
|
727
|
+
"""
|
|
728
|
+
pass
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
class AttributeMatchSearch(AttributeSearch):
|
|
732
|
+
def __init__(
|
|
733
|
+
self,
|
|
734
|
+
attribute_name: str,
|
|
735
|
+
value: Union[float | int | date | datetime | bool | str],
|
|
736
|
+
use_prefix: bool = False,
|
|
737
|
+
use_full_text: bool = False,
|
|
738
|
+
use_exact: bool = False,
|
|
739
|
+
ignore_errors: bool = False
|
|
740
|
+
) -> None:
|
|
741
|
+
super().__init__(attribute_name, ignore_errors)
|
|
742
|
+
self.value: Union[float | int | date | datetime | bool | str] = value
|
|
743
|
+
self.use_prefix: bool = use_prefix
|
|
744
|
+
self.use_full_text: bool = use_full_text
|
|
745
|
+
self.use_exact: bool = use_exact
|
|
708
746
|
|
|
709
747
|
def get_search_params(self, attribute_profile: AttributeProfile) -> List[SearchParam]:
|
|
710
748
|
search_params = []
|
|
711
749
|
if self.value is not None:
|
|
712
750
|
if not self.ignore_errors:
|
|
713
|
-
if
|
|
751
|
+
if (
|
|
752
|
+
not attribute_profile.has_search_capability()
|
|
753
|
+
or (not attribute_profile._exact_search_option
|
|
754
|
+
and not attribute_profile._prefix_search_option
|
|
755
|
+
and not attribute_profile._full_text_search_option)
|
|
756
|
+
):
|
|
714
757
|
raise BadRequest(f"Attribute {self.attribute_name} does not support search. Available options: {attribute_profile.search_options}")
|
|
715
|
-
if not attribute_profile._exact_search_option and not attribute_profile._prefix_search_option:
|
|
716
|
-
raise BadRequest(f"Attribute {self.attribute_name} does not support exact search. Available options: {attribute_profile.search_options}")
|
|
717
758
|
|
|
718
|
-
#
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
759
|
+
# Extract available search options
|
|
760
|
+
full_text_option = attribute_profile._full_text_search_option
|
|
761
|
+
prefix_option = attribute_profile._prefix_search_option
|
|
762
|
+
exact_option = attribute_profile._exact_search_option
|
|
763
|
+
|
|
764
|
+
selected_option = None
|
|
765
|
+
|
|
766
|
+
# Check if any specific search type is explicitly requested
|
|
767
|
+
any_use_flag_set = self.use_prefix or self.use_full_text or self.use_exact
|
|
768
|
+
|
|
769
|
+
if any_use_flag_set:
|
|
770
|
+
# Use explicitly requested search types (in priority order)
|
|
771
|
+
if self.use_full_text and full_text_option:
|
|
772
|
+
selected_option = full_text_option
|
|
773
|
+
elif self.use_prefix and prefix_option:
|
|
774
|
+
selected_option = prefix_option
|
|
775
|
+
elif self.use_exact and exact_option:
|
|
776
|
+
selected_option = exact_option
|
|
777
|
+
else:
|
|
778
|
+
# No specific type requested, use default priority:
|
|
779
|
+
# 1. Full-text match (if available)
|
|
780
|
+
# 2. Prefix match (if available)
|
|
781
|
+
# 3. Exact match (if available)
|
|
782
|
+
# 4. Any available option
|
|
783
|
+
if full_text_option:
|
|
784
|
+
selected_option = full_text_option
|
|
785
|
+
elif prefix_option:
|
|
786
|
+
selected_option = prefix_option
|
|
787
|
+
elif exact_option:
|
|
788
|
+
selected_option = exact_option
|
|
789
|
+
elif attribute_profile.has_search_capability():
|
|
790
|
+
selected_option = attribute_profile.search_options[0]
|
|
791
|
+
|
|
792
|
+
if selected_option:
|
|
793
|
+
search_params.append(SearchParam(value=self.value, option=selected_option))
|
|
723
794
|
|
|
724
795
|
return search_params
|
|
725
796
|
|
|
726
797
|
|
|
727
|
-
class AttributeRangeSearch:
|
|
798
|
+
class AttributeRangeSearch(AttributeSearch):
|
|
728
799
|
def __init__(
|
|
729
800
|
self,
|
|
730
801
|
attribute_name: str,
|
|
@@ -733,7 +804,7 @@ class AttributeRangeSearch:
|
|
|
733
804
|
include_boundaries: bool = True,
|
|
734
805
|
ignore_errors: bool = False,
|
|
735
806
|
) -> None:
|
|
736
|
-
|
|
807
|
+
super().__init__(attribute_name, ignore_errors)
|
|
737
808
|
|
|
738
809
|
self.min_value: Optional[Union[float | int | str]]
|
|
739
810
|
if isinstance(min_value, datetime):
|
|
@@ -752,7 +823,6 @@ class AttributeRangeSearch:
|
|
|
752
823
|
self.max_value = max_value
|
|
753
824
|
|
|
754
825
|
self.include_boundaries: bool = include_boundaries
|
|
755
|
-
self.ignore_errors: bool = ignore_errors
|
|
756
826
|
|
|
757
827
|
def get_search_params(self, attribute_profile: AttributeProfile) -> List[SearchParam]:
|
|
758
828
|
search_params = []
|
|
@@ -925,7 +995,7 @@ class ContentGridApplicationClient(HALFormsClient):
|
|
|
925
995
|
return self.follow_link(profile_link, infer_type=EntityProfile, params=params)
|
|
926
996
|
|
|
927
997
|
def fetch_openapi_yaml(self) -> tuple[str, bytes]:
|
|
928
|
-
res = self.get("/openapi.yml")
|
|
998
|
+
res = self.get("/openapi.yml", headers={"Accept": "application/yaml"})
|
|
929
999
|
self._validate_non_json_response(res)
|
|
930
1000
|
return ("openapi.yml", res.content)
|
|
931
1001
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contentgrid-application-client
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Python Client for interacting with ContentGrid Applications
|
|
5
5
|
Author-email: Ranec Belpaire <ranec.belpaire@xenit.eu>
|
|
6
6
|
License: Copyright 2024 Xenit Solutions
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
22
22
|
Requires-Python: >=3.5
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: contentgrid-hal-client==0.2.
|
|
25
|
+
Requires-Dist: contentgrid-hal-client==0.2.7
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
Dynamic: requires-dist
|
|
28
28
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
contentgrid-hal-client==0.2.7
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_openapi.py
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from contentgrid_application_client import ContentGridApplicationClient
|
|
2
|
-
from fixtures import auth_manager, cg_client # noqa: F401
|
|
3
2
|
import os
|
|
4
|
-
|
|
3
|
+
from fixtures import auth_manager, cg_client # noqa: F401
|
|
5
4
|
|
|
6
5
|
def test_fetch_openapi_spec(cg_client: ContentGridApplicationClient):
|
|
7
6
|
filename, file = cg_client.fetch_openapi_yaml()
|
|
@@ -9,4 +8,4 @@ def test_fetch_openapi_spec(cg_client: ContentGridApplicationClient):
|
|
|
9
8
|
output_path = os.path.join("output", filename)
|
|
10
9
|
with open(output_path, 'wb') as f:
|
|
11
10
|
f.write(file)
|
|
12
|
-
assert os.path.exists(output_path)
|
|
11
|
+
assert os.path.exists(output_path)
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_search.py
RENAMED
|
@@ -258,4 +258,88 @@ def test_conversion_of_datetime():
|
|
|
258
258
|
date_range = AttributeRangeSearch("datetime", min_value=datetime(2025,4,24), max_value=datetime(2026, 4, 24))
|
|
259
259
|
assert isinstance(date_range.min_value, str)
|
|
260
260
|
assert isinstance(date_range.max_value, str)
|
|
261
|
-
assert is_valid_datetime_format(date_range.min_value)
|
|
261
|
+
assert is_valid_datetime_format(date_range.min_value)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_full_text_preference_over_prefix(cg_client):
|
|
265
|
+
"""Test that full-text search is used when explicitly requested."""
|
|
266
|
+
entity_name = "skill"
|
|
267
|
+
|
|
268
|
+
# Create a search with full-text explicitly requested
|
|
269
|
+
match_search = AttributeMatchSearch(
|
|
270
|
+
attribute_name="name",
|
|
271
|
+
value="machine",
|
|
272
|
+
use_full_text=True
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
entity_search = EntitySearch(
|
|
276
|
+
entity_name=entity_name,
|
|
277
|
+
attribute_searches=[match_search]
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Get the entity profile to verify the search option selected
|
|
281
|
+
entity_profile = cg_client.get_entity_profile(entity_name)
|
|
282
|
+
attribute_profile = entity_profile.get_attribute_profile("name")
|
|
283
|
+
|
|
284
|
+
# Get the search params to verify full-text option is used
|
|
285
|
+
search_params = match_search.get_search_params(attribute_profile)
|
|
286
|
+
|
|
287
|
+
assert len(search_params) > 0
|
|
288
|
+
# If full-text is available, it should be selected
|
|
289
|
+
if attribute_profile._full_text_search_option:
|
|
290
|
+
assert search_params[0].option.type.value == "full-text"
|
|
291
|
+
|
|
292
|
+
results = cg_client.execute_entity_search(entity_search=entity_search, nb_results=3)
|
|
293
|
+
assert results is not None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_full_text_search_default_behavior(cg_client):
|
|
297
|
+
"""Test default behavior which prioritizes full-text, then prefix, then exact match."""
|
|
298
|
+
entity_name = "skill"
|
|
299
|
+
|
|
300
|
+
# Default behavior (no explicit use_* flags) uses priority: full-text > prefix > exact
|
|
301
|
+
match_search = AttributeMatchSearch(
|
|
302
|
+
attribute_name="name",
|
|
303
|
+
value="learning"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
entity_search = EntitySearch(
|
|
307
|
+
entity_name=entity_name,
|
|
308
|
+
attribute_searches=[match_search]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
results = cg_client.execute_entity_search(entity_search=entity_search, nb_results=5)
|
|
312
|
+
assert results is not None
|
|
313
|
+
assert len(results) >= 0
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_exact_match_fallback_when_preferences_disabled(cg_client):
|
|
317
|
+
"""Test that exact match is used when explicitly requested."""
|
|
318
|
+
entity_name = "skill"
|
|
319
|
+
|
|
320
|
+
# Create a search with exact match explicitly requested
|
|
321
|
+
match_search = AttributeMatchSearch(
|
|
322
|
+
attribute_name="name",
|
|
323
|
+
value="Python",
|
|
324
|
+
use_exact=True
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
entity_search = EntitySearch(
|
|
328
|
+
entity_name=entity_name,
|
|
329
|
+
attribute_searches=[match_search]
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Get the entity profile to verify the search option selected
|
|
333
|
+
entity_profile = cg_client.get_entity_profile(entity_name)
|
|
334
|
+
attribute_profile = entity_profile.get_attribute_profile("name")
|
|
335
|
+
|
|
336
|
+
# Get the search params to verify exact match option is used when explicitly requested
|
|
337
|
+
search_params = match_search.get_search_params(attribute_profile)
|
|
338
|
+
|
|
339
|
+
assert len(search_params) > 0
|
|
340
|
+
# Should use exact match (or case-insensitive match) as explicitly requested
|
|
341
|
+
if attribute_profile._exact_search_option:
|
|
342
|
+
assert search_params[0].option.type.value in ["exact-match", "case-insensitive-match"]
|
|
343
|
+
|
|
344
|
+
results = cg_client.execute_entity_search(entity_search=entity_search, nb_results=3)
|
|
345
|
+
assert results is not None
|
contentgrid_application_client-0.2.6/src/contentgrid_application_client.egg-info/requires.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
contentgrid-hal-client==0.2.6
|
|
File without changes
|
|
File without changes
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/requirements.txt
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
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_content.py
RENAMED
|
File without changes
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_curies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentgrid_application_client-0.2.6 → contentgrid_application_client-0.2.7}/tests/test_profile.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|