groundx 2.0.15__py3-none-any.whl → 2.7.7__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.
Files changed (147) hide show
  1. groundx/__init__.py +73 -21
  2. groundx/buckets/__init__.py +2 -0
  3. groundx/buckets/client.py +55 -388
  4. groundx/buckets/raw_client.py +628 -0
  5. groundx/client.py +22 -21
  6. groundx/core/__init__.py +5 -0
  7. groundx/core/api_error.py +13 -5
  8. groundx/core/client_wrapper.py +4 -3
  9. groundx/core/force_multipart.py +16 -0
  10. groundx/core/http_client.py +76 -32
  11. groundx/core/http_response.py +55 -0
  12. groundx/core/jsonable_encoder.py +0 -1
  13. groundx/core/pydantic_utilities.py +71 -112
  14. groundx/core/serialization.py +7 -3
  15. groundx/csv_splitter.py +64 -0
  16. groundx/customer/__init__.py +2 -0
  17. groundx/customer/client.py +31 -43
  18. groundx/customer/raw_client.py +91 -0
  19. groundx/documents/__init__.py +1 -2
  20. groundx/documents/client.py +455 -953
  21. groundx/documents/raw_client.py +1450 -0
  22. groundx/errors/__init__.py +2 -0
  23. groundx/errors/bad_request_error.py +4 -3
  24. groundx/errors/unauthorized_error.py +4 -3
  25. groundx/extract/__init__.py +48 -0
  26. groundx/extract/agents/__init__.py +7 -0
  27. groundx/extract/agents/agent.py +202 -0
  28. groundx/extract/classes/__init__.py +24 -0
  29. groundx/extract/classes/agent.py +23 -0
  30. groundx/extract/classes/api.py +15 -0
  31. groundx/extract/classes/document.py +338 -0
  32. groundx/extract/classes/field.py +88 -0
  33. groundx/extract/classes/groundx.py +147 -0
  34. groundx/extract/classes/prompt.py +36 -0
  35. groundx/extract/classes/test_document.py +109 -0
  36. groundx/extract/classes/test_field.py +43 -0
  37. groundx/extract/classes/test_groundx.py +223 -0
  38. groundx/extract/classes/test_prompt.py +68 -0
  39. groundx/extract/post_process/__init__.py +7 -0
  40. groundx/extract/post_process/post_process.py +33 -0
  41. groundx/extract/services/.DS_Store +0 -0
  42. groundx/extract/services/__init__.py +14 -0
  43. groundx/extract/services/csv.py +76 -0
  44. groundx/extract/services/logger.py +126 -0
  45. groundx/extract/services/logging_cfg.py +53 -0
  46. groundx/extract/services/ratelimit.py +104 -0
  47. groundx/extract/services/sheets_client.py +160 -0
  48. groundx/extract/services/status.py +197 -0
  49. groundx/extract/services/upload.py +68 -0
  50. groundx/extract/services/upload_minio.py +122 -0
  51. groundx/extract/services/upload_s3.py +91 -0
  52. groundx/extract/services/utility.py +52 -0
  53. groundx/extract/settings/__init__.py +15 -0
  54. groundx/extract/settings/settings.py +212 -0
  55. groundx/extract/settings/test_settings.py +512 -0
  56. groundx/extract/tasks/__init__.py +6 -0
  57. groundx/extract/tasks/utility.py +27 -0
  58. groundx/extract/utility/__init__.py +15 -0
  59. groundx/extract/utility/classes.py +193 -0
  60. groundx/extract/utility/test_utility.py +81 -0
  61. groundx/groups/__init__.py +2 -0
  62. groundx/groups/client.py +63 -550
  63. groundx/groups/raw_client.py +901 -0
  64. groundx/health/__init__.py +2 -0
  65. groundx/health/client.py +35 -101
  66. groundx/health/raw_client.py +193 -0
  67. groundx/ingest.py +771 -0
  68. groundx/search/__init__.py +2 -0
  69. groundx/search/client.py +94 -227
  70. groundx/search/raw_client.py +442 -0
  71. groundx/search/types/__init__.py +2 -0
  72. groundx/types/__init__.py +68 -16
  73. groundx/types/bounding_box_detail.py +4 -4
  74. groundx/types/bucket_detail.py +5 -5
  75. groundx/types/bucket_list_response.py +17 -3
  76. groundx/types/bucket_response.py +3 -3
  77. groundx/types/bucket_update_detail.py +4 -4
  78. groundx/types/bucket_update_response.py +3 -3
  79. groundx/types/customer_detail.py +2 -2
  80. groundx/types/customer_response.py +3 -3
  81. groundx/types/document.py +54 -0
  82. groundx/types/document_detail.py +16 -4
  83. groundx/types/document_list_response.py +4 -4
  84. groundx/types/document_local_ingest_request.py +7 -0
  85. groundx/types/document_lookup_response.py +8 -3
  86. groundx/types/document_response.py +3 -3
  87. groundx/types/document_type.py +21 -1
  88. groundx/types/group_detail.py +4 -4
  89. groundx/types/group_list_response.py +17 -3
  90. groundx/types/group_response.py +3 -3
  91. groundx/types/health_response.py +3 -3
  92. groundx/types/health_response_health.py +3 -3
  93. groundx/types/health_service.py +5 -5
  94. groundx/types/ingest_local_document.py +25 -0
  95. groundx/types/ingest_local_document_metadata.py +51 -0
  96. groundx/types/ingest_remote_document.py +15 -6
  97. groundx/types/ingest_response.py +4 -4
  98. groundx/types/{process_status_response_ingest.py → ingest_status.py} +8 -7
  99. groundx/types/{ingest_response_ingest.py → ingest_status_light.py} +7 -5
  100. groundx/types/ingest_status_progress.py +26 -0
  101. groundx/types/{process_status_response_ingest_progress_errors.py → ingest_status_progress_cancelled.py} +4 -4
  102. groundx/types/{process_status_response_ingest_progress_complete.py → ingest_status_progress_complete.py} +4 -4
  103. groundx/types/{process_status_response_ingest_progress_cancelled.py → ingest_status_progress_errors.py} +4 -4
  104. groundx/types/{process_status_response_ingest_progress_processing.py → ingest_status_progress_processing.py} +4 -4
  105. groundx/types/message_response.py +2 -2
  106. groundx/types/meter_detail.py +2 -2
  107. groundx/types/process_level.py +5 -0
  108. groundx/types/{process_status_response.py → processes_status_response.py} +8 -5
  109. groundx/types/processing_status.py +3 -1
  110. groundx/types/search_response.py +3 -3
  111. groundx/types/search_response_search.py +3 -3
  112. groundx/types/search_result_item.py +7 -5
  113. groundx/types/search_result_item_pages_item.py +41 -0
  114. groundx/types/subscription_detail.py +3 -3
  115. groundx/types/subscription_detail_meters.py +5 -5
  116. groundx/{documents/types/website_crawl_request_websites_item.py → types/website_source.py} +7 -7
  117. groundx/types/workflow_apply_request.py +24 -0
  118. groundx/types/workflow_detail.py +59 -0
  119. groundx/types/workflow_detail_chunk_strategy.py +5 -0
  120. groundx/types/workflow_detail_relationships.py +36 -0
  121. groundx/types/workflow_engine.py +58 -0
  122. groundx/types/workflow_engine_reasoning_effort.py +5 -0
  123. groundx/types/workflow_engine_service.py +7 -0
  124. groundx/types/workflow_prompt.py +37 -0
  125. groundx/types/workflow_prompt_group.py +25 -0
  126. groundx/types/workflow_prompt_role.py +5 -0
  127. groundx/types/workflow_request.py +31 -0
  128. groundx/types/workflow_request_chunk_strategy.py +5 -0
  129. groundx/types/workflow_response.py +20 -0
  130. groundx/types/workflow_step.py +33 -0
  131. groundx/types/workflow_step_config.py +33 -0
  132. groundx/types/workflow_step_config_field.py +8 -0
  133. groundx/types/workflow_steps.py +38 -0
  134. groundx/types/workflows_response.py +20 -0
  135. groundx/workflows/__init__.py +7 -0
  136. groundx/workflows/client.py +736 -0
  137. groundx/workflows/raw_client.py +841 -0
  138. groundx/workflows/types/__init__.py +7 -0
  139. groundx/workflows/types/workflows_get_request_id.py +5 -0
  140. {groundx-2.0.15.dist-info → groundx-2.7.7.dist-info}/LICENSE +1 -1
  141. {groundx-2.0.15.dist-info → groundx-2.7.7.dist-info}/METADATA +39 -22
  142. groundx-2.7.7.dist-info/RECORD +155 -0
  143. groundx/documents/types/__init__.py +0 -6
  144. groundx/documents/types/documents_ingest_local_request_files_item.py +0 -43
  145. groundx/types/process_status_response_ingest_progress.py +0 -26
  146. groundx-2.0.15.dist-info/RECORD +0 -82
  147. {groundx-2.0.15.dist-info → groundx-2.7.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,193 @@
1
+ import typing
2
+
3
+ if typing.TYPE_CHECKING:
4
+ from ..classes.prompt import Prompt
5
+
6
+
7
+ def class_fields(cls: typing.Any) -> typing.Set[str]:
8
+ fields: typing.Set[str] = set()
9
+ if hasattr(cls, "model_fields"):
10
+ fields = set(cls.model_fields.keys())
11
+ elif hasattr(cls, "__fields__"):
12
+ fields = set(cls.__fields__.keys()) # type: ignore[reportDeprecated]
13
+ else:
14
+ fields = set()
15
+
16
+ return fields
17
+
18
+
19
+ def clean_json(txt: str) -> str:
20
+ for p in ("json```\n", "```json\n", "json\n"):
21
+ if txt.startswith(p):
22
+ txt = txt[len(p) :]
23
+ if txt.endswith("```"):
24
+ txt = txt[:-3]
25
+ return txt.strip()
26
+
27
+
28
+ def coerce_numeric_string(
29
+ value: typing.Any,
30
+ et: typing.Union[str, typing.List[str]],
31
+ ) -> typing.Union[typing.Any, typing.List[typing.Any]]:
32
+ expected_types = str_to_type_sequence(et)
33
+
34
+ if any(t in (int, float) for t in expected_types):
35
+ if isinstance(value, str):
36
+ value = value.replace(",", "")
37
+ try:
38
+ value = float(value)
39
+ except ValueError:
40
+ return value
41
+ if float in expected_types:
42
+ return value
43
+ return int(value)
44
+
45
+ if str in expected_types and isinstance(value, str) and value == "0":
46
+ return None
47
+
48
+ return value
49
+
50
+
51
+ def from_attr_name(
52
+ name: str, prompts: typing.Sequence[typing.Mapping[str, "Prompt"]]
53
+ ) -> typing.Tuple[typing.Optional[str], typing.Optional[typing.Any]]:
54
+ for pmps in prompts:
55
+ for key, prompt in pmps.items():
56
+ if getattr(prompt, "attr_name", None) == name:
57
+ return key, prompt
58
+
59
+ return None, None
60
+
61
+
62
+ def from_key(
63
+ name: str,
64
+ prompts: typing.Sequence[typing.Mapping[str, "Prompt"]],
65
+ ) -> typing.Tuple[typing.Optional[str], typing.Optional[typing.Any]]:
66
+ for pmps in prompts:
67
+ for k, prompt in pmps.items():
68
+ if k == name:
69
+ return k, prompt
70
+
71
+ key, pmp = from_attr_name(name, prompts)
72
+ if pmp:
73
+ return key, pmp
74
+
75
+ return None, None
76
+
77
+
78
+ def str_to_type_sequence(
79
+ ty: typing.Union[str, typing.List[str]],
80
+ ) -> typing.Sequence[typing.Type[typing.Any]]:
81
+ if isinstance(ty, list):
82
+ tys: typing.List[typing.Any] = []
83
+ for t in ty:
84
+ tys.append(str_to_type(t))
85
+
86
+ return tys
87
+
88
+ return [str_to_type(ty)]
89
+
90
+
91
+ def str_to_type(
92
+ ty: str,
93
+ ) -> typing.Type[typing.Any]:
94
+ if ty == "int":
95
+ return int
96
+ elif ty == "float":
97
+ return float
98
+ elif ty == "list":
99
+ return list
100
+ elif ty == "dict":
101
+ return dict
102
+
103
+ return str
104
+
105
+
106
+ def type_to_str(
107
+ ty: typing.Union[typing.Type[typing.Any], typing.Sequence[typing.Type[typing.Any]]],
108
+ ) -> typing.Union[str, typing.List[str]]:
109
+ if isinstance(ty, list):
110
+ tys: typing.List[str] = []
111
+ for t in ty:
112
+ nt = type_to_str(t)
113
+ if isinstance(nt, str):
114
+ tys.append(nt)
115
+ else:
116
+ tys.append("list")
117
+
118
+ if ty == int:
119
+ return "int"
120
+ if ty == float:
121
+ return "float"
122
+ if ty == list:
123
+ return "list"
124
+ if ty == dict:
125
+ return "dict"
126
+
127
+ return "str"
128
+
129
+
130
+ def validate_confidence(
131
+ key: str,
132
+ key_data: typing.Any,
133
+ fields: typing.Set[str],
134
+ value: typing.Any,
135
+ errors: typing.Dict[str, str],
136
+ ) -> typing.Tuple[
137
+ typing.Union[typing.Any, typing.List[typing.Any]],
138
+ typing.Optional[str],
139
+ typing.Optional[str],
140
+ ]:
141
+ if key_data.attr_name not in fields:
142
+ return None, None, f"unexpected attribute [{key_data.attr_name}]"
143
+
144
+ if value is None:
145
+ return None, None, None
146
+
147
+ if not isinstance(value, dict):
148
+ return (
149
+ None,
150
+ None,
151
+ f"unexpected value type [{key_data.attr_name}] [{type(value)}] [{key_data.type}]\n[{value}]",
152
+ )
153
+
154
+ if "value" not in value:
155
+ return (
156
+ None,
157
+ None,
158
+ f'value is missing "value" key [{key_data.attr_name}]\n[{value}]',
159
+ )
160
+
161
+ if value["value"] is None:
162
+ return None, None, None
163
+
164
+ final_value = coerce_numeric_string(value["value"], key_data.type)
165
+ if not key_data.valid_value(final_value):
166
+ return (
167
+ final_value,
168
+ None,
169
+ f"unexpected type for statement [{key}] value [{type(final_value)}]\n\n{final_value}",
170
+ )
171
+
172
+ if "confidence" not in value:
173
+ return (
174
+ final_value,
175
+ None,
176
+ f'value is missing "confidence" key [{key_data.attr_name}]\n[{value}]',
177
+ )
178
+
179
+ if not isinstance(value["confidence"], str):
180
+ return (
181
+ final_value,
182
+ None,
183
+ f"confidence is not type str [{key_data.attr_name}]\n[{value}]",
184
+ )
185
+
186
+ if value["confidence"] not in ["low", "medium", "high"]:
187
+ return (
188
+ final_value,
189
+ None,
190
+ f'confidence value is unsupported value [{key_data.attr_name}]\n[{value["confidence"]}]',
191
+ )
192
+
193
+ return final_value, value["confidence"], None
@@ -0,0 +1,81 @@
1
+ import typing, unittest
2
+
3
+ from .classes import class_fields, coerce_numeric_string
4
+
5
+
6
+ class DummyModelFields:
7
+ model_fields = {"a": 1, "b": 2}
8
+
9
+
10
+ class DummyDunderFields:
11
+ __fields__ = {"x": 10, "y": 20}
12
+
13
+
14
+ class DummyBothFields:
15
+ model_fields = {"m": None}
16
+ __fields__ = {"f": None}
17
+
18
+
19
+ class DummyNoFields:
20
+ pass
21
+
22
+
23
+ class TestUtilClassFields(unittest.TestCase):
24
+ def test_model_fields(self):
25
+ expected = {"a", "b"}
26
+ # class and instance both should return model_fields keys
27
+ self.assertEqual(class_fields(DummyModelFields), expected)
28
+ self.assertEqual(class_fields(DummyModelFields()), expected)
29
+
30
+ def test_dunder_fields(self):
31
+ expected = {"x", "y"}
32
+ # fallback to __fields__ when model_fields not present
33
+ self.assertEqual(class_fields(DummyDunderFields), expected)
34
+ self.assertEqual(class_fields(DummyDunderFields()), expected)
35
+
36
+ def test_prefers_model_over_dunder(self):
37
+ # when both exist, model_fields takes precedence
38
+ expected = {"m"}
39
+ self.assertEqual(class_fields(DummyBothFields), expected)
40
+ self.assertEqual(class_fields(DummyBothFields()), expected)
41
+
42
+ def test_no_fields(self):
43
+ # no field attributes yields empty set
44
+ self.assertEqual(class_fields(DummyNoFields), set())
45
+ self.assertEqual(class_fields(DummyNoFields()), set())
46
+
47
+
48
+ class TestUtilCoerceNumericString(unittest.TestCase):
49
+ def test_expected_str(self) -> None:
50
+ # When expected type is str, no coercion occurs
51
+ self.assertEqual(coerce_numeric_string("42", "str"), "42")
52
+ self.assertEqual(coerce_numeric_string("foo", "str"), "foo")
53
+ self.assertEqual(coerce_numeric_string(7, "str"), 7)
54
+ self.assertEqual(coerce_numeric_string(2.71, "str"), 2.71)
55
+
56
+ def test_expected_int(self) -> None:
57
+ # Numeric string to int or float based on content
58
+ self.assertEqual(coerce_numeric_string("42", "int"), 42)
59
+ self.assertEqual(coerce_numeric_string("3.14", "int"), 3)
60
+ self.assertEqual(coerce_numeric_string("foo", "int"), "foo")
61
+ self.assertEqual(coerce_numeric_string(8, "int"), 8)
62
+ self.assertEqual(coerce_numeric_string(3.14, "int"), 3)
63
+
64
+ def test_expected_float(self) -> None:
65
+ self.assertEqual(coerce_numeric_string("42", "float"), 42.0)
66
+ self.assertEqual(coerce_numeric_string("3.14", "float"), 3.14)
67
+ self.assertEqual(coerce_numeric_string("foo", "float"), "foo")
68
+ self.assertEqual(coerce_numeric_string(9.81, "float"), 9.81)
69
+ self.assertEqual(coerce_numeric_string(10, "float"), 10)
70
+
71
+ def test_expected_int_float_list(self) -> None:
72
+ types: typing.List[str] = ["int", "float"]
73
+ self.assertEqual(coerce_numeric_string("42", types), 42)
74
+ self.assertEqual(coerce_numeric_string("3.14", types), 3.14)
75
+ self.assertEqual(coerce_numeric_string("foo", types), "foo")
76
+ self.assertEqual(coerce_numeric_string(11, types), 11)
77
+ self.assertEqual(coerce_numeric_string(2.718, types), 2.718)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ unittest.main()
@@ -1,2 +1,4 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
+ # isort: skip_file
4
+