apexdevkit 1.27.2__tar.gz → 1.28.1__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 (62) hide show
  1. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/PKG-INFO +2 -2
  2. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/connector.py +14 -19
  3. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/pyproject.toml +4 -13
  4. apexdevkit-1.27.2/pycodex/__init__.py +0 -0
  5. apexdevkit-1.27.2/pycodex/cli.py +0 -120
  6. apexdevkit-1.27.2/pycodex/reporters.py +0 -85
  7. apexdevkit-1.27.2/pycodex/service.py +0 -31
  8. apexdevkit-1.27.2/pycodex/tasks/__init__.py +0 -10
  9. apexdevkit-1.27.2/pycodex/tasks/result.py +0 -31
  10. apexdevkit-1.27.2/pycodex/tasks/shell.py +0 -86
  11. apexdevkit-1.27.2/pycodex/tasks/update.py +0 -117
  12. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/LICENSE +0 -0
  13. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/README.md +0 -0
  14. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/__init__.py +0 -0
  15. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/annotation/__init__.py +0 -0
  16. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/annotation/deprecate.py +0 -0
  17. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/date.py +0 -0
  18. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/environment.py +0 -0
  19. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/error.py +0 -0
  20. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/__init__.py +0 -0
  21. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/builder.py +0 -0
  22. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/dependable.py +0 -0
  23. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/docs.py +0 -0
  24. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/name.py +0 -0
  25. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/resource.py +0 -0
  26. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/response.py +0 -0
  27. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/router.py +0 -0
  28. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/schema.py +0 -0
  29. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fastapi/service.py +0 -0
  30. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/fluent.py +0 -0
  31. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/formatter.py +0 -0
  32. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/__init__.py +0 -0
  33. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/fake.py +0 -0
  34. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/fluent.py +0 -0
  35. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/httpx/__init__.py +0 -0
  36. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/httpx/client.py +0 -0
  37. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/httpx/hooks.py +0 -0
  38. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/json.py +0 -0
  39. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/http/url.py +0 -0
  40. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/id.py +0 -0
  41. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/key_fn.py +0 -0
  42. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/py.typed +0 -0
  43. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/query/__init__.py +0 -0
  44. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/query/generator.py +0 -0
  45. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/query/query.py +0 -0
  46. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/__init__.py +0 -0
  47. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/base.py +0 -0
  48. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/database.py +0 -0
  49. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/decorator.py +0 -0
  50. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/in_memory.py +0 -0
  51. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/interface.py +0 -0
  52. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/mssql.py +0 -0
  53. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/repository.py +0 -0
  54. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/sql.py +0 -0
  55. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/repository/sqlite.py +0 -0
  56. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/server.py +0 -0
  57. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/synchronization.py +0 -0
  58. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/testing/__init__.py +0 -0
  59. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/testing/database.py +0 -0
  60. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/testing/fake.py +0 -0
  61. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/testing/rest.py +0 -0
  62. {apexdevkit-1.27.2 → apexdevkit-1.28.1}/apexdevkit/value.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apexdevkit
3
- Version: 1.27.2
3
+ Version: 1.28.1
4
4
  Summary: Apex Development Tools for python.
5
5
  License-File: LICENSE
6
6
  Author: Apex Dev
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.13
13
13
  Classifier: Programming Language :: Python :: 3.14
14
14
  Requires-Dist: fastapi
15
15
  Requires-Dist: httpx
16
- Requires-Dist: pymssql (==2.3.2)
16
+ Requires-Dist: pymssql (==2.*)
17
17
  Requires-Dist: python-dotenv
18
18
  Requires-Dist: sentry-sdk[fastapi]
19
19
  Requires-Dist: tomlkit
@@ -7,8 +7,6 @@ from functools import cached_property
7
7
  from typing import Any
8
8
 
9
9
  import pymssql
10
- from pymssql import Connection as _Connection
11
- from pymssql import Cursor
12
10
 
13
11
  from apexdevkit.repository import Connection
14
12
 
