apexcodexpy 0.7.2__tar.gz → 0.7.4__tar.gz

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 (27) hide show
  1. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/PKG-INFO +1 -1
  2. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/program.py +5 -2
  3. apexcodexpy-0.7.4/codex/infra/pyproject.py +190 -0
  4. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/reporters.py +4 -2
  5. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/result.py +7 -2
  6. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/service.py +22 -18
  7. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/tools.py +4 -1
  8. apexcodexpy-0.7.4/codex/infra/typer/device.py +52 -0
  9. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/typer/root.py +0 -15
  10. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/workflows/base.py +5 -7
  11. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/workflows/ci.py +4 -1
  12. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/yamler.py +5 -3
  13. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/pyproject.toml +18 -1
  14. apexcodexpy-0.7.2/codex/infra/pyproject.py +0 -112
  15. apexcodexpy-0.7.2/codex/infra/typer/device.py +0 -28
  16. apexcodexpy-0.7.2/codex/infra/workflows/lock.py +0 -33
  17. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/LICENSE +0 -0
  18. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/README.md +0 -0
  19. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/__init__.py +0 -0
  20. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/__init__.py +0 -0
  21. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/typer/__init__.py +0 -0
  22. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/typer/testing.py +0 -0
  23. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/workflows/__init__.py +1 -1
  24. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/workflows/build.py +0 -0
  25. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/workflows/dependabot.py +0 -0
  26. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/infra/workflows/validate.py +0 -0
  27. {apexcodexpy-0.7.2 → apexcodexpy-0.7.4}/codex/runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apexcodexpy
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: The Non-Destructive Configuration Steward for Python Projects.
5
5
  License-File: LICENSE
6
6
  Author: Apex Dev
@@ -1,12 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import subprocess
4
- from collections.abc import Callable, Iterable, Iterator, MutableSequence
5
4
  from dataclasses import dataclass, field, replace
6
- from typing import Any, Protocol
5
+ from typing import TYPE_CHECKING, Any, Protocol
7
6
 
8
7
  from codex.infra.result import ComboResult, ProgramResult, TaskResult
9
8
 
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable, Iterable, Iterator, MutableSequence
11
+
10
12
 
11
13
  @dataclass(frozen=True)
12
14
  class Program:
@@ -22,6 +24,7 @@ class Program:
22
24
  text=True,
23
25
  encoding="utf-8",
24
26
  capture_output=True,
27
+ check=False,
25
28
  )
26
29
  ),
27
30
  )
