corio 2.2.3__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.3 → corio-2.2.4}/PKG-INFO +6 -1
  2. {corio-2.2.3 → corio-2.2.4}/corio/entrypoint.py +13 -0
  3. {corio-2.2.3 → corio-2.2.4}/corio/infra/releaser.py +20 -11
  4. {corio-2.2.3 → corio-2.2.4}/corio/pyproject.package.toml +9 -5
  5. corio-2.2.4/corio/tests/test_dns.py +89 -0
  6. corio-2.2.4/corio/tests/test_dt.py +10 -0
  7. {corio-2.2.3 → corio-2.2.4}/corio/tests/test_env.py +10 -0
  8. corio-2.2.4/corio/tests/test_hash.py +18 -0
  9. corio-2.2.4/corio/tests/test_hook.py +28 -0
  10. {corio-2.2.3 → corio-2.2.4}/corio/tests/test_infra.py +86 -0
  11. corio-2.2.4/corio/tests/test_iterator.py +48 -0
  12. corio-2.2.4/corio/tests/test_jsn.py +24 -0
  13. corio-2.2.4/corio/tests/test_name.py +20 -0
  14. corio-2.2.4/corio/tests/test_path.py +158 -0
  15. corio-2.2.4/corio/tests/test_patterns.py +71 -0
  16. corio-2.2.4/corio/tests/test_rand.py +46 -0
  17. corio-2.2.4/corio/tests/test_strings.py +54 -0
  18. corio-2.2.4/corio/tests/test_toml.py +37 -0
  19. corio-2.2.4/corio/tests/test_tools.py +14 -0
  20. corio-2.2.4/corio/tests/test_yaml.py +26 -0
  21. {corio-2.2.3 → corio-2.2.4}/corio.egg-info/PKG-INFO +6 -1
  22. {corio-2.2.3 → corio-2.2.4}/corio.egg-info/SOURCES.txt +11 -0
  23. {corio-2.2.3 → corio-2.2.4}/corio.egg-info/requires.txt +7 -0
  24. {corio-2.2.3 → corio-2.2.4}/pyproject.toml +9 -5
  25. corio-2.2.3/corio/tests/test_jsn.py +0 -13
  26. corio-2.2.3/corio/tests/test_path.py +0 -99
  27. corio-2.2.3/corio/tests/test_yaml.py +0 -13
  28. {corio-2.2.3 → corio-2.2.4}/LICENSE +0 -0
  29. {corio-2.2.3 → corio-2.2.4}/README.md +0 -0
  30. {corio-2.2.3 → corio-2.2.4}/corio/__init__.py +0 -0
  31. {corio-2.2.3 → corio-2.2.4}/corio/ai/__init__.py +0 -0
  32. {corio-2.2.3 → corio-2.2.4}/corio/ai/agentic.py +0 -0
  33. {corio-2.2.3 → corio-2.2.4}/corio/ai/infer.py +0 -0
  34. {corio-2.2.3 → corio-2.2.4}/corio/aio.py +0 -0
  35. {corio-2.2.3 → corio-2.2.4}/corio/api.py +0 -0
  36. {corio-2.2.3 → corio-2.2.4}/corio/augmentation.py +0 -0
  37. {corio-2.2.3 → corio-2.2.4}/corio/av.py +0 -0
  38. {corio-2.2.3 → corio-2.2.4}/corio/caching.py +0 -0
  39. {corio-2.2.3 → corio-2.2.4}/corio/constants.py +0 -0
  40. {corio-2.2.3 → corio-2.2.4}/corio/context.py +0 -0
  41. {corio-2.2.3 → corio-2.2.4}/corio/dataclass.py +0 -0
  42. {corio-2.2.3 → corio-2.2.4}/corio/datatype.py +0 -0
  43. {corio-2.2.3 → corio-2.2.4}/corio/db/__init__.py +0 -0
  44. {corio-2.2.3 → corio-2.2.4}/corio/db/document.py +0 -0
  45. {corio-2.2.3 → corio-2.2.4}/corio/debug.py +0 -0
  46. {corio-2.2.3 → corio-2.2.4}/corio/dm.py +0 -0
  47. {corio-2.2.3 → corio-2.2.4}/corio/dns/__init__.py +0 -0
  48. {corio-2.2.3 → corio-2.2.4}/corio/dns/client.py +0 -0
  49. {corio-2.2.3 → corio-2.2.4}/corio/dns/dm.py +0 -0
  50. {corio-2.2.3 → corio-2.2.4}/corio/dns/proxy.py +0 -0
  51. {corio-2.2.3 → corio-2.2.4}/corio/dns/server.py +0 -0
  52. {corio-2.2.3 → corio-2.2.4}/corio/docker/__init__.py +0 -0
  53. {corio-2.2.3 → corio-2.2.4}/corio/dt.py +0 -0
  54. {corio-2.2.3 → corio-2.2.4}/corio/encrypt.py +0 -0
  55. {corio-2.2.3 → corio-2.2.4}/corio/env.py +0 -0
  56. {corio-2.2.3 → corio-2.2.4}/corio/function.py +0 -0
  57. {corio-2.2.3 → corio-2.2.4}/corio/google_api.py +0 -0
  58. {corio-2.2.3 → corio-2.2.4}/corio/ha/__init__.py +0 -0
  59. {corio-2.2.3 → corio-2.2.4}/corio/ha/constants.py +0 -0
  60. {corio-2.2.3 → corio-2.2.4}/corio/ha/core.py +0 -0
  61. {corio-2.2.3 → corio-2.2.4}/corio/ha/supervisor.py +0 -0
  62. {corio-2.2.3 → corio-2.2.4}/corio/ha/utils.py +0 -0
  63. {corio-2.2.3 → corio-2.2.4}/corio/hash.py +0 -0
  64. {corio-2.2.3 → corio-2.2.4}/corio/hfh.py +0 -0
  65. {corio-2.2.3 → corio-2.2.4}/corio/hook.py +0 -0
  66. {corio-2.2.3 → corio-2.2.4}/corio/https.py +0 -0
  67. {corio-2.2.3 → corio-2.2.4}/corio/infra/__init__.py +0 -0
  68. {corio-2.2.3 → corio-2.2.4}/corio/infra/api.py +0 -0
  69. {corio-2.2.3 → corio-2.2.4}/corio/infra/incrementor_pyproject.py +0 -0
  70. {corio-2.2.3 → corio-2.2.4}/corio/infra/project.py +0 -0
  71. {corio-2.2.3 → corio-2.2.4}/corio/infra/repository.py +0 -0
  72. {corio-2.2.3 → corio-2.2.4}/corio/infra/stack.py +0 -0
  73. {corio-2.2.3 → corio-2.2.4}/corio/inherit.py +0 -0
  74. {corio-2.2.3 → corio-2.2.4}/corio/inspection.py +0 -0
  75. {corio-2.2.3 → corio-2.2.4}/corio/interface/__init__.py +0 -0
  76. {corio-2.2.3 → corio-2.2.4}/corio/interface/context.py +0 -0
  77. {corio-2.2.3 → corio-2.2.4}/corio/interface/controls.py +0 -0
  78. {corio-2.2.3 → corio-2.2.4}/corio/interface/interface.py +0 -0
  79. {corio-2.2.3 → corio-2.2.4}/corio/iterator.py +0 -0
  80. {corio-2.2.3 → corio-2.2.4}/corio/jsn.py +0 -0
  81. {corio-2.2.3 → corio-2.2.4}/corio/json_fix.py +0 -0
  82. {corio-2.2.3 → corio-2.2.4}/corio/logs.py +0 -0
  83. {corio-2.2.3 → corio-2.2.4}/corio/markup.py +0 -0
  84. {corio-2.2.3 → corio-2.2.4}/corio/merging.py +0 -0
  85. {corio-2.2.3 → corio-2.2.4}/corio/metric.py +0 -0
  86. {corio-2.2.3 → corio-2.2.4}/corio/mqtt.py +0 -0
  87. {corio-2.2.3 → corio-2.2.4}/corio/name.py +0 -0
  88. {corio-2.2.3 → corio-2.2.4}/corio/net.py +0 -0
  89. {corio-2.2.3 → corio-2.2.4}/corio/netrc.py +0 -0
  90. {corio-2.2.3 → corio-2.2.4}/corio/openai.py +0 -0
  91. {corio-2.2.3 → corio-2.2.4}/corio/parallel.py +0 -0
  92. {corio-2.2.3 → corio-2.2.4}/corio/path/__init__.py +0 -0
  93. {corio-2.2.3 → corio-2.2.4}/corio/path/app.py +0 -0
  94. {corio-2.2.3 → corio-2.2.4}/corio/path/path.py +0 -0
  95. {corio-2.2.3 → corio-2.2.4}/corio/path/type.py +0 -0
  96. {corio-2.2.3 → corio-2.2.4}/corio/paths.py +0 -0
  97. {corio-2.2.3 → corio-2.2.4}/corio/patterns.py +0 -0
  98. {corio-2.2.3 → corio-2.2.4}/corio/pdf.py +0 -0
  99. {corio-2.2.3 → corio-2.2.4}/corio/plat.py +0 -0
  100. {corio-2.2.3 → corio-2.2.4}/corio/process.py +0 -0
  101. {corio-2.2.3 → corio-2.2.4}/corio/profiling.py +0 -0
  102. {corio-2.2.3 → corio-2.2.4}/corio/rand.py +0 -0
  103. {corio-2.2.3 → corio-2.2.4}/corio/sec.py +0 -0
  104. {corio-2.2.3 → corio-2.2.4}/corio/semantic.py +0 -0
  105. {corio-2.2.3 → corio-2.2.4}/corio/sets.py +0 -0
  106. {corio-2.2.3 → corio-2.2.4}/corio/setup/__init__.py +0 -0
  107. {corio-2.2.3 → corio-2.2.4}/corio/spaces.py +0 -0
  108. {corio-2.2.3 → corio-2.2.4}/corio/strings.py +0 -0
  109. {corio-2.2.3 → corio-2.2.4}/corio/tabular.py +0 -0
  110. {corio-2.2.3 → corio-2.2.4}/corio/tests/__init__.py +0 -0
  111. {corio-2.2.3 → corio-2.2.4}/corio/tests/conftest.py +0 -0
  112. {corio-2.2.3 → corio-2.2.4}/corio/tests/helpers.py +0 -0
  113. {corio-2.2.3 → corio-2.2.4}/corio/tests/test_caching.py +0 -0
  114. {corio-2.2.3 → corio-2.2.4}/corio/tests/test_datatype.py +0 -0
  115. {corio-2.2.3 → corio-2.2.4}/corio/tests/test_encrypt.py +0 -0
  116. {corio-2.2.3 → corio-2.2.4}/corio/tokenization.py +0 -0
  117. {corio-2.2.3 → corio-2.2.4}/corio/toml.py +0 -0
  118. {corio-2.2.3 → corio-2.2.4}/corio/tools.py +0 -0
  119. {corio-2.2.3 → corio-2.2.4}/corio/unicode.py +0 -0
  120. {corio-2.2.3 → corio-2.2.4}/corio/version/__init__.py +0 -0
  121. {corio-2.2.3 → corio-2.2.4}/corio/version/version.py +0 -0
  122. {corio-2.2.3 → corio-2.2.4}/corio/webhook.py +0 -0
  123. {corio-2.2.3 → corio-2.2.4}/corio/yml.py +0 -0
  124. {corio-2.2.3 → corio-2.2.4}/corio/youtube.py +0 -0
  125. {corio-2.2.3 → corio-2.2.4}/corio.egg-info/dependency_links.txt +0 -0
  126. {corio-2.2.3 → corio-2.2.4}/corio.egg-info/entry_points.txt +0 -0
  127. {corio-2.2.3 → corio-2.2.4}/corio.egg-info/top_level.txt +0 -0
  128. {corio-2.2.3 → corio-2.2.4}/scripts/add-service +0 -0
  129. {corio-2.2.3 → corio-2.2.4}/scripts/add-user-path +0 -0
  130. {corio-2.2.3 → corio-2.2.4}/scripts/add-ve-shell +0 -0
  131. {corio-2.2.3 → corio-2.2.4}/scripts/apt-essentials +0 -0
  132. {corio-2.2.3 → corio-2.2.4}/scripts/apt-headless +0 -0
  133. {corio-2.2.3 → corio-2.2.4}/scripts/auth-token +0 -0
  134. {corio-2.2.3 → corio-2.2.4}/scripts/compose-update +0 -0
  135. {corio-2.2.3 → corio-2.2.4}/scripts/compress-dir +0 -0
  136. {corio-2.2.3 → corio-2.2.4}/scripts/cru +0 -0
  137. {corio-2.2.3 → corio-2.2.4}/scripts/docker-build-bases +0 -0
  138. {corio-2.2.3 → corio-2.2.4}/scripts/docker-build-bases-dev +0 -0
  139. {corio-2.2.3 → corio-2.2.4}/scripts/docker-install-deps +0 -0
  140. {corio-2.2.3 → corio-2.2.4}/scripts/docker-install-prod +0 -0
  141. {corio-2.2.3 → corio-2.2.4}/scripts/docker-prune +0 -0
  142. {corio-2.2.3 → corio-2.2.4}/scripts/docker-sandbox +0 -0
  143. {corio-2.2.3 → corio-2.2.4}/scripts/docker-sandbox-init +0 -0
  144. {corio-2.2.3 → corio-2.2.4}/scripts/docs-deploy +0 -0
  145. {corio-2.2.3 → corio-2.2.4}/scripts/download +0 -0
  146. {corio-2.2.3 → corio-2.2.4}/scripts/encrypt-secrets +0 -0
  147. {corio-2.2.3 → corio-2.2.4}/scripts/fmtr-test-script +0 -0
  148. {corio-2.2.3 → corio-2.2.4}/scripts/git-clone +0 -0
  149. {corio-2.2.3 → corio-2.2.4}/scripts/ha-addon-launch +0 -0
  150. {corio-2.2.3 → corio-2.2.4}/scripts/infra +0 -0
  151. {corio-2.2.3 → corio-2.2.4}/scripts/infra-sync +0 -0
  152. {corio-2.2.3 → corio-2.2.4}/scripts/install-browser +0 -0
  153. {corio-2.2.3 → corio-2.2.4}/scripts/install-docker +0 -0
  154. {corio-2.2.3 → corio-2.2.4}/scripts/install-ts +0 -0
  155. {corio-2.2.3 → corio-2.2.4}/scripts/install-ys +0 -0
  156. {corio-2.2.3 → corio-2.2.4}/scripts/mirror-dir +0 -0
  157. {corio-2.2.3 → corio-2.2.4}/scripts/opt-dev-init +0 -0
  158. {corio-2.2.3 → corio-2.2.4}/scripts/parse-args +0 -0
  159. {corio-2.2.3 → corio-2.2.4}/scripts/pypi-check +0 -0
  160. {corio-2.2.3 → corio-2.2.4}/scripts/pypi-reserve +0 -0
  161. {corio-2.2.3 → corio-2.2.4}/scripts/run-script +0 -0
  162. {corio-2.2.3 → corio-2.2.4}/scripts/set-password +0 -0
  163. {corio-2.2.3 → corio-2.2.4}/scripts/set-secure-path +0 -0
  164. {corio-2.2.3 → corio-2.2.4}/scripts/set-user-sudo +0 -0
  165. {corio-2.2.3 → corio-2.2.4}/scripts/snips-install +0 -0
  166. {corio-2.2.3 → corio-2.2.4}/scripts/ssh-auth +0 -0
  167. {corio-2.2.3 → corio-2.2.4}/scripts/ssh-serve +0 -0
  168. {corio-2.2.3 → corio-2.2.4}/scripts/tasmota-config +0 -0
  169. {corio-2.2.3 → corio-2.2.4}/scripts/tasmota-flash +0 -0
  170. {corio-2.2.3 → corio-2.2.4}/scripts/tasmota-terminal +0 -0
  171. {corio-2.2.3 → corio-2.2.4}/scripts/vlc-tn +0 -0
  172. {corio-2.2.3 → corio-2.2.4}/scripts/vm-launch +0 -0
  173. {corio-2.2.3 → 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.3
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]
@@ -584,13 +584,7 @@ class Tester(Inherit[Releaser]):
584
584
  @cached_property