@@ -46,26 +44,23 @@ class MsSqlConnector:
46
44
  db_password: str
47
45
  db_name: str
48
46
  db_tds_version = "7.0"
49
- db_port: str | None = None
47
+ db_port: str = "1433"
50
48
 
51
49
  def connect(self) -> AbstractContextManager[Connection]:
52
50
  return ConnectionContextManager(self._connection())
53
51
 
54
52
  def _connection(self) -> Connection:
55
- connection_params = {
56
- "tds_version": self.db_tds_version,
57
- "server": self.db_host,
58
- "user": self.db_user,
59
- "password": self.db_password,
60
- "database": self.db_name,
61
- "as_dict": True,
62
- "autocommit": True,
63
- }
64
-
65
- if self.db_port is not None:
66
- connection_params["port"] = self.db_port
67
-
68
- return MsSqlConnectionAdapter(pymssql.connect(**connection_params))
53
+ return MsSqlConnectionAdapter(
54
+ pymssql.connect(
55
+ server=self.db_host,
56
+ user=self.db_user,
57
+ password=self.db_password,
58
+ database=self.db_name,
59
+ tds_version=self.db_tds_version,
60
+ as_dict=True,
61
+ autocommit=True,
62
+ )
63
+ )
69
64
 
70
65
 
71
66
  class ConnectionContextManager(AbstractContextManager[Connection]):
@@ -82,7 +77,7 @@ class ConnectionContextManager(AbstractContextManager[Connection]):
82
77
 
83
78
  @dataclass
84
79
  class MsSqlConnectionAdapter:
85
- connection: _Connection
80
+ connection: pymssql.Connection[pymssql.DictRow]
86
81
 
87
82
  def cursor(self) -> MsSqlCursorAdapter:
88
83
  return MsSqlCursorAdapter(self.connection.cursor())
@@ -93,7 +88,7 @@ class MsSqlConnectionAdapter:
93
88
 
94
89
  @dataclass
95
90
  class MsSqlCursorAdapter:
96
- cursor: Cursor
91
+ cursor: pymssql.Cursor[pymssql.DictRow]
97
92
 
98
93
  def execute(self, *args: Any, **kwargs: Any) -> Any:
99
94
  self.cursor.execute(*args, **kwargs)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "apexdevkit"
