groundx 2.4.5__py3-none-any.whl → 2.4.10__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 groundx might be problematic. Click here for more details.

Files changed (36) hide show
  1. groundx/core/client_wrapper.py +2 -2
  2. groundx/extract/__init__.py +38 -0
  3. groundx/extract/agents/__init__.py +7 -0
  4. groundx/extract/agents/agent.py +202 -0
  5. groundx/extract/classes/__init__.py +27 -0
  6. groundx/extract/classes/agent.py +22 -0
  7. groundx/extract/classes/api.py +15 -0
  8. groundx/extract/classes/document.py +311 -0
  9. groundx/extract/classes/field.py +88 -0
  10. groundx/extract/classes/groundx.py +123 -0
  11. groundx/extract/classes/post_process.py +33 -0
  12. groundx/extract/classes/prompt.py +36 -0
  13. groundx/extract/classes/settings.py +169 -0
  14. groundx/extract/classes/test_document.py +126 -0
  15. groundx/extract/classes/test_field.py +43 -0
  16. groundx/extract/classes/test_groundx.py +188 -0
  17. groundx/extract/classes/test_prompt.py +68 -0
  18. groundx/extract/classes/test_settings.py +515 -0
  19. groundx/extract/classes/test_utility.py +81 -0
  20. groundx/extract/classes/utility.py +193 -0
  21. groundx/extract/services/.DS_Store +0 -0
  22. groundx/extract/services/__init__.py +14 -0
  23. groundx/extract/services/csv.py +76 -0
  24. groundx/extract/services/logger.py +127 -0
  25. groundx/extract/services/logging_cfg.py +55 -0
  26. groundx/extract/services/ratelimit.py +104 -0
  27. groundx/extract/services/sheets_client.py +160 -0
  28. groundx/extract/services/status.py +197 -0
  29. groundx/extract/services/upload.py +73 -0
  30. groundx/extract/services/upload_minio.py +122 -0
  31. groundx/extract/services/upload_s3.py +84 -0
  32. groundx/extract/services/utility.py +52 -0
  33. {groundx-2.4.5.dist-info → groundx-2.4.10.dist-info}/METADATA +17 -1
  34. {groundx-2.4.5.dist-info → groundx-2.4.10.dist-info}/RECORD +36 -5
  35. {groundx-2.4.5.dist-info → groundx-2.4.10.dist-info}/LICENSE +0 -0
  36. {groundx-2.4.5.dist-info → groundx-2.4.10.dist-info}/WHEEL +0 -0