@@ -0,0 +1,190 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import hashlib
5
+ from dataclasses import dataclass, field
6
+ from functools import cached_property
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ import tomlkit
11
+
12
+ from codex.infra.result import ProgramResult
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Mapping
16
+
17
+ from codex.infra.program import Executable
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class PyProjectToml:
22
+ name: str = "PyProject"
23
+ path: Path = Path("pyproject.toml")
24
+
25
+ def sync(self, target: Path) -> Executable:
26
+ pyproject = PyProjectFile(target / self.path, standards=self.standards)
27
+
28
+ if not pyproject.exists():
29
+ return ProgramResult(name=self.name).fail(message=f"{self.name} not found.")
30
+
31
+ if not pyproject.is_outdated():
32
+ return ProgramResult(name=self.name).no_changes()
33
+
34
+ pyproject.sync()
35
+
36
+ return ProgramResult(name=self.name).workflow_success()
37
+
38
+ @property
39
+ def standards(self) -> Mapping[str, Any]:
40
+ return {
41
+ "tool": {
42
+ "mypy": self._mypy,
43
+ "ruff": self._ruff,
44
+ "coverage": self._coveragepy,
45
+ }
46
+ }
47
+
48
+ @property
49
+ def _mypy(self) -> dict[str, Any]:
50
+ return {
51
+ "strict": True,
52
+ "ignore_missing_imports": True,
53
+ "show_error_codes": True,
54
+ "pretty": True,
55
+ }
56
+
57
+ @property
58
+ def _ruff(self) -> dict[str, Any]:
59
+ return {
60
+ "lint": {
61
+ "select": [
62
+ "A",
63
+ "ARG",
64
+ "B",
65
+ "C4",
66
+ "PT",
67
+ "RSE",
68
+ "RET",
69
+ "SIM",
70
+ "T20",
71
+ "E",
72
+ "W",
73
+ "F",
74
+ "I",
75
+ "N",
76
+ "UP",
77
+ "RUF",
78
+ "FURB",
79
+ "PERF",
80
+ "LOG",
81
+ "PIE",
82
+ "SLF",
83
+ "DTZ",
84
+ "PTH",
85
+ "TRY",
86
+ "PL",
87
+ "TCH",
88
+ ],
89
+ "mccabe": {
90
+ "max-complexity": 10,
91
+ },
92
+ }
93
+ }
94
+
95
+ @property
96
+ def _coveragepy(self) -> dict[str, Any]:
97
+ return {
98
+ "run": {
99
+ "branch": True,
100
+ },
101
+ "report": {
102
+ "skip_empty": True,
103
+ "skip_covered": True,
104
+ "show_missing": True,
105
+ },
106
+ }
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class PyProjectFile:
111
+ path: Path
112
+
113
+ standards: Mapping[str, Any] = field(default_factory=dict)
114
+
115
+ @cached_property
116
+ def current(self) -> Tomler:
117
+ return Tomler.read(self.path)
118
+
119
+ @cached_property
120
+ def latest(self) -> Tomler:
121
+ return self.current.update_with(self.standards)
122
+
123
+ def exists(self) -> bool:
124
+ return self.path.exists()
125
+
126
+ def is_outdated(self) -> bool:
127
+ return self.current != self.latest
128
+
129
+ def sync(self) -> None:
130
+ self.path.write_text(self.latest.dumps(), encoding="utf-8", newline="\n")
131
+
132
+
133
+ @dataclass(frozen=True)
134
+ class Tomler:
135
+ document: Mapping[str, Any]
136
+
137
+ @classmethod
138
+ def read(cls, path: Path) -> Tomler:
139
+ return cls(tomlkit.parse(path.read_text(encoding="utf-8")))
140
+
141
+ def dumps(self) -> str:
142
+ return tomlkit.dumps(self.document)
143
+
144
+ def update_with(self, source: Mapping[str, Any]) -> Tomler:
145
+ return Tomler(deep_merge(source, self.document))
146
+
147
+ def hashed(self) -> str:
148
+ return hashlib.sha256(self.dumps().encode()).hexdigest()
149
+
150
+
151
+ def deep_merge(
152
+ source: Mapping[str, Any],
153
+ destination: Mapping[str, Any],
154
+ ) -> Mapping[str, Any]:
155
+ """
156
+ Recursively merges two mappings, taking the source and merging it into
157
+ the destination. Performs a deep merge such that nested dictionaries or
158
+ arrays are combined intelligently.
159
+
160
+ The function identifies the type of each key-value pair in the `source`
161
+ and `destination` mappings and determines how to merge them based on
162
+ their data types, supporting nested dictionaries and TOML data
163
+ structures.
164
+
165
+ :param source: The source mapping to merge into the destination.
166
+ :param destination: The destination mapping that will be updated with the
167
+ data from `source`.
168
+ :return: A new mapping that represents the merged result of `source`
169
+ and `destination`.
170
+ """
171
+ result = {**destination}
172
+
173
+ for key, value in source.items():
174
+ match (result.get(key), value):
175
+ case (dict(destination_value), dict(source_value)):
176
+ # Both exist and are dicts -> recursively merge
177
+ result[key] = deep_merge(source_value, destination_value)
178
+ case (_, dict(source_value)):
179
+ # Only source is a dict -> convert to TOML table
180
+ result[key] = deep_merge(source_value, {})
181
+ case (_, list(src_list)):
182
+ # Formatted TOML array
183
+ new_array = tomlkit.array()
184
+ new_array.extend(copy.deepcopy(src_list))
185
+ new_array.multiline(True)
186
+ result[key] = new_array
187
+ case _:
188
+ result[key] = copy.deepcopy(value)
189
+
190
+ return result
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import Protocol
4
+ from typing import TYPE_CHECKING, Protocol
5
5
 