3
- version = "1.27.2"
3
+ version = "1.28.1"
4
4
  description = "Apex Development Tools for python."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -9,22 +9,13 @@ authors = [
9
9
  dynamic = ["dependencies"]
10
10
  requires-python = ">=3.11"
11
11
 
12
- [project.scripts]
13
- codex = "pycodex.cli:cli"
14
-
15
- [tool.poetry]
16
- packages = [
17
- { include = "apexdevkit" },
18
- { include = "pycodex" }
19
- ]
20
-
21
12
  [tool.poetry.dependencies]
22
13
  httpx = "*"
23
14
  fastapi = "*"
24
15
  uvicorn = "*"
25
16
  sentry-sdk = { extras = ["fastapi"], version = "*" }
26
17
  python-dotenv = "*"
27
- pymssql = "2.3.2"
18
+ pymssql = "2.*"
28
19
  typer = "*"
29
20
  tomlkit = "*"
30
21
 
@@ -37,9 +28,9 @@ pytest-recording = "*"
37
28
  coverage = "*"
38
29
  faker = "*"
39
30
 
31
+
40
32
  [tool.poetry.group.lint.dependencies]
41
- mypy = "*"
42
- ruff = "*"
33
+ apexcodexpy = "*"
43
34
 
44
35
  [tool.mypy]
45
36
  ignore_missing_imports = true
File without changes
@@ -1,120 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from pathlib import Path
5
-
6
- import typer
7
-
8
- from pycodex.reporters import DefaultReporter
9
- from pycodex.service import PyCodex
10
-
11
- cli = typer.Typer(add_completion=False)
12
-
13
-
14
- @cli.command(
15
- name="lint",
16
- help="Run linting (Ruff) and type checking (MyPy).",
17
- )
18
- def _(
19
- path: str = typer.Option(
20
- ".",
21
- "--path",
22
- "-p",
23
- help="Target directory.",
24
- ),
25
- silent: bool = typer.Option(
26
- False,
27
- "--silent",
28
- "-s",
29
- help="Suppress output.",
30
- ),
31
- ) -> None:
32
- raise typer.Exit(
33
- PyCodex(
34
- target=Path(path),
35
- reporter=DefaultReporter.using(TyperDevice()).silenced().when(silent),
36
- ).lint()
37
- )
38
-
39
-
40
- @cli.command(
41
- name="fix",
42
- help="Automatically fix Ruff linting errors.",
43
- )
44
- def _(
45
- path: str = typer.Option(
46
- ".",
47
- "--path",
48
- "-p",
49
- help="Target directory.",
50
- ),
51
- silent: bool = typer.Option(
52
- False,
53
- "--silent",
54
- "-s",
55
- help="Suppress output.",
56
- ),
57
- unsafe: bool = typer.Option(
58
- False,
59
- "--unsafe",
60
- help="Run unsafe Ruff fixes.",
61
- ),
62
- ) -> None:
63
- raise typer.Exit(
64
- PyCodex(
65
- target=Path(path),
66
- reporter=DefaultReporter.using(TyperDevice()).silenced().when(silent),
67
- ).fix(unsafe=unsafe)
68
- )
69
-
70
-
71
- @cli.command(
72
- name="sync",
73
- help="Inject pycodex standards into pyproject TOML file.",
74
- )
75
- def _(
76
- path: str = typer.Option(
77
- ".",
78
- "--path",
79
- "-p",
80
- help="Target directory.",
81
- ),
82
- dry_run: bool = typer.Option(
83
- False,
84
- "--dry-run",
85
- help="Show changes without applying them.",
86
- ),
87
- ) -> None:
88
- raise typer.Exit(
89
- PyCodex(
90
- target=Path(path),
91
- reporter=DefaultReporter.using(TyperDevice()),
92
- ).sync(dry_run=dry_run)
93
- )
94
-
95
-
96
- @dataclass(frozen=True)
97
- class TyperDevice:
98
- color: str = typer.colors.RESET
99
-
100
- def with_green(self) -> TyperDevice:
101
- return TyperDevice(typer.colors.GREEN)
102
-
103
- def with_red(self) -> TyperDevice:
104
- return TyperDevice(typer.colors.RED)
105
-
106
- def with_white(self) -> TyperDevice:
107
- return TyperDevice(typer.colors.WHITE)
108
-
109
- def with_yellow(self) -> TyperDevice:
110
- return TyperDevice(typer.colors.YELLOW)
111
-
112
- def echo(self, message: str) -> TyperDevice:
113
- if message.strip():
114
- typer.secho(message, fg=self.color)
115
-
116
- return self
117
-
118
-
119
- if __name__ == "__main__":
120
- cli()
@@ -1,85 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import Protocol
5
-
6
- from pycodex.tasks import TaskResult
7
-
8
-
9
- class Reporter(Protocol):
10
- def report(self, **results: TaskResult) -> int:
11
- pass
12
-
13
-
14
- @dataclass(frozen=True)
15
- class DefaultReporter:
16
- device: _Device
17
-
18
- @classmethod
19
- def using(cls, device: _Device) -> DefaultReporter:
20
- return cls(device)
21
-
22
- def silenced(self) -> _Silenced:
23
- return _Silenced(self)
24
-
25
- def report(self, **results: TaskResult) -> int:
26
- return sum(
27
- self.report_one(name.capitalize(), result)
28
- for name, result in results.items()
29
- )
30
-
31
- def report_one(self, name: str, result: TaskResult) -> int:
32
- if result:
33
- self.device.with_green().echo(f"✅ {name} passed!")
34
- else:
35
- self.device.with_red().echo(f"❌ {name} failed!")
36
-
37
- self.device.with_white().echo(result.stdout)
38
- self.device.with_yellow().echo(result.stderr)
39
-
40
- return result.exit_code
41
-
42
-
43
- class _Device(Protocol):
44
- def with_green(self) -> _Device:
45
- pass
46
-
47
- def with_red(self) -> _Device:
48
- pass
49
-
50
- def with_white(self) -> _Device:
51
- pass
52
-
53
- def with_yellow(self) -> _Device:
54
- pass
55
-
56
- def echo(self, message: str) -> _Device:
57
- pass
58
-
59
-
60
- @dataclass(frozen=True)
61
- class _Silenced:
62
- reporter: Reporter
63
-
64
- def when(self, silenced: bool) -> Reporter:
65
- return self.reporter if not silenced else DefaultReporter.using(_NoDevice())
66
-
67
-
68
- @dataclass(frozen=True)
69
- class _NoDevice:
70
- def with_green(self) -> _Device:
71
- return self
72
-
73
- def with_red(self) -> _Device:
74
- return self
75
-
76
- def with_white(self) -> _Device:
77
- return self
78
-
79
- def with_yellow(self) -> _Device:
80
- return self
81
-
82
- def echo(self, message: str) -> _Device:
83
- _ = message # suppresses IDE warning for unused argument
84
-
85
- return self
@@ -1,31 +0,0 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
-
4
- from pycodex.reporters import Reporter
5
- from pycodex.tasks import RunMypy, RunRuffCheck, SyncToml
6
- from pycodex.tasks.shell import RunPoetryCheck, RunRuffFormat
7
-
8
-
9
- @dataclass(frozen=True)
10
- class PyCodex:
11
- target: Path
12
- reporter: Reporter
13
-
14
- def sync(self, dry_run: bool = False) -> int:
15
- return self.reporter.report(
16
- sync=SyncToml(of=self.target, dry_run=dry_run).run()
17
- )
18
-
19
- def lint(self) -> int:
20
- return self.reporter.report(
21
- poetry=RunPoetryCheck(on=self.target).run(),
22
- black=RunRuffFormat(on=self.target, check=True).run(),
23
- ruff=RunRuffCheck(on=self.target).run(),
24
- mypy=RunMypy(on=self.target).run(),
25
- )
26
-
27
- def fix(self, unsafe: bool = False) -> int:
28
- return self.reporter.report(
29
- black=RunRuffFormat(on=self.target).run(),
30
- ruff=RunRuffCheck(on=self.target, fix=True, unsafe=unsafe).run(),
31
- )
@@ -1,10 +0,0 @@
1
- from pycodex.tasks.result import TaskResult
2
- from pycodex.tasks.shell import RunMypy, RunRuffCheck
3
- from pycodex.tasks.update import SyncToml
4
-
5
- __all__ = [
6
- "TaskResult",
7
- "RunMypy",
8
- "RunRuffCheck",
9
- "SyncToml",
10
- ]
@@ -1,31 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from subprocess import CompletedProcess
5
- from typing import Any
6
-
7
-
8
- @dataclass(frozen=True, kw_only=True)
9
- class TaskResult:
10
- stdout: str
11
- stderr: str
12
- exit_code: int
13
-
14
- def __bool__(self) -> bool:
15
- return self.exit_code == 0
16
-
17
- @classmethod
18
- def parse(cls, result: CompletedProcess[Any]) -> TaskResult:
19
- return cls(
20
- stdout=result.stdout,
21
- stderr=result.stderr,
22
- exit_code=result.returncode,
23
- )
24
-
25
- @classmethod
26
- def success(cls, *, stdout: str = "", stderr: str = "") -> TaskResult:
27
- return cls(stdout=stdout, stderr=stderr, exit_code=0)
28
-
29
- @classmethod
30
- def fail(cls, *, stdout: str = "", stderr: str = "") -> TaskResult:
31
- return cls(stdout=stdout, stderr=stderr, exit_code=1)
@@ -1,86 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import subprocess
4
- from abc import ABC, abstractmethod
5
- from collections.abc import Iterable
6
- from dataclasses import dataclass
7
- from pathlib import Path
8
- from subprocess import CompletedProcess
9
-
10
- from pycodex.tasks.result import TaskResult
11
-
12
-
13
- class ShellTask(ABC):
14
- def run(self) -> TaskResult:
15
- return TaskResult.parse(self._execute())
16
-
17
- def _execute(self) -> CompletedProcess[str]:
18
- return subprocess.run(
19
- list(self._command),
20
- text=True,
21
- encoding="utf-8",
22
- capture_output=True,
23
- )
24
-
25
- @property
26
- @abstractmethod
27
- def _command(self) -> Iterable[str]:
28
- pass
29
-
30
-
31
- @dataclass(frozen=True)
32
- class RunRuffCheck(ShellTask):
33
- on: Path
34
-
35
- fix: bool = False
36
- unsafe: bool = False
37
-
38
- @property
39
- def _command(self) -> Iterable[str]:
40
- yield "ruff"
41
- yield "check"
42
- yield str(self.on)
43
-
44
- if self.fix:
45
- yield "--fix"
46
-
47
- if self.unsafe:
48
- yield "--unsafe-fixes"
49
-
50
-
51
- @dataclass(frozen=True)
52
- class RunRuffFormat(ShellTask):
53
- on: Path
54
-
55
- check: bool = False
56
-
57
- @property
58
- def _command(self) -> Iterable[str]:
59
- yield "ruff"
60
- yield "format"
61
- yield str(self.on)
62
-
63
- if self.check:
64
- yield "--check"
65
-
66
-
67
- @dataclass(frozen=True)
68
- class RunMypy(ShellTask):
69
- on: Path
70
-
71
- @property
72
- def _command(self) -> Iterable[str]:
73
- yield "mypy"
74
- yield str(self.on)
75
-
76
-
77
- @dataclass(frozen=True)
78
- class RunPoetryCheck(ShellTask):
79
- on: Path
80
-
81
- @property
82
- def _command(self) -> Iterable[str]:
83
- yield "poetry"
84
- yield "check"
85
- yield "--strict"
86
- yield f"--project={self.on}"
@@ -1,117 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from functools import cached_property
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- import tomlkit
9
-
10
- from pycodex.tasks.result import TaskResult
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 SyncToml:
71
- of: Path
72
-
73
- dry_run: bool = False
74
-
75
- @cached_property
76
- def _toml(self) -> Path:
77
- return self.of / "pyproject.toml"
78
-
79
- def run(self) -> TaskResult:
80
- if not self._toml.exists():
81
- return TaskResult.fail(stderr=f"{self._toml.name} not found.")
82
-
83
- document = tomlkit.parse(self._toml.read_text(encoding="utf-8"))
84
-
85
- # Create a copy for comparison if dry-run
86
- original = tomlkit.dumps(document)
87
- deep_merge(STANDARDS, document)
88
- modified = tomlkit.dumps(document)
89
-
90
- if original == modified:
91
- return TaskResult.success(stdout="Configuration is already up to date.")
92
-
93
- if self.dry_run:
94
- return TaskResult.success(stdout=f"Changes would be applied:\n{modified}")
95
-
96
- self._toml.write_text(modified, encoding="utf-8", newline="\n")
97
-
98
- return TaskResult.success(stdout="Configuration synchronized successfully.")
99
-
100
-
101
- def deep_merge(source: dict[str, Any], destination: dict[str, Any]) -> None:
102
- """Recursively merges the source into destination with TOML formatting."""
103
- for key, value in source.items():
104
- if isinstance(value, dict):
105
- # Create a table if the key doesn't exist
106
- if key not in destination:
107
- destination[key] = tomlkit.table()
108
- deep_merge(value, destination[key])
109
- elif isinstance(value, list):
110
- # Create a multiline array
111
- new_array = tomlkit.array()
112
- for item in value:
113
- new_array.append(item)
114
- new_array.multiline(True)
115
- destination[key] = new_array
116
- else:
117
- destination[key] = value
File without changes
File without changes