omlish 0.0.0.dev219__py3-none-any.whl → 0.0.0.dev221__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.
- omlish/__about__.py +2 -2
- omlish/algorithm/__init__.py +0 -0
- omlish/algorithm/all.py +13 -0
- omlish/algorithm/distribute.py +46 -0
- omlish/algorithm/toposort.py +26 -0
- omlish/algorithm/unify.py +31 -0
- omlish/antlr/dot.py +13 -6
- omlish/collections/__init__.py +0 -2
- omlish/collections/utils.py +0 -46
- omlish/dataclasses/__init__.py +1 -0
- omlish/dataclasses/impl/api.py +10 -0
- omlish/dataclasses/impl/fields.py +8 -1
- omlish/dataclasses/impl/init.py +1 -1
- omlish/dataclasses/impl/main.py +1 -1
- omlish/dataclasses/impl/metaclass.py +112 -29
- omlish/dataclasses/impl/overrides.py +53 -0
- omlish/dataclasses/impl/params.py +3 -0
- omlish/dataclasses/impl/reflect.py +17 -5
- omlish/dataclasses/impl/simple.py +0 -42
- omlish/docker/oci/building.py +122 -0
- omlish/docker/oci/data.py +62 -8
- omlish/docker/oci/datarefs.py +98 -0
- omlish/docker/oci/loading.py +120 -0
- omlish/docker/oci/media.py +44 -14
- omlish/docker/oci/repositories.py +72 -0
- omlish/graphs/trees.py +2 -1
- omlish/http/coro/server.py +53 -24
- omlish/http/{simple.py → coro/simple.py} +17 -17
- omlish/http/handlers.py +8 -0
- omlish/io/fileno.py +11 -0
- omlish/lang/__init__.py +4 -1
- omlish/lang/cached.py +0 -1
- omlish/lang/classes/__init__.py +3 -1
- omlish/lang/classes/abstract.py +14 -1
- omlish/lang/classes/restrict.py +5 -5
- omlish/lang/classes/virtual.py +0 -1
- omlish/lang/clsdct.py +0 -1
- omlish/lang/contextmanagers.py +0 -8
- omlish/lang/descriptors.py +0 -1
- omlish/lang/maybes.py +0 -1
- omlish/lang/objects.py +0 -2
- omlish/secrets/ssl.py +9 -0
- omlish/secrets/tempssl.py +50 -0
- omlish/sockets/bind.py +6 -1
- omlish/sockets/server/server.py +18 -5
- omlish/specs/irc/__init__.py +0 -0
- omlish/specs/irc/format/LICENSE +11 -0
- omlish/specs/irc/format/__init__.py +61 -0
- omlish/specs/irc/format/consts.py +6 -0
- omlish/specs/irc/format/errors.py +30 -0
- omlish/specs/irc/format/message.py +18 -0
- omlish/specs/irc/format/nuh.py +52 -0
- omlish/specs/irc/format/parsing.py +155 -0
- omlish/specs/irc/format/rendering.py +150 -0
- omlish/specs/irc/format/tags.py +99 -0
- omlish/specs/irc/format/utils.py +27 -0
- omlish/specs/irc/numerics/__init__.py +0 -0
- omlish/specs/irc/numerics/formats.py +94 -0
- omlish/specs/irc/numerics/numerics.py +808 -0
- omlish/specs/irc/numerics/types.py +59 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/RECORD +66 -38
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/top_level.txt +0 -0
@@ -1,53 +1,11 @@
|
|
1
1
|
import inspect
|
2
2
|
|
3
|
-
from ... import lang
|
4
|
-
from .fields import field_assign
|
5
3
|
from .init import get_init_fields
|
6
|
-
from .params import get_field_extras
|
7
4
|
from .processing import Processor
|
8
5
|
from .utils import create_fn
|
9
6
|
from .utils import set_new_attribute
|
10
7
|
|
11
8
|
|
12
|
-
class OverridesProcessor(Processor):
|
13
|
-
def _process(self) -> None:
|
14
|
-
for f in self._info.instance_fields:
|
15
|
-
fx = get_field_extras(f)
|
16
|
-
if not fx.override:
|
17
|
-
continue
|
18
|
-
|
19
|
-
if self._info.params.slots:
|
20
|
-
raise TypeError
|
21
|
-
|
22
|
-
self_name = '__dataclass_self__' if 'self' in self._info.fields else 'self'
|
23
|
-
|
24
|
-
getter = create_fn(
|
25
|
-
f.name,
|
26
|
-
(self_name,),
|
27
|
-
[f'return {self_name}.__dict__[{f.name!r}]'],
|
28
|
-
globals=self._info.globals,
|
29
|
-
return_type=lang.just(f.type),
|
30
|
-
)
|
31
|
-
prop = property(getter)
|
32
|
-
|
33
|
-
if not self._info.params.frozen:
|
34
|
-
setter = create_fn(
|
35
|
-
f.name,
|
36
|
-
(self_name, f'{f.name}: __dataclass_type_{f.name}__'),
|
37
|
-
[field_assign(self._info.params.frozen, f.name, f.name, self_name, fx.override)],
|
38
|
-
globals=self._info.globals,
|
39
|
-
locals={f'__dataclass_type_{f.name}__': f.type},
|
40
|
-
return_type=lang.just(None),
|
41
|
-
)
|
42
|
-
prop = prop.setter(setter)
|
43
|
-
|
44
|
-
set_new_attribute(
|
45
|
-
self._cls,
|
46
|
-
f.name,
|
47
|
-
prop,
|
48
|
-
)
|
49
|
-
|
50
|
-
|
51
9
|
class EqProcessor(Processor):
|
52
10
|
def _process(self) -> None:
|
53
11
|
if not self._info.params.eq:
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import dataclasses as dc
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from ...lite.check import check
|
7
|
+
from ...lite.json import json_dumps_compact
|
8
|
+
from ...lite.marshal import marshal_obj
|
9
|
+
from .data import OciDataclass
|
10
|
+
from .data import OciImageConfig
|
11
|
+
from .data import OciImageIndex
|
12
|
+
from .data import OciImageLayer
|
13
|
+
from .data import OciImageManifest
|
14
|
+
from .datarefs import BytesOciDataRef
|
15
|
+
from .datarefs import OciDataRef
|
16
|
+
from .datarefs import OciDataRefInfo
|
17
|
+
from .media import OCI_IMAGE_LAYER_KIND_MEDIA_TYPES
|
18
|
+
from .media import OciMediaDataclass
|
19
|
+
from .media import OciMediaDescriptor
|
20
|
+
from .media import OciMediaImageConfig
|
21
|
+
from .media import OciMediaImageIndex
|
22
|
+
from .media import OciMediaImageManifest
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
|
27
|
+
|
28
|
+
class OciRepositoryBuilder:
|
29
|
+
def __init__(self) -> None:
|
30
|
+
super().__init__()
|
31
|
+
|
32
|
+
self._blobs: ta.Dict[str, OciDataRef] = {}
|
33
|
+
|
34
|
+
def get_blobs(self) -> ta.Dict[str, OciDataRef]:
|
35
|
+
return dict(self._blobs)
|
36
|
+
|
37
|
+
def add_blob(
|
38
|
+
self,
|
39
|
+
r: OciDataRef,
|
40
|
+
ri: ta.Optional[OciDataRefInfo] = None,
|
41
|
+
) -> None:
|
42
|
+
if ri is None:
|
43
|
+
ri = OciDataRefInfo(r)
|
44
|
+
if ri.digest() in self._blobs:
|
45
|
+
raise KeyError(ri.digest())
|
46
|
+
self._blobs[ri.digest()] = r
|
47
|
+
|
48
|
+
def marshal_media(self, obj: OciMediaDataclass) -> bytes:
|
49
|
+
check.isinstance(obj, OciMediaDataclass)
|
50
|
+
m = marshal_obj(obj)
|
51
|
+
j = json_dumps_compact(m)
|
52
|
+
b = j.encode('utf-8')
|
53
|
+
return b
|
54
|
+
|
55
|
+
def add_media(self, obj: OciMediaDataclass) -> OciMediaDescriptor:
|
56
|
+
b = self.marshal_media(obj)
|
57
|
+
|
58
|
+
r = BytesOciDataRef(b)
|
59
|
+
ri = OciDataRefInfo(r)
|
60
|
+
self.add_blob(r, ri)
|
61
|
+
|
62
|
+
return OciMediaDescriptor(
|
63
|
+
media_type=getattr(obj, 'media_type'),
|
64
|
+
digest=ri.digest(),
|
65
|
+
size=ri.size(),
|
66
|
+
)
|
67
|
+
|
68
|
+
def to_media(self, obj: OciDataclass) -> ta.Union[OciMediaDataclass, OciMediaDescriptor]:
|
69
|
+
def make_kw(*exclude):
|
70
|
+
return {
|
71
|
+
a: v
|
72
|
+
for f in dc.fields(obj)
|
73
|
+
if (a := f.name) not in exclude
|
74
|
+
for v in [getattr(obj, a)]
|
75
|
+
if v is not None
|
76
|
+
}
|
77
|
+
|
78
|
+
if isinstance(obj, OciImageIndex):
|
79
|
+
return OciMediaImageIndex(
|
80
|
+
**make_kw('manifests'),
|
81
|
+
manifests=[
|
82
|
+
self.add_data(m)
|
83
|
+
for m in obj.manifests
|
84
|
+
],
|
85
|
+
)
|
86
|
+
|
87
|
+
elif isinstance(obj, OciImageManifest):
|
88
|
+
return OciMediaImageManifest(
|
89
|
+
**make_kw('config', 'layers'),
|
90
|
+
config=self.add_data(obj.config),
|
91
|
+
layers=[
|
92
|
+
self.add_data(l)
|
93
|
+
for l in obj.layers
|
94
|
+
],
|
95
|
+
)
|
96
|
+
|
97
|
+
elif isinstance(obj, OciImageLayer):
|
98
|
+
ri = OciDataRefInfo(obj.data)
|
99
|
+
self.add_blob(obj.data, ri)
|
100
|
+
return OciMediaDescriptor(
|
101
|
+
media_type=OCI_IMAGE_LAYER_KIND_MEDIA_TYPES[obj.kind],
|
102
|
+
digest=ri.digest(),
|
103
|
+
size=ri.size(),
|
104
|
+
)
|
105
|
+
|
106
|
+
elif isinstance(obj, OciImageConfig):
|
107
|
+
return OciMediaImageConfig(**make_kw())
|
108
|
+
|
109
|
+
else:
|
110
|
+
raise TypeError(obj)
|
111
|
+
|
112
|
+
def add_data(self, obj: OciDataclass) -> OciMediaDescriptor:
|
113
|
+
ret = self.to_media(obj)
|
114
|
+
|
115
|
+
if isinstance(ret, OciMediaDataclass):
|
116
|
+
return self.add_media(ret)
|
117
|
+
|
118
|
+
elif isinstance(ret, OciMediaDescriptor):
|
119
|
+
return ret
|
120
|
+
|
121
|
+
else:
|
122
|
+
raise TypeError(ret)
|
omlish/docker/oci/data.py
CHANGED
@@ -2,16 +2,18 @@
|
|
2
2
|
# @omlish-lite
|
3
3
|
import abc
|
4
4
|
import dataclasses as dc
|
5
|
+
import enum
|
5
6
|
import typing as ta
|
6
7
|
|
7
8
|
from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
|
8
9
|
from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
|
10
|
+
from .datarefs import OciDataRef
|
9
11
|
|
10
12
|
|
11
13
|
##
|
12
14
|
|
13
15
|
|
14
|
-
@dc.dataclass(
|
16
|
+
@dc.dataclass()
|
15
17
|
class OciDataclass(abc.ABC): # noqa
|
16
18
|
pass
|
17
19
|
|
@@ -19,17 +21,52 @@ class OciDataclass(abc.ABC): # noqa
|
|
19
21
|
##
|
20
22
|
|
21
23
|
|
22
|
-
@dc.dataclass(
|
24
|
+
@dc.dataclass()
|
25
|
+
class OciImageIndex(OciDataclass):
|
26
|
+
manifests: ta.List[ta.Union['OciImageIndex', 'OciImageManifest']]
|
27
|
+
|
28
|
+
annotations: ta.Optional[ta.Dict[str, str]] = None
|
29
|
+
|
30
|
+
|
31
|
+
#
|
32
|
+
|
33
|
+
|
34
|
+
@dc.dataclass()
|
35
|
+
class OciImageManifest(OciDataclass):
|
36
|
+
config: 'OciImageConfig'
|
37
|
+
|
38
|
+
layers: ta.List['OciImageLayer']
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
|
43
|
+
|
44
|
+
@dc.dataclass()
|
45
|
+
class OciImageLayer(OciDataclass):
|
46
|
+
class Kind(enum.Enum):
|
47
|
+
TAR = enum.auto()
|
48
|
+
TAR_GZIP = enum.auto()
|
49
|
+
TAR_ZSTD = enum.auto()
|
50
|
+
|
51
|
+
kind: Kind
|
52
|
+
|
53
|
+
data: OciDataRef
|
54
|
+
|
55
|
+
|
56
|
+
#
|
57
|
+
|
58
|
+
|
59
|
+
@dc.dataclass()
|
23
60
|
class OciImageConfig(OciDataclass):
|
24
61
|
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/config.md"""
|
25
62
|
|
26
63
|
architecture: str
|
27
64
|
os: str
|
28
65
|
|
29
|
-
@dc.dataclass(
|
66
|
+
@dc.dataclass()
|
30
67
|
class RootFs:
|
31
68
|
type: str
|
32
|
-
diff_ids: ta.
|
69
|
+
diff_ids: ta.List[str]
|
33
70
|
|
34
71
|
rootfs: RootFs
|
35
72
|
|
@@ -38,7 +75,7 @@ class OciImageConfig(OciDataclass):
|
|
38
75
|
created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
39
76
|
author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
40
77
|
os_version: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.version', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
41
|
-
os_features: ta.Optional[ta.
|
78
|
+
os_features: ta.Optional[ta.List[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.features', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
42
79
|
variant: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
43
80
|
|
44
81
|
"""
|
@@ -58,9 +95,9 @@ class OciImageConfig(OciDataclass):
|
|
58
95
|
CpuShares integer, OPTIONAL
|
59
96
|
Healthcheck object, OPTIONAL
|
60
97
|
"""
|
61
|
-
config: ta.Optional[ta.
|
98
|
+
config: ta.Optional[ta.Dict[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
62
99
|
|
63
|
-
@dc.dataclass(
|
100
|
+
@dc.dataclass()
|
64
101
|
class History:
|
65
102
|
created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
66
103
|
author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
@@ -68,4 +105,21 @@ class OciImageConfig(OciDataclass):
|
|
68
105
|
comment: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
69
106
|
empty_layer: ta.Optional[bool] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
70
107
|
|
71
|
-
history: ta.Optional[ta.
|
108
|
+
history: ta.Optional[ta.List[History]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
109
|
+
|
110
|
+
|
111
|
+
##
|
112
|
+
|
113
|
+
|
114
|
+
def is_empty_oci_dataclass(obj: OciDataclass) -> bool:
|
115
|
+
if not isinstance(obj, OciDataclass):
|
116
|
+
raise TypeError(obj)
|
117
|
+
|
118
|
+
elif isinstance(obj, OciImageIndex):
|
119
|
+
return not obj.manifests
|
120
|
+
|
121
|
+
elif isinstance(obj, OciImageManifest):
|
122
|
+
return not obj.layers
|
123
|
+
|
124
|
+
else:
|
125
|
+
return False
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import dataclasses as dc
|
5
|
+
import hashlib
|
6
|
+
import io
|
7
|
+
import os.path
|
8
|
+
import shutil
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from omlish.lite.cached import cached_nullary
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
@dc.dataclass(frozen=True)
|
18
|
+
class OciDataRef(abc.ABC): # noqa
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
@dc.dataclass(frozen=True)
|
23
|
+
class BytesOciDataRef(OciDataRef):
|
24
|
+
data: bytes
|
25
|
+
|
26
|
+
|
27
|
+
@dc.dataclass(frozen=True)
|
28
|
+
class FileOciDataRef(OciDataRef):
|
29
|
+
path: str
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
|
34
|
+
|
35
|
+
@dc.dataclass(frozen=True)
|
36
|
+
class OciDataRefInfo:
|
37
|
+
data: OciDataRef
|
38
|
+
|
39
|
+
@cached_nullary
|
40
|
+
def sha256(self) -> str:
|
41
|
+
if isinstance(self.data, FileOciDataRef):
|
42
|
+
with open(self.data.path, 'rb') as f:
|
43
|
+
return hashlib.file_digest(f, 'sha256').hexdigest() # noqa
|
44
|
+
|
45
|
+
elif isinstance(self.data, BytesOciDataRef):
|
46
|
+
return hashlib.sha256(self.data.data).hexdigest()
|
47
|
+
|
48
|
+
else:
|
49
|
+
raise TypeError(self.data)
|
50
|
+
|
51
|
+
@cached_nullary
|
52
|
+
def digest(self) -> str:
|
53
|
+
return f'sha256:{self.sha256()}'
|
54
|
+
|
55
|
+
@cached_nullary
|
56
|
+
def size(self) -> int:
|
57
|
+
if isinstance(self.data, FileOciDataRef):
|
58
|
+
return os.path.getsize(self.data.path)
|
59
|
+
|
60
|
+
elif isinstance(self.data, BytesOciDataRef):
|
61
|
+
return len(self.data.data)
|
62
|
+
|
63
|
+
else:
|
64
|
+
raise TypeError(self.data)
|
65
|
+
|
66
|
+
|
67
|
+
def write_oci_data_ref_to_file(
|
68
|
+
data: OciDataRef,
|
69
|
+
dst: str,
|
70
|
+
*,
|
71
|
+
symlink: bool = False,
|
72
|
+
) -> None:
|
73
|
+
if isinstance(data, FileOciDataRef):
|
74
|
+
if symlink:
|
75
|
+
os.symlink(
|
76
|
+
os.path.relpath(data.path, os.path.dirname(dst)),
|
77
|
+
dst,
|
78
|
+
)
|
79
|
+
else:
|
80
|
+
shutil.copyfile(data.path, dst)
|
81
|
+
|
82
|
+
elif isinstance(data, BytesOciDataRef):
|
83
|
+
with open(dst, 'wb') as f:
|
84
|
+
f.write(data.data)
|
85
|
+
|
86
|
+
else:
|
87
|
+
raise TypeError(data)
|
88
|
+
|
89
|
+
|
90
|
+
def open_oci_data_ref(data: OciDataRef) -> ta.BinaryIO:
|
91
|
+
if isinstance(data, FileOciDataRef):
|
92
|
+
return open(data.path, 'rb')
|
93
|
+
|
94
|
+
elif isinstance(data, BytesOciDataRef):
|
95
|
+
return io.BytesIO(data.data)
|
96
|
+
|
97
|
+
else:
|
98
|
+
raise TypeError(data)
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import dataclasses as dc
|
4
|
+
import json
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ...lite.check import check
|
8
|
+
from .data import OciImageConfig
|
9
|
+
from .data import OciImageIndex
|
10
|
+
from .data import OciImageLayer
|
11
|
+
from .data import OciImageManifest
|
12
|
+
from .data import is_empty_oci_dataclass
|
13
|
+
from .media import OCI_IMAGE_LAYER_KIND_MEDIA_TYPES_
|
14
|
+
from .media import OCI_MEDIA_FIELDS
|
15
|
+
from .media import OciMediaDescriptor
|
16
|
+
from .media import OciMediaImageConfig
|
17
|
+
from .media import OciMediaImageIndex
|
18
|
+
from .media import OciMediaImageManifest
|
19
|
+
from .media import unmarshal_oci_media_dataclass
|
20
|
+
from .repositories import OciRepository
|
21
|
+
|
22
|
+
|
23
|
+
T = ta.TypeVar('T')
|
24
|
+
|
25
|
+
|
26
|
+
##
|
27
|
+
|
28
|
+
|
29
|
+
class OciRepositoryLoader:
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
repo: OciRepository,
|
33
|
+
) -> None:
|
34
|
+
super().__init__()
|
35
|
+
|
36
|
+
self._repo = repo
|
37
|
+
|
38
|
+
#
|
39
|
+
|
40
|
+
def load_object(
|
41
|
+
self,
|
42
|
+
data: bytes,
|
43
|
+
cls: ta.Type[T] = object, # type: ignore[assignment]
|
44
|
+
*,
|
45
|
+
media_type: ta.Optional[str] = None,
|
46
|
+
) -> T:
|
47
|
+
text = data.decode('utf-8')
|
48
|
+
dct = json.loads(text)
|
49
|
+
obj = unmarshal_oci_media_dataclass(
|
50
|
+
dct,
|
51
|
+
media_type=media_type,
|
52
|
+
)
|
53
|
+
return check.isinstance(obj, cls)
|
54
|
+
|
55
|
+
def read_object(
|
56
|
+
self,
|
57
|
+
digest: str,
|
58
|
+
cls: ta.Type[T] = object, # type: ignore[assignment]
|
59
|
+
*,
|
60
|
+
media_type: ta.Optional[str] = None,
|
61
|
+
) -> T:
|
62
|
+
data = self._repo.read_blob(digest)
|
63
|
+
return self.load_object(
|
64
|
+
data,
|
65
|
+
cls,
|
66
|
+
media_type=media_type,
|
67
|
+
)
|
68
|
+
|
69
|
+
def read_descriptor(
|
70
|
+
self,
|
71
|
+
desc: OciMediaDescriptor,
|
72
|
+
cls: ta.Type[T] = object, # type: ignore[assignment]
|
73
|
+
) -> ta.Any:
|
74
|
+
return self.read_object(
|
75
|
+
desc.digest,
|
76
|
+
cls,
|
77
|
+
media_type=desc.media_type,
|
78
|
+
)
|
79
|
+
|
80
|
+
#
|
81
|
+
|
82
|
+
def from_media(self, obj: ta.Any) -> ta.Any:
|
83
|
+
def make_kw(*exclude):
|
84
|
+
return {
|
85
|
+
a: getattr(obj, a)
|
86
|
+
for f in dc.fields(obj)
|
87
|
+
if (a := f.name) not in OCI_MEDIA_FIELDS
|
88
|
+
and a not in exclude
|
89
|
+
}
|
90
|
+
|
91
|
+
if isinstance(obj, OciMediaImageConfig):
|
92
|
+
return OciImageConfig(**make_kw())
|
93
|
+
|
94
|
+
elif isinstance(obj, OciMediaImageManifest):
|
95
|
+
return OciImageManifest(
|
96
|
+
**make_kw('config', 'layers'),
|
97
|
+
config=self.from_media(self.read_descriptor(obj.config)),
|
98
|
+
layers=[
|
99
|
+
OciImageLayer(
|
100
|
+
kind=lk,
|
101
|
+
data=self._repo.ref_blob(l.digest),
|
102
|
+
)
|
103
|
+
for l in obj.layers
|
104
|
+
if (lk := OCI_IMAGE_LAYER_KIND_MEDIA_TYPES_.get(l.media_type)) is not None
|
105
|
+
],
|
106
|
+
)
|
107
|
+
|
108
|
+
elif isinstance(obj, OciMediaImageIndex):
|
109
|
+
return OciImageIndex(
|
110
|
+
**make_kw('manifests'),
|
111
|
+
manifests=[
|
112
|
+
fm
|
113
|
+
for m in obj.manifests
|
114
|
+
for fm in [self.from_media(self.read_descriptor(m))]
|
115
|
+
if not is_empty_oci_dataclass(fm)
|
116
|
+
],
|
117
|
+
)
|
118
|
+
|
119
|
+
else:
|
120
|
+
raise TypeError(obj)
|
omlish/docker/oci/media.py
CHANGED
@@ -8,18 +8,29 @@ from ...lite.check import check
|
|
8
8
|
from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
|
9
9
|
from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
|
10
10
|
from ...lite.marshal import unmarshal_obj
|
11
|
-
from .data import OciDataclass
|
12
11
|
from .data import OciImageConfig
|
12
|
+
from .data import OciImageLayer
|
13
13
|
|
14
14
|
|
15
15
|
##
|
16
16
|
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
OCI_MEDIA_FIELDS: ta.Collection[str] = frozenset([
|
19
|
+
'schema_version',
|
20
|
+
'media_type',
|
21
|
+
])
|
22
|
+
|
23
|
+
|
24
|
+
@dc.dataclass()
|
25
|
+
class OciMediaDataclass(abc.ABC): # noqa
|
20
26
|
SCHEMA_VERSION: ta.ClassVar[int]
|
21
27
|
MEDIA_TYPE: ta.ClassVar[str]
|
22
28
|
|
29
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
30
|
+
super().__init_subclass__(**kwargs)
|
31
|
+
for a in OCI_MEDIA_FIELDS:
|
32
|
+
check.in_(a, cls.__dict__)
|
33
|
+
|
23
34
|
|
24
35
|
_REGISTERED_OCI_MEDIA_DATACLASSES: ta.Dict[str, ta.Type[OciMediaDataclass]] = {}
|
25
36
|
|
@@ -48,11 +59,11 @@ def unmarshal_oci_media_dataclass(
|
|
48
59
|
return unmarshal_obj(dct, cls)
|
49
60
|
|
50
61
|
|
51
|
-
|
62
|
+
##
|
52
63
|
|
53
64
|
|
54
|
-
@dc.dataclass(
|
55
|
-
class OciMediaDescriptor
|
65
|
+
@dc.dataclass()
|
66
|
+
class OciMediaDescriptor:
|
56
67
|
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/descriptor.md#properties""" # noqa
|
57
68
|
|
58
69
|
media_type: str = dc.field(metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
@@ -71,8 +82,11 @@ class OciMediaDescriptor(OciDataclass):
|
|
71
82
|
platform: ta.Optional[ta.Mapping[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
72
83
|
|
73
84
|
|
85
|
+
##
|
86
|
+
|
87
|
+
|
74
88
|
@_register_oci_media_dataclass
|
75
|
-
@dc.dataclass(
|
89
|
+
@dc.dataclass()
|
76
90
|
class OciMediaImageIndex(OciMediaDataclass):
|
77
91
|
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/image-index.md"""
|
78
92
|
|
@@ -91,18 +105,16 @@ class OciMediaImageIndex(OciMediaDataclass):
|
|
91
105
|
media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
92
106
|
|
93
107
|
|
108
|
+
#
|
109
|
+
|
110
|
+
|
94
111
|
@_register_oci_media_dataclass
|
95
|
-
@dc.dataclass(
|
112
|
+
@dc.dataclass()
|
96
113
|
class OciMediaImageManifest(OciMediaDataclass):
|
97
114
|
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/manifest.md"""
|
98
115
|
|
99
116
|
config: OciMediaDescriptor # -> OciMediaImageConfig
|
100
117
|
|
101
|
-
# MEDIA_TYPES: ta.ClassVar[ta.Mapping[str, str]] = {
|
102
|
-
# 'TAR': 'application/vnd.oci.image.layer.v1.tar',
|
103
|
-
# 'TAR_GZIP': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
104
|
-
# 'TAR_ZSTD': 'application/vnd.oci.image.layer.v1.tar+zstd',
|
105
|
-
# }
|
106
118
|
layers: ta.Sequence[OciMediaDescriptor]
|
107
119
|
|
108
120
|
#
|
@@ -114,8 +126,26 @@ class OciMediaImageManifest(OciMediaDataclass):
|
|
114
126
|
media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
115
127
|
|
116
128
|
|
129
|
+
#
|
130
|
+
|
131
|
+
|
132
|
+
OCI_IMAGE_LAYER_KIND_MEDIA_TYPES: ta.Mapping[OciImageLayer.Kind, str] = {
|
133
|
+
OciImageLayer.Kind.TAR: 'application/vnd.oci.image.layer.v1.tar',
|
134
|
+
OciImageLayer.Kind.TAR_GZIP: 'application/vnd.oci.image.layer.v1.tar+gzip',
|
135
|
+
OciImageLayer.Kind.TAR_ZSTD: 'application/vnd.oci.image.layer.v1.tar+zstd',
|
136
|
+
}
|
137
|
+
|
138
|
+
OCI_IMAGE_LAYER_KIND_MEDIA_TYPES_: ta.Mapping[str, OciImageLayer.Kind] = {
|
139
|
+
v: k
|
140
|
+
for k, v in OCI_IMAGE_LAYER_KIND_MEDIA_TYPES.items()
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
#
|
145
|
+
|
146
|
+
|
117
147
|
@_register_oci_media_dataclass
|
118
|
-
@dc.dataclass(
|
148
|
+
@dc.dataclass()
|
119
149
|
class OciMediaImageConfig(OciImageConfig, OciMediaDataclass):
|
120
150
|
SCHEMA_VERSION: ta.ClassVar[int] = 2
|
121
151
|
schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import os.path
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ...lite.check import check
|
8
|
+
from ...os.paths import is_path_in_dir
|
9
|
+
from .datarefs import BytesOciDataRef
|
10
|
+
from .datarefs import FileOciDataRef
|
11
|
+
from .datarefs import OciDataRef
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class OciRepository(abc.ABC):
|
18
|
+
@abc.abstractmethod
|
19
|
+
def read_blob(self, digest: str) -> bytes:
|
20
|
+
raise NotImplementedError
|
21
|
+
|
22
|
+
@abc.abstractmethod
|
23
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
24
|
+
raise NotImplementedError
|
25
|
+
|
26
|
+
|
27
|
+
#
|
28
|
+
|
29
|
+
|
30
|
+
class DirectoryOciRepository(OciRepository):
|
31
|
+
def __init__(self, data_dir: str) -> None:
|
32
|
+
super().__init__()
|
33
|
+
|
34
|
+
self._data_dir = check.non_empty_str(data_dir)
|
35
|
+
|
36
|
+
def read_file(self, path: str) -> bytes:
|
37
|
+
full_path = os.path.join(self._data_dir, path)
|
38
|
+
check.arg(is_path_in_dir(self._data_dir, full_path))
|
39
|
+
with open(full_path, 'rb') as f:
|
40
|
+
return f.read()
|
41
|
+
|
42
|
+
def blob_path(self, digest: str) -> str:
|
43
|
+
scheme, value = digest.split(':')
|
44
|
+
return os.path.join('blobs', scheme, value)
|
45
|
+
|
46
|
+
def blob_full_path(self, digest: str) -> str:
|
47
|
+
path = self.blob_path(digest)
|
48
|
+
full_path = os.path.join(self._data_dir, path)
|
49
|
+
check.arg(is_path_in_dir(self._data_dir, full_path))
|
50
|
+
return full_path
|
51
|
+
|
52
|
+
def read_blob(self, digest: str) -> bytes:
|
53
|
+
return self.read_file(self.blob_path(digest))
|
54
|
+
|
55
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
56
|
+
return FileOciDataRef(self.blob_full_path(digest))
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
|
61
|
+
|
62
|
+
class DictionaryOciRepository(OciRepository):
|
63
|
+
def __init__(self, blobs: ta.Mapping[str, bytes]) -> None:
|
64
|
+
super().__init__()
|
65
|
+
|
66
|
+
self._blobs = blobs
|
67
|
+
|
68
|
+
def read_blob(self, digest: str) -> bytes:
|
69
|
+
return self._blobs[digest]
|
70
|
+
|
71
|
+
def ref_blob(self, digest: str) -> OciDataRef:
|
72
|
+
return BytesOciDataRef(self._blobs[digest])
|