corio 2.2.2a4__tar.gz → 2.2.4__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.
Files changed (173) hide show
  1. {corio-2.2.2a4 → corio-2.2.4}/PKG-INFO +6 -1
  2. {corio-2.2.2a4 → corio-2.2.4}/corio/entrypoint.py +13 -0
  3. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/incrementor_pyproject.py +5 -2
  4. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/releaser.py +21 -11
  5. {corio-2.2.2a4 → corio-2.2.4}/corio/pyproject.package.toml +9 -5
  6. corio-2.2.4/corio/tests/test_dns.py +89 -0
  7. corio-2.2.4/corio/tests/test_dt.py +10 -0
  8. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/test_env.py +10 -0
  9. corio-2.2.4/corio/tests/test_hash.py +18 -0
  10. corio-2.2.4/corio/tests/test_hook.py +28 -0
  11. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/test_infra.py +86 -1
  12. corio-2.2.4/corio/tests/test_iterator.py +48 -0
  13. corio-2.2.4/corio/tests/test_jsn.py +24 -0
  14. corio-2.2.4/corio/tests/test_name.py +20 -0
  15. corio-2.2.4/corio/tests/test_path.py +158 -0
  16. corio-2.2.4/corio/tests/test_patterns.py +71 -0
  17. corio-2.2.4/corio/tests/test_rand.py +46 -0
  18. corio-2.2.4/corio/tests/test_strings.py +54 -0
  19. corio-2.2.4/corio/tests/test_toml.py +37 -0
  20. corio-2.2.4/corio/tests/test_tools.py +14 -0
  21. corio-2.2.4/corio/tests/test_yaml.py +26 -0
  22. {corio-2.2.2a4 → corio-2.2.4}/corio.egg-info/PKG-INFO +6 -1
  23. {corio-2.2.2a4 → corio-2.2.4}/corio.egg-info/SOURCES.txt +11 -0
  24. {corio-2.2.2a4 → corio-2.2.4}/corio.egg-info/requires.txt +7 -0
  25. {corio-2.2.2a4 → corio-2.2.4}/pyproject.toml +9 -5
  26. corio-2.2.2a4/corio/tests/test_jsn.py +0 -13
  27. corio-2.2.2a4/corio/tests/test_path.py +0 -99
  28. corio-2.2.2a4/corio/tests/test_yaml.py +0 -13
  29. {corio-2.2.2a4 → corio-2.2.4}/LICENSE +0 -0
  30. {corio-2.2.2a4 → corio-2.2.4}/README.md +0 -0
  31. {corio-2.2.2a4 → corio-2.2.4}/corio/__init__.py +0 -0
  32. {corio-2.2.2a4 → corio-2.2.4}/corio/ai/__init__.py +0 -0
  33. {corio-2.2.2a4 → corio-2.2.4}/corio/ai/agentic.py +0 -0
  34. {corio-2.2.2a4 → corio-2.2.4}/corio/ai/infer.py +0 -0
  35. {corio-2.2.2a4 → corio-2.2.4}/corio/aio.py +0 -0
  36. {corio-2.2.2a4 → corio-2.2.4}/corio/api.py +0 -0
  37. {corio-2.2.2a4 → corio-2.2.4}/corio/augmentation.py +0 -0
  38. {corio-2.2.2a4 → corio-2.2.4}/corio/av.py +0 -0
  39. {corio-2.2.2a4 → corio-2.2.4}/corio/caching.py +0 -0
  40. {corio-2.2.2a4 → corio-2.2.4}/corio/constants.py +0 -0
  41. {corio-2.2.2a4 → corio-2.2.4}/corio/context.py +0 -0
  42. {corio-2.2.2a4 → corio-2.2.4}/corio/dataclass.py +0 -0
  43. {corio-2.2.2a4 → corio-2.2.4}/corio/datatype.py +0 -0
  44. {corio-2.2.2a4 → corio-2.2.4}/corio/db/__init__.py +0 -0
  45. {corio-2.2.2a4 → corio-2.2.4}/corio/db/document.py +0 -0
  46. {corio-2.2.2a4 → corio-2.2.4}/corio/debug.py +0 -0
  47. {corio-2.2.2a4 → corio-2.2.4}/corio/dm.py +0 -0
  48. {corio-2.2.2a4 → corio-2.2.4}/corio/dns/__init__.py +0 -0
  49. {corio-2.2.2a4 → corio-2.2.4}/corio/dns/client.py +0 -0
  50. {corio-2.2.2a4 → corio-2.2.4}/corio/dns/dm.py +0 -0
  51. {corio-2.2.2a4 → corio-2.2.4}/corio/dns/proxy.py +0 -0
  52. {corio-2.2.2a4 → corio-2.2.4}/corio/dns/server.py +0 -0
  53. {corio-2.2.2a4 → corio-2.2.4}/corio/docker/__init__.py +0 -0
  54. {corio-2.2.2a4 → corio-2.2.4}/corio/dt.py +0 -0
  55. {corio-2.2.2a4 → corio-2.2.4}/corio/encrypt.py +0 -0
  56. {corio-2.2.2a4 → corio-2.2.4}/corio/env.py +0 -0
  57. {corio-2.2.2a4 → corio-2.2.4}/corio/function.py +0 -0
  58. {corio-2.2.2a4 → corio-2.2.4}/corio/google_api.py +0 -0
  59. {corio-2.2.2a4 → corio-2.2.4}/corio/ha/__init__.py +0 -0
  60. {corio-2.2.2a4 → corio-2.2.4}/corio/ha/constants.py +0 -0
  61. {corio-2.2.2a4 → corio-2.2.4}/corio/ha/core.py +0 -0
  62. {corio-2.2.2a4 → corio-2.2.4}/corio/ha/supervisor.py +0 -0
  63. {corio-2.2.2a4 → corio-2.2.4}/corio/ha/utils.py +0 -0
  64. {corio-2.2.2a4 → corio-2.2.4}/corio/hash.py +0 -0
  65. {corio-2.2.2a4 → corio-2.2.4}/corio/hfh.py +0 -0
  66. {corio-2.2.2a4 → corio-2.2.4}/corio/hook.py +0 -0
  67. {corio-2.2.2a4 → corio-2.2.4}/corio/https.py +0 -0
  68. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/__init__.py +0 -0
  69. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/api.py +0 -0
  70. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/project.py +0 -0
  71. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/repository.py +0 -0
  72. {corio-2.2.2a4 → corio-2.2.4}/corio/infra/stack.py +0 -0
  73. {corio-2.2.2a4 → corio-2.2.4}/corio/inherit.py +0 -0
  74. {corio-2.2.2a4 → corio-2.2.4}/corio/inspection.py +0 -0
  75. {corio-2.2.2a4 → corio-2.2.4}/corio/interface/__init__.py +0 -0
  76. {corio-2.2.2a4 → corio-2.2.4}/corio/interface/context.py +0 -0
  77. {corio-2.2.2a4 → corio-2.2.4}/corio/interface/controls.py +0 -0
  78. {corio-2.2.2a4 → corio-2.2.4}/corio/interface/interface.py +0 -0
  79. {corio-2.2.2a4 → corio-2.2.4}/corio/iterator.py +0 -0
  80. {corio-2.2.2a4 → corio-2.2.4}/corio/jsn.py +0 -0
  81. {corio-2.2.2a4 → corio-2.2.4}/corio/json_fix.py +0 -0
  82. {corio-2.2.2a4 → corio-2.2.4}/corio/logs.py +0 -0
  83. {corio-2.2.2a4 → corio-2.2.4}/corio/markup.py +0 -0
  84. {corio-2.2.2a4 → corio-2.2.4}/corio/merging.py +0 -0
  85. {corio-2.2.2a4 → corio-2.2.4}/corio/metric.py +0 -0
  86. {corio-2.2.2a4 → corio-2.2.4}/corio/mqtt.py +0 -0
  87. {corio-2.2.2a4 → corio-2.2.4}/corio/name.py +0 -0
  88. {corio-2.2.2a4 → corio-2.2.4}/corio/net.py +0 -0
  89. {corio-2.2.2a4 → corio-2.2.4}/corio/netrc.py +0 -0
  90. {corio-2.2.2a4 → corio-2.2.4}/corio/openai.py +0 -0
  91. {corio-2.2.2a4 → corio-2.2.4}/corio/parallel.py +0 -0
  92. {corio-2.2.2a4 → corio-2.2.4}/corio/path/__init__.py +0 -0
  93. {corio-2.2.2a4 → corio-2.2.4}/corio/path/app.py +0 -0
  94. {corio-2.2.2a4 → corio-2.2.4}/corio/path/path.py +0 -0
  95. {corio-2.2.2a4 → corio-2.2.4}/corio/path/type.py +0 -0
  96. {corio-2.2.2a4 → corio-2.2.4}/corio/paths.py +0 -0
  97. {corio-2.2.2a4 → corio-2.2.4}/corio/patterns.py +0 -0
  98. {corio-2.2.2a4 → corio-2.2.4}/corio/pdf.py +0 -0
  99. {corio-2.2.2a4 → corio-2.2.4}/corio/plat.py +0 -0
  100. {corio-2.2.2a4 → corio-2.2.4}/corio/process.py +0 -0
  101. {corio-2.2.2a4 → corio-2.2.4}/corio/profiling.py +0 -0
  102. {corio-2.2.2a4 → corio-2.2.4}/corio/rand.py +0 -0
  103. {corio-2.2.2a4 → corio-2.2.4}/corio/sec.py +0 -0
  104. {corio-2.2.2a4 → corio-2.2.4}/corio/semantic.py +0 -0
  105. {corio-2.2.2a4 → corio-2.2.4}/corio/sets.py +0 -0
  106. {corio-2.2.2a4 → corio-2.2.4}/corio/setup/__init__.py +0 -0
  107. {corio-2.2.2a4 → corio-2.2.4}/corio/spaces.py +0 -0
  108. {corio-2.2.2a4 → corio-2.2.4}/corio/strings.py +0 -0
  109. {corio-2.2.2a4 → corio-2.2.4}/corio/tabular.py +0 -0
  110. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/__init__.py +0 -0
  111. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/conftest.py +0 -0
  112. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/helpers.py +0 -0
  113. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/test_caching.py +0 -0
  114. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/test_datatype.py +0 -0
  115. {corio-2.2.2a4 → corio-2.2.4}/corio/tests/test_encrypt.py +0 -0
  116. {corio-2.2.2a4 → corio-2.2.4}/corio/tokenization.py +0 -0
  117. {corio-2.2.2a4 → corio-2.2.4}/corio/toml.py +0 -0
  118. {corio-2.2.2a4 → corio-2.2.4}/corio/tools.py +0 -0
  119. {corio-2.2.2a4 → corio-2.2.4}/corio/unicode.py +0 -0
  120. {corio-2.2.2a4 → corio-2.2.4}/corio/version/__init__.py +0 -0
  121. {corio-2.2.2a4 → corio-2.2.4}/corio/version/version.py +0 -0
  122. {corio-2.2.2a4 → corio-2.2.4}/corio/webhook.py +0 -0
  123. {corio-2.2.2a4 → corio-2.2.4}/corio/yml.py +0 -0
  124. {corio-2.2.2a4 → corio-2.2.4}/corio/youtube.py +0 -0
  125. {corio-2.2.2a4 → corio-2.2.4}/corio.egg-info/dependency_links.txt +0 -0
  126. {corio-2.2.2a4 → corio-2.2.4}/corio.egg-info/entry_points.txt +0 -0
  127. {corio-2.2.2a4 → corio-2.2.4}/corio.egg-info/top_level.txt +0 -0
  128. {corio-2.2.2a4 → corio-2.2.4}/scripts/add-service +0 -0
  129. {corio-2.2.2a4 → corio-2.2.4}/scripts/add-user-path +0 -0
  130. {corio-2.2.2a4 → corio-2.2.4}/scripts/add-ve-shell +0 -0
  131. {corio-2.2.2a4 → corio-2.2.4}/scripts/apt-essentials +0 -0
  132. {corio-2.2.2a4 → corio-2.2.4}/scripts/apt-headless +0 -0
  133. {corio-2.2.2a4 → corio-2.2.4}/scripts/auth-token +0 -0
  134. {corio-2.2.2a4 → corio-2.2.4}/scripts/compose-update +0 -0
  135. {corio-2.2.2a4 → corio-2.2.4}/scripts/compress-dir +0 -0
  136. {corio-2.2.2a4 → corio-2.2.4}/scripts/cru +0 -0
  137. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-build-bases +0 -0
  138. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-build-bases-dev +0 -0
  139. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-install-deps +0 -0
  140. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-install-prod +0 -0
  141. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-prune +0 -0
  142. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-sandbox +0 -0
  143. {corio-2.2.2a4 → corio-2.2.4}/scripts/docker-sandbox-init +0 -0
  144. {corio-2.2.2a4 → corio-2.2.4}/scripts/docs-deploy +0 -0
  145. {corio-2.2.2a4 → corio-2.2.4}/scripts/download +0 -0
  146. {corio-2.2.2a4 → corio-2.2.4}/scripts/encrypt-secrets +0 -0
  147. {corio-2.2.2a4 → corio-2.2.4}/scripts/fmtr-test-script +0 -0
  148. {corio-2.2.2a4 → corio-2.2.4}/scripts/git-clone +0 -0
  149. {corio-2.2.2a4 → corio-2.2.4}/scripts/ha-addon-launch +0 -0
  150. {corio-2.2.2a4 → corio-2.2.4}/scripts/infra +0 -0
  151. {corio-2.2.2a4 → corio-2.2.4}/scripts/infra-sync +0 -0
  152. {corio-2.2.2a4 → corio-2.2.4}/scripts/install-browser +0 -0
  153. {corio-2.2.2a4 → corio-2.2.4}/scripts/install-docker +0 -0
  154. {corio-2.2.2a4 → corio-2.2.4}/scripts/install-ts +0 -0
  155. {corio-2.2.2a4 → corio-2.2.4}/scripts/install-ys +0 -0
  156. {corio-2.2.2a4 → corio-2.2.4}/scripts/mirror-dir +0 -0
  157. {corio-2.2.2a4 → corio-2.2.4}/scripts/opt-dev-init +0 -0
  158. {corio-2.2.2a4 → corio-2.2.4}/scripts/parse-args +0 -0
  159. {corio-2.2.2a4 → corio-2.2.4}/scripts/pypi-check +0 -0
  160. {corio-2.2.2a4 → corio-2.2.4}/scripts/pypi-reserve +0 -0
  161. {corio-2.2.2a4 → corio-2.2.4}/scripts/run-script +0 -0
  162. {corio-2.2.2a4 → corio-2.2.4}/scripts/set-password +0 -0
  163. {corio-2.2.2a4 → corio-2.2.4}/scripts/set-secure-path +0 -0
  164. {corio-2.2.2a4 → corio-2.2.4}/scripts/set-user-sudo +0 -0
  165. {corio-2.2.2a4 → corio-2.2.4}/scripts/snips-install +0 -0
  166. {corio-2.2.2a4 → corio-2.2.4}/scripts/ssh-auth +0 -0
  167. {corio-2.2.2a4 → corio-2.2.4}/scripts/ssh-serve +0 -0
  168. {corio-2.2.2a4 → corio-2.2.4}/scripts/tasmota-config +0 -0
  169. {corio-2.2.2a4 → corio-2.2.4}/scripts/tasmota-flash +0 -0
  170. {corio-2.2.2a4 → corio-2.2.4}/scripts/tasmota-terminal +0 -0
  171. {corio-2.2.2a4 → corio-2.2.4}/scripts/vlc-tn +0 -0
  172. {corio-2.2.2a4 → corio-2.2.4}/scripts/vm-launch +0 -0
  173. {corio-2.2.2a4 → corio-2.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: corio
3
- Version: 2.2.2a4
3
+ Version: 2.2.4
4
4
  Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
5
5
  Author-email: Frontmatter AI <innovative.fowler@mask.pro.fmtr.dev>
6
6
  License-Expression: Apache-2.0
@@ -188,6 +188,8 @@ Requires-Dist: httpx; extra == "dns"
188
188
  Requires-Dist: httpx_retries; extra == "dns"
189
189
  Requires-Dist: logfire; extra == "dns"
190
190
  Requires-Dist: logfire[httpx]; extra == "dns"
191
+ Requires-Dist: diskcache; extra == "dns"
192
+ Requires-Dist: cachetools; extra == "dns"
191
193
  Provides-Extra: patterns
192
194
  Requires-Dist: regex; extra == "patterns"
193
195
  Provides-Extra: http
@@ -214,6 +216,9 @@ Requires-Dist: av; extra == "av"
214
216
  Provides-Extra: env
215
217
  Provides-Extra: env-io
216
218
  Requires-Dist: dotenv; extra == "env-io"
219
+ Provides-Extra: toml
220
+ Provides-Extra: toml-write
221
+ Requires-Dist: tomlkit; extra == "toml-write"
217
222
  Provides-Extra: ha
218
223
  Requires-Dist: dotenv; extra == "ha"
219
224
  Provides-Extra: ha-api
@@ -3,6 +3,7 @@ from pydantic_settings import CliSubCommand
3
3
  from corio import dm
4
4
  from corio import sec
5
5
  from corio import sets
6
+ from corio.path import Path
6
7
 
7
8
 
8
9
  class DocServe(dm.Base):
@@ -42,6 +43,17 @@ class EpTest(dm.Base):
42
43
  print("Ran test entrypoint.")
43
44
 
44
45
 
46
+ class Test(dm.Base):
47
+ name: str = Path.cwd().name
48
+
49
+ def run(self):
50
+ from corio.infra.project import Project
51
+
52
+ project = Project(self.name)
53
+ is_passed = project.releaser.tester.run()
54
+ return int(not is_passed)
55
+
56
+
45
57
  class ShellDebug(dm.Base):
46
58
  def run(self):
47
59
  from corio import debug
@@ -81,6 +93,7 @@ class Cli(sets.Base, cli_parse_args=True):
81
93
  secrets: CliSubCommand[sec.Cli]
82
94
  docs: CliSubCommand[Docs]
83
95
  pyproject: CliSubCommand[Pyproject]
96
+ test: CliSubCommand[Test]
84
97
  ep_test: CliSubCommand[EpTest]
85
98
  shell_debug: CliSubCommand[ShellDebug]
86
99
  cache_hfh: CliSubCommand[CacheHfh]
@@ -72,8 +72,6 @@ class IncrementorPyproject(Incrementor):
72
72
  except InvalidRequirement:
73
73
  return dep
74
74
 
75
- if requirement.specifier:
76
- return dep
77
75
  if requirement.url:
78
76
  return dep
79
77
 
@@ -83,6 +81,11 @@ class IncrementorPyproject(Incrementor):
83
81
  if metadata is None:
84
82
  return dep
85
83
 
84
+ if requirement.specifier:
85
+ operators = {specifier.operator for specifier in requirement.specifier}
86
+ if not operators.issubset({"==", "==="}):
87
+ return dep
88
+
86
89
  extras = ""
87
90
  if requirement.extras:
88
91
  extras = f"[{','.join(sorted(requirement.extras))}]"
@@ -525,6 +525,7 @@ class ReleaseDocumentation(Release):
525
525
  {"pymdownx.tabbed": {"alternate_style": True}},
526
526
  {"pymdownx.emoji": {"emoji_index": twemoji, "emoji_generator": to_svg}},
527
527
  ],
