corio 2.3.4__tar.gz → 2.3.6__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 (178) hide show
  1. {corio-2.3.4 → corio-2.3.6}/PKG-INFO +13 -1
  2. corio-2.3.6/corio/api/__init__.py +1 -0
  3. {corio-2.3.4/corio → corio-2.3.6/corio/api}/api.py +118 -5
  4. corio-2.3.6/corio/api/mcp.py +1 -0
  5. {corio-2.3.4 → corio-2.3.6}/corio/infra/releaser.py +3 -7
  6. {corio-2.3.4 → corio-2.3.6}/corio/pyproject.package.toml +18 -6
  7. corio-2.3.6/corio/sandbox/__init__.py +1 -0
  8. corio-2.3.6/corio/sandbox/api.py +64 -0
  9. corio-2.3.6/corio/shell/__init__.py +3 -0
  10. corio-2.3.6/corio/shell/dsl.py +230 -0
  11. corio-2.3.6/corio/tests/test_api.py +96 -0
  12. corio-2.3.6/corio/tests/test_shell.py +277 -0
  13. {corio-2.3.4 → corio-2.3.6}/corio.egg-info/PKG-INFO +13 -1
  14. {corio-2.3.4 → corio-2.3.6}/corio.egg-info/SOURCES.txt +9 -1
  15. {corio-2.3.4 → corio-2.3.6}/corio.egg-info/requires.txt +13 -0
  16. {corio-2.3.4 → corio-2.3.6}/pyproject.toml +18 -6
  17. {corio-2.3.4 → corio-2.3.6}/LICENSE +0 -0
  18. {corio-2.3.4 → corio-2.3.6}/README.md +0 -0
  19. {corio-2.3.4 → corio-2.3.6}/corio/__init__.py +0 -0
  20. {corio-2.3.4 → corio-2.3.6}/corio/ai/__init__.py +0 -0
  21. {corio-2.3.4 → corio-2.3.6}/corio/ai/agentic.py +0 -0
  22. {corio-2.3.4 → corio-2.3.6}/corio/ai/infer.py +0 -0
  23. {corio-2.3.4 → corio-2.3.6}/corio/aio.py +0 -0
  24. {corio-2.3.4 → corio-2.3.6}/corio/augmentation.py +0 -0
  25. {corio-2.3.4 → corio-2.3.6}/corio/av.py +0 -0
  26. {corio-2.3.4 → corio-2.3.6}/corio/caching.py +0 -0
  27. {corio-2.3.4 → corio-2.3.6}/corio/constants.py +0 -0
  28. {corio-2.3.4 → corio-2.3.6}/corio/context.py +0 -0
  29. {corio-2.3.4 → corio-2.3.6}/corio/dataclass.py +0 -0
  30. {corio-2.3.4 → corio-2.3.6}/corio/datatype.py +0 -0
  31. {corio-2.3.4 → corio-2.3.6}/corio/db/__init__.py +0 -0
  32. {corio-2.3.4 → corio-2.3.6}/corio/db/document.py +0 -0
  33. {corio-2.3.4 → corio-2.3.6}/corio/debug.py +0 -0
  34. {corio-2.3.4 → corio-2.3.6}/corio/dm.py +0 -0
  35. {corio-2.3.4 → corio-2.3.6}/corio/dns/__init__.py +0 -0
  36. {corio-2.3.4 → corio-2.3.6}/corio/dns/client.py +0 -0
  37. {corio-2.3.4 → corio-2.3.6}/corio/dns/dm.py +0 -0
  38. {corio-2.3.4 → corio-2.3.6}/corio/dns/proxy.py +0 -0
  39. {corio-2.3.4 → corio-2.3.6}/corio/dns/server.py +0 -0
  40. {corio-2.3.4 → corio-2.3.6}/corio/docker/__init__.py +0 -0
  41. {corio-2.3.4 → corio-2.3.6}/corio/dt.py +0 -0
  42. {corio-2.3.4 → corio-2.3.6}/corio/encrypt.py +0 -0
  43. {corio-2.3.4 → corio-2.3.6}/corio/entrypoint.py +0 -0
  44. {corio-2.3.4 → corio-2.3.6}/corio/env.py +0 -0
  45. {corio-2.3.4 → corio-2.3.6}/corio/function.py +0 -0
  46. {corio-2.3.4 → corio-2.3.6}/corio/google_api.py +0 -0
  47. {corio-2.3.4 → corio-2.3.6}/corio/ha/__init__.py +0 -0
  48. {corio-2.3.4 → corio-2.3.6}/corio/ha/constants.py +0 -0
  49. {corio-2.3.4 → corio-2.3.6}/corio/ha/core.py +0 -0
  50. {corio-2.3.4 → corio-2.3.6}/corio/ha/supervisor.py +0 -0
  51. {corio-2.3.4 → corio-2.3.6}/corio/ha/utils.py +0 -0
  52. {corio-2.3.4 → corio-2.3.6}/corio/hash.py +0 -0
  53. {corio-2.3.4 → corio-2.3.6}/corio/hfh.py +0 -0
  54. {corio-2.3.4 → corio-2.3.6}/corio/hook.py +0 -0
  55. {corio-2.3.4 → corio-2.3.6}/corio/https.py +0 -0
  56. {corio-2.3.4 → corio-2.3.6}/corio/infra/__init__.py +0 -0
  57. {corio-2.3.4 → corio-2.3.6}/corio/infra/api.py +0 -0
  58. {corio-2.3.4 → corio-2.3.6}/corio/infra/incrementor_pyproject.py +0 -0
  59. {corio-2.3.4 → corio-2.3.6}/corio/infra/project.py +0 -0
  60. {corio-2.3.4 → corio-2.3.6}/corio/infra/repository.py +0 -0
  61. {corio-2.3.4 → corio-2.3.6}/corio/infra/stack.py +0 -0
  62. {corio-2.3.4 → corio-2.3.6}/corio/inherit.py +0 -0
  63. {corio-2.3.4 → corio-2.3.6}/corio/inspection.py +0 -0
  64. {corio-2.3.4 → corio-2.3.6}/corio/interface/__init__.py +0 -0
  65. {corio-2.3.4 → corio-2.3.6}/corio/interface/context.py +0 -0
  66. {corio-2.3.4 → corio-2.3.6}/corio/interface/controls.py +0 -0
  67. {corio-2.3.4 → corio-2.3.6}/corio/interface/interface.py +0 -0
  68. {corio-2.3.4 → corio-2.3.6}/corio/iterator.py +0 -0
  69. {corio-2.3.4 → corio-2.3.6}/corio/jsn.py +0 -0
  70. {corio-2.3.4 → corio-2.3.6}/corio/json_fix.py +0 -0
  71. {corio-2.3.4 → corio-2.3.6}/corio/logs.py +0 -0
  72. {corio-2.3.4 → corio-2.3.6}/corio/markup.py +0 -0
  73. {corio-2.3.4 → corio-2.3.6}/corio/merging.py +0 -0
  74. {corio-2.3.4 → corio-2.3.6}/corio/metric.py +0 -0
  75. {corio-2.3.4 → corio-2.3.6}/corio/mqtt.py +0 -0
  76. {corio-2.3.4 → corio-2.3.6}/corio/name.py +0 -0
  77. {corio-2.3.4 → corio-2.3.6}/corio/net.py +0 -0
  78. {corio-2.3.4 → corio-2.3.6}/corio/netrc.py +0 -0
  79. {corio-2.3.4 → corio-2.3.6}/corio/openai.py +0 -0
  80. {corio-2.3.4 → corio-2.3.6}/corio/parallel.py +0 -0
  81. {corio-2.3.4 → corio-2.3.6}/corio/path/__init__.py +0 -0
  82. {corio-2.3.4 → corio-2.3.6}/corio/path/app.py +0 -0
  83. {corio-2.3.4 → corio-2.3.6}/corio/path/path.py +0 -0
  84. {corio-2.3.4 → corio-2.3.6}/corio/path/type.py +0 -0
  85. {corio-2.3.4 → corio-2.3.6}/corio/paths.py +0 -0
  86. {corio-2.3.4 → corio-2.3.6}/corio/patterns.py +0 -0
  87. {corio-2.3.4 → corio-2.3.6}/corio/pdf.py +0 -0
  88. {corio-2.3.4 → corio-2.3.6}/corio/plat.py +0 -0
  89. {corio-2.3.4 → corio-2.3.6}/corio/process.py +0 -0
  90. {corio-2.3.4 → corio-2.3.6}/corio/profiling.py +0 -0
  91. {corio-2.3.4 → corio-2.3.6}/corio/rand.py +0 -0
  92. {corio-2.3.4 → corio-2.3.6}/corio/sec.py +0 -0
  93. {corio-2.3.4 → corio-2.3.6}/corio/semantic.py +0 -0
  94. {corio-2.3.4 → corio-2.3.6}/corio/sets.py +0 -0
  95. {corio-2.3.4 → corio-2.3.6}/corio/setup/__init__.py +0 -0
  96. {corio-2.3.4 → corio-2.3.6}/corio/spaces.py +0 -0
  97. {corio-2.3.4 → corio-2.3.6}/corio/strings.py +0 -0
  98. {corio-2.3.4 → corio-2.3.6}/corio/tabular.py +0 -0
  99. {corio-2.3.4 → corio-2.3.6}/corio/tests/__init__.py +0 -0
  100. {corio-2.3.4 → corio-2.3.6}/corio/tests/conftest.py +0 -0
  101. {corio-2.3.4 → corio-2.3.6}/corio/tests/helpers.py +0 -0
  102. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_caching.py +0 -0
  103. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_datatype.py +0 -0
  104. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_dns.py +0 -0
  105. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_dt.py +0 -0
  106. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_encrypt.py +0 -0
  107. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_env.py +0 -0
  108. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_hash.py +0 -0
  109. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_hook.py +0 -0
  110. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_infra.py +0 -0
  111. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_iterator.py +0 -0
  112. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_jsn.py +0 -0
  113. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_name.py +0 -0
  114. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_path.py +0 -0
  115. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_patterns.py +0 -0
  116. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_rand.py +0 -0
  117. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_strings.py +0 -0
  118. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_toml.py +0 -0
  119. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_tools.py +0 -0
  120. {corio-2.3.4 → corio-2.3.6}/corio/tests/test_yml.py +0 -0
  121. {corio-2.3.4 → corio-2.3.6}/corio/tokenization.py +0 -0
  122. {corio-2.3.4 → corio-2.3.6}/corio/toml.py +0 -0
  123. {corio-2.3.4 → corio-2.3.6}/corio/tools.py +0 -0
  124. {corio-2.3.4 → corio-2.3.6}/corio/unicode.py +0 -0
  125. {corio-2.3.4 → corio-2.3.6}/corio/version/__init__.py +0 -0
  126. {corio-2.3.4 → corio-2.3.6}/corio/version/version.py +0 -0
  127. {corio-2.3.4 → corio-2.3.6}/corio/webhook.py +0 -0
  128. {corio-2.3.4 → corio-2.3.6}/corio/yml.py +0 -0
  129. {corio-2.3.4 → corio-2.3.6}/corio/youtube.py +0 -0
  130. {corio-2.3.4 → corio-2.3.6}/corio.egg-info/dependency_links.txt +0 -0
  131. {corio-2.3.4 → corio-2.3.6}/corio.egg-info/entry_points.txt +0 -0
  132. {corio-2.3.4 → corio-2.3.6}/corio.egg-info/top_level.txt +0 -0
  133. {corio-2.3.4 → corio-2.3.6}/scripts/add-service +0 -0
  134. {corio-2.3.4 → corio-2.3.6}/scripts/add-user-path +0 -0
  135. {corio-2.3.4 → corio-2.3.6}/scripts/add-ve-shell +0 -0
  136. {corio-2.3.4 → corio-2.3.6}/scripts/apt-essentials +0 -0
  137. {corio-2.3.4 → corio-2.3.6}/scripts/apt-headless +0 -0
  138. {corio-2.3.4 → corio-2.3.6}/scripts/auth-token +0 -0
  139. {corio-2.3.4 → corio-2.3.6}/scripts/compose-update +0 -0
  140. {corio-2.3.4 → corio-2.3.6}/scripts/compress-dir +0 -0
  141. {corio-2.3.4 → corio-2.3.6}/scripts/cru +0 -0
  142. {corio-2.3.4 → corio-2.3.6}/scripts/docker-build-bases +0 -0
  143. {corio-2.3.4 → corio-2.3.6}/scripts/docker-build-bases-dev +0 -0
  144. {corio-2.3.4 → corio-2.3.6}/scripts/docker-install-deps +0 -0
  145. {corio-2.3.4 → corio-2.3.6}/scripts/docker-install-prod +0 -0
  146. {corio-2.3.4 → corio-2.3.6}/scripts/docker-prune +0 -0
  147. {corio-2.3.4 → corio-2.3.6}/scripts/docker-sandbox +0 -0
  148. {corio-2.3.4 → corio-2.3.6}/scripts/docker-sandbox-init +0 -0
  149. {corio-2.3.4 → corio-2.3.6}/scripts/docs-deploy +0 -0
  150. {corio-2.3.4 → corio-2.3.6}/scripts/download +0 -0
  151. {corio-2.3.4 → corio-2.3.6}/scripts/encrypt-secrets +0 -0
  152. {corio-2.3.4 → corio-2.3.6}/scripts/fmtr-test-script +0 -0
  153. {corio-2.3.4 → corio-2.3.6}/scripts/git-clone +0 -0
  154. {corio-2.3.4 → corio-2.3.6}/scripts/ha-addon-launch +0 -0
  155. {corio-2.3.4 → corio-2.3.6}/scripts/infra +0 -0
  156. {corio-2.3.4 → corio-2.3.6}/scripts/install-browser +0 -0
  157. {corio-2.3.4 → corio-2.3.6}/scripts/install-docker +0 -0
  158. {corio-2.3.4 → corio-2.3.6}/scripts/install-ts +0 -0
  159. {corio-2.3.4 → corio-2.3.6}/scripts/install-ys +0 -0
  160. {corio-2.3.4 → corio-2.3.6}/scripts/mirror-dir +0 -0
  161. {corio-2.3.4 → corio-2.3.6}/scripts/opt-dev-init +0 -0
  162. {corio-2.3.4 → corio-2.3.6}/scripts/parse-args +0 -0
  163. {corio-2.3.4 → corio-2.3.6}/scripts/pypi-check +0 -0
  164. {corio-2.3.4 → corio-2.3.6}/scripts/pypi-reserve +0 -0
  165. {corio-2.3.4 → corio-2.3.6}/scripts/run-script +0 -0
  166. {corio-2.3.4 → corio-2.3.6}/scripts/run-tests +0 -0
  167. {corio-2.3.4 → corio-2.3.6}/scripts/set-password +0 -0
  168. {corio-2.3.4 → corio-2.3.6}/scripts/set-secure-path +0 -0
  169. {corio-2.3.4 → corio-2.3.6}/scripts/set-user-sudo +0 -0
  170. {corio-2.3.4 → corio-2.3.6}/scripts/snips-install +0 -0
  171. {corio-2.3.4 → corio-2.3.6}/scripts/ssh-auth +0 -0
  172. {corio-2.3.4 → corio-2.3.6}/scripts/ssh-serve +0 -0
  173. {corio-2.3.4 → corio-2.3.6}/scripts/tasmota-config +0 -0
  174. {corio-2.3.4 → corio-2.3.6}/scripts/tasmota-flash +0 -0
  175. {corio-2.3.4 → corio-2.3.6}/scripts/tasmota-terminal +0 -0
  176. {corio-2.3.4 → corio-2.3.6}/scripts/vlc-tn +0 -0
  177. {corio-2.3.4 → corio-2.3.6}/scripts/vm-launch +0 -0
  178. {corio-2.3.4 → corio-2.3.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: corio
3
- Version: 2.3.4
3
+ Version: 2.3.6
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
@@ -90,6 +90,17 @@ Requires-Dist: pydantic; extra == "api"
90
90
  Requires-Dist: pydantic-extra-types; extra == "api"
91
91
  Requires-Dist: pycountry; extra == "api"
92
92
  Requires-Dist: logfire[fastapi]; extra == "api"
93
+ Requires-Dist: httpx; extra == "api"
94
+ Provides-Extra: api-mcp
95
+ Requires-Dist: fastapi; extra == "api-mcp"
96
+ Requires-Dist: uvicorn[standard]; extra == "api-mcp"
97
+ Requires-Dist: logfire; extra == "api-mcp"
98
+ Requires-Dist: pydantic; extra == "api-mcp"
99
+ Requires-Dist: pydantic-extra-types; extra == "api-mcp"
100
+ Requires-Dist: pycountry; extra == "api-mcp"
101
+ Requires-Dist: logfire[fastapi]; extra == "api-mcp"
102
+ Requires-Dist: httpx; extra == "api-mcp"
103
+ Requires-Dist: fastmcp; extra == "api-mcp"
93
104
  Provides-Extra: dm
94
105
  Requires-Dist: pydantic; extra == "dm"
95
106
  Requires-Dist: pydantic-extra-types; extra == "dm"
@@ -342,6 +353,7 @@ Requires-Dist: contexttimer; extra == "all"
342
353
  Requires-Dist: Unidecode; extra == "all"
343
354
  Requires-Dist: tinynetrc; extra == "all"
344
355
  Requires-Dist: huggingface_hub; extra == "all"
356
+ Requires-Dist: fastmcp; extra == "all"
345
357
  Requires-Dist: peft; extra == "all"
346
358
  Requires-Dist: transformers[sentencepiece]; extra == "all"
347
359
  Requires-Dist: torchvision; extra == "all"
@@ -0,0 +1 @@
1
+ from corio.api.api import *
@@ -1,15 +1,22 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
4
+ from contextlib import AsyncExitStack, asynccontextmanager
2
5
  from dataclasses import dataclass
3
6
  from functools import cached_property
4
- from typing import Callable, List, Optional, Union, Self
7
+ from typing import Callable, List, Optional, Union, Self, TYPE_CHECKING
5
8
 
9
+ import httpx
6
10
  import uvicorn
7
11
  from fastapi import FastAPI, Request
8
12
 
9
- from corio import env
13
+ from corio import env, strings
10
14
  from corio.iterator import enlist
11
15
  from corio.logs import logger
12
16
 
17
+ if TYPE_CHECKING:
18
+ from corio.api.mcp import FastMCP
19
+
13
20
  for name in ["uvicorn.access", "uvicorn.error", "uvicorn"]:
14
21
  logger_uvicorn = logging.getLogger(name)
15
22
  logger_uvicorn.handlers.clear()
@@ -44,6 +51,9 @@ class Base:
44
51
  URL = None
45
52
  URL_DOCS = '/'
46
53
  URL_PREFIX = None
54
+ IS_MCP = False
55
+ MCP_PATH = '/mcp'
56
+
47
57
 
48
58
  def add_endpoint(self, endpoint: Endpoint):
49
59
  """
@@ -63,7 +73,13 @@ class Base:
63
73
  )(endpoint.method)
64
74
 
65
75
  def __init__(self):
66
- self.app = FastAPI(title=self.TITLE, swagger_ui_parameters=self.SWAGGER_PARAMS, docs_url=self.URL_DOCS)
76
+ self.app = FastAPI(
77
+ title=self.TITLE,
78
+ swagger_ui_parameters=self.SWAGGER_PARAMS,
79
+ docs_url=self.URL_DOCS,
80
+ lifespan=self.lifespan,
81
+ description=self.description,
82
+ )
67
83
  logger.instrument_fastapi(self.app)
68
84
 
69
85
  for endpoint in self.get_endpoints():
@@ -75,6 +91,10 @@ class Base:
75
91
  for child in self.children:
76
92
  self.app.mount(child.url_prefix, child.app)
77
93
 
94
+ if self.IS_MCP:
95
+ self.app.mount(self.MCP_PATH, self.mcp_app)
96
+ self.app.router.lifespan_context = self.lifespan_mcp
97
+
78
98
  @cached_property
79
99
  def children(self) -> List[Self]:
80
100
  """
@@ -92,7 +112,7 @@ class Base:
92
112
 
93
113
  """
94
114
  if self.URL_PREFIX:
95
- self.URL_PREFIX
115
+ return self.URL_PREFIX
96
116
 
97
117
  return f'/{self.__class__.__name__.lower()}'
98
118
 
@@ -137,7 +157,29 @@ class Base:
137
157
  Launch message.
138
158
 
139
159
  """
140
- return f"Launching {self.TITLE} at {self.url}"
160
+ message = f"Launching {self.TITLE} at {self.url}"
161
+ if self.IS_MCP:
162
+ message = f"{message} (with MCP at {self.MCP_PATH})"
163
+ return message
164
+
165
+ @property
166
+ def description(self) -> str|None:
167
+ """
168
+
169
+ Optional OpenAPI description with immediate child API links.
170
+
171
+ """
172
+ if not self.children:
173
+ return None
174
+
175
+ lines = [f"- [{child.TITLE}]({child.url_prefix})" for child in self.children]
176
+ lines= "\n".join(lines)
177
+ description=strings.trim(f"""
178
+ ### APIs
179
+
180
+ {lines}
181
+ """)
182
+ return description
141
183
 
142
184
  @property
143
185
  def config(self) -> uvicorn.Config:
@@ -178,3 +220,74 @@ class Base:
178
220
  """
179
221
  import asyncio
180
222
  return asyncio.run(cls.launch_async(*args, **kwargs))
223
+
224
+ @staticmethod
225
+ @asynccontextmanager
226
+ async def lifespan(app: FastAPI):
227
+ """
228
+
229
+ Default app lifespan.
230
+
231
+ """
232
+ yield
233
+ logger.info(f"Closing {app.title}")
234
+
235
+
236
+ # MCP integration methods
237
+
238
+ @cached_property
239
+ def mcp(self) -> FastMCP:
240
+ """
241
+
242
+ Build an MCP server from this API's OpenAPI schema.
243
+
244
+ """
245
+ from corio.api.mcp import FastMCP
246
+ client = httpx.AsyncClient(transport=httpx.ASGITransport(app=self.app), base_url=self.url)
247
+ return FastMCP.from_openapi(openapi_spec=self.app.openapi(), client=client, name=self.TITLE)
248
+
249
+ @cached_property
250
+ def mcp_app(self):
251
+ """
252
+
253
+ HTTP ASGI app for this API's MCP server.
254
+
255
+ """
256
+ return self.mcp.http_app(path='/')
257
+
258
+
259
+ @cached_property
260
+ def mcp_lifespans_flat(self) -> list[tuple[Callable, FastAPI]]:
261
+ """
262
+
263
+ Flattened MCP lifespans for this API and immediate descendants.
264
+
265
+ """
266
+
267
+ if self.IS_MCP:
268
+ lifespans = [(self.mcp_app.lifespan, self.mcp_app)]
269
+ else:
270
+ lifespans = []
271
+
272
+ for child in self.children:
273
+ lifespans.extend(child.mcp_lifespans_flat)
274
+ return lifespans
275
+
276
+ @cached_property
277
+ def lifespan_mcp(self):
278
+ """
279
+
280
+ Combined lifespan wrapper for FastAPI and mounted MCP apps.
281
+
282
+ """
283
+ original_lifespan = self.app.router.lifespan_context
284
+
285
+ @asynccontextmanager
286
+ async def lifespan(app):
287
+ async with AsyncExitStack() as stack:
288
+ await stack.enter_async_context(original_lifespan(app))
289
+ for mcp_lifespan, mcp_owner_app in self.mcp_lifespans_flat:
290
+ await stack.enter_async_context(mcp_lifespan(mcp_owner_app))
291
+ yield
292
+
293
+ return lifespan
@@ -0,0 +1 @@
1
+ from fastmcp import FastMCP
@@ -228,13 +228,9 @@ class IncrementorVersion(Incrementor):
228
228
  def old(self):
229
229
  return self.paths.metadata.version_obj
230
230
 
231
- @cached_property
232
- def old_tag(self):
233
- return self.old.tag
234
-
235
231
  @cached_property
236
232
  def has_old_tag(self):
237
- return self.old_tag in self.repo.tags.all
233
+ return self.old.tag in self.repo.tags.all
238
234
 
239
235
  @cached_property
240
236
  def pinned_tag(self):
@@ -250,7 +246,7 @@ class IncrementorVersion(Incrementor):
250
246
 
251
247
  if not self.has_old_tag:
252
248
  raise RuntimeError(
253
- f'Current version tag "{self.old_tag}" was not found. '
249
+ f'Current version tag "{self.old.tag}" was not found. '
254
250
  f'Refusing pinned release "{new}" until the previous release state is resolved.'
255
251
  )
256
252
 
@@ -267,7 +263,7 @@ class IncrementorVersion(Incrementor):
267
263
 
268
264
  if not self.has_old_tag:
269
265
  logger.warning(
270
- f'Current version tag "{self.old_tag}" was not found. '
266
+ f'Current version tag "{self.old.tag}" was not found. '
271
267
  f'Assuming previous release failed and reusing version "{self.old}".'
272
268
  )
273
269
  return self.old
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "corio"
3
- version = "2.3.4"
3
+ version = "2.3.6"
4
4
  description = "Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML"
5
5
  readme = "README.md"
6
6
  dependencies = []
@@ -27,7 +27,8 @@ spaces = ["tinynetrc"]
27
27
  netrc = ["tinynetrc"]
28
28
  hfh = ["huggingface_hub"]
29
29
  merging = ["deepmerge"]
30
- api = ["fastapi", "uvicorn[standard]", "logfire", "pydantic", "pydantic-extra-types", "pycountry", "logfire[fastapi]"]
30
+ api = ["fastapi", "uvicorn[standard]", "logfire", "pydantic", "pydantic-extra-types", "pycountry", "logfire[fastapi]", "httpx"]
31
+ "api.mcp" = ["fastapi", "uvicorn[standard]", "logfire", "pydantic", "pydantic-extra-types", "pycountry", "logfire[fastapi]", "httpx", "fastmcp"]
31
32
  dm = ["pydantic", "pydantic-extra-types", "pycountry"]
32
33
  ai = ["peft", "transformers[sentencepiece]", "torchvision", "torchaudio", "pydantic", "pydantic-extra-types", "pycountry"]
33
34
  "ai.compute" = ["cuda-bindings==13.2.0", "cuda-pathfinder==1.5.4", "cuda-toolkit==13.0.2", "nvidia-cublas==13.1.0.3", "nvidia-cuda-cupti==13.0.85", "nvidia-cuda-nvrtc==13.0.88", "nvidia-cuda-runtime==13.0.96", "nvidia-cudnn-cu13==9.19.0.56", "nvidia-cufft==12.0.0.61", "nvidia-cufile==1.15.1.6", "nvidia-curand==10.4.0.35", "nvidia-cusolver==12.0.4.66", "nvidia-cusparse==12.6.3.3", "nvidia-cusparselt-cu13==0.8.0", "nvidia-nccl-cu13==2.28.9", "nvidia-nvjitlink==13.0.88", "nvidia-nvshmem-cu13==3.4.5", "nvidia-nvtx==13.0.85", "torch==2.11.0", "triton==3.6.0"]
@@ -70,7 +71,7 @@ tasmota = ["decode-config", "esptool"]
70
71
  encrypt = ["pyrage"]
71
72
  secrets = ["pyrage", "dotenv", "yamlscript", "pyyaml", "logfire", "pydantic-settings", "pydantic", "pydantic-extra-types", "pycountry", "pygit2"]
72
73
  cli = ["pydantic-settings", "pydantic", "pydantic-extra-types", "pycountry", "yamlscript", "pyyaml", "logfire"]
73
- all = ["logfire", "semver", "pydevd-pycharm~=251.25410.159", "pydantic-settings", "pydantic", "pydantic-extra-types", "pycountry", "yamlscript", "pyyaml", "beanie", "setuptools", "mkdocs", "mkdocs-material", "mkdocstrings[python]", "mike", "mkdocs-include-dir-to-nav", "build", "twine", "packaging", "pygit2", "python-on-whales", "deepmerge", "httpx", "httpx_retries", "logfire[httpx]", "fastapi", "uvicorn[standard]", "logfire[fastapi]", "tomlkit", "pyrage", "dotenv", "pytest", "pytest-cov", "pytest-asyncio", "dask[bag]", "distributed", "bokeh", "tokenizers", "faker", "sre_yield", "contexttimer", "Unidecode", "tinynetrc", "huggingface_hub", "peft", "transformers[sentencepiece]", "torchvision", "torchaudio", "cuda-bindings==13.2.0", "cuda-pathfinder==1.5.4", "cuda-toolkit==13.0.2", "nvidia-cublas==13.1.0.3", "nvidia-cuda-cupti==13.0.85", "nvidia-cuda-nvrtc==13.0.88", "nvidia-cuda-runtime==13.0.96", "nvidia-cudnn-cu13==9.19.0.56", "nvidia-cufft==12.0.0.61", "nvidia-cufile==1.15.1.6", "nvidia-curand==10.4.0.35", "nvidia-cusolver==12.0.4.66", "nvidia-cusparse==12.6.3.3", "nvidia-cusparselt-cu13==0.8.0", "nvidia-nccl-cu13==2.28.9", "nvidia-nvjitlink==13.0.88", "nvidia-nvshmem-cu13==3.4.5", "nvidia-nvtx==13.0.85", "torch==2.11.0", "triton==3.6.0", "openai", "pydantic-ai-slim[logfire,openai]", "ollama", "json_repair", "sentence_transformers", "pandas", "tabulate", "openpyxl", "odfpy", "deepdiff", "html2text", "flet[all] <0.80.0", "flet-video", "flet-webview", "google-auth", "google-auth-oauthlib", "google-auth-httplib2", "google-api-python-client", "diskcache", "cachetools", "pymupdf", "pymupdf4llm", "appdirs", "filetype", "dnspython[doh]", "regex", "playwright", "aiomqtt", "av", "homeassistant_api", "aiohasupervisor", "pytubefix", "decode-config", "esptool"]
74
+ all = ["logfire", "semver", "pydevd-pycharm~=251.25410.159", "pydantic-settings", "pydantic", "pydantic-extra-types", "pycountry", "yamlscript", "pyyaml", "beanie", "setuptools", "mkdocs", "mkdocs-material", "mkdocstrings[python]", "mike", "mkdocs-include-dir-to-nav", "build", "twine", "packaging", "pygit2", "python-on-whales", "deepmerge", "httpx", "httpx_retries", "logfire[httpx]", "fastapi", "uvicorn[standard]", "logfire[fastapi]", "tomlkit", "pyrage", "dotenv", "pytest", "pytest-cov", "pytest-asyncio", "dask[bag]", "distributed", "bokeh", "tokenizers", "faker", "sre_yield", "contexttimer", "Unidecode", "tinynetrc", "huggingface_hub", "fastmcp", "peft", "transformers[sentencepiece]", "torchvision", "torchaudio", "cuda-bindings==13.2.0", "cuda-pathfinder==1.5.4", "cuda-toolkit==13.0.2", "nvidia-cublas==13.1.0.3", "nvidia-cuda-cupti==13.0.85", "nvidia-cuda-nvrtc==13.0.88", "nvidia-cuda-runtime==13.0.96", "nvidia-cudnn-cu13==9.19.0.56", "nvidia-cufft==12.0.0.61", "nvidia-cufile==1.15.1.6", "nvidia-curand==10.4.0.35", "nvidia-cusolver==12.0.4.66", "nvidia-cusparse==12.6.3.3", "nvidia-cusparselt-cu13==0.8.0", "nvidia-nccl-cu13==2.28.9", "nvidia-nvjitlink==13.0.88", "nvidia-nvshmem-cu13==3.4.5", "nvidia-nvtx==13.0.85", "torch==2.11.0", "triton==3.6.0", "openai", "pydantic-ai-slim[logfire,openai]", "ollama", "json_repair", "sentence_transformers", "pandas", "tabulate", "openpyxl", "odfpy", "deepdiff", "html2text", "flet[all] <0.80.0", "flet-video", "flet-webview", "google-auth", "google-auth-oauthlib", "google-auth-httplib2", "google-api-python-client", "diskcache", "cachetools", "pymupdf", "pymupdf4llm", "appdirs", "filetype", "dnspython[doh]", "regex", "playwright", "aiomqtt", "av", "homeassistant_api", "aiohasupervisor", "pytubefix", "decode-config", "esptool"]
74
75
 
75
76
  [[project.authors]]
76
77
  name = "Frontmatter AI"
@@ -83,7 +84,7 @@ Homepage = "https://github.com/fmtr/corio"
83
84
  corio = "corio.entrypoint:main"
84
85
 
85
86
  [tool.corio.metadata]
86
- version = "2.3.4"
87
+ version = "2.3.6"
87
88
  port = 0
88
89
  base = "python"
89
90
  description = "Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML"
@@ -120,7 +121,8 @@ spaces = ["netrc"]
120
121
  netrc = ["tinynetrc"]
121
122
  hfh = ["huggingface_hub"]
122
123
  merging = ["deepmerge"]
123
- api = ["fastapi", "uvicorn[standard]", "logging", "dm", "logfire[fastapi]"]
124
+ api = ["fastapi", "uvicorn[standard]", "logging", "dm", "logfire[fastapi]", "httpx"]
125
+ "api.mcp" = ["api", "fastmcp"]
124
126
  dm = ["pydantic", "pydantic-extra-types", "pycountry"]
125
127
  ai = ["peft", "transformers[sentencepiece]", "torchvision", "torchaudio", "dm"]
126
128
  "ai.compute" = ["cuda-bindings==13.2.0", "cuda-pathfinder==1.5.4", "cuda-toolkit==13.0.2", "nvidia-cublas==13.1.0.3", "nvidia-cuda-cupti==13.0.85", "nvidia-cuda-nvrtc==13.0.88", "nvidia-cuda-runtime==13.0.96", "nvidia-cudnn-cu13==9.19.0.56", "nvidia-cufft==12.0.0.61", "nvidia-cufile==1.15.1.6", "nvidia-curand==10.4.0.35", "nvidia-cusolver==12.0.4.66", "nvidia-cusparse==12.6.3.3", "nvidia-cusparselt-cu13==0.8.0", "nvidia-nccl-cu13==2.28.9", "nvidia-nvjitlink==13.0.88", "nvidia-nvshmem-cu13==3.4.5", "nvidia-nvtx==13.0.85", "torch==2.11.0", "triton==3.6.0"]
@@ -177,7 +179,12 @@ corio = ["pyproject.package.toml"]
177
179
 
178
180
  [tool.tox]
179
181
  requires = ["tox>=4.22", "tox-uv>=1"]
180
- env_list = ["caching", "datatype", "dns", "dt", "encrypt", "env", "hash", "hook", "infra", "iterator", "jsn", "name", "path", "patterns", "rand", "strings", "toml", "tools", "yml"]
182
+ env_list = ["api", "caching", "datatype", "dns", "dt", "encrypt", "env", "hash", "hook", "infra", "iterator", "jsn", "name", "path", "patterns", "rand", "shell", "strings", "toml", "tools", "yml"]
183
+
184
+ [tool.tox.env.api]
185
+ description = "Run api tests."
186
+ deps = ["fastapi", "fastmcp", "httpx", "logfire", "logfire[fastapi]", "pycountry", "pydantic", "pydantic-extra-types", "pytest", "pytest-asyncio", "pytest-cov", "uvicorn[standard]"]
187
+ commands = [["python", "-m", "pytest", "-q", "corio/tests/test_api.py"]]
181
188
 
182
189
  [tool.tox.env.caching]
183
190
  description = "Run caching tests."
@@ -254,6 +261,11 @@ description = "Run rand tests."
254
261
  deps = ["pytest", "pytest-asyncio", "pytest-cov"]
255
262
  commands = [["python", "-m", "pytest", "-q", "corio/tests/test_rand.py"]]
256
263
 
264
+ [tool.tox.env.shell]
265
+ description = "Run shell tests."
266
+ deps = ["pytest", "pytest-asyncio", "pytest-cov"]
267
+ commands = [["python", "-m", "pytest", "-q", "corio/tests/test_shell.py"]]
268
+
257
269
  [tool.tox.env.strings]
258
270
  description = "Run strings tests."
259
271
  deps = ["pytest", "pytest-asyncio", "pytest-cov"]
@@ -0,0 +1 @@
1
+ from corio.sandbox.api import SandboxApi
@@ -0,0 +1,64 @@
1
+ from corio import api
2
+ from functools import cached_property
3
+
4
+
5
+ class Child(api.Base):
6
+ TITLE = 'Sandbox Child API'
7
+ IS_MCP = True
8
+
9
+
10
+ def get_endpoints(self):
11
+ """
12
+
13
+ Sandbox child endpoints.
14
+
15
+ """
16
+ endpoints = [
17
+ api.Endpoint(method_http=self.app.get, path='/pong', method=self.pong, tags='sandbox'),
18
+ ]
19
+ return endpoints
20
+
21
+ async def pong(self, value: str = 'ok'):
22
+ """
23
+
24
+ Child ping variant.
25
+
26
+ """
27
+ return dict(child=value)
28
+
29
+
30
+ class SandboxApi(api.Base):
31
+ TITLE = 'Sandbox API'
32
+ PORT = 8001
33
+ IS_MCP = True
34
+
35
+ def get_endpoints(self):
36
+ """
37
+
38
+ Sandbox parent endpoints.
39
+
40
+ """
41
+ endpoints = [
42
+ api.Endpoint(method_http=self.app.get, path='/ping', method=self.ping, tags='sandbox'),
43
+ ]
44
+ return endpoints
45
+
46
+ async def ping(self, name: str = 'world'):
47
+ """
48
+
49
+ Parent ping endpoint.
50
+
51
+ """
52
+ return dict(message=f'hello {name}')
53
+
54
+ @cached_property
55
+ def children(self):
56
+ """
57
+
58
+ Immediate sandbox child APIs.
59
+
60
+ """
61
+ return [Child()]
62
+
63
+ if __name__ == '__main__':
64
+ SandboxApi.launch()
@@ -0,0 +1,3 @@
1
+ from corio.shell.dsl import Argument, Expression, Shell, Subcommand, Value
2
+
3
+ __all__ = ["Argument", "Expression", "Shell", "Subcommand", "Value"]
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ import shlex
5
+ import subprocess
6
+
7
+
8
+ def _normalize(name: str) -> str:
9
+ return name.strip("_").replace("_", "-")
10
+
11
+
12
+ def _to_value(item: object) -> Value:
13
+ if isinstance(item, Value):
14
+ return item
15
+ if isinstance(item, Argument):
16
+ return Value(name=item.name)
17
+ return Value(name=str(item))
18
+
19
+
20
+ def _to_subcommand_part(item: object) -> Argument:
21
+ if isinstance(item, Argument):
22
+ return item
23
+ if isinstance(item, Value):
24
+ return Argument(name=item.name)
25
+ return Argument(name=str(item))
26
+
27
+
28
+ def _to_expression_part(item: object) -> Subcommand | Argument:
29
+ if isinstance(item, Subcommand):
30
+ return item
31
+ if isinstance(item, Argument):
32
+ return item
33
+ if isinstance(item, Value):
34
+ return Argument(name=item.name)
35
+ return Argument(name=str(item))
36
+
37
+
38
+ def _to_argument_child(item: object) -> Argument | Value:
39
+ if isinstance(item, Argument):
40
+ return item
41
+ if isinstance(item, Value):
42
+ return item
43
+ return Argument(name=str(item))
44
+
45
+
46
+ def _flatten_parts(parts: list[object]) -> list[str]:
47
+ return [token for part in parts for token in part.tokens()]
48
+
49
+
50
+ @dataclass
51
+ class Value:
52
+ name: str = ""
53
+ children: list[object] = field(default_factory=list)
54
+
55
+ def __getattr__(self, name: str) -> Value:
56
+ return Value(name=_normalize(name))
57
+
58
+ def tokens(self) -> list[str]:
59
+ return [shlex.quote(self.name)]
60
+
61
+ def __str__(self) -> str:
62
+ return " ".join(self.tokens())
63
+
64
+ def __repr__(self) -> str:
65
+ return f"{self.__class__.__name__}('{self}')"
66
+
67
+
68
+ @dataclass
69
+ class Argument:
70
+ name: str = ""
71
+ prefixes: str = ""
72
+ children: list[Argument | Value] = field(default_factory=list)
73
+ sep: str = " "
74
+
75
+ def __getattr__(self, name: str) -> Argument:
76
+ return Argument(name=_normalize(name), prefixes=self.prefixes)
77
+
78
+ def __neg__(self) -> Argument:
79
+ return Argument(name=self.name, prefixes=f"-{self.prefixes}", children=list(self.children), sep=self.sep)
80
+
81
+ def __pos__(self) -> Argument:
82
+ return Argument(name=self.name, prefixes=f"+{self.prefixes}", children=list(self.children), sep=self.sep)
83
+
84
+ def __add__(self, other: object) -> Argument:
85
+ child = _to_value(other)
86
+ return Argument(
87
+ name=self.name,
88
+ prefixes=self.prefixes,
89
+ children=[*self.children, child],
90
+ sep=self.sep,
91
+ )
92
+
93
+ def __call__(self, *parts: object) -> Argument:
94
+ return Argument(
95
+ name=self.name,
96
+ prefixes=self.prefixes,
97
+ children=[*self.children, *[_to_argument_child(part) for part in parts]],
98
+ sep=self.sep,
99
+ )
100
+
101
+ def __eq__(self, other: object) -> Argument: # type: ignore[override]
102
+ return Argument(
103
+ name=self.name,
104
+ prefixes=self.prefixes,
105
+ children=[_to_value(other)],
106
+ sep="=",
107
+ )
108
+
109
+ @property
110
+ def token(self) -> str:
111
+ return f"{self.prefixes}{shlex.quote(self.name)}"
112
+
113
+ def tokens(self) -> list[str]:
114
+ if not self.children:
115
+ return [self.token]
116
+ if self.sep != " ":
117
+ first, rest = self.children[0], self.children[1:]
118
+ return [f"{self.token}{self.sep}{first.tokens()[0]}", *_flatten_parts(rest)]
119
+ return [self.token, *_flatten_parts(self.children)]
120
+
121
+ def __str__(self) -> str:
122
+ return " ".join(self.tokens())
123
+
124
+ def __repr__(self) -> str:
125
+ return f"{self.__class__.__name__}('{self}')"
126
+
127
+
128
+ @dataclass
129
+ class Subcommand:
130
+ name: str
131
+ children: list[Subcommand | Argument] = field(default_factory=list)
132
+ active: Subcommand = field(init=False)
133
+
134
+ def __post_init__(self) -> None:
135
+ self.active = self
136
+
137
+ def add_subcommand(self, name: str) -> Subcommand:
138
+ child = Subcommand(name=name)
139
+ self.active.children.append(child)
140
+ self.active = child
141
+ return child
142
+
143
+ def __call__(self, *parts: object) -> Subcommand:
144
+ self.active.children.extend(_to_subcommand_part(part) for part in parts)
145
+ return self
146
+
147
+ def tokens(self) -> list[str]:
148
+ return [self.name, *_flatten_parts(self.children)]
149
+
150
+ def __str__(self) -> str:
151
+ return " ".join(self.tokens())
152
+
153
+ def __repr__(self) -> str:
154
+ return f"{self.__class__.__name__}('{self}')"
155
+
156
+
157
+ @dataclass
158
+ class Expression:
159
+ name: str
160
+ shell: Shell
161
+ children: list[Subcommand | Argument] = field(default_factory=list)
162
+ active: Expression | Subcommand = field(init=False)
163
+
164
+ def __post_init__(self) -> None:
165
+ self.active = self
166
+
167
+ def __getattr__(self, name: str) -> Expression:
168
+ command = _normalize(name)
169
+ if self.active is self:
170
+ child = Subcommand(name=command)
171
+ self.children.append(child)
172
+ self.active = child
173
+ return self
174
+ self.active = self.active.add_subcommand(command)
175
+ return self
176
+
177
+ def __call__(self, *parts: object) -> Expression:
178
+ if self.active is self:
179
+ self.children.extend(_to_expression_part(part) for part in parts)
180
+ return self
181
+ self.active(*parts)
182
+ return self
183
+
184
+ def tokens(self) -> list[str]:
185
+ return [self.name, *_flatten_parts(self.children)]
186
+
187
+ def popen(self) -> subprocess.Popen[str]:
188
+ return self.shell.popen(self)
189
+
190
+ def iterate_lines(self):
191
+ return self.shell.iterate_lines(self)
192
+
193
+ def __iter__(self):
194
+ return self.iterate_lines()
195
+
196
+ def __pos__(self):
197
+ return self.iterate_lines()
198
+
199
+ def __str__(self) -> str:
200
+ return " ".join(self.tokens())
201
+
202
+ def __repr__(self) -> str:
203
+ return f"{self.__class__.__name__}('{self}')"
204
+
205
+
206
+ @dataclass
207
+ class Shell:
208
+ context: str = ""
209
+
210
+ def __getattr__(self, name: str) -> Expression:
211
+ return Expression(name=_normalize(name), shell=self)
212
+
213
+ def popen(self, expression: Expression) -> subprocess.Popen[str]:
214
+ return subprocess.Popen(
215
+ expression.tokens(),
216
+ stdout=subprocess.PIPE,
217
+ stderr=subprocess.PIPE,
218
+ text=True,
219
+ bufsize=1,
220
+ )
221
+
222
+ def iterate_lines(self, expression: Expression):
223
+ process = self.popen(expression)
224
+ if process.stdout is not None:
225
+ for line in process.stdout:
226
+ yield line.rstrip("\n")
227
+ process.wait()
228
+
229
+ def __repr__(self) -> str:
230
+ return f"{self.__class__.__name__}('{self.context}')"