docker-devtools 0.0.1__py3-none-win_amd64.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.
@@ -0,0 +1,198 @@
1
+ """Work on the Dockerfiles, Compose files and build context in a repository.
2
+
3
+ This package bundles the ``docker-devtools`` binary and wraps its JSON output in
4
+ typed dataclasses. The work happens in Go, against the same libraries BuildKit
5
+ uses, so the results match what ``docker build`` would do.
6
+
7
+ >>> from docker_devtools import image_ls
8
+ >>> for ref in image_ls("testdata").refs:
9
+ ... print(ref.path, ref.line, ref.raw)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import subprocess
16
+ from dataclasses import dataclass
17
+ from dataclasses import field
18
+ from typing import TYPE_CHECKING
19
+ from typing import Any
20
+ from typing import Literal
21
+
22
+ from docker_devtools._find import BINARY_ENV_VAR
23
+ from docker_devtools._find import BINARY_NAME
24
+ from docker_devtools._find import BinaryNotFoundError
25
+ from docker_devtools._find import find_binary
26
+
27
+ if TYPE_CHECKING:
28
+ from collections.abc import Sequence
29
+
30
+ __all__ = [
31
+ "BINARY_ENV_VAR",
32
+ "BINARY_NAME",
33
+ "IMAGE_SCHEMA_VERSION",
34
+ "BinaryNotFoundError",
35
+ "Change",
36
+ "ImageRef",
37
+ "ImageResult",
38
+ "UpdateReport",
39
+ "find_binary",
40
+ "image_ls",
41
+ "image_update",
42
+ ]
43
+
44
+ #: The image-scan schema this wrapper understands. A mismatch means the binary
45
+ #: and the Python half are out of step.
46
+ IMAGE_SCHEMA_VERSION = 1
47
+
48
+ TagPolicy = Literal["same-pattern", "minor", "patch", "latest"]
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class ImageRef:
53
+ """One image reference and where it sits."""
54
+
55
+ path: str
56
+ line: int
57
+ kind: str
58
+ raw: str
59
+ resolved: bool
60
+ registry: str | None = None
61
+ repository: str | None = None
62
+ tag: str | None = None
63
+ digest: str | None = None
64
+ stage: str | None = None
65
+ note: str | None = None
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class ImageResult:
70
+ """Every reference found by a scan."""
71
+
72
+ schema: int
73
+ refs: tuple[ImageRef, ...]
74
+ warnings: tuple[str, ...] = ()
75
+
76
+ def resolved(self) -> tuple[ImageRef, ...]:
77
+ """Return only the references that name a real image."""
78
+ return tuple(r for r in self.refs if r.resolved)
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class Change:
83
+ """One reference an update would rewrite."""
84
+
85
+ path: str
86
+ line: int
87
+ old: str
88
+ new: str
89
+ reason: str
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class UpdateReport:
94
+ """The plan an update produced."""
95
+
96
+ schema: int
97
+ changes: tuple[Change, ...]
98
+ skipped: int = 0
99
+ warnings: tuple[str, ...] = field(default=())
100
+
101
+
102
+ def image_ls(*paths: str) -> ImageResult:
103
+ """List every image reference under ``paths``.
104
+
105
+ Args:
106
+ paths: files or directories. Defaults to the working directory.
107
+
108
+ Returns:
109
+ The parsed scan.
110
+ """
111
+ data = _run(["image", "ls", "--json", *paths])
112
+ _check_schema(data.get("schema"))
113
+ return ImageResult(
114
+ schema=data["schema"],
115
+ refs=tuple(
116
+ ImageRef(
117
+ path=r["path"],
118
+ line=r["line"],
119
+ kind=r["kind"],
120
+ raw=r["raw"],
121
+ resolved=r["resolved"],
122
+ registry=r.get("registry"),
123
+ repository=r.get("repository"),
124
+ tag=r.get("tag"),
125
+ digest=r.get("digest"),
126
+ stage=r.get("stage"),
127
+ note=r.get("note"),
128
+ )
129
+ for r in data["refs"]
130
+ ),
131
+ warnings=tuple(data.get("warnings") or ()),
132
+ )
133
+
134
+
135
+ def image_update(
136
+ *paths: str,
137
+ pin_digest: bool = False,
138
+ tag_policy: TagPolicy | None = None,
139
+ dry_run: bool = True,
140
+ ) -> UpdateReport:
141
+ """Plan, and optionally apply, changes to image references.
142
+
143
+ ``dry_run`` defaults to True so that calling this by accident cannot
144
+ rewrite a repository.
145
+
146
+ Args:
147
+ paths: files or directories. Defaults to the working directory.
148
+ pin_digest: append or refresh the ``@sha256`` digest.
149
+ tag_policy: how far a tag may move. None leaves tags alone.
150
+ dry_run: report without writing.
151
+
152
+ Returns:
153
+ The plan, whether or not it was applied.
154
+ """
155
+ args = ["image", "update", "--json"]
156
+ if pin_digest:
157
+ args.append("--pin-digest")
158
+ if tag_policy is not None:
159
+ args += ["--tag-policy", tag_policy]
160
+ if dry_run:
161
+ args.append("--dry-run")
162
+ args += list(paths)
163
+
164
+ data = _run(args)
165
+ _check_schema(data.get("schema"))
166
+ return UpdateReport(
167
+ schema=data["schema"],
168
+ changes=tuple(
169
+ Change(path=c["path"], line=c["line"], old=c["old"], new=c["new"], reason=c["reason"])
170
+ for c in data["changes"]
171
+ ),
172
+ skipped=data.get("skipped", 0),
173
+ warnings=tuple(data.get("warnings") or ()),
174
+ )
175
+
176
+
177
+ def _run(args: Sequence[str]) -> dict[str, Any]:
178
+ binary = find_binary()
179
+ proc = subprocess.run( # noqa: S603
180
+ [binary, *args],
181
+ capture_output=True,
182
+ text=True,
183
+ check=False,
184
+ )
185
+ if proc.returncode != 0:
186
+ msg = f"{BINARY_NAME} {' '.join(args)} failed: {proc.stderr.strip()}"
187
+ raise RuntimeError(msg)
188
+ result: dict[str, Any] = json.loads(proc.stdout)
189
+ return result
190
+
191
+
192
+ def _check_schema(schema: object) -> None:
193
+ if schema != IMAGE_SCHEMA_VERSION:
194
+ msg = (
195
+ f"{BINARY_NAME} emitted schema {schema!r}, but this package understands "
196
+ f"{IMAGE_SCHEMA_VERSION}. The binary and the Python wrapper are out of step."
197
+ )
198
+ raise RuntimeError(msg)
@@ -0,0 +1,33 @@
1
+ """Run the bundled binary via ``python -m docker_devtools``.
2
+
3
+ The binary is normally on PATH already, since the wheel installs it into the
4
+ environment's scripts directory. This module exists for the cases where it is
5
+ not, such as an environment whose scripts directory is not on PATH.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import subprocess
12
+ import sys
13
+
14
+ from docker_devtools._find import find_binary
15
+
16
+
17
+ def _run() -> None:
18
+ binary = find_binary()
19
+ if sys.platform == "win32":
20
+ # Windows has no exec that replaces the process cleanly, and a
21
+ # KeyboardInterrupt here would print a traceback over the child's own
22
+ # output.
23
+ try:
24
+ completed = subprocess.run([binary, *sys.argv[1:]], check=False) # noqa: S603
25
+ except KeyboardInterrupt:
26
+ sys.exit(130)
27
+ sys.exit(completed.returncode)
28
+ else:
29
+ os.execv(binary, [binary, *sys.argv[1:]]) # noqa: S606
30
+
31
+
32
+ if __name__ == "__main__":
33
+ _run()
@@ -0,0 +1,116 @@
1
+ """Locate the bundled docker-devtools binary.
2
+
3
+ The search order is adapted from Astral's ``uv`` package (``python/uv/_find_uv.py``,
4
+ MIT OR Apache-2.0). The binary ships in the wheel's ``.data/scripts`` directory,
5
+ which installs into the environment's scripts directory rather than next to this
6
+ module, and that directory moves depending on how the wheel was installed --
7
+ ``pip install --target``, ``--prefix``, the user scheme, or ``uv run --with``.
8
+ A naive ``os.path.dirname(__file__)`` lookup gets all of those wrong.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ import sysconfig
16
+ from fnmatch import fnmatch
17
+
18
+ BINARY_NAME = "docker-devtools"
19
+
20
+ #: Set this to an absolute path to use a specific binary instead of searching.
21
+ #: Useful when running against a locally built binary, and as an escape hatch
22
+ #: for install layouts the search below does not anticipate.
23
+ BINARY_ENV_VAR = "DOCKER_DEVTOOLS_BINARY"
24
+
25
+
26
+ class BinaryNotFoundError(FileNotFoundError):
27
+ """The bundled binary could not be located."""
28
+
29
+
30
+ def find_binary() -> str:
31
+ """Return the path to the bundled docker-devtools binary.
32
+
33
+ Raises:
34
+ BinaryNotFoundError: if no candidate directory holds the binary.
35
+ """
36
+ override = os.environ.get(BINARY_ENV_VAR)
37
+ if override:
38
+ if not os.path.isfile(override):
39
+ msg = f"{BINARY_ENV_VAR} points at {override!r}, which is not a file"
40
+ raise BinaryNotFoundError(msg)
41
+ return override
42
+
43
+ exe = BINARY_NAME + (sysconfig.get_config_var("EXE") or "")
44
+
45
+ targets = [
46
+ # The scripts directory for the current interpreter.
47
+ sysconfig.get_path("scripts"),
48
+ # The scripts directory for the base prefix, for virtualenvs.
49
+ sysconfig.get_path("scripts", vars={"base": sys.base_prefix}),
50
+ # Above the package root, e.g. `pip install --prefix` or `uv run --with`.
51
+ (
52
+ _join(
53
+ _matching_parents(_module_path(), "Lib/site-packages/docker_devtools"),
54
+ "Scripts",
55
+ )
56
+ if sys.platform == "win32"
57
+ else _join(
58
+ _matching_parents(_module_path(), "lib/python*/site-packages/docker_devtools"),
59
+ "bin",
60
+ )
61
+ ),
62
+ # Adjacent to the package root, e.g. `pip install --target`.
63
+ _join(_matching_parents(_module_path(), "docker_devtools"), "bin"),
64
+ _matching_parents(_module_path(), "docker_devtools"),
65
+ # The user scheme's scripts directory, e.g. `~/.local/bin`.
66
+ sysconfig.get_path("scripts", scheme=_user_scheme()),
67
+ ]
68
+
69
+ seen: list[str] = []
70
+ for target in targets:
71
+ if not target or target in seen:
72
+ continue
73
+ seen.append(target)
74
+ path = os.path.join(target, exe)
75
+ if os.path.isfile(path):
76
+ return path
77
+
78
+ locations = "\n".join(f" - {target}" for target in seen)
79
+ msg = f"Could not find the {BINARY_NAME} binary in any of:\n{locations}\n"
80
+ raise BinaryNotFoundError(msg)
81
+
82
+
83
+ def _module_path() -> str:
84
+ return os.path.dirname(__file__)
85
+
86
+
87
+ def _matching_parents(path: str | None, match: str) -> str | None:
88
+ """Trim ``match`` off the end of ``path`` and return what is left.
89
+
90
+ ``match`` uses ``/`` separators and may contain ``*`` wildcards; ``path``
91
+ uses the platform separator. Components compare case-insensitively.
92
+ """
93
+ if not path:
94
+ return None
95
+ parts = path.split(os.sep)
96
+ match_parts = match.split("/")
97
+ if len(parts) < len(match_parts):
98
+ return None
99
+ if not all(
100
+ fnmatch(part, match_part)
101
+ for part, match_part in zip(reversed(parts), reversed(match_parts), strict=False)
102
+ ):
103
+ return None
104
+ return os.sep.join(parts[: -len(match_parts)])
105
+
106
+
107
+ def _join(path: str | None, *parts: str) -> str | None:
108
+ if not path:
109
+ return None
110
+ return os.path.join(path, *parts)
111
+
112
+
113
+ def _user_scheme() -> str:
114
+ # uv's original branches on sys.version_info here because it supports 3.8.
115
+ # This package requires 3.10, where get_preferred_scheme always exists.
116
+ return sysconfig.get_preferred_scheme("user")
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.1'
22
+ __version_tuple__ = version_tuple = (0, 0, 1)
23
+
24
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.5
2
+ Name: docker-devtools
3
+ Version: 0.0.1
4
+ Summary: Work on the Dockerfiles, Compose files and build context in a repository.
5
+ Project-URL: Documentation, https://github.com/FlavioAmurrioCS/docker-devtools#readme
6
+ Project-URL: Issues, https://github.com/FlavioAmurrioCS/docker-devtools/issues
7
+ Project-URL: Source, https://github.com/FlavioAmurrioCS/docker-devtools
8
+ Author-email: Flavio Amurrio <25621374+FlavioAmurrioCS@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: build-context,buildkit,docker,docker-compose,dockerfile,dockerignore,pre-commit
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Go
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
24
+ Classifier: Topic :: Software Development :: Build Tools
25
+ Classifier: Topic :: Utilities
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+
29
+ # docker-devtools
30
+
31
+ Work on the Docker files in a repository: the build context a Dockerfile would
32
+ send, and the image references it and your Compose files point at.
33
+
34
+ ```console
35
+ $ docker-devtools image ls
36
+ Dockerfile:1 python:3.11-slim
37
+ compose.yaml:3 nginx:1.25-alpine
38
+
39
+ $ docker-devtools image update --tag-policy same-pattern --dry-run
40
+ Dockerfile:1 python:3.11-slim -> python:3.14-slim (tag 3.11-slim -> 3.14-slim)
41
+ compose.yaml:3 nginx:1.25-alpine -> nginx:1.31-alpine (tag 1.25-alpine -> 1.31-alpine)
42
+ ```
43
+
44
+ ## Why another one
45
+
46
+ Renovate and Dependabot already update image references, and they do it well.
47
+ They run as bots against a repository and open pull requests. This one runs on
48
+ your machine and edits the files in place. It is fast enough for a pre-commit
49
+ hook, so a stale base image gets caught before it is ever committed.
50
+
51
+ Where the semantics are Docker's, this defers to Docker's own code:
52
+
53
+ | Step | Package |
54
+ | --- | --- |
55
+ | Parse Dockerfiles | `moby/buildkit/frontend/dockerfile/parser` and `instructions` |
56
+ | Parse image references | `google/go-containerregistry/pkg/name` |
57
+ | Talk to registries | `google/go-containerregistry/pkg/v1/remote` |
58
+ | Match .dockerignore rules | `moby/patternmatcher` |
59
+ | Walk a build context | `tonistiigi/fsutil`, the package BuildKit sends contexts with |
60
+
61
+ None of the `.dockerignore` semantics are reimplemented here, and CI checks
62
+ that rather than asserting it: for every fixture, `scripts/conformance.sh`
63
+ builds `FROM scratch` with `COPY . /`, exports the image as a tarball, and
64
+ diffs the tar members against what `context ls` reports.
65
+
66
+ ## Install
67
+
68
+ ```console
69
+ $ uvx docker-devtools image ls # no install
70
+ $ pipx run docker-devtools image ls # no install
71
+ $ uv tool install docker-devtools
72
+ $ pip install docker-devtools
73
+ $ mise use ubi:FlavioAmurrioCS/docker-devtools
74
+ ```
75
+
76
+ Prebuilt binaries are attached to each
77
+ [release](https://github.com/FlavioAmurrioCS/docker-devtools/releases). With a
78
+ Go toolchain:
79
+
80
+ ```console
81
+ $ go install github.com/FlavioAmurrioCS/docker-devtools/cmd/docker-devtools@latest
82
+ ```
83
+
84
+ Reading and updating files doesn't require a Docker installation or a running
85
+ daemon.
86
+ Registry lookups authenticate with the same `~/.docker/config.json` the docker
87
+ CLI uses.
88
+
89
+ ## Usage
90
+
91
+ ```text
92
+ docker-devtools context ls [PATH] list the files Docker would send
93
+ docker-devtools context explain PATH show which .dockerignore rule decided a path
94
+ docker-devtools image ls [PATH...] list every image reference, with file and line
95
+ docker-devtools image update [PATH...] rewrite references in place
96
+ docker-devtools install-docker-plugin register as "docker devtools"
97
+ ```
98
+
99
+ ### Updating image references
100
+
101
+ What changes is split by how much judgement it needs.
102
+
103
+ `--pin-digest` resolves the current tag to a digest and appends it, turning
104
+ `nginx:1.29` into `nginx:1.29@sha256:…`. It doesn't decide anything about versions, so it is
105
+ reversible and safe to run anywhere.
106
+
107
+ `--tag-policy` moves the tag. The default, `same-pattern`, moves only the last
108
+ component and keeps the suffix, so how specific your tag is decides how far it
109
+ may move:
110
+
111
+ | Current tag | same-pattern | minor | patch | latest |
112
+ | --- | --- | --- | --- | --- |
113
+ | `3.12-slim` | `3.13-slim` | `3.13-slim` | no change | `4.0-slim` |
114
+ | `3.12.1-slim` | `3.12.7-slim` | `3.13.0-slim` | `3.12.7-slim` | `4.0-slim` |
115
+ | `latest` | no change | no change | no change | no change |
116
+
117
+ No policy ever changes the suffix: `-alpine` and `-slim` are different images,
118
+ and swapping them would change your base distribution without saying so. Tags
119
+ with no version, such as `latest` or `bookworm`, are never moved, because there
120
+ is no ordering to move along.
121
+
122
+ Add `--dry-run` to see the plan without writing, and `--fail-on-diff` to exit
123
+ non-zero when anything would change, which is what makes it useful in CI.
124
+
125
+ ### What it will not touch
126
+
127
+ Some references cannot be resolved to an image, and those are reported rather
128
+ than guessed at. Pass `--unresolved` to `image ls` to see them:
129
+
130
+ - `FROM builder`, where `builder` is an earlier stage
131
+ - `COPY --from=0`, which indexes a stage
132
+ - `FROM $BASE`, which depends on a build argument
133
+ - `FROM scratch`, which is the empty base rather than a registry image
134
+ - Compose values built from variables, such as `${REGISTRY}/app:latest`
135
+
136
+ ### Editing in place
137
+
138
+ An update splices the new reference into the exact byte range the parser
139
+ reported. It never re-encodes the file, so comments, quoting style, anchors and
140
+ whitespace all survive:
141
+
142
+ ```yaml
143
+ image: "nginx:1.29-alpine" # keep this comment and the quotes
144
+ ```
145
+
146
+ becomes
147
+
148
+ ```yaml
149
+ image: "nginx:1.31-alpine" # keep this comment and the quotes
150
+ ```
151
+
152
+ If a byte range no longer holds the text the parse said it held, the update
153
+ fails instead of writing. A rewrite that has drifted from the parse is a bug,
154
+ and corrupting the file would hide it.
155
+
156
+ ## Shell completion
157
+
158
+ The binary emits a [usage](https://usage.jdx.dev) spec describing its own
159
+ command tree, and the `usage` CLI turns that into completions for bash, zsh,
160
+ fish, powershell and nushell:
161
+
162
+ ```console
163
+ $ mise use usage
164
+ $ usage g completion zsh docker-devtools --usage-cmd 'docker-devtools --usage-spec' --install
165
+ ```
166
+
167
+ The generated scripts call back to `usage` at completion time, so it has to stay
168
+ on your PATH. `mise run completions` regenerates all five, plus a markdown
169
+ reference, into `build/`.
170
+
171
+ ## As a Docker CLI plugin
172
+
173
+ ```console
174
+ $ docker-devtools install-docker-plugin
175
+ $ docker devtools image ls
176
+ ```
177
+
178
+ This symlinks the binary into `~/.docker/cli-plugins/`. Use `--system` to
179
+ install it for every user.
180
+
181
+ The subcommand is `devtools` because Docker validates plugin names against
182
+ `^[a-z][a-z0-9]*$` and refuses to load anything else. Python wheels cannot do
183
+ this step at install time: they have no post-install hook, and
184
+ `~/.docker/cli-plugins/` sits outside every Python install path.
185
+
186
+ ## Python API
187
+
188
+ The wheel bundles the binary and a typed wrapper.
189
+
190
+ ```python
191
+ from docker_devtools import image_ls
192
+ from docker_devtools import image_update
193
+
194
+ for ref in image_ls(".").resolved():
195
+ print(f"{ref.path}:{ref.line}", ref.repository, ref.tag)
196
+
197
+ report = image_update(".", pin_digest=True, dry_run=True)
198
+ for change in report.changes:
199
+ print(change.old, "->", change.new, f"({change.reason})")
200
+ ```
201
+
202
+ `image_update` defaults to `dry_run=True`, so calling it by accident cannot
203
+ rewrite a repository.
204
+
205
+ ## Development
206
+
207
+ `mise.toml` defines the tools and the tasks.
208
+
209
+ ```console
210
+ $ mise run build # compile into ./build
211
+ $ mise run test # go test + pytest
212
+ $ mise run lint # pre-commit across the repo
213
+ $ mise run conformance # diff context listing against real docker build
214
+ $ mise run completions # regenerate completions and docs
215
+ $ mise run wheels # every platform wheel into ./dist
216
+ $ mise run test-clone # verify a fresh clone in a container
217
+ ```
218
+
219
+ Registry behaviour is tested against `go-containerregistry`'s in-process
220
+ registry, so the suite doesn't touch the network or carry recorded fixtures.
221
+
222
+ ## License
223
+
224
+ MIT. See [LICENSE](LICENSE).
225
+
226
+ `src/docker_devtools/_find.py` adapts the binary-discovery search order from
227
+ [uv](https://github.com/astral-sh/uv), which is MIT OR Apache-2.0.
@@ -0,0 +1,10 @@
1
+ docker_devtools/__init__.py,sha256=9aT8SmaV-5c6agMYBr2V5Q484FnHJ4c_xOyHaXlgZ5I,5361
2
+ docker_devtools/__main__.py,sha256=Z69rv0HFIRO51fDaDbkJGcVjKBU5xLJy5JwBVLlHicY,977
3
+ docker_devtools/_find.py,sha256=05nOAAWz_wfte_oC2xgLAdbGibxK8Lh8x_HaXGlPP34,4098
4
+ docker_devtools/_version.py,sha256=8OsTLsIVB9D0HdPTmt5rVwyVUBe9xTVGkRslXicxzkM,520
5
+ docker_devtools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ docker_devtools-0.0.1.data/scripts/docker-devtools.exe,sha256=jX9xCryrgtIrDGc52o7IGC8NT2blvNVrnkb0CM587_0,12755968
7
+ docker_devtools-0.0.1.dist-info/METADATA,sha256=uuK1ZLszlCU6EPa0RCSPy931e-vFSS_mTK7FW5wshmw,8563
8
+ docker_devtools-0.0.1.dist-info/WHEEL,sha256=OA-gEgWbLnh0Tf6JrLOMFR4vOWY4QbBMSZuN6osy-Q0,94
9
+ docker_devtools-0.0.1.dist-info/licenses/LICENSE,sha256=fbYeK7h6gKfDaN_ycO18fIdTlYGsYJZypz-JAR_-ybA,1071
10
+ docker_devtools-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flavio Amurrio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.