528
+ exclude_docs="*.hidden.md\n**/*.hidden.md",
528
529
  extra={
529
530
  "version": {"provider": "mike"},
530
531
  },
@@ -583,13 +584,7 @@ class Tester(Inherit[Releaser]):
583
584
  @cached_property
584
585
  def dependencies(self) -> dict[str, list[str]]:
585
586
  data = self.paths.pyproject_repo.read_toml()
586
- table = data.get("tool", {}).get("corio", {}).get("dependencies", {})
587
- dependencies = {
588
- str(key): [str(value) for value in values]
589
- for key, values in table.items()
590
- if isinstance(values, list)
591
- }
592
- return dependencies
587
+ return data["project"].get("optional-dependencies",{})
593
588
 
594
589
  @cached_property
595
590
  def modules(self) -> list[str]:
@@ -607,9 +602,11 @@ class Tester(Inherit[Releaser]):
607
602
  def get_env(self, name: str, path_tests: Path, extras: list[str]) -> dict:
608
603
  if path_tests.is_relative_to(self.paths.repo):
609
604
  path_tests = path_tests.relative_to(self.paths.repo)
605
+ deps = self.get_deps_extras(extras)
610
606
  env = {
611
607
  "description": f"Run {name} tests.",
612
608
  "extras": extras,
609
+ "deps": deps,
613
610
  "commands": [["python", "-m", "pytest", "-q", str(path_tests)]],
614
611
  }
