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.
Files changed (66) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/algorithm/__init__.py +0 -0
  3. omlish/algorithm/all.py +13 -0
  4. omlish/algorithm/distribute.py +46 -0
  5. omlish/algorithm/toposort.py +26 -0
  6. omlish/algorithm/unify.py +31 -0
  7. omlish/antlr/dot.py +13 -6
  8. omlish/collections/__init__.py +0 -2
  9. omlish/collections/utils.py +0 -46
  10. omlish/dataclasses/__init__.py +1 -0
  11. omlish/dataclasses/impl/api.py +10 -0
  12. omlish/dataclasses/impl/fields.py +8 -1
  13. omlish/dataclasses/impl/init.py +1 -1
  14. omlish/dataclasses/impl/main.py +1 -1
  15. omlish/dataclasses/impl/metaclass.py +112 -29
  16. omlish/dataclasses/impl/overrides.py +53 -0
  17. omlish/dataclasses/impl/params.py +3 -0
  18. omlish/dataclasses/impl/reflect.py +17 -5
  19. omlish/dataclasses/impl/simple.py +0 -42
  20. omlish/docker/oci/building.py +122 -0
  21. omlish/docker/oci/data.py +62 -8
  22. omlish/docker/oci/datarefs.py +98 -0
  23. omlish/docker/oci/loading.py +120 -0
  24. omlish/docker/oci/media.py +44 -14
  25. omlish/docker/oci/repositories.py +72 -0
  26. omlish/graphs/trees.py +2 -1
  27. omlish/http/coro/server.py +53 -24
  28. omlish/http/{simple.py → coro/simple.py} +17 -17
  29. omlish/http/handlers.py +8 -0
  30. omlish/io/fileno.py +11 -0
  31. omlish/lang/__init__.py +4 -1
  32. omlish/lang/cached.py +0 -1
  33. omlish/lang/classes/__init__.py +3 -1
  34. omlish/lang/classes/abstract.py +14 -1
  35. omlish/lang/classes/restrict.py +5 -5
  36. omlish/lang/classes/virtual.py +0 -1
  37. omlish/lang/clsdct.py +0 -1
  38. omlish/lang/contextmanagers.py +0 -8
  39. omlish/lang/descriptors.py +0 -1
  40. omlish/lang/maybes.py +0 -1
  41. omlish/lang/objects.py +0 -2
  42. omlish/secrets/ssl.py +9 -0
  43. omlish/secrets/tempssl.py +50 -0
  44. omlish/sockets/bind.py +6 -1
  45. omlish/sockets/server/server.py +18 -5
  46. omlish/specs/irc/__init__.py +0 -0
  47. omlish/specs/irc/format/LICENSE +11 -0
  48. omlish/specs/irc/format/__init__.py +61 -0
  49. omlish/specs/irc/format/consts.py +6 -0
  50. omlish/specs/irc/format/errors.py +30 -0
  51. omlish/specs/irc/format/message.py +18 -0
  52. omlish/specs/irc/format/nuh.py +52 -0
  53. omlish/specs/irc/format/parsing.py +155 -0
  54. omlish/specs/irc/format/rendering.py +150 -0
  55. omlish/specs/irc/format/tags.py +99 -0
  56. omlish/specs/irc/format/utils.py +27 -0
  57. omlish/specs/irc/numerics/__init__.py +0 -0
  58. omlish/specs/irc/numerics/formats.py +94 -0
  59. omlish/specs/irc/numerics/numerics.py +808 -0
  60. omlish/specs/irc/numerics/types.py +59 -0
  61. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/METADATA +1 -1
  62. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/RECORD +66 -38
  63. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/LICENSE +0 -0
  64. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/WHEEL +0 -0
  65. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/entry_points.txt +0 -0
  66. {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(frozen=True)
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(frozen=True)
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(frozen=True)
66
+ @dc.dataclass()
30
67
  class RootFs:
31
68
  type: str
32
- diff_ids: ta.Sequence[str]
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.Sequence[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.features', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
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.Mapping[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
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(frozen=True)
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.Sequence[History]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
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)
@@ -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
- @dc.dataclass(frozen=True)
19
- class OciMediaDataclass(OciDataclass, abc.ABC): # noqa
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(frozen=True)
55
- class OciMediaDescriptor(OciDataclass):
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(frozen=True)
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(frozen=True)
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(frozen=True)
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])