cf-runtime 0.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cf_runtime/paths.py ADDED
@@ -0,0 +1,257 @@
1
+ """Canonical filesystem layout and its only directory-creation API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ import re
8
+
9
+ from .domain import RuntimeId
10
+
11
+ _IDENTIFIER = re.compile(r"[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?\Z")
12
+ _VERSION_IDENTIFIER = re.compile(r"[A-Za-z0-9](?:[A-Za-z0-9._+-]*[A-Za-z0-9])?\Z")
13
+
14
+
15
+ def _absolute(path: Path) -> Path:
16
+ return path.expanduser().resolve(strict=False)
17
+
18
+
19
+ def _under(path: Path, parent: Path, *, label: str) -> Path:
20
+ resolved = _absolute(path)
21
+ try:
22
+ resolved.relative_to(parent)
23
+ except ValueError as error:
24
+ raise ValueError(f"{label} must be below Cogniflow home") from error
25
+ return resolved
26
+
27
+
28
+ def _identifier(value: str, label: str) -> str:
29
+ if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value):
30
+ raise ValueError(f"invalid {label}: {value!r}")
31
+ return value
32
+
33
+
34
+ def _version_identifier(value: str, label: str) -> str:
35
+ if not isinstance(value, str) or not _VERSION_IDENTIFIER.fullmatch(value):
36
+ raise ValueError(f"invalid {label}: {value!r}")
37
+ return value
38
+
39
+
40
+ @dataclass(frozen=True, slots=True)
41
+ class CogniflowHome:
42
+ root: Path
43
+
44
+ def __post_init__(self) -> None:
45
+ root = _absolute(Path(self.root))
46
+ if root == Path(root.anchor):
47
+ raise ValueError("Cogniflow home cannot be a filesystem root")
48
+ object.__setattr__(self, "root", root)
49
+
50
+ @classmethod
51
+ def default(cls) -> CogniflowHome:
52
+ return cls(Path.home() / ".cogniflow")
53
+
54
+ @classmethod
55
+ def at(cls, root: str | Path) -> CogniflowHome:
56
+ return cls(Path(root))
57
+
58
+ @property
59
+ def tools(self) -> Path:
60
+ return self.root / "tools"
61
+
62
+ @property
63
+ def releases(self) -> Path:
64
+ return self.root / "releases"
65
+
66
+ tools_root = tools
67
+ releases_root = releases
68
+
69
+ def runtime(self, runtime_id: RuntimeId) -> RuntimePaths:
70
+ if not isinstance(runtime_id, RuntimeId):
71
+ raise TypeError("runtime_id must be a RuntimeId")
72
+ return RuntimePaths(self, runtime_id)
73
+
74
+ def fuseki_install(self, version: str) -> Path:
75
+ return self.tools / "fuseki" / _version_identifier(version, "Fuseki version")
76
+
77
+ def java_install(self, version: str) -> Path:
78
+ return self.tools / "java" / _version_identifier(version, "Java version")
79
+
80
+ def ensure_fuseki_install(self, version: str) -> Path:
81
+ path = self.fuseki_install(version)
82
+ path.mkdir(parents=True, exist_ok=True)
83
+ return path
84
+
85
+ def ensure_java_install(self, version: str) -> Path:
86
+ path = self.java_install(version)
87
+ path.mkdir(parents=True, exist_ok=True)
88
+ return path
89
+
90
+ def release(self, version: str) -> Path:
91
+ return self.releases / _version_identifier(version, "release version")
92
+
93
+ def ensure(self) -> Path:
94
+ self.root.mkdir(parents=True, exist_ok=True)
95
+ return self.root
96
+
97
+ def ensure_tools(self) -> Path:
98
+ self.tools.mkdir(parents=True, exist_ok=True)
99
+ return self.tools
100
+
101
+ @property
102
+ def tool_cache(self) -> Path:
103
+ return self.root / "cache" / "downloads"
104
+
105
+ @property
106
+ def tool_locks(self) -> Path:
107
+ return self.root / "locks" / "managed-tools.lock"
108
+
109
+ @property
110
+ def tool_state(self) -> Path:
111
+ return self.root / "state" / "managed-tools"
112
+
113
+ def ensure_releases(self) -> Path:
114
+ self.releases.mkdir(parents=True, exist_ok=True)
115
+ return self.releases
116
+
117
+ def ensure_release(self, version: str) -> Path:
118
+ path = self.release(version)
119
+ path.mkdir(parents=True, exist_ok=True)
120
+ return path
121
+
122
+ def validate_path(self, path: str | Path, *, label: str = "path") -> Path:
123
+ return _under(Path(path), self.root, label=label)
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class RuntimePaths:
128
+ home: CogniflowHome
129
+ runtime_id: RuntimeId
130
+
131
+ def __post_init__(self) -> None:
132
+ object.__setattr__(self, "runtime_id", RuntimeId(self.runtime_id))
133
+
134
+ @property
135
+ def runtime_root(self) -> Path:
136
+ return self.home.root / "runtimes" / self.runtime_id.value
137
+
138
+ @property
139
+ def state(self) -> Path:
140
+ return self.runtime_root / "state"
141
+
142
+ @property
143
+ def locks(self) -> Path:
144
+ return self.runtime_root / "locks"
145
+
146
+ @property
147
+ def lifecycle_lock(self) -> Path:
148
+ return self.locks / "lifecycle.lock"
149
+
150
+ @property
151
+ def logs(self) -> Path:
152
+ return self.runtime_root / "logs"
153
+
154
+ @property
155
+ def temp(self) -> Path:
156
+ return self.runtime_root / "temp"
157
+
158
+ @property
159
+ def fuseki(self) -> Path:
160
+ return self.runtime_root / "fuseki"
161
+
162
+ @property
163
+ def fuseki_config(self) -> Path:
164
+ return self.fuseki / "config"
165
+
166
+ @property
167
+ def fuseki_database(self) -> Path:
168
+ return self.fuseki / "database"
169
+
170
+ @property
171
+ def fuseki_server_config(self) -> Path:
172
+ return self.fuseki_config / "server.ttl"
173
+
174
+ @property
175
+ def data(self) -> Path:
176
+ return self.home.root / "data" / self.runtime_id.value
177
+
178
+ @property
179
+ def pipelines(self) -> Path:
180
+ return self.data / "pipelines"
181
+
182
+ @property
183
+ def results(self) -> Path:
184
+ return self.data / "results"
185
+
186
+ @property
187
+ def artifacts(self) -> Path:
188
+ return self.data / "artifacts"
189
+
190
+ @property
191
+ def manifest(self) -> Path:
192
+ return self.runtime_root / "runtime.toml"
193
+
194
+ @property
195
+ def identity(self) -> Path:
196
+ return self.state / "identity.json"
197
+
198
+ @property
199
+ def lifecycle_state(self) -> Path:
200
+ return self.state / "fuseki.json"
201
+
202
+ # Explicit *_dir aliases make call sites unambiguous without changing layout.
203
+ state_dir = state
204
+ locks_dir = locks
205
+ logs_dir = logs
206
+ temp_dir = temp
207
+ fuseki_config_dir = fuseki_config
208
+ fuseki_database_dir = fuseki_database
209
+ data_root = data
210
+ pipelines_dir = pipelines
211
+ results_dir = results
212
+ artifacts_dir = artifacts
213
+ manifest_file = manifest
214
+
215
+ def pipeline_run_dir(self, pipeline_id: str, run_id: str) -> Path:
216
+ pipeline = _identifier(pipeline_id, "pipeline identifier")
217
+ run = _identifier(run_id, "run identifier")
218
+ return self.pipelines / pipeline / run
219
+
220
+ def result_run_dir(self, pipeline_id: str, run_id: str) -> Path:
221
+ return self.results / _identifier(pipeline_id, "pipeline identifier") / _identifier(run_id, "run identifier")
222
+
223
+ def artifact_run_dir(self, pipeline_id: str, run_id: str) -> Path:
224
+ return self.artifacts / _identifier(pipeline_id, "pipeline identifier") / _identifier(run_id, "run identifier")
225
+
226
+ def validate(self, path: str | Path) -> Path:
227
+ resolved = self.home.validate_path(path)
228
+ for root in (self.runtime_root, self.data):
229
+ try:
230
+ resolved.relative_to(root)
231
+ return resolved
232
+ except ValueError:
233
+ continue
234
+ raise ValueError("path does not belong to this runtime")
235
+
236
+ def ensure_directories(self) -> None:
237
+ for path in (
238
+ self.state, self.locks, self.logs, self.temp,
239
+ self.fuseki_config, self.fuseki_database,
240
+ self.pipelines, self.results, self.artifacts,
241
+ ):
242
+ path.mkdir(parents=True, exist_ok=True)
243
+
244
+ def ensure_pipeline_run(self, pipeline_id: str, run_id: str) -> Path:
245
+ path = self.pipeline_run_dir(pipeline_id, run_id)
246
+ path.mkdir(parents=True, exist_ok=True)
247
+ return path
248
+
249
+ def ensure_result_run(self, pipeline_id: str, run_id: str) -> Path:
250
+ path = self.result_run_dir(pipeline_id, run_id)
251
+ path.mkdir(parents=True, exist_ok=True)
252
+ return path
253
+
254
+ def ensure_artifact_run(self, pipeline_id: str, run_id: str) -> Path:
255
+ path = self.artifact_run_dir(pipeline_id, run_id)
256
+ path.mkdir(parents=True, exist_ok=True)
257
+ return path
@@ -0,0 +1,36 @@
1
+ @prefix cfpkg: <https://cogniflow.odea-project.org/cf#> .
2
+ @prefix pkg: <urn:cf:pkg:> .
3
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
4
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
5
+
6
+ pkg:cf_runtime {
7
+ pkg:cf_runtime
8
+ a cfpkg:CfPackage ;
9
+ cfpkg:hasPackageManifest [
10
+ a cfpkg:PackageManifest ;
11
+ cfpkg:hasDistributionName [
12
+ a cfpkg:DistributionName ;
13
+ rdf:value "cf-runtime"
14
+ ] ;
15
+ cfpkg:hasPythonPackageName [
16
+ a cfpkg:PythonPackageName ;
17
+ rdf:value "cf_runtime"
18
+ ] ;
19
+ cfpkg:hasPackageVersion [
20
+ a cfpkg:PackageVersion ;
21
+ rdf:value "0.1.8"
22
+ ] ;
23
+ cfpkg:hasImplementationLanguage [
24
+ a cfpkg:ImplementationLanguage ;
25
+ rdf:value "Python"
26
+ ]
27
+ ] ;
28
+ cfpkg:hasPackageRole [
29
+ a cfpkg:PackageRole ;
30
+ rdf:value cfpkg:SpecificationPackageRole
31
+ ] ;
32
+ skos:prefLabel "cf_runtime" ;
33
+ skos:definition "Standalone provider for Cogniflow runtime paths, manifests, identity, and development lifecycle control." ;
34
+ skos:scopeNote "Exposes a local pre-runtime bootstrap protocol and is not a Fuseki-backed ServiceOperation." ;
35
+ skos:example "Consumers invoke cf-runtime-bootstrap and validate its plain JSON target response without importing cf_runtime." .
36
+ }