615
612
  return env
@@ -618,6 +615,22 @@ class Tester(Inherit[Releaser]):
618
615
  def env(self) -> dict:
619
616
  return self.get_env(name=self.paths.name_ns, path_tests=self.paths.tests, extras=["test"])
620
617
 
618
+ def get_extras_module(self, module: str) -> list[str]:
619
+ extras = ["test"]
620
+ extras_available = set(self.dependencies.keys())
621
+
622
+ if module in extras_available:
623
+ extras.insert(0, module)
624
+
625
+ extras_children = sorted(extra for extra in extras_available if extra.startswith(f"{module}."))
626
+ return extras_children + extras
627
+
628
+ def get_deps_extras(self, extras: list[str]) -> list[str]:
629
+ resolved = []
630
+ for extra in extras:
631
+ resolved.extend(self.dependencies.get(extra, []))
632
+ return list(dict.fromkeys(resolved))
633
+
621
634
  @cached_property
622
635
  def envs(self) -> dict[str, dict]:
623
636
  if not self.paths.metadata.test_envs:
@@ -626,12 +639,9 @@ class Tester(Inherit[Releaser]):
626
639
  return {self.paths.name_ns: self.env}
627
640
 
628
641
  envs = {}
629
- extras_available = set(self.dependencies.keys())
630
642
 
