ominfra 0.0.0.dev172__py3-none-any.whl → 0.0.0.dev173__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.
@@ -1,7 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
- import datetime
3
2
  import os.path
4
- import shutil
5
3
  import typing as ta
6
4
 
7
5
  from omlish.lite.cached import cached_nullary
@@ -12,28 +10,12 @@ from .conf import DeployConfManager
12
10
  from .git import DeployGitManager
13
11
  from .paths.owners import DeployPathOwner
14
12
  from .paths.paths import DeployPath
15
- from .specs import DeploySpec
16
- from .types import DeployAppTag
13
+ from .specs import DeployAppSpec
14
+ from .tags import DeployTagMap
17
15
  from .types import DeployHome
18
- from .types import DeployKey
19
- from .types import DeployRev
20
- from .types import DeployTag
21
16
  from .venvs import DeployVenvManager
22
17
 
23
18
 
24
- def make_deploy_tag(
25
- rev: DeployRev,
26
- key: DeployKey,
27
- *,
28
- utcnow: ta.Optional[datetime.datetime] = None,
29
- ) -> DeployTag:
30
- if utcnow is None:
31
- utcnow = datetime.datetime.now(tz=datetime.timezone.utc) # noqa
32
- now_fmt = '%Y%m%dT%H%M%SZ'
33
- now_str = utcnow.strftime(now_fmt)
34
- return DeployTag('-'.join([now_str, rev, key]))
35
-
36
-
37
19
  class DeployAppManager(DeployPathOwner):
38
20
  def __init__(
39
21
  self,
@@ -54,32 +36,27 @@ class DeployAppManager(DeployPathOwner):
54
36
 
55
37
  #
56
38
 
57
- _APP_TAG_DIR_STR = 'tags/apps/@app/@tag/'
58
- _APP_TAG_DIR = DeployPath.parse(_APP_TAG_DIR_STR)
39
+ _APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
40
+ _APP_DIR = DeployPath.parse(_APP_DIR_STR)
59
41
 
60
- _CONF_TAG_DIR_STR = 'tags/conf/@tag--@app/'
61
- _CONF_TAG_DIR = DeployPath.parse(_CONF_TAG_DIR_STR)
62
-
63
- _DEPLOY_DIR_STR = 'deploys/@tag--@app/'
42
+ _DEPLOY_DIR_STR = 'deploys/@time--@deploy-key/'
64
43
  _DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
65
44
 
66
45
  _APP_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}apps/@app')
67
- _CONF_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf')
46
+ _CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
68
47
 
69
48
  @cached_nullary
70
49
  def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
