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/__init__.py +74 -0
- cf_runtime/bootstrap.py +376 -0
- cf_runtime/domain.py +25 -0
- cf_runtime/installed_seed.py +57 -0
- cf_runtime/lifecycle.py +457 -0
- cf_runtime/locking.py +64 -0
- cf_runtime/managed_tools.py +373 -0
- cf_runtime/manifest.py +464 -0
- cf_runtime/paths.py +257 -0
- cf_runtime/semantics/package.trig +36 -0
- cf_runtime/semantics.py +392 -0
- cf_runtime/target.py +137 -0
- cf_runtime/teardown.py +237 -0
- cf_runtime-0.1.8.dist-info/METADATA +35 -0
- cf_runtime-0.1.8.dist-info/RECORD +17 -0
- cf_runtime-0.1.8.dist-info/WHEEL +4 -0
- cf_runtime-0.1.8.dist-info/entry_points.txt +5 -0
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
|
+
}
|