631
643
  for module in self.modules:
632
- extras = ["test"]
633
- if module in extras_available:
634
- extras.insert(0, module)
644
+ extras = self.get_extras_module(module)
635
645
 
636
646
  path_test = self.paths.tests / f"{self.TEST_FILENAME_PREFIX}{module}{self.TEST_FILENAME_SUFFIX}"
637
647
  name = f"{self.paths.name_ns}.{module}"
@@ -40,7 +40,7 @@ debug = ["pydevd-pycharm~=251.25410.159"]
40
40
  sets = ["pydantic-settings", "dm", "yaml"]
41
41
  "path.app" = ["appdirs"]
42
42
  "path.type" = ["filetype"]
43
- dns = ["dnspython[doh]", "http"]
43
+ dns = ["dnspython[doh]", "http", "caching"]
44
44
  patterns = ["regex"]
45
45
  http = ["httpx", "httpx_retries", "logging", "logfire[httpx]"]
46
46
  setup = ["setuptools"]
@@ -52,11 +52,13 @@ mqtt = ["aiomqtt"]
52
52
  av = ["av"]
53
53
  env = []
54
54
  "env.io" = ["dotenv"]
55
+ toml = []
56
+ "toml.write" = ["tomlkit"]
55
57
  ha = ["env.io"]