@@ -0,0 +1,515 @@
1
+ import typing, os, unittest
2
+
3
+ from .settings import (
4
+ AgentSettings,
5
+ ContainerSettings,
6
+ ContainerUploadSettings,
7
+ GroundXSettings,
8
+ AWS_REGION,
9
+ CALLBACK_KEY,
10
+ GX_AGENT_KEY,
11
+ GX_API_KEY,
12
+ GX_KEY,
13
+ GX_REGION,
14
+ GX_SECRET,
15
+ VALID_KEYS,
16
+ )
17
+
18
+
19
+ AWS_KEY: str = "AWS_ACCESS_KEY_ID"
20
+ AWS_SECRET: str = "AWS_SECRET_ACCESS_KEY"
21
+ AWS_TOKEN: str = "AWS_SESSION_TOKEN"
22
+
23
+
24
+ def clearEnv() -> None:
25
+ os.environ.clear()
26
+
27
+
28
+ class TestAgentSettings(unittest.TestCase):
29
+ def test(self) -> None:
30
+ tsts: typing.List[typing.Dict[str, typing.Any]] = [
31
+ {
32
+ "expect": {
33
+ "api_base": None,
34
+ "api_key": Exception,
35
+ "max_steps": 7,
36
+ "model_id": "gpt-5-mini",
37
+ },
38
+ },
39
+ {
40
+ "api_base": "http://test.com",
41
+ "api_key": "mykey",
42
+ "api_key_env_val": "val",
43
+ "max_steps": 4,
44
+ "model_id": "gpt-5",
45
+ "expect": {
46
+ "api_base": "http://test.com",
47
+ "api_key": "mykey",
48
+ "api_key_env": "myenv",
49
+ "max_steps": 4,
50
+ "model_id": "gpt-5",
51
+ },
52
+ },
53
+ {
54
+ "api_key_env_val": "val",
55
+ "expect": {
56
+ "api_base": None,
57
+ "api_key": "val",
58
+ "max_steps": 7,
59
+ "model_id": "gpt-5-mini",
60
+ },
61
+ },
62
+ ]
63
+
64
+ for _, tst in enumerate(tsts):
65
+ clearEnv()
66
+
67
+ input: typing.Dict[str, typing.Any] = {}
68
+ if "api_base" in tst:
69
+ input["api_base"] = tst["api_base"]
70
+ if "api_key" in tst:
71
+ input["api_key"] = tst["api_key"]
72
+ if "api_key_env_val" in tst:
73
+ os.environ.update({GX_AGENT_KEY: tst["api_key_env_val"]})
74
+ if "max_steps" in tst:
75
+ input["max_steps"] = tst["max_steps"]
76
+ if "model_id" in tst:
77
+ input["model_id"] = tst["model_id"]
78
+
79
+ settings = AgentSettings(**input)
80
+
81
+ self.assertIsInstance(settings, AgentSettings)
82
+
83
+ self.assertEqual(settings.api_base, tst["expect"]["api_base"])
84
+
85
+ if tst["expect"]["api_key"] == Exception:
86
+ self.assertRaises(Exception, settings.get_api_key)
87
+ else:
88
+ self.assertEqual(settings.get_api_key(), tst["expect"]["api_key"])
89
+
90
+ self.assertEqual(settings.max_steps, tst["expect"]["max_steps"])
91
+
92
+ self.assertEqual(settings.model_id, tst["expect"]["model_id"])
93
+
94
+
95
+ class TestContainerUploadSettings(unittest.TestCase):
96
+ def test(self) -> None:
97
+ tsts: typing.List[typing.Dict[str, typing.Any]] = [
98
+ {
99
+ "base_domain": "https://base.com",
100
+ "bucket": "test-bucket",
101
+ "type": "s3",
102
+ "url": "https://test.com",
103
+ "aws_key_env_val": "valk",
104
+ "aws_region_env_val": "vale",
105
+ "aws_secret_env_val": "vals",
106
+ "expect": {
107
+ "base_domain": "https://base.com",
108
+ "base_path": "layout/processed/",
109
+ "bucket": "test-bucket",
110
+ "ssl": False,
111
+ "type": "s3",
112
+ "url": "https://test.com",
113
+ "key": None,
114
+ "region": "vale",
115
+ "secret": None,
116
+ },
117
+ },
118
+ {
119
+ "base_domain": "https://base.com",
120
+ "bucket": "test-bucket",
121
+ "type": "s3",
122
+ "url": "https://test.com",
123
+ "expect": {
124
+ "base_domain": "https://base.com",
125
+ "base_path": "layout/processed/",
126
+ "bucket": "test-bucket",
127
+ "ssl": False,
128
+ "type": "s3",
129
+ "url": "https://test.com",
130
+ "key": None,
131
+ "region": None,
132
+ "secret": None,
133
+ },
134
+ },
135
+ {
136
+ "base_domain": "https://base.com",
137
+ "base_path": "layout/",
138
+ "bucket": "test-bucket",
139
+ "ssl": True,
140
+ "type": "s3",
141
+ "url": "https://test.com",
142
+ "key": "mykey",
143
+ "gx_key_env_val": "valk",
144
+ "region": "myregion",
145
+ "gx_region_env_val": "vale",
146
+ "secret": "mysecret",
147
+ "gx_secret_env_val": "vals",
148
+ "expect": {
149
+ "base_domain": "https://base.com",
150
+ "base_path": "layout/",
151
+ "bucket": "test-bucket",
152
+ "ssl": True,
153
+ "type": "s3",
154
+ "url": "https://test.com",
155
+ "key": "mykey",
156
+ "region": "myregion",
157
+ "secret": "mysecret",
158
+ },
159
+ },
160
+ {
161
+ "base_domain": "https://base.com",
162
+ "bucket": "test-bucket",
163
+ "type": "s3",
164
+ "url": "https://test.com",
165
+ "gx_key_env_val": "valk",
166
+ "gx_region_env_val": "vale",
167
+ "gx_secret_env_val": "vals",
168
+ "expect": {
169
+ "base_domain": "https://base.com",
170
+ "base_path": "layout/processed/",
171
+ "bucket": "test-bucket",
172
+ "ssl": False,
173
+ "type": "s3",
174
+ "url": "https://test.com",
175
+ "key": "valk",
176
+ "region": "vale",
177
+ "secret": "vals",
178
+ },
179
+ },
180
+ {
181
+ "base_domain": "https://base.com",
182
+ "bucket": "test-bucket",
183
+ "type": "s3",
184
+ "url": "https://test.com",
185
+ "gx_key_env_val": "valk",
186
+ "gx_region_env_val": "vale",
187
+ "gx_secret_env_val": "vals",
188
+ "expect": {
189
+ "base_domain": "https://base.com",
190
+ "base_path": "layout/processed/",
191
+ "bucket": "test-bucket",
192
+ "ssl": False,
193
+ "type": "s3",
194
+ "url": "https://test.com",
195
+ "key": "valk",
196
+ "region": "vale",
197
+ "secret": "vals",
198
+ },
199
+ },
200
+ ]
201
+
202
+ for _, tst in enumerate(tsts):
203
+ clearEnv()
204
+
205
+ input: typing.Dict[str, typing.Any] = {}
206
+ if "base_domain" in tst:
207
+ input["base_domain"] = tst["base_domain"]
208
+ if "base_path" in tst:
209
+ input["base_path"] = tst["base_path"]
210
+ if "bucket" in tst:
211
+ input["bucket"] = tst["bucket"]
212
+ if "ssl" in tst:
213
+ input["ssl"] = tst["ssl"]
214
+ if "type" in tst:
215
+ input["type"] = tst["type"]
216
+ if "url" in tst:
217
+ input["url"] = tst["url"]
218
+ if "key" in tst:
219
+ input["key"] = tst["key"]
220
+ if "gx_key_env_val" in tst:
221
+ os.environ.update({GX_KEY: tst["gx_key_env_val"]})
222
+ if "aws_key_env_val" in tst:
223
+ os.environ.update({AWS_KEY: tst["aws_key_env_val"]})
224
+ if "region" in tst:
225
+ input["region"] = tst["region"]
226
+ if "gx_region_env_val" in tst:
227
+ os.environ.update({GX_REGION: tst["gx_region_env_val"]})
228
+ if "aws_region_env_val" in tst:
229
+ os.environ.update({AWS_REGION: tst["aws_region_env_val"]})
230
+ if "secret" in tst:
231
+ input["secret"] = tst["secret"]
232
+ if "gx_secret_env_val" in tst:
233
+ os.environ.update({GX_SECRET: tst["gx_secret_env_val"]})
234
+ if "aws_secret_env_val" in tst:
235
+ os.environ.update({AWS_SECRET: tst["aws_secret_env_val"]})
236
+
237
+ settings = ContainerUploadSettings(**input)
238
+
239
+ self.assertIsInstance(settings, ContainerUploadSettings)
240
+
241
+ self.assertEqual(settings.base_domain, tst["expect"]["base_domain"])
242
+
243
+ self.assertEqual(settings.base_path, tst["expect"]["base_path"])
244
+
245
+ self.assertEqual(settings.bucket, tst["expect"]["bucket"])
246
+
247
+ self.assertEqual(settings.ssl, tst["expect"]["ssl"])
248
+
249
+ self.assertEqual(settings.type, tst["expect"]["type"])
250
+
251
+ self.assertEqual(settings.url, tst["expect"]["url"])
252
+
253
+ if tst["expect"]["key"] == None:
254
+ self.assertIsNone(settings.get_key())
255
+ else:
256
+ self.assertEqual(settings.get_key(), tst["expect"]["key"])
257
+
258
+ if tst["expect"]["region"] == None:
259
+ self.assertIsNone(settings.get_region())
260
+ else:
261
+ self.assertEqual(settings.get_region(), tst["expect"]["region"])
262
+
263
+ if tst["expect"]["secret"] == None:
264
+ self.assertIsNone(settings.get_secret())
265
+ else:
266
+ self.assertEqual(settings.get_secret(), tst["expect"]["secret"])
267
+
268
+
269
+ class TestContainerSettings(unittest.TestCase):
270
+ def test(self) -> None:
271
+ tsts: typing.List[typing.Dict[str, typing.Any]] = [
272
+ {
273
+ "broker": "mybroker",
274
+ "service": "myservice",
275
+ "workers": 1,
276
+ "upload": {
277
+ "base_domain": "https://base.com",
278
+ "bucket": "test-bucket",
279
+ "type": "s3",
280
+ "url": "https://test.com",
281
+ },
282
+ "expect": {
283
+ "broker": "mybroker",
284
+ "cache_to": 300,
285
+ "google_sheets_drive_id": None,
286
+ "google_sheets_template_id": None,
287
+ "log_level": "info",
288
+ "metrics_broker": None,
289
+ "refresh_to": 60,
290
+ "service": "myservice",
291
+ "task_to": 600,
292
+ "workers": 1,
293
+ "callback_api_key": Exception,
294
+ "valid_api_keys": Exception,
295
+ "loglevel": "INFO",
296
+ "status_broker": "mybroker",
297
+ },
298
+ },
299
+ {
300
+ "broker": "mybroker",
301
+ "cache_to": 100,
302
+ "google_sheets_drive_id": "drive_id",
303
+ "google_sheets_template_id": "template_id",
304
+ "log_level": "error",
305
+ "metrics_broker": "mymetrics",
306
+ "refresh_to": 30,
307
+ "service": "myservice",
308
+ "task_to": 300,
309
+ "workers": 1,
310
+ "upload": {
311
+ "base_domain": "https://base.com",
312
+ "bucket": "test-bucket",
313
+ "type": "s3",
314
+ "url": "https://test.com",
315
+ },
316
+ "callback_api_key": "cbkey",
317
+ "callback_api_key_env_val": "vale",
318
+ "valid_api_keys": ["vkeys"],
319
+ "valid_api_keys_env_val": '["valv"]',
320
+ "expect": {
321
+ "broker": "mybroker",
322
+ "cache_to": 100,
323
+ "google_sheets_drive_id": "drive_id",
324
+ "google_sheets_template_id": "template_id",
325
+ "log_level": "error",
326
+ "metrics_broker": "mymetrics",
327
+ "refresh_to": 30,
328
+ "service": "myservice",
329
+ "task_to": 300,
330
+ "workers": 1,
331
+ "callback_api_key": "cbkey",
332
+ "valid_api_keys": ["vkeys"],
333
+ "loglevel": "ERROR",
334
+ "status_broker": "mymetrics",
335
+ },
336
+ },
337
+ {
338
+ "broker": "mybroker",
339
+ "service": "myservice",
340
+ "workers": 1,
341
+ "upload": {
342
+ "base_domain": "https://base.com",
343
+ "bucket": "test-bucket",
344
+ "type": "s3",
345
+ "url": "https://test.com",
346
+ },
347
+ "callback_api_key_env_val": "vale",
348
+ "valid_api_keys_env_val": '["valv"]',
349
+ "expect": {
350
+ "broker": "mybroker",
351
+ "cache_to": 300,
352
+ "google_sheets_drive_id": None,
353
+ "google_sheets_template_id": None,
354
+ "log_level": "info",
355
+ "metrics_broker": None,
356
+ "refresh_to": 60,
357
+ "service": "myservice",
358
+ "task_to": 600,
359
+ "workers": 1,
360
+ "callback_api_key": "vale",
361
+ "valid_api_keys": ["valv"],
362
+ "loglevel": "INFO",
363
+ "status_broker": "mybroker",
364
+ },
365
+ },
366
+ ]
367
+
368
+ for _, tst in enumerate(tsts):
369
+ clearEnv()
370
+
371
+ input: typing.Dict[str, typing.Any] = {}
372
+ if "broker" in tst:
373
+ input["broker"] = tst["broker"]
374
+ if "cache_to" in tst:
375
+ input["cache_to"] = tst["cache_to"]
376
+ if "google_sheets_drive_id" in tst:
377
+ input["google_sheets_drive_id"] = tst["google_sheets_drive_id"]
378
+ if "google_sheets_template_id" in tst:
379
+ input["google_sheets_template_id"] = tst["google_sheets_template_id"]
380
+ if "log_level" in tst:
381
+ input["log_level"] = tst["log_level"]
382
+ if "metrics_broker" in tst:
383
+ input["metrics_broker"] = tst["metrics_broker"]
384
+ if "refresh_to" in tst:
385
+ input["refresh_to"] = tst["refresh_to"]
386
+ if "service" in tst:
387
+ input["service"] = tst["service"]
388
+ if "task_to" in tst:
389
+ input["task_to"] = tst["task_to"]
390
+ if "upload" in tst:
391
+ input["upload"] = tst["upload"]
392
+ if "workers" in tst:
393
+ input["workers"] = tst["workers"]
394
+ if "callback_api_key" in tst:
395
+ input["callback_api_key"] = tst["callback_api_key"]
396
+ if "callback_api_key_env_val" in tst:
397
+ os.environ.update({CALLBACK_KEY: tst["callback_api_key_env_val"]})
398
+ if "valid_api_keys" in tst:
399
+ input["valid_api_keys"] = tst["valid_api_keys"]
400
+ if "valid_api_keys_env_val" in tst:
401
+ os.environ.update({VALID_KEYS: tst["valid_api_keys_env_val"]})
402
+
403
+ settings = ContainerSettings(**input)
404
+
405
+ self.assertIsInstance(settings, ContainerSettings)
406
+
407
+ self.assertEqual(settings.broker, tst["expect"]["broker"])
408
+
409
+ self.assertEqual(settings.cache_to, tst["expect"]["cache_to"])
410
+
411
+ self.assertEqual(
412
+ settings.google_sheets_drive_id, tst["expect"]["google_sheets_drive_id"]
413
+ )
414
+
415
+ self.assertEqual(
416
+ settings.google_sheets_template_id,
417
+ tst["expect"]["google_sheets_template_id"],
418
+ )
419
+
420
+ self.assertEqual(settings.log_level, tst["expect"]["log_level"])
421
+
422
+ self.assertEqual(settings.metrics_broker, tst["expect"]["metrics_broker"])
423
+
424
+ self.assertEqual(settings.refresh_to, tst["expect"]["refresh_to"])
425
+
426
+ self.assertEqual(settings.service, tst["expect"]["service"])
427
+
428
+ self.assertEqual(settings.task_to, tst["expect"]["task_to"])
429
+
430
+ self.assertEqual(settings.workers, tst["expect"]["workers"])
431
+
432
+ if tst["expect"]["callback_api_key"] == Exception:
433
+ self.assertRaises(Exception, settings.get_callback_api_key)
434
+ else:
435
+ self.assertEqual(
436
+ settings.get_callback_api_key(), tst["expect"]["callback_api_key"]
437
+ )
438
+
439
+ if tst["expect"]["valid_api_keys"] == Exception:
440
+ self.assertRaises(Exception, settings.get_valid_api_keys)
441
+ else:
442
+ self.assertEqual(
443
+ settings.get_valid_api_keys(), tst["expect"]["valid_api_keys"]
444
+ )
445
+
446
+ self.assertEqual(settings.loglevel(), tst["expect"]["loglevel"])
447
+
448
+ self.assertEqual(settings.status_broker(), tst["expect"]["status_broker"])
449
+
450
+
451
+ class TestGroundXSettings(unittest.TestCase):
452
+ def test(self) -> None:
453
+ tsts: typing.List[typing.Dict[str, typing.Any]] = [
454
+ {
455
+ "api_key_env": "",
456
+ "expect": {
457
+ "api_key": Exception,
458
+ "api_key_env": "",
459
+ "base_url": None,
460
+ "upload_url": "https://upload.eyelevel.ai",
461
+ },
462
+ },
463
+ {
464
+ "api_key": "mykey",
465
+ "api_key_env_val": "val",
466
+ "base_url": "http://api.example.com",
467
+ "upload_url": "http://upload.example.com",
468
+ "expect": {
469
+ "api_key": "mykey",
470
+ "base_url": "http://api.example.com",
471
+ "upload_url": "http://upload.example.com",
472
+ },
473
+ },
474
+ {
475
+ "api_key_env_val": "val",
476
+ "expect": {
477
+ "api_key": "val",
478
+ "base_url": None,
479
+ "upload_url": "https://upload.eyelevel.ai",
480
+ },
481
+ },
482
+ ]
483
+
484
+ for _, tst in enumerate(tsts):
485
+ clearEnv()
486
+
487
+ input: typing.Dict[str, str] = {}
488
+ if "api_key" in tst:
489
+ input["api_key"] = tst["api_key"]
490
+ if "api_key_env_val" in tst:
491
+ os.environ.update({GX_API_KEY: tst["api_key_env_val"]})
492
+ if "base_url" in tst:
493
+ input["base_url"] = tst["base_url"]
494
+ if "upload_url" in tst:
495
+ input["upload_url"] = tst["upload_url"]
496
+
497
+ settings = GroundXSettings(**input)
498
+
499
+ self.assertIsInstance(settings, GroundXSettings)
500
+
501
+ if tst["expect"]["api_key"] == Exception:
502
+ self.assertRaises(Exception, settings.get_api_key)
503
+ else:
504
+ self.assertEqual(settings.get_api_key(), tst["expect"]["api_key"])
505
+
506
+ if tst["expect"]["base_url"]:
507
+ self.assertEqual(settings.base_url, tst["expect"]["base_url"])
508
+ else:
509
+ self.assertIsNone(settings.base_url)
510
+
511
+ self.assertEqual(settings.upload_url, tst["expect"]["upload_url"])
512
+
513
+
514
+ if __name__ == "__main__":
515
+ unittest.main()
@@ -0,0 +1,81 @@
1
+ import typing, unittest
2
+
3
+ from .utility 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()