585
585
  def dependencies(self) -> dict[str, list[str]]:
586
586
  data = self.paths.pyproject_repo.read_toml()
587
- table = data.get("tool", {}).get("corio", {}).get("dependencies", {})
588
- dependencies = {
589
- str(key): [str(value) for value in values]
590
- for key, values in table.items()
591
- if isinstance(values, list)
592
- }
593
- return dependencies
587
+ return data["project"].get("optional-dependencies",{})
594
588
 
595
589
  @cached_property
596
590
  def modules(self) -> list[str]:
@@ -608,9 +602,11 @@ class Tester(Inherit[Releaser]):
608
602
  def get_env(self, name: str, path_tests: Path, extras: list[str]) -> dict:
609
603
  if path_tests.is_relative_to(self.paths.repo):
610
604
  path_tests = path_tests.relative_to(self.paths.repo)
605
+ deps = self.get_deps_extras(extras)
611
606
  env = {
612
607
  "description": f"Run {name} tests.",
613
608
  "extras": extras,
609
+ "deps": deps,
614
610
  "commands": [["python", "-m", "pytest", "-q", str(path_tests)]],
615
611
  }
616
612
  return env
@@ -619,6 +615,22 @@ class Tester(Inherit[Releaser]):
619
615
  def env(self) -> dict:
620
616
  return self.get_env(name=self.paths.name_ns, path_tests=self.paths.tests, extras=["test"])
621
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
+
622
634
  @cached_property
623
635
  def envs(self) -> dict[str, dict]:
624
636
  if not self.paths.metadata.test_envs:
@@ -627,12 +639,9 @@ class Tester(Inherit[Releaser]):
627
639
  return {self.paths.name_ns: self.env}
628
640
 
629
641
  envs = {}
630
- extras_available = set(self.dependencies.keys())
631
642
 
632
643
  for module in self.modules:
633
- extras = ["test"]
634
- if module in extras_available:
635
- extras.insert(0, module)
644
+ extras = self.get_extras_module(module)
636
645
 
637
646
  path_test = self.paths.tests / f"{self.TEST_FILENAME_PREFIX}{module}{self.TEST_FILENAME_SUFFIX}"
638
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.3"
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.3"
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
 
@@ -160,3 +161,88 @@ def test_process_deps_pins_project_dependencies(tmp_path):
160
161
 
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"]
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