56
58
  "ha.api" = ["ha", "homeassistant_api", "aiohasupervisor"]
57
59
  doc = ["mkdocs", "mkdocs-material", "mkdocstrings[python]", "mike", "mkdocs-include-dir-to-nav"]
58
60
  youtube = ["pytubefix"]
59
- infra = ["version.dev", "logging", "setup", "doc", "sets", "build", "twine", "packaging", "vcs", "docker.client", "merging", "http", "api", "tomlkit", "secrets", "cli", "test"]
61
+ infra = ["version.dev", "logging", "setup", "doc", "sets", "build", "twine", "packaging", "vcs", "docker.client", "merging", "http", "api", "toml.write", "secrets", "cli", "test"]
60
62
  vcs = ["pygit2"]
61
63
  tasmota = ["decode-config", "esptool"]
62
64
  encrypt = ["pyrage"]
@@ -64,7 +66,7 @@ secrets = ["encrypt", "env.io", "yaml", "logging", "sets", "vcs"]
64
66
  cli = ["sets", "logging"]
65
67
 
66
68
  [tool.corio.metadata]
67
- version = "2.2.2-alpha004"
69
+ version = "2.2.4"
68
70
  port = 0
69
71
  base = "python"
70
72
  description = "Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML"
@@ -94,7 +96,7 @@ corio = ["pyproject.package.toml"]
94
96
 
95
97
  [project]
96
98
  name = "corio"
97
- version = "2.2.2-alpha004"
99
+ version = "2.2.4"
98
100
  description = "Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML"
99
101
  readme = "README.md"
100
102
  dependencies = []
@@ -139,7 +141,7 @@ debug = ["pydevd-pycharm~=251.25410.159"]
139
141
  sets = ["pydantic-settings", "pydantic", "pydantic-extra-types", "pycountry", "yamlscript", "pyyaml"]
140
142
  "path.app" = ["appdirs"]
141
143
  "path.type" = ["filetype"]
142
- dns = ["dnspython[doh]", "httpx", "httpx_retries", "logfire", "logfire[httpx]"]
144
+ dns = ["dnspython[doh]", "httpx", "httpx_retries", "logfire", "logfire[httpx]", "diskcache", "cachetools"]
143
145
  patterns = ["regex"]
144
146
  http = ["httpx", "httpx_retries", "logfire", "logfire[httpx]"]
145
147
  setup = ["setuptools"]
@@ -151,6 +153,8 @@ mqtt = ["aiomqtt"]
151
153
  av = ["av"]
152
154
  env = []
153
155
  "env.io" = ["dotenv"]
156
+ toml = []
157
+ "toml.write" = ["tomlkit"]
154
158
  ha = ["dotenv"]
155
159
  "ha.api" = ["dotenv", "homeassistant_api", "aiohasupervisor"]
156
160
  doc = ["mkdocs", "mkdocs-material", "mkdocstrings[python]", "mike", "mkdocs-include-dir-to-nav"]