71
50
  return {
72
- self._APP_TAG_DIR,
73
-
74
- self._CONF_TAG_DIR,
51
+ self._APP_DIR,
75
52
 
76
53
  self._DEPLOY_DIR,
77
54
 
78
55
  self._APP_DEPLOY_LINK,
79
- self._CONF_DEPLOY_LINK,
56
+ self._CONF_DEPLOY_DIR,
80
57
 
81
58
  *[
82
- DeployPath.parse(f'{self._APP_TAG_DIR_STR}{sfx}/')
59
+ DeployPath.parse(f'{self._APP_DIR_STR}{sfx}/')
83
60
  for sfx in [
84
61
  'conf',
85
62
  'git',
@@ -92,26 +69,21 @@ class DeployAppManager(DeployPathOwner):
92
69
 
93
70
  async def prepare_app(
94
71
  self,
95
- spec: DeploySpec,
72
+ spec: DeployAppSpec,
73
+ tags: DeployTagMap,
96
74
  ) -> None:
97
- app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.git.rev, spec.key()))
98
-
99
- #
100
-
101
75
  deploy_home = check.non_empty_str(self._deploy_home)
102
76
 
103
77
  def build_path(pth: DeployPath) -> str:
104
- return os.path.join(deploy_home, pth.render(app_tag.placeholders()))
78
+ return os.path.join(deploy_home, pth.render(tags))
105
79
 
106
- app_tag_dir = build_path(self._APP_TAG_DIR)
107
- conf_tag_dir = build_path(self._CONF_TAG_DIR)
80
+ app_dir = build_path(self._APP_DIR)
108
81
  deploy_dir = build_path(self._DEPLOY_DIR)
109
82
  app_deploy_link = build_path(self._APP_DEPLOY_LINK)
110
- conf_deploy_link_file = build_path(self._CONF_DEPLOY_LINK)
111
83
 
112
84
  #
113
85
 
114
- os.makedirs(deploy_dir)
86
+ os.makedirs(deploy_dir, exist_ok=True)
115
87
 
116
88
  deploying_link = os.path.join(deploy_home, 'deploys/deploying')
117
89
  relative_symlink(
@@ -123,9 +95,9 @@ class DeployAppManager(DeployPathOwner):
123
95
 
124
96
  #
125
97
 
126
- os.makedirs(app_tag_dir)
98
+ os.makedirs(app_dir)
127
99
  relative_symlink(
128
- app_tag_dir,
100
+ app_dir,
129
101
  app_deploy_link,
130
102
  target_is_directory=True,
131
103
  make_dirs=True,
@@ -133,37 +105,33 @@ class DeployAppManager(DeployPathOwner):
133
105
 
134
106
  #
135
107
 
136
- os.makedirs(conf_tag_dir)
137
- relative_symlink(
138
- conf_tag_dir,
139
- conf_deploy_link_file,
140
- target_is_directory=True,
141
- make_dirs=True,
142
- )
108
+ deploy_conf_dir = os.path.join(deploy_dir, 'conf')
109
+ os.makedirs(deploy_conf_dir, exist_ok=True)
143
110
 
144
111
  #
145
112
 
146
- def mirror_symlinks(src: str, dst: str) -> None:
147
- def mirror_link(lp: str) -> None:
148
- check.state(os.path.islink(lp))
149
- shutil.copy2(
150
- lp,
151
- os.path.join(dst, os.path.relpath(lp, src)),
152
- follow_symlinks=False,
153
- )
154
-
155
- for dp, dns, fns in os.walk(src, followlinks=False):
156
- for fn in fns:
157
- mirror_link(os.path.join(dp, fn))
158
-
159
- for dn in dns:
160
- dp2 = os.path.join(dp, dn)
161
- if os.path.islink(dp2):
162
- mirror_link(dp2)
163
- else:
164
- os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
113
+ # def mirror_symlinks(src: str, dst: str) -> None:
114
+ # def mirror_link(lp: str) -> None:
115
+ # check.state(os.path.islink(lp))
116
+ # shutil.copy2(
117
+ # lp,
118
+ # os.path.join(dst, os.path.relpath(lp, src)),
119
+ # follow_symlinks=False,
120
+ # )
121
+ #
122
+ # for dp, dns, fns in os.walk(src, followlinks=False):
123
+ # for fn in fns:
124
+ # mirror_link(os.path.join(dp, fn))
125
+ #
126
+ # for dn in dns:
127
+ # dp2 = os.path.join(dp, dn)
128
+ # if os.path.islink(dp2):
129
+ # mirror_link(dp2)
130
+ # else:
131
+ # os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
165
132
 
166
133
  current_link = os.path.join(deploy_home, 'deploys/current')
134
+
167
135
  # if os.path.exists(current_link):
168
136
  # mirror_symlinks(
169
137
  # os.path.join(current_link, 'conf'),
@@ -176,31 +144,31 @@ class DeployAppManager(DeployPathOwner):
176
144
 
177
145
  #
178
146
 
179
- git_dir = os.path.join(app_tag_dir, 'git')
147
+ app_git_dir = os.path.join(app_dir, 'git')
180
148
  await self._git.checkout(
181
149
  spec.git,
182
- git_dir,
150
+ app_git_dir,
183
151
  )
184
152
 
185
153
  #
186
154
 
187
155
  if spec.venv is not None:
188
- venv_dir = os.path.join(app_tag_dir, 'venv')
156
+ app_venv_dir = os.path.join(app_dir, 'venv')
189
157
  await self._venvs.setup_venv(
190
158
  spec.venv,
191
- git_dir,
192
- venv_dir,
159
+ app_git_dir,
160
+ app_venv_dir,
193
161
  )
194
162
 
195
163
  #
196
164
 
197
165
  if spec.conf is not None:
198
- conf_dir = os.path.join(app_tag_dir, 'conf')
199
- await self._conf.write_conf(
166
+ app_conf_dir = os.path.join(app_dir, 'conf')
167
+ await self._conf.write_app_conf(
200
168
  spec.conf,
201
- app_tag,
202
- conf_dir,
203
- conf_tag_dir,
169
+ tags,
170
+ app_conf_dir,
171
+ deploy_conf_dir,
204
172
  )
205
173
 
206
174
  #
@@ -23,13 +23,15 @@ from omlish.lite.check import check
23
23
  from omlish.os.paths import is_path_in_dir
24
24
  from omlish.os.paths import relative_symlink
25
25
 
26
- from .paths.paths import DEPLOY_PATH_PLACEHOLDER_SEPARATOR
27
- from .specs import AppDeployConfLink
28
- from .specs import DeployConfFile
29
- from .specs import DeployConfLink
30
- from .specs import DeployConfSpec
31
- from .specs import TagDeployConfLink
32
- from .types import DeployAppTag
26
+ from .paths.paths import DeployPath
27
+ from .specs import AllActiveDeployAppConfLink
28
+ from .specs import CurrentOnlyDeployAppConfLink
29
+ from .specs import DeployAppConfFile
30
+ from .specs import DeployAppConfLink
31
+ from .specs import DeployAppConfSpec
32
+ from .tags import DEPLOY_TAG_SEPARATOR
33
+ from .tags import DeployApp
34
+ from .tags import DeployTagMap
33
35
  from .types import DeployHome
34
36
 
35
37
 
@@ -45,18 +47,18 @@ class DeployConfManager:
45
47
 
46
48
  #
47
49
 
48
- async def _write_conf_file(
50
+ async def _write_app_conf_file(
49
51
  self,
50
- cf: DeployConfFile,
51
- conf_dir: str,
52
+ acf: DeployAppConfFile,
53
+ app_conf_dir: str,
52
54
  ) -> None:
53
- conf_file = os.path.join(conf_dir, cf.path)
54
- check.arg(is_path_in_dir(conf_dir, conf_file))
55
+ conf_file = os.path.join(app_conf_dir, acf.path)
56
+ check.arg(is_path_in_dir(app_conf_dir, conf_file))
55
57
 
56
58
  os.makedirs(os.path.dirname(conf_file), exist_ok=True)
57
59
 
58
60
  with open(conf_file, 'w') as f: # noqa
59
- f.write(cf.body)
61
+ f.write(acf.body)
60
62
 
61
63
  #
62
64
 
@@ -65,15 +67,18 @@ class DeployConfManager:
65
67
  link_src: str
66
68
  link_dst: str
67
69
 
68
- def _compute_conf_link_dst(
70
+ _UNIQUE_LINK_NAME_STR = '@app--@time--@app-key'
71
+ _UNIQUE_LINK_NAME = DeployPath.parse(_UNIQUE_LINK_NAME_STR)
72
+
73
+ def _compute_app_conf_link_dst(
69
74
  self,
70
- link: DeployConfLink,
71
- app_tag: DeployAppTag,
72
- conf_dir: str,
73
- link_dir: str,
75
+ link: DeployAppConfLink,
76
+ tags: DeployTagMap,
77
+ app_conf_dir: str,
78
+ conf_link_dir: str,
74
79
  ) -> _ComputedConfLink:
75
- link_src = os.path.join(conf_dir, link.src)
76
- check.arg(is_path_in_dir(conf_dir, link_src))
80
+ link_src = os.path.join(app_conf_dir, link.src)
81
+ check.arg(is_path_in_dir(app_conf_dir, link_src))
77
82
 
78
83
  #
79
84
 
@@ -88,7 +93,7 @@ class DeployConfManager:
88
93
  d, f = os.path.split(link.src)
89
94
  # TODO: check filename :|
90
95
  link_dst_pfx = d + '/'
91
- link_dst_sfx = DEPLOY_PATH_PLACEHOLDER_SEPARATOR + f
96
+ link_dst_sfx = DEPLOY_TAG_SEPARATOR + f
92
97
 
93
98
  else: # noqa
94
99
  # @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
@@ -102,10 +107,10 @@ class DeployConfManager:
102
107
 
103
108
  #
104
109
 
105
- if isinstance(link, AppDeployConfLink):
106
- link_dst_mid = str(app_tag.app)
107
- elif isinstance(link, TagDeployConfLink):
108
- link_dst_mid = DEPLOY_PATH_PLACEHOLDER_SEPARATOR.join([app_tag.app, app_tag.tag])
110
+ if isinstance(link, CurrentOnlyDeployAppConfLink):
111
+ link_dst_mid = str(tags[DeployApp].s)
112
+ elif isinstance(link, AllActiveDeployAppConfLink):
113
+ link_dst_mid = self._UNIQUE_LINK_NAME.render(tags)
109
114
  else:
110
115
  raise TypeError(link)
111
116
 
@@ -116,7 +121,7 @@ class DeployConfManager:
116
121
  link_dst_mid,
117
122
  link_dst_sfx,
118
123
  ])
119
- link_dst = os.path.join(link_dir, link_dst_name)
124
+ link_dst = os.path.join(conf_link_dir, link_dst_name)
120
125
 
121
126
  return DeployConfManager._ComputedConfLink(
122
127
  is_dir=is_dir,
@@ -124,24 +129,24 @@ class DeployConfManager:
124
129
  link_dst=link_dst,
125
130
  )
126
131
 
127
- async def _make_conf_link(
132
+ async def _make_app_conf_link(
128
133
  self,
129
- link: DeployConfLink,
130
- app_tag: DeployAppTag,
131
- conf_dir: str,
132
- link_dir: str,
134
+ link: DeployAppConfLink,
135
+ tags: DeployTagMap,
136
+ app_conf_dir: str,
137
+ conf_link_dir: str,
133
138
  ) -> None:
134
- comp = self._compute_conf_link_dst(
139
+ comp = self._compute_app_conf_link_dst(
135
140
  link,
136
- app_tag,
137
- conf_dir,
138
- link_dir,
141
+ tags,
142
+ app_conf_dir,
143
+ conf_link_dir,
139
144
  )
140
145
 
141
146
  #
142
147
 
143
- check.arg(is_path_in_dir(conf_dir, comp.link_src))
144
- check.arg(is_path_in_dir(link_dir, comp.link_dst))
148
+ check.arg(is_path_in_dir(app_conf_dir, comp.link_src))
149
+ check.arg(is_path_in_dir(conf_link_dir, comp.link_dst))
145
150
 
146
151
  if comp.is_dir:
147
152
  check.arg(os.path.isdir(comp.link_src))
@@ -159,25 +164,25 @@ class DeployConfManager:
159
164
 
160
165
  #
161
166
 
162
- async def write_conf(
167
+ async def write_app_conf(
163
168
  self,
164
- spec: DeployConfSpec,
165
- app_tag: DeployAppTag,
166
- conf_dir: str,
167
- link_dir: str,
169
+ spec: DeployAppConfSpec,
170
+ tags: DeployTagMap,
171
+ app_conf_dir: str,
172
+ conf_link_dir: str,
168
173
  ) -> None:
169
- for cf in spec.files or []:
170
- await self._write_conf_file(
171
- cf,
172
- conf_dir,
174
+ for acf in spec.files or []:
175
+ await self._write_app_conf_file(
176
+ acf,
177
+ app_conf_dir,
173
178
  )
174
179
 
175
180
  #
176
181
 
177
182
  for link in spec.links or []:
178
- await self._make_conf_link(
183
+ await self._make_app_conf_link(
179
184
  link,
180
- app_tag,
181
- conf_dir,
182
- link_dir,
185
+ tags,
186
+ app_conf_dir,
187
+ conf_link_dir,
183
188
  )
@@ -1,7 +1,21 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import datetime
3
+ import typing as ta
4
+
5
+ from omlish.lite.typing import Func0
6
+
2
7
  from .apps import DeployAppManager
3
8
  from .paths.manager import DeployPathsManager
4
9
  from .specs import DeploySpec
10
+ from .tags import DeployAppRev
11
+ from .tags import DeployTagMap
12
+ from .tags import DeployTime
13
+
14
+
15
+ DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
16
+
17
+
18
+ DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
5
19
 
6
20
 
7
21
  class DeployManager:
@@ -10,12 +24,25 @@ class DeployManager:
10
24
  *,
11
25
  apps: DeployAppManager,
12
26
  paths: DeployPathsManager,
27
+
28
+ utc_clock: ta.Optional[DeployManagerUtcClock] = None,
13
29
  ):
14
30
  super().__init__()
15
31
 
16
32
  self._apps = apps
17
33
  self._paths = paths
18
34
 
35
+ self._utc_clock = utc_clock
36
+
37
+ def _utc_now(self) -> datetime.datetime:
38
+ if self._utc_clock is not None:
39
+ return self._utc_clock() # noqa
40
+ else:
41
+ return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
42
+
43
+ def _make_deploy_time(self) -> DeployTime:
44
+ return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
45
+
19
46
  async def run_deploy(
20
47
  self,
21
48
  spec: DeploySpec,
@@ -24,4 +51,21 @@ class DeployManager:
24
51
 
25
52
  #
26
53
 
27
- await self._apps.prepare_app(spec)
54
+ deploy_tags = DeployTagMap(
55
+ self._make_deploy_time(),
56
+ spec.key(),
57
+ )
58
+
59
+ #
60
+
61
+ for app in spec.apps:
62
+ app_tags = deploy_tags.add(
63
+ app.app,
64
+ app.key(),
65
+ DeployAppRev(app.git.rev),
66
+ )
67
+
68
+ await self._apps.prepare_app(
69
+ app,
70
+ app_tags,
71
+ )
@@ -13,38 +13,28 @@ import dataclasses as dc
13
13
  import itertools
14
14
  import typing as ta
15
15
 
16
+ from omlish.lite.cached import cached_nullary
16
17
  from omlish.lite.check import check
17
18
  from omlish.lite.strings import split_keep_delimiter
18
19
 
19
- from ..types import DeployPathKind
20
- from ..types import DeployPathPlaceholder
20
+ from ..tags import DEPLOY_TAG_DELIMITERS
21
+ from ..tags import DEPLOY_TAG_SIGIL
22
+ from ..tags import DEPLOY_TAGS_BY_NAME
23
+ from ..tags import DeployTag
24
+ from ..tags import DeployTagMap
25
+ from .types import DeployPathKind
21
26
 
22
27
 
23
28
  ##
24
29
 
25
30
 
26
- DEPLOY_PATH_PLACEHOLDER_SIGIL = '@'
27
- DEPLOY_PATH_PLACEHOLDER_SEPARATOR = '--'
28
-
29
- DEPLOY_PATH_PLACEHOLDER_DELIMITERS: ta.AbstractSet[str] = frozenset([
30
- DEPLOY_PATH_PLACEHOLDER_SEPARATOR,
31
- '.',
32
- ])
33
-
34
- DEPLOY_PATH_PLACEHOLDERS: ta.FrozenSet[str] = frozenset([
35
- 'app',
36
- 'tag',
37
- 'conf',
38
- ])
39
-
40
-
41
31
  class DeployPathError(Exception):
42
32
  pass
43
33
 
44
34
 
45
35
  class DeployPathRenderable(abc.ABC):
46
36
  @abc.abstractmethod
47
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
37
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
48
38
  raise NotImplementedError
49
39
 
50
40
 
@@ -55,26 +45,30 @@ class DeployPathNamePart(DeployPathRenderable, abc.ABC):
55
45
  @classmethod
56
46
  def parse(cls, s: str) -> 'DeployPathNamePart':
57
47
  check.non_empty_str(s)
58
- if s.startswith(DEPLOY_PATH_PLACEHOLDER_SIGIL):
59
- return PlaceholderDeployPathNamePart(s[1:])
60
- elif s in DEPLOY_PATH_PLACEHOLDER_DELIMITERS:
48
+ if s.startswith(DEPLOY_TAG_SIGIL):
49
+ return TagDeployPathNamePart(s[1:])
50
+ elif s in DEPLOY_TAG_DELIMITERS:
61
51
  return DelimiterDeployPathNamePart(s)
62
52
  else:
63
53
  return ConstDeployPathNamePart(s)
64
54
 
65
55
 
66
56
  @dc.dataclass(frozen=True)
67
- class PlaceholderDeployPathNamePart(DeployPathNamePart):
68
- placeholder: str # DeployPathPlaceholder
57
+ class TagDeployPathNamePart(DeployPathNamePart):
58
+ name: str
69
59
 
70
60
  def __post_init__(self) -> None:
71
- check.in_(self.placeholder, DEPLOY_PATH_PLACEHOLDERS)
61
+ check.in_(self.name, DEPLOY_TAGS_BY_NAME)
72
62
 
73
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
74
- if placeholders is not None:
75
- return placeholders[self.placeholder] # type: ignore
63
+ @property
64
+ def tag(self) -> ta.Type[DeployTag]:
65
+ return DEPLOY_TAGS_BY_NAME[self.name]
66
+
67
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
68
+ if tags is not None:
69
+ return tags[self.tag].s
76
70
  else:
77
- return DEPLOY_PATH_PLACEHOLDER_SIGIL + self.placeholder
71
+ return DEPLOY_TAG_SIGIL + self.name
78
72
 
79
73
 
80
74
  @dc.dataclass(frozen=True)
@@ -82,9 +76,9 @@ class DelimiterDeployPathNamePart(DeployPathNamePart):
82
76
  delimiter: str
83
77
 
84
78
  def __post_init__(self) -> None:
85
- check.in_(self.delimiter, DEPLOY_PATH_PLACEHOLDER_DELIMITERS)
79
+ check.in_(self.delimiter, DEPLOY_TAG_DELIMITERS)
86
80
 
87
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
81
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
88
82
  return self.delimiter
89
83
 
90
84
 
@@ -94,10 +88,10 @@ class ConstDeployPathNamePart(DeployPathNamePart):
94
88
 
95
89
  def __post_init__(self) -> None:
96
90
  check.non_empty_str(self.const)
97
- for c in [*DEPLOY_PATH_PLACEHOLDER_DELIMITERS, DEPLOY_PATH_PLACEHOLDER_SIGIL, '/']:
91
+ for c in [*DEPLOY_TAG_DELIMITERS, DEPLOY_TAG_SIGIL, '/']:
98
92
  check.not_in(c, self.const)
99
93
 
100
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
94
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
101
95
  return self.const
102
96
 
103
97
 
@@ -112,8 +106,8 @@ class DeployPathName(DeployPathRenderable):
112
106
  if len(gl := list(g)) > 1:
113
107
  raise DeployPathError(f'May not have consecutive path name part types: {k} {gl}')
114
108
 
115
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
116
- return ''.join(p.render(placeholders) for p in self.parts)
109
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
110
+ return ''.join(p.render(tags) for p in self.parts)
117
111
 
118
112
  @classmethod
119
113
  def parse(cls, s: str) -> 'DeployPathName':
@@ -123,7 +117,7 @@ class DeployPathName(DeployPathRenderable):
123
117
  i = 0
124
118
  ps = []
125
119
  while i < len(s):
126
- ns = [(n, d) for d in DEPLOY_PATH_PLACEHOLDER_DELIMITERS if (n := s.find(d, i)) >= 0]
120
+ ns = [(n, d) for d in DEPLOY_TAG_DELIMITERS if (n := s.find(d, i)) >= 0]
127
121
  if not ns:
128
122
  ps.append(s[i:])
129
123
  break
@@ -147,8 +141,8 @@ class DeployPathPart(DeployPathRenderable, abc.ABC): # noqa
147
141
  def kind(self) -> DeployPathKind:
148
142
  raise NotImplementedError
149
143
 
150
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
151
- return self.name.render(placeholders) + ('/' if self.kind == 'dir' else '')
144
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
145
+ return self.name.render(tags) + ('/' if self.kind == 'dir' else '')
152
146
 
153
147
  @classmethod
154
148
  def parse(cls, s: str) -> 'DeployPathPart':
@@ -194,20 +188,20 @@ class DeployPath:
194
188
  for p in self.parts[:-1]:
195
189
  check.equal(p.kind, 'dir')
196
190
 
197
- pd: ta.Dict[DeployPathPlaceholder, ta.List[int]] = {}
191
+ @cached_nullary
192
+ def tag_indices(self) -> ta.Mapping[ta.Type[DeployTag], ta.Sequence[int]]:
193
+ pd: ta.Dict[ta.Type[DeployTag], ta.List[int]] = {}
198
194
  for i, np in enumerate(self.name_parts):
199
- if isinstance(np, PlaceholderDeployPathNamePart):
200
- pd.setdefault(ta.cast(DeployPathPlaceholder, np.placeholder), []).append(i)
201
-
202
- # if 'tag' in pd and 'app' not in pd:
203
- # raise DeployPathError('Tag placeholder in path without app', self)
195
+ if isinstance(np, TagDeployPathNamePart):
196
+ pd.setdefault(np.tag, []).append(i)
197
+ return pd
204
198
 
205
199
  @property
206
200
  def kind(self) -> ta.Literal['file', 'dir']:
207
201
  return self.parts[-1].kind
208
202
 
209
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
210
- return ''.join([p.render(placeholders) for p in self.parts])
203
+ def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
204
+ return ''.join([p.render(tags) for p in self.parts])
211
205
 
212
206
  @classmethod
213
207
  def parse(cls, s: str) -> 'DeployPath':
@@ -0,0 +1,8 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+
5
+ DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
6
+
7
+
8
+ ##