6
- from codex.infra.result import Reporter
7
6
  from codex.infra.typer.device import TyperDevice
8
7
 
8
+ if TYPE_CHECKING:
9
+ from codex.infra.result import Reporter
10
+
9
11
 
10
12
  @dataclass(frozen=True)
11
13
  class SimpleReporter:
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from subprocess import CompletedProcess
5
- from typing import Any, Protocol
4
+ from typing import TYPE_CHECKING, Any, Protocol
5
+
6
+ if TYPE_CHECKING:
7
+ from subprocess import CompletedProcess
6
8
 
7
9
 
8
10
  class TaskResult(Protocol):
@@ -59,6 +61,9 @@ class ProgramResult:
59
61
  def no_changes(self) -> ProgramResult:
60
62
  return self.success(message="Already up to date.\n")
61
63
 
64
+ def workflow_success(self) -> ProgramResult:
65
+ return self.success(message="Workflow succeeded!\n")
66
+
62
67
  def success(self, *, message: str = "") -> ProgramResult:
63
68
  return ProgramResult(
64
69
  name=self.name,
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from pathlib import Path
5
4
  from time import time
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  from codex.infra.program import Command, Executable, MultiProgram, Program
8
8
  from codex.infra.pyproject import PyProjectToml
@@ -22,7 +22,9 @@ from codex.infra.workflows import (
22
22
  CheckCodeWorkflow,
23
23
  DependabotWorkflow,
24
24
  )
25
- from codex.infra.workflows.lock import CodexLockWorkflow
25
+
26
+ if TYPE_CHECKING:
27
+ from pathlib import Path
26
28
 
27
29
 
28
30
  @dataclass(frozen=True)
@@ -134,14 +136,12 @@ class CodexTools:
134
136
  def sync(self, force: bool) -> Executable:
135
137
  return (
136
138
  MultiProgram()
137
- .attach(PyProjectToml().sync(self.target))
138
139
  .attach(DependabotWorkflow().sync(self.target, force=force))
139
140
  .attach(CheckCodeWorkflow().sync(self.target, force=force))
140
141
  .attach(
141
142
  BuildCodeWorkflow(disclaimer=False).sync(self.target, force=force),
142
143
  when=self.target.joinpath("Dockerfile").exists(),
143
144
  )
144
- .attach(CodexLockWorkflow().sync(self.target, force=force))
145
145
  )
146
146
 
147
147
  def lock(self) -> Executable:
@@ -204,23 +204,27 @@ class CodexTools:
204
204
 
205
205
  def fix(self, unsafe: bool = False) -> Executable:
206
206
  return (
207
- PoetryRun()
207
+ MultiProgram()
208
+ .attach(PyProjectToml().sync(self.target))
208
209
  .attach(
209
- Program(
210
- name="black",
211
- command=Ruff(on=self.target).format(),
210
+ PoetryRun()
211
+ .attach(
212
+ Program(
213
+ name="black",
214
+ command=Ruff(on=self.target).format(),
215
+ )
212
216
  )
213
- )
214
- .attach(
215
- Program(
216
- name="ruff",
217
- command=(
218
- Ruff(on=self.target)
219
- .lint()
220
- .flag(fix=True)
221
- .flag(unsafe_fixes=unsafe)
217
+ .attach(
218
+ Program(
219
+ name="ruff",
220
+ command=(
221
+ Ruff(on=self.target)
222
+ .lint()
223
+ .flag(fix=True)
224
+ .flag(unsafe_fixes=unsafe)
225
+ ),
222
226
  ),
223
- ),
227
+ )
224
228
  )
225
229
  )
226
230
 
@@ -4,6 +4,7 @@ import sys
4
4
  from dataclasses import dataclass, field
5
5
  from importlib import metadata
6
6
  from pathlib import Path
7
+ from typing import TYPE_CHECKING
7
8
 
8
9
  from codex.infra.program import (
9
10
  BoundedProgram,
@@ -11,7 +12,9 @@ from codex.infra.program import (
11
12
  MultiProgram,
12
13
  Program,
13
14
  )
14
- from codex.infra.result import TaskResult
15
+
16
+ if TYPE_CHECKING:
17
+ from codex.infra.result import TaskResult
15
18
 
16
19
 
17
20
  @dataclass(frozen=True, kw_only=True)
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from textwrap import dedent
5
+ from typing import TYPE_CHECKING
6
+
7
+ import typer
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class TyperDevice:
15
+ color: str = typer.colors.RESET
16
+
17
+ def with_green(self) -> TyperDevice:
18
+ return TyperDevice(typer.colors.GREEN)
19
+
20
+ def with_red(self) -> TyperDevice:
21
+ return TyperDevice(typer.colors.RED)
22
+
23
+ def with_white(self) -> TyperDevice:
24
+ return TyperDevice(typer.colors.WHITE)
25
+
26
+ def with_yellow(self) -> TyperDevice:
27
+ return TyperDevice(typer.colors.YELLOW)
28
+
29
+ def echo(self, message: str) -> TyperDevice:
30
+ _Message.parse(message).display(using=lambda m: typer.secho(m, fg=self.color))
31
+
32
+ return self
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class _Message:
37
+ raw: str
38
+
39
+ @staticmethod
40
+ def parse(raw: str) -> _Message | _NoMessage:
41
+ message = dedent(raw)
42
+
43
+ return _Message(message) if message.strip() else _NoMessage()
44
+
45
+ def display(self, using: Callable[[str], None]) -> None:
46
+ using(self.raw)
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class _NoMessage:
51
+ def display(self, using: Callable[[str], None]) -> None:
52
+ _ = using
@@ -4,25 +4,10 @@ import typer
4
4
 
5
5
  from codex.infra.reporters import DefaultReporter, SimpleReporter
6
6
  from codex.infra.service import Codex, Git
7
- from codex.infra.workflows.lock import CodexLockWorkflow
8
7
 
9
8
  cli = typer.Typer(no_args_is_help=True)
10
9
 
11
10
 
12
- @cli.callback()
13
- def verify_codex_lock(ctx: typer.Context) -> None:
14
- if ctx.invoked_subcommand == "sync":
15
- return
16
-
17
- if CodexLockWorkflow().verify():
18
- return
19
-
20
- typer.echo("Codex is out of sync!")
21
- typer.echo("Run `codex sync` to bring configuration up to date")
22
-
23
- raise typer.Exit(1)
24
-
25
-
26
11
  @cli.command(
27
12
  name="version",
28
13
  help="Show the codex version and exit.",
@@ -3,13 +3,16 @@ from __future__ import annotations
3
3
  from abc import ABC, abstractmethod
4
4
  from dataclasses import dataclass
5
5
  from datetime import UTC, datetime
6
- from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
7
 
8
8
  from codex.infra.program import Executable, LazyExecutable
9
9
  from codex.infra.result import ProgramResult
10
10
  from codex.infra.tools import Version
11
11
  from codex.infra.yamler import Yamler
12
12
 
13
+ if TYPE_CHECKING:
14
+ from pathlib import Path
15
+
13
16
 
14
17
  @dataclass(frozen=True, kw_only=True)
15
18
  class YamlWorkflow(ABC):
@@ -43,14 +46,9 @@ class YamlWorkflow(ABC):
43
46
 
44
47
  standards.dump_to(path)
45
48
 
46
- return self.success()
49
+ return ProgramResult(name=self.name).workflow_success()
47
50
 
48
51
  return LazyExecutable(_)
49
52
 
50
53
  def _utcnow(self) -> str:
51
54
  return datetime.now(tz=UTC).replace(microsecond=0).isoformat(sep=" ")
52
-
53
- def success(self) -> ProgramResult:
54
- return ProgramResult(name=self.name).success(
55
- message=f"{self.name} workflow succeeded.\n"
56
- )
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import MutableMapping, MutableSequence
4
3
  from dataclasses import dataclass, field, replace
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from codex.infra.yamler import Yamler
7
7
 
8
+ if TYPE_CHECKING:
9
+ from collections.abc import MutableMapping, MutableSequence
10
+
8
11
 
9
12
  @dataclass(frozen=True)
10
13
  class CiWorkflow:
@@ -1,14 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Iterable, Mapping
4
3
  from dataclasses import dataclass
5
- from pathlib import Path
6
- from typing import Any
4
+ from typing import TYPE_CHECKING, Any
7
5
 
8
6
  from ruamel.yaml import YAML
9
7
  from ruamel.yaml.comments import CommentedMap
10
8
  from ruamel.yaml.scalarstring import FoldedScalarString
11
9
 
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Iterable, Mapping
12
+ from pathlib import Path
13
+
12
14
 
13
15
  @dataclass(frozen=True, kw_only=True)
14
16
  class Yamler:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "apexcodexpy"
3
- version = "0.7.2"
3
+ version = "0.7.4"
4
4
  description = "The Non-Destructive Configuration Steward for Python Projects."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -54,6 +54,23 @@ select = [
54
54
  "I",
55
55
  "N",
56
56
  "UP",
57
+ "RUF",
58
+ "FURB",
59
+ "PERF",
60
+ "LOG",
61
+ "PIE",
62
+ "SLF",
63
+ "DTZ",
64
+ "PTH",
65
+ "TRY",
66
+ "PL",
67
+ "TCH",
68
+ ]
69
+ ignore = [
70
+ "TRY003",
71
+ "PLR0913",
72
+ "PLR0917",
73
+ "PLR2004",
57
74
  ]
58
75
 
59
76
  [tool.ruff.lint.mccabe]
@@ -1,112 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field
4
- from pathlib import Path
5
- from typing import Any
6
-
7
- import tomlkit
8
-
9
- from codex.infra.program import Executable
10
- from codex.infra.result import ProgramResult
11
-
12
-
13
- def _mypy_config() -> dict[str, Any]:
14
- return {
15
- "strict": True,
16
- "ignore_missing_imports": True,
17
- "show_error_codes": True,
18
- "pretty": True,
19
- }
20
-
21
-
22
- def _ruff_config() -> dict[str, Any]:
23
- return {
24
- "lint": {
25
- "select": [
26
- "A",
27
- "ARG",
28
- "B",
29
- "C4",
30
- "PT",
31
- "RSE",
32
- "RET",
33
- "SIM",
34
- "T20",
35
- "E",
36
- "W",
37
- "F",
38
- "I",
39
- "N",
40
- "UP",
41
- ],
42
- "mccabe": {"max-complexity": 10},
43
- }
44
- }
45
-
46
-
47
- def _coverage_config() -> dict[str, Any]:
48
- return {
49
- "run": {
50
- "branch": True,
51
- },
52
- "report": {
53
- "skip_empty": True,
54
- "skip_covered": True,
55
- "show_missing": True,
56
- },
57
- }
58
-
59
-
60
- STANDARDS = {
61
- "tool": {
62
- "mypy": _mypy_config(),
63
- "ruff": _ruff_config(),
64
- "coverage": _coverage_config(),
65
- }
66
- }
67
-
68
-
69
- @dataclass(frozen=True)
70
- class PyProjectToml:
71
- _result: ProgramResult = field(init=False, default=ProgramResult(name="PyProject"))
72
-
73
- NAME = "pyproject.toml"
74
-
75
- def sync(self, target: Path) -> Executable:
76
- return self._sync(self.toml_on(target))
77
-
78
- def _sync(self, pyproject: Path) -> Executable:
79
- if not pyproject.exists():
80
- return self._result.fail(message=f"{self.NAME} not found.")
81
-
82
- document = tomlkit.parse(pyproject.read_text(encoding="utf-8"))
83
- original = tomlkit.dumps(document)
84
- deep_merge(STANDARDS, document)
85
- modified = tomlkit.dumps(document)
86
-
87
- if original == modified:
88
- return self._result.no_changes()
89
-
90
- pyproject.write_text(modified, encoding="utf-8", newline="\n")
91
-
92
- return self._result.success(message=f"{self.NAME} synchronized successfully.\n")
93
-
94
- def toml_on(self, path: Path) -> Path:
95
- return path / self.NAME
96
-
97
-
98
- def deep_merge(source: dict[str, Any], destination: dict[str, Any]) -> None:
99
- """Recursively merges the source into destination with TOML formatting."""
100
- for key, value in source.items():
101
- match value:
102
- case dict():
103
- if key not in destination:
104
- destination[key] = tomlkit.table()
105
- deep_merge(value, destination[key])
106
- case list():
107
- new_array = tomlkit.array()
108
- new_array.extend(value)
109
- new_array.multiline(True)
110
- destination[key] = new_array
111
- case _:
112
- destination[key] = value
@@ -1,28 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
-
5
- import typer
6
-
7
-
8
- @dataclass(frozen=True)
9
- class TyperDevice:
10
- color: str = typer.colors.RESET
11
-
12
- def with_green(self) -> TyperDevice:
13
- return TyperDevice(typer.colors.GREEN)
14
-
15
- def with_red(self) -> TyperDevice:
16
- return TyperDevice(typer.colors.RED)
17
-
18
- def with_white(self) -> TyperDevice:
19
- return TyperDevice(typer.colors.WHITE)
20
-
21
- def with_yellow(self) -> TyperDevice:
22
- return TyperDevice(typer.colors.YELLOW)
23
-
24
- def echo(self, message: str) -> TyperDevice:
25
- if message.strip():
26
- typer.secho(message, fg=self.color)
27
-
28
- return self
@@ -1,33 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
-
7
- from codex.infra.yamler import Yamler
8
-
9
- from .base import YamlWorkflow
10
-
11
-
12
- def hash_of(path: Path) -> str:
13
- with path.open("rb") as f:
14
- return hashlib.file_digest(f, hashlib.sha256).hexdigest()
15
-
16
-
17
- @dataclass(frozen=True, kw_only=True)
18
- class CodexLockWorkflow(YamlWorkflow):
19
- name: str = "CodexLock"
20
- path: Path = Path("codex.lock")
21
-
22
- @property
23
- def standards(self) -> Yamler:
24
- return Yamler.parse(
25
- {
26
- "hashes": {
27
- "pyproject": hash_of(Path("pyproject.toml")),
28
- },
29
- }
30
- )
31
-
32
- def verify(self) -> bool:
33
- return self.path.exists() and self.standards == Yamler.read(self.path)
File without changes
File without changes
@@ -3,7 +3,7 @@ from .dependabot import DependabotWorkflow
3
3
  from .validate import CheckCodeWorkflow
4
4
 
5
5
  __all__ = [
6
- "DependabotWorkflow",
7
6
  "BuildCodeWorkflow",
8
7
  "CheckCodeWorkflow",
8
+ "DependabotWorkflow",
9
9
  ]
File without changes