@@ -0,0 +1,89 @@
1
+ import dns
2
+ from dns import rcode as dnspython_rcode
3
+
4
+ from corio.dns import client as dns_client
5
+ from corio.dns import dm as dns_dm
6
+
7
+
8
+ def _make_exchange(name: str = "example.com.", rdtype=dns.rdatatype.A) -> dns_dm.Exchange:
9
+ query = dns.message.make_query(name, rdtype)
10
+ return dns_dm.Exchange.from_wire(query.to_wire(), ip="127.0.0.1", port=5353)
11
+
12
+
13
+ def test_response_ttl_from_answers_authority_and_rcode_defaults():
14
+ exchange = _make_exchange()
15
+ message = exchange.request.get_response_template()
16
+ message.answer.append(dns.rrset.from_text("example.com.", 300, "IN", "A", "1.1.1.1"))
17
+ message.answer.append(dns.rrset.from_text("example.com.", 120, "IN", "A", "1.1.1.2"))
18
+ response = dns_dm.Response.from_message(message)
19
+ assert response.ttl == 120
20
+
21
+ message = exchange.request.get_response_template()
22
+ message.authority.append(dns.rrset.from_text("example.com.", 42, "IN", "NS", "ns1.example.com."))
23
+ response = dns_dm.Response.from_message(message)
24
+ assert response.ttl == 42
25
+
26
+ message = exchange.request.get_response_template()
27
+ message.set_rcode(dnspython_rcode.SERVFAIL)
28
+ response = dns_dm.Response.from_message(message)
29
+ assert response.ttl == 10
30
+
31
+ response = dns_dm.Response.from_message(message, ttl_defaults={"SERVFAIL": 77})
32
+ assert response.ttl == 77
33
+
34
+
35
+ def test_exchange_question_last_and_query_last_use_latest_answer_name():
36
+ exchange = _make_exchange(name="example.com.")
37
+ message = exchange.request.get_response_template()
38
+ message.answer.append(dns.rrset.from_text("edge.example.com.", 60, "IN", "A", "9.9.9.9"))
39
+ exchange.response = dns_dm.Response.from_message(message)
40
+
41
+ question_last = exchange.question_last
42
+ assert question_last.name.to_text() == "9.9.9.9"
43
+ assert question_last.rdtype == exchange.request.type
44
+
45
+ query_last = exchange.query_last
46
+ assert query_last.question[0].name.to_text() == "9.9.9.9"
47
+ assert query_last.id == exchange.request.message.id
48
+
49
+
50
+ def test_exchange_reverse_builds_internal_ptr_query():
51
+ exchange = _make_exchange()
52
+ reverse = exchange.reverse
53
+
54
+ assert reverse.is_internal is True
55
+ assert reverse.ip == exchange.ip
56
+ assert reverse.port == exchange.port
57
+ assert reverse.request.type_text == "PTR"
58
+ assert reverse.request.name_text.endswith(".in-addr.arpa.")
59
+
60
+
61
+ def test_plain_client_resolve_applies_ttl_min(monkeypatch):
62
+ exchange = _make_exchange()
63
+ upstream = exchange.request.get_response_template()
64
+ upstream.answer.append(dns.rrset.from_text("example.com.", 3, "IN", "A", "1.1.1.1"))
65
+
66
+ monkeypatch.setattr(dns_client.dnspython_query, "udp", lambda q, where, port: upstream)
67
+
68
+ client_plain = dns_client.Plain(host="8.8.8.8", ttl_min=30)
69
+ client_plain.resolve(exchange)
70
+
71
+ assert exchange.response.answer is not None
72
+ assert exchange.response.answer.ttl == 30
73
+
74
+
75
+ def test_http_client_resolve_sets_servfail_on_exception(monkeypatch):
76
+ exchange = _make_exchange()
77
+ client_http = dns_client.HTTP(host="dns.google", url="https://{host}/dns-query")
78
+ client_http.__dict__["ip"] = "1.1.1.1"
79
+
80
+ def _raise(*_args, **_kwargs):
81
+ raise RuntimeError("boom")
82
+
83
+ monkeypatch.setattr(dns_client, "logger", type("L", (), {"exception": staticmethod(lambda *_args, **_kwargs: None)})())
84
+ monkeypatch.setattr(client_http.CLIENT, "post", _raise)
85
+
86
+ client_http.resolve(exchange)
87
+
88
+ assert exchange.response.rcode == dnspython_rcode.SERVFAIL
89
+ assert exchange.response.is_complete is True
@@ -0,0 +1,10 @@
1
+ from datetime import timezone
2
+
3
+ from corio import dt
4
+
5
+
6
+ def test_now_is_utc_and_in_bounds():
7
+ value = dt.now()
8
+
9
+ assert value.tzinfo == timezone.utc
10
+ assert dt.MIN <= value <= dt.MAX
@@ -73,3 +73,13 @@ def test_get_env_dict():
73
73
  with helpers.patch_environment(clear=True, **ENVIRONMENT_DATA):
74
74
  actual = env.get_dict()
75
75
  assert actual == expected
76
+
77
+
78
+ def test_env_file_round_trip_via_path_data(tmp_path):
79
+ path_env = Path(tmp_path / ".env")
80
+ expected = {"A": "1", "B": "two"}
81
+
82
+ path_env.write_data(expected)
83
+ actual = path_env.read_data()
84
+
85
+ assert actual == expected
@@ -0,0 +1,18 @@
1
+ from corio import hash as hash_module
2
+
3
+
4
+ def test_hash_unit_is_stable_and_bounded():
5
+ first = hash_module.hash_unit("corio")
6
+ second = hash_module.hash_unit("corio")
7
+
8
+ assert first == second
9
+ assert 0.0 <= first < 1.0
10
+
11
+
12
+ def test_get_hash_readable_length_and_replacements():
13
+ value = hash_module.get_hash_readable("corio", length=16)
14
+
15
+ assert len(value) == 16
16
+ assert "O" not in value
17
+ assert "I" not in value
18
+ assert "=" not in value
@@ -0,0 +1,28 @@
1
+ import pytest
2
+
3
+ from corio.hook import ImportHook, MissingExtraError, MissingExtraMockModule
4
+
5
+
6
+ def test_missing_extra_mock_module_raises_with_context():
7
+ mock_module = MissingExtraMockModule("path.app", ModuleNotFoundError("no appdirs"))
8
+
9
+ with pytest.raises(MissingExtraError):
10
+ mock_module()
11
+
12
+ with pytest.raises(MissingExtraError):
13
+ _ = mock_module.any_attr
14
+
15
+
16
+ def test_import_hook_translates_module_not_found_for_corio_callers():
17
+ hook = ImportHook(auto_register=False)
18
+
19
+ def fake_import(*_args, **_kwargs):
20
+ raise ModuleNotFoundError("missing dep")
21
+
22
+ hook._previous_import = fake_import
23
+
24
+ with pytest.raises(MissingExtraError):
25
+ hook("missing", globals={"__name__": "corio.path.app"})
26
+
27
+ with pytest.raises(ModuleNotFoundError):
28
+ hook("missing", globals={"__name__": "external.module"})
@@ -3,6 +3,7 @@ from types import SimpleNamespace
3
3
  from packaging.requirements import Requirement
4
4
 
5
5
  from corio.infra.incrementor_pyproject import IncrementorPyproject
6
+ from corio.infra.releaser import Tester as ReleaserTester
6
7
  from corio.path import Path
7
8
 
8
9
 
@@ -95,7 +96,7 @@ def test_pin_editables_preserves_extras_and_skips_existing_specifiers(tmp_path):
95
96
  assert req.extras == {"version.dev", "logging", "sets", "yaml", "debug", "caching", "api", "mqtt"}
96
97
  assert str(req.specifier) == "==1.2.3"
97
98
 
98
- assert incrementor._pin_editable("haco==1.0.0") == "haco==1.0.0"
99
+ assert incrementor._pin_editable("haco==1.0.0") == "haco==1.2.3"
99
100
  assert incrementor._pin_editable("haco>=1.0.0") == "haco>=1.0.0"
100
101
 
101
102
 
@@ -161,3 +162,87 @@ def test_process_deps_pins_project_dependencies(tmp_path):
161
162
  assert incrementor._process_deps(dependencies) == ["haco==1.2.3", "requests>=2"]
162
163
  assert incrementor._process_deps(optional_dev) == ["haco[logging]==1.2.3", "pytest"]
163
164
 
165
+
166
+ def _make_tester(path_repo: Path, *, test_envs: bool) -> ReleaserTester:
167
+ path_tests = path_repo / "corio" / "tests"
168
+ path_tests.mkdir(parents=True)
169
+ path_pyproject = path_repo / "pyproject.toml"
170
+ path_pyproject.write_text(
171
+ "\n".join(
172
+ [
173
+ "[project]",
174
+ 'name = "corio"',
175
+ 'version = "0.0.0"',
176
+ "",
177
+ "[project.optional-dependencies]",
178
+ 'test = ["pytest", "pytest-cov"]',
179
+ 'path = []',
180
+ '"path.app" = ["appdirs"]',
181
+ '"path.type" = ["filetype"]',
182
+ "strings = []",
183
+ "",
184
+ ]
185
+ ),
186
+ encoding="utf-8",
187
+ )
188
+ parent = SimpleNamespace(
189
+ name="corio",
190
+ paths=SimpleNamespace(
191
+ repo=path_repo,
192
+ tests=path_tests,
193
+ pyproject_repo=path_pyproject,
194
+ name_ns="corio",
195
+ metadata=SimpleNamespace(test_envs=test_envs),
196
+ ),
197
+ )
198
+ return ReleaserTester(parent)
199
+
200
+
201
+ def test_tester_get_extras_module_merges_module_and_dotted_children(tmp_path):
202
+ path_repo = Path(tmp_path / "corio")
203
+ path_repo.mkdir(parents=True)
204
+ tester = _make_tester(path_repo, test_envs=True)
205
+
206
+ assert tester.get_extras_module("path") == ["path.app", "path.type", "path", "test"]
207
+ assert tester.get_extras_module("strings") == ["strings", "test"]
208
+ assert tester.get_extras_module("missing") == ["test"]
209
+
210
+
211
+ def test_tester_get_deps_extras_resolves_recursive_dependencies(tmp_path):
212
+ path_repo = Path(tmp_path / "corio")
213
+ path_repo.mkdir(parents=True)
214
+ tester = _make_tester(path_repo, test_envs=True)
215
+
216
+ deps = tester.get_deps_extras(["path.app", "path.type", "test"])
217
+ assert "pytest" in deps
218
+ assert "pytest-cov" in deps
219
+
220
+ deps_missing = tester.get_deps_extras(["missing"])
221
+ assert deps_missing == []
222
+
223
+
224
+ def test_tester_envs_use_file_module_name_with_test_envs(tmp_path):
225
+ path_repo = Path(tmp_path / "corio")
226
+ path_repo.mkdir(parents=True)
227
+ tester = _make_tester(path_repo, test_envs=True)
228
+ (tester.paths.tests / "test_path.py").write_text("", encoding="utf-8")
229
+ (tester.paths.tests / "test_strings.py").write_text("", encoding="utf-8")
230
+
231
+ envs = tester.envs
232
+
233
+ assert set(envs) == {"corio.path", "corio.strings"}
234
+ assert envs["corio.path"]["extras"] == ["path.app", "path.type", "path", "test"]
235
+ assert envs["corio.strings"]["extras"] == ["strings", "test"]
236
+ assert "pytest" in envs["corio.path"]["deps"]
237
+
238
+
239
+ def test_tester_envs_fall_back_to_single_env_when_test_envs_disabled(tmp_path):
240
+ path_repo = Path(tmp_path / "corio")
241
+ path_repo.mkdir(parents=True)
242
+ tester = _make_tester(path_repo, test_envs=False)
243
+ (tester.paths.tests / "test_path.py").write_text("", encoding="utf-8")
244
+
245
+ envs = tester.envs
246
+
247
+ assert set(envs) == {"corio"}
248
+ assert envs["corio"]["extras"] == ["test"]
@@ -0,0 +1,48 @@
1
+ from dataclasses import dataclass
2
+
3
+ from corio import iterator
4
+
5
+
6
+ def test_enlist_and_dedupe():
7
+ assert iterator.enlist("x") == ["x"]
8
+ assert iterator.enlist(["x"]) == ["x"]
9
+ assert iterator.dedupe(["a", "b", "a", "c"]) == ["a", "b", "c"]
10
+
11
+
12
+ def test_dict_records_to_lists_and_chunking():
13
+ data = [{"a": 1}, {"b": 2}]
14
+ as_lists = iterator.dict_records_to_lists(data, missing=None)
15
+ assert as_lists == {"a": [1, None], "b": [None, 2]}
16
+
17
+ assert iterator.chunk_data([1, 2, 3, 4, 5], size=2) == [[1, 2], [3, 4], [5]]
18
+ assert iterator.get_batch_sizes(total=10, num_batches=3) == [4, 3, 3]
19
+ assert list(iterator.rebatch([[1, 2], [3], [4, 5]], size=2)) == [(1, 2), (3, 4), (5,)]
20
+
21
+
22
+ def test_strip_none_flatten_tree_and_iterdiffer():
23
+ assert iterator.strip_none(1, None, 2) == [1, 2]
24
+
25
+ tree = {"a": {"b": 1}, "list": [2, 3]}
26
+ flat = iterator.flatten_tree(tree, sep=".")
27
+ assert flat["a.b"] == 1
28
+ assert flat["list.[0]"] == 2
29
+ assert flat["list.[1]"] == 3
30
+
31
+ diff = iterator.IterDiffer(before=[1, 2], after=[2, 3])
32
+ assert diff.added == {3}
33
+ assert diff.removed == {1}
34
+ assert diff.is_changed is True
35
+
36
+
37
+ @dataclass
38
+ class _Obj:
39
+ key: str
40
+ value: int
41
+
42
+
43
+ def test_index_list_lookup_by_attr_and_key():
44
+ objects = iterator.IndexList([_Obj(key="a", value=1), _Obj(key="b", value=2)])
45
+ assert objects.key["a"].value == 1
46
+
47
+ dicts = iterator.IndexList([{"id": "x", "value": 1}, {"id": "y", "value": 2}])
48
+ assert dicts.id["y"]["value"] == 2
@@ -0,0 +1,24 @@
1
+ from corio import jsn
2
+ from corio.path import Path
3
+ from corio.tests.helpers import SERIALIZATION_DATA
4
+
5
+
6
+ def test_json():
7
+ """
8
+
9
+ Simple YAML round trip test
10
+
11
+ """
12
+ expected = SERIALIZATION_DATA
13
+ actual = jsn.from_json(jsn.to_json(expected))
14
+ assert actual == expected
15
+
16
+
17
+ def test_json_path_round_trip(tmp_path):
18
+ expected = SERIALIZATION_DATA
19
+ path_json = Path(tmp_path / "serialization_test.json")
20
+
21
+ path_json.write_json(expected)
22
+ actual = path_json.read_json()
23
+
24
+ assert actual == expected
@@ -0,0 +1,20 @@
1
+ import random
2
+
3
+ from corio import name
4
+
5
+
6
+ def test_name_lists_are_non_empty():
7
+ assert len(name.get_left()) > 0
8
+ assert len(name.get_right()) > 0
9
+
10
+
11
+ def test_get_name_tuple_or_string():
12
+ random.seed(0)
13
+ left_right = name.get(sep=None)
14
+ assert isinstance(left_right, tuple)
15
+ assert len(left_right) == 2
16
+
17
+ random.seed(0)
18
+ text = name.get(sep="-")
19
+ assert isinstance(text, str)
20
+ assert "-" in text