qontract-development-cli 0.14.5__tar.gz → 0.15.2__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 (23) hide show
  1. qontract_development_cli-0.15.2/LICENSE +21 -0
  2. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/PKG-INFO +34 -23
  3. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/README.md +18 -4
  4. qontract_development_cli-0.15.2/pyproject.toml +101 -0
  5. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/cli.py +18 -11
  6. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/config.py +2 -8
  7. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/env.py +10 -7
  8. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/profile.py +114 -98
  9. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/completions.py +1 -4
  10. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/config.py +30 -24
  11. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/models.py +33 -32
  12. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/shell.py +11 -16
  13. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates.py +2 -4
  14. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/utils.py +3 -6
  15. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/watchdog.py +1 -5
  16. qontract_development_cli-0.14.5/pyproject.toml +0 -89
  17. qontract_development_cli-0.14.5/setup.py +0 -41
  18. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/__init__.py +0 -0
  19. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/__main__.py +0 -0
  20. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/__init__.py +0 -0
  21. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates/compose.override.yml.j2 +0 -0
  22. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates/compose.yml.j2 +0 -0
  23. {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates/prep-worktree.sh.j2 +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Christian Assing
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.
@@ -1,24 +1,22 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: qontract-development-cli
3
- Version: 0.14.5
3
+ Version: 0.15.2
4
4
  Summary: Helper tool for qontract-reconcile development
5
- Home-page: http://github.com/chassing/qontract-development-cli
5
+ Project-URL: homepage, http://github.com/chassing/qontract-development-cli
6
+ Project-URL: repository, http://github.com/chassing/qontract-development-cli
7
+ Project-URL: documentation, http://github.com/chassing/qontract-development-cli
8
+ Author-email: Christian Assing <cassing@redhat.com>
6
9
  License: MIT
7
- Author: Christian Assing
8
- Author-email: cassing@redhat.com
9
- Requires-Python: >=3.11,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.11
13
- Requires-Dist: Jinja2 (>=3.1.2,<4.0.0)
14
- Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
15
- Requires-Dist: appdirs (>=1.4.4,<2.0.0)
16
- Requires-Dist: getkey (>=0.6.5,<0.7.0)
17
- Requires-Dist: pydantic (>=1.10.2,<2.0.0)
18
- Requires-Dist: rich (>=13.7.0,<14.0.0)
19
- Requires-Dist: typer (>=0.9.0,<0.10.0)
20
- Requires-Dist: watchfiles (>=0.21.0,<0.22.0)
21
- Project-URL: Repository, http://github.com/chassing/qontract-development-cli
10
+ Requires-Python: >=3.11
11
+ Requires-Dist: appdirs~=1.4.4
12
+ Requires-Dist: getkey~=0.6.5
13
+ Requires-Dist: jinja2~=3.1
14
+ Requires-Dist: pydantic-settings~=2.6
15
+ Requires-Dist: pydantic~=2.9
16
+ Requires-Dist: pyyaml~=6.0.2
17
+ Requires-Dist: rich~=13.9
18
+ Requires-Dist: typer~=0.12.5
19
+ Requires-Dist: watchfiles~=0.24.0
22
20
  Description-Content-Type: text/markdown
23
21
 
24
22
  # Qontract Development CLI
@@ -31,7 +29,21 @@ Qontract Development CLI supports your daily [qontract-reconcile][qontract-recon
31
29
 
32
30
  [toc]
33
31
 
34
- ## Installation
32
+ ## Recommended Installation Method
33
+
34
+ The recommended way to install `qontract-development-cli` is to use the [uv](https://docs.astral.sh/uv/) tool:
35
+
36
+ ```shell
37
+ uv tool install qontract-development-cli
38
+ ```
39
+
40
+ and upgrade an existing installation with:
41
+
42
+ ```shell
43
+ uv tool upgrade qontract-development-cli
44
+ ```
45
+
46
+ ## Alternative Installation Methods
35
47
 
36
48
  You can install this library from [PyPI][pypi-link] with `pip`:
37
49
 
@@ -39,16 +51,16 @@ You can install this library from [PyPI][pypi-link] with `pip`:
39
51
  python3 -m pip install qontract-development-cli
40
52
  ```
41
53
 
42
- Or install it with `pipx`:
54
+ or install it with `pipx`:
43
55
 
44
56
  ```shell
45
57
  pipx install qontract-development-cli
46
58
  ```
47
59
 
48
- You can also use `pipx` to run the library without installing it:
60
+ and upgrade an existing installation with:
49
61
 
50
62
  ```shell
51
- pipx run qontract-development-cli
63
+ pipx upgrade qontract-development-cli
52
64
  ```
53
65
 
54
66
  ## Quickstart
@@ -251,4 +263,3 @@ Running this profile will:
251
263
  [pypi-link]: https://pypi.org/project/qontract-development-cli/
252
264
  [pypi-platforms]: https://img.shields.io/pypi/pyversions/qontract-development-cli
253
265
  [qontract-reconcile]: https://github.com/app-sre/qontract-reconcile
254
-
@@ -8,7 +8,21 @@ Qontract Development CLI supports your daily [qontract-reconcile][qontract-recon
8
8
 
9
9
  [toc]
10
10
 
11
- ## Installation
11
+ ## Recommended Installation Method
12
+
13
+ The recommended way to install `qontract-development-cli` is to use the [uv](https://docs.astral.sh/uv/) tool:
14
+
15
+ ```shell
16
+ uv tool install qontract-development-cli
17
+ ```
18
+
19
+ and upgrade an existing installation with:
20
+
21
+ ```shell
22
+ uv tool upgrade qontract-development-cli
23
+ ```
24
+
25
+ ## Alternative Installation Methods
12
26
 
13
27
  You can install this library from [PyPI][pypi-link] with `pip`:
14
28
 
@@ -16,16 +30,16 @@ You can install this library from [PyPI][pypi-link] with `pip`:
16
30
  python3 -m pip install qontract-development-cli
17
31
  ```
18
32
 
19
- Or install it with `pipx`:
33
+ or install it with `pipx`:
20
34
 
21
35
  ```shell
22
36
  pipx install qontract-development-cli
23
37
  ```
24
38
 
25
- You can also use `pipx` to run the library without installing it:
39
+ and upgrade an existing installation with:
26
40
 
27
41
  ```shell
28
- pipx run qontract-development-cli
42
+ pipx upgrade qontract-development-cli
29
43
  ```
30
44
 
31
45
  ## Quickstart
@@ -0,0 +1,101 @@
1
+ [project]
2
+ name = "qontract-development-cli"
3
+ version = "0.15.2"
4
+ description = "Helper tool for qontract-reconcile development"
5
+ authors = [{ name = "Christian Assing", email = "cassing@redhat.com" }]
6
+ license = { text = "MIT" }
7
+ readme = "README.md"
8
+ requires-python = ">= 3.11"
9
+ dependencies = [
10
+ "appdirs ~=1.4.4",
11
+ "getkey ~=0.6.5",
12
+ "jinja2 ~=3.1",
13
+ "pydantic ~=2.9",
14
+ "pydantic-settings ~=2.6",
15
+ "pyyaml ~=6.0.2",
16
+ "rich ~=13.9",
17
+ "typer ~=0.12.5",
18
+ "watchfiles ~=0.24.0",
19
+ ]
20
+
21
+ [dependency-groups]
22
+ dev = ["mypy ~=1.13", "ruff ~=0.7", "types-pyyaml ~=6.0"]
23
+ [project.urls]
24
+ homepage = "http://github.com/chassing/qontract-development-cli"
25
+ repository = "http://github.com/chassing/qontract-development-cli"
26
+ documentation = "http://github.com/chassing/qontract-development-cli"
27
+
28
+ [project.scripts]
29
+ qd = "qontract_development_cli.__main__:app"
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.sdist]
36
+ only-include = ["qontract_development_cli"]
37
+
38
+ [tool.mypy]
39
+ plugins = "pydantic.mypy"
40
+ enable_error_code = "truthy-bool, redundant-expr"
41
+ no_implicit_optional = true
42
+ check_untyped_defs = true
43
+ warn_unused_ignores = true
44
+ show_error_codes = true
45
+ disallow_untyped_defs = true
46
+ disallow_incomplete_defs = true
47
+
48
+ [[tool.mypy.overrides]]
49
+ module = ["getkey.*", "appdirs.*"]
50
+ ignore_missing_imports = true
51
+
52
+ [tool.ruff]
53
+ line-length = 88
54
+ target-version = 'py311'
55
+ src = ["qontract_development_cli"]
56
+ extend-exclude = [".local", ".cache"]
57
+ fix = true
58
+
59
+ [tool.ruff.lint]
60
+ preview = true
61
+ select = ["ALL"]
62
+ ignore = [
63
+ "ANN401", # Disallow *args, **kwargs with ANY
64
+ "ARG003", # Unusable arguments
65
+ "CPY", # Missing copyright header
66
+ "D10", # docstrings
67
+ "D203", # 1 blank line required before class docstring
68
+ "D211", # No blank lines allowed before class docstring
69
+ "D212", # multi-line-summary-first-line
70
+ "D213", # multi-line-summary-second-line
71
+ "D4", # Doc string style
72
+ "E501", # Line too long
73
+ "G004", # Logging statement uses string formatting
74
+ "S101", # Use of assert detected. Pytest uses assert
75
+ "EM101",
76
+ "EM102",
77
+ "S404", # subprocess possibly insecure
78
+ "S603", # subprocess check user input
79
+ "S607", # subprocess partical executable path
80
+ "TID252", # relative imports
81
+ "TRY003",
82
+ # pydoclint
83
+ "DOC",
84
+ # May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
85
+ "W191",
86
+ "E111",
87
+ "E114",
88
+ "E117",
89
+ "D206",
90
+ "D300",
91
+ "Q",
92
+ "COM812",
93
+ "COM819",
94
+ "ISC001",
95
+ ]
96
+
97
+ [tool.ruff.format]
98
+ preview = true
99
+
100
+ [tool.ruff.lint.isort]
101
+ known-first-party = ["qontract_development_cli"]
@@ -1,21 +1,18 @@
1
1
  import atexit
2
2
  import logging
3
3
  import sys
4
+ from importlib.metadata import version
4
5
  from pathlib import Path
6
+ from typing import Annotated
5
7
 
6
8
  import typer
9
+ from rich import print as rich_print
7
10
  from rich.logging import RichHandler
8
11
 
9
12
  from .commands import config as config_cmd
10
- from .commands import (
11
- env,
12
- profile,
13
- )
13
+ from .commands import env, profile
14
14
  from .config import config
15
- from .utils import (
16
- console,
17
- screenshot,
18
- )
15
+ from .utils import console, screenshot
19
16
 
20
17
  app = typer.Typer()
21
18
  app.add_typer(env.app, name="env", help="Environment related commands.")
@@ -25,10 +22,20 @@ app.add_typer(
25
22
  app.add_typer(profile.app, name="profile", help="Profile related commands.")
26
23
 
27
24
 
25
+ def version_callback(value: bool) -> None: # noqa: FBT001
26
+ if value:
27
+ rich_print(f"Version: {version('qontract-development-cli')}")
28
+ raise typer.Exit
29
+
30
+
28
31
  @app.callback(no_args_is_help=True)
29
32
  def main(
30
- debug: bool = typer.Option(False, help="Enable debug"),
31
- screen_capture_file: Path = typer.Option(None, writable=True),
33
+ *,
34
+ debug: Annotated[bool, typer.Option(help="Enable debug")] = False,
35
+ screen_capture_file: Annotated[Path | None, typer.Option(writable=True)] = None,
36
+ version: Annotated[ # noqa: ARG001
37
+ bool | None, typer.Option(callback=version_callback, help="Display version")
38
+ ] = None,
32
39
  ) -> None:
33
40
  logging.basicConfig(
34
41
  level="DEBUG" if config.debug or debug else "INFO",
@@ -37,7 +44,7 @@ def main(
37
44
  handlers=[RichHandler()],
38
45
  )
39
46
  if screen_capture_file is not None:
40
- print(f"Screen recording: {screen_capture_file}")
47
+ rich_print(f"Screen recording: {screen_capture_file}")
41
48
  # strip $0 and screen_capture_file option
42
49
  args = sys.argv[3:]
43
50
  console.print(f"$ qd {' '.join(args)}")
@@ -5,14 +5,8 @@ from pathlib import Path
5
5
  import typer
6
6
  from rich.prompt import Prompt
7
7
 
8
- from ..config import (
9
- config,
10
- user_config_file,
11
- )
12
- from ..models import (
13
- DEFAULT_PROFILE,
14
- Env,
15
- )
8
+ from ..config import config, user_config_file
9
+ from ..models import DEFAULT_PROFILE, Env
16
10
  from ..utils import console
17
11
 
18
12
  app = typer.Typer()
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import subprocess
3
+ from typing import Annotated
3
4
 
4
5
  import typer
5
6
  from rich.prompt import Confirm
@@ -14,7 +15,9 @@ log = logging.getLogger(__name__)
14
15
 
15
16
 
16
17
  @app.command()
17
- def edit(env_name: str = typer.Argument(..., help="Env to edit or create.")) -> None:
18
+ def edit(
19
+ env_name: Annotated[str, typer.Argument(help="Env to edit or create.")],
20
+ ) -> None:
18
21
  """Create/edit an environment file in your editor."""
19
22
  env = Env(name=env_name)
20
23
  console.print(f"Opening [b]{env.name}[/] in your editor ...")
@@ -32,9 +35,9 @@ def ls() -> None:
32
35
 
33
36
  @app.command()
34
37
  def rm(
35
- env_name: str = typer.Argument(
36
- ..., help="Environment to remove.", autocompletion=complete_env
37
- ),
38
+ env_name: Annotated[
39
+ str, typer.Argument(help="Environment to remove.", autocompletion=complete_env)
40
+ ],
38
41
  ) -> None:
39
42
  """Remove environment."""
40
43
  env = Env(name=env_name)
@@ -44,9 +47,9 @@ def rm(
44
47
 
45
48
  @app.command()
46
49
  def show(
47
- env_name: str = typer.Argument(
48
- ..., help="Environment to display.", autocompletion=complete_env
49
- ),
50
+ env_name: Annotated[
51
+ str, typer.Argument(help="Environment to display.", autocompletion=complete_env)
52
+ ],
50
53
  ) -> None:
51
54
  """Display environment."""
52
55
  env = Env(name=env_name)
@@ -1,16 +1,13 @@
1
+ # noqa: A005 - profile is shadowing python built-in 'profile'
1
2
  import logging
2
3
  import subprocess
3
4
  import tempfile
4
- from multiprocessing import Process
5
5
  from pathlib import Path
6
- from typing import Optional
6
+ from typing import TYPE_CHECKING, Annotated
7
7
 
8
8
  import typer
9
9
  from getkey import getkey
10
- from rich.prompt import (
11
- Confirm,
12
- Prompt,
13
- )
10
+ from rich.prompt import Confirm, Prompt
14
11
  from rich.table import Table
15
12
 
16
13
  from qontract_development_cli.watchdog import watch_files
@@ -37,57 +34,67 @@ from ..shell import (
37
34
  from ..templates import template
38
35
  from ..utils import console
39
36
 
37
+ if TYPE_CHECKING:
38
+ from multiprocessing import Process
39
+
40
40
  app = typer.Typer()
41
41
  log = logging.getLogger(__name__)
42
42
 
43
- # see https://github.com/tiangolo/typer/issues/533
44
- OptionalStr = Optional[str] # noqa: UP007
45
- OptionalInt = Optional[int] # noqa: UP007
46
- OptionalPath = Optional[Path] # noqa: UP007
47
-
48
43
 
49
44
  @app.command()
50
45
  def create( # noqa: PLR0913, PLR0917
51
46
  profile_name: str = typer.Argument(..., help="Profile to create."),
52
- integration_name: OptionalStr = typer.Option(None),
53
- integration_extra_args: OptionalStr = typer.Option(None),
54
- app_interface: OptionalPath = typer.Option(
55
- None,
56
- file_okay=False,
57
- dir_okay=True,
58
- readable=True,
59
- resolve_path=True,
60
- exists=True,
61
- help="Path to local app-interface instance git working copy.",
62
- ),
63
- app_interface_pr: OptionalInt = typer.Option(None, help="PR/MR to use"),
64
- app_interface_upstream: str = typer.Option("upstream", help="Upstream remote name"),
65
- qontract_schemas: OptionalPath = typer.Option(
66
- None,
67
- file_okay=False,
68
- dir_okay=True,
69
- readable=True,
70
- resolve_path=True,
71
- exists=True,
72
- help="Path to local qontract-schemas git working copy.",
73
- ),
74
- qontract_schemas_pr: OptionalInt = typer.Option(None, help="PR/MR to use"),
75
- qontract_schemas_upstream: str = typer.Option(
76
- "upstream", help="Upstream remote name"
77
- ),
78
- qontract_reconcile: OptionalPath = typer.Option(
79
- None,
80
- file_okay=False,
81
- dir_okay=True,
82
- readable=True,
83
- resolve_path=True,
84
- exists=True,
85
- help="Path to local qontract-reconcile git working copy.",
86
- ),
87
- qontract_reconcile_pr: OptionalInt = typer.Option(None, help="PR/MR to use"),
88
- qontract_reconcile_upstream: str = typer.Option(
89
- "upstream", help="Upstream remote name"
90
- ),
47
+ integration_name: Annotated[str | None, typer.Option()] = None,
48
+ integration_extra_args: Annotated[str | None, typer.Option()] = None,
49
+ app_interface: Annotated[
50
+ Path | None,
51
+ typer.Option(
52
+ file_okay=False,
53
+ dir_okay=True,
54
+ readable=True,
55
+ resolve_path=True,
56
+ exists=True,
57
+ help="Path to local app-interface instance git working copy.",
58
+ ),
59
+ ] = None,
60
+ app_interface_pr: Annotated[int | None, typer.Option(help="PR/MR to use")] = None,
61
+ app_interface_upstream: Annotated[
62
+ str, typer.Option(help="Upstream remote name")
63
+ ] = "upstream",
64
+ qontract_schemas: Annotated[
65
+ Path | None,
66
+ typer.Option(
67
+ file_okay=False,
68
+ dir_okay=True,
69
+ readable=True,
70
+ resolve_path=True,
71
+ exists=True,
72
+ help="Path to local qontract-schemas git working copy.",
73
+ ),
74
+ ] = None,
75
+ qontract_schemas_pr: Annotated[
76
+ int | None, typer.Option(help="PR/MR to use")
77
+ ] = None,
78
+ qontract_schemas_upstream: Annotated[
79
+ str, typer.Option(help="Upstream remote name")
80
+ ] = "upstream",
81
+ qontract_reconcile: Annotated[
82
+ Path | None,
83
+ typer.Option(
84
+ file_okay=False,
85
+ dir_okay=True,
86
+ readable=True,
87
+ resolve_path=True,
88
+ exists=True,
89
+ help="Path to local qontract-reconcile git working copy.",
90
+ ),
91
+ ] = None,
92
+ qontract_reconcile_pr: Annotated[
93
+ int | None, typer.Option(help="PR/MR to use")
94
+ ] = None,
95
+ qontract_reconcile_upstream: Annotated[
96
+ str, typer.Option(help="Upstream remote name")
97
+ ] = "upstream",
91
98
  ) -> None:
92
99
  """Create a new profile to run an integration."""
93
100
  if profile_name in [p.name for p in Profile.list_all()]:
@@ -132,9 +139,9 @@ def create( # noqa: PLR0913, PLR0917
132
139
 
133
140
  @app.command()
134
141
  def edit(
135
- profile_name: str = typer.Argument(
136
- ..., help="Profile to edit.", autocompletion=complete_profile
137
- ),
142
+ profile_name: Annotated[
143
+ str, typer.Argument(help="Profile to edit.", autocompletion=complete_profile)
144
+ ],
138
145
  ) -> None:
139
146
  """Edit a profile in your editor."""
140
147
  profile = Profile(name=profile_name)
@@ -153,10 +160,13 @@ def ls() -> None:
153
160
 
154
161
  @app.command()
155
162
  def rm(
156
- profile_name: str = typer.Argument(
157
- ..., help="Profile to remove.", autocompletion=complete_profile
158
- ),
159
- force: bool = typer.Option(False, "--force", "-f", help="Do not ask any question."),
163
+ profile_name: Annotated[
164
+ str, typer.Argument(help="Profile to remove.", autocompletion=complete_profile)
165
+ ],
166
+ *,
167
+ force: Annotated[
168
+ bool, typer.Option("--force", "-f", help="Do not ask any question.")
169
+ ] = False,
160
170
  ) -> None:
161
171
  """Remove profile."""
162
172
  profile = Profile(name=profile_name)
@@ -178,47 +188,53 @@ def show(
178
188
 
179
189
 
180
190
  @app.command(no_args_is_help=True)
181
- def run( # noqa: PLR0912, PLR0917, PLR0913, PLR0915
182
- env_name: str = typer.Argument(
183
- ..., help="Environment to use.", autocompletion=complete_env
184
- ),
185
- profile_name: str = typer.Argument(
186
- ..., help="Profile to run.", autocompletion=complete_profile
187
- ),
188
- force_recreate: bool = typer.Option(False, help="Recreate all containers."),
189
- force_build: bool = typer.Option(False, help="Rebuild all containers."),
190
- qontract_reconcile_monitor_file_changes: bool = typer.Option(
191
- True,
192
- help="Restart integration when files changed in qontract-reconcile path",
193
- ),
194
- qontract_reconcile_monitor_file_extensions: str = typer.Option(
195
- ".py .pyx .pyd",
196
- help="Monitor these file extensions",
197
- ),
198
- qontract_schemas_monitor_file_changes: bool = typer.Option(
199
- True,
200
- help="Rebuild bundle and restart qontract-server when files changed in qontract-schemas path",
201
- ),
202
- qontract_schemas_monitor_file_extensions: str = typer.Option(
203
- ".json .yml .yaml",
204
- help="Monitor these file extensions",
205
- ),
206
- app_interface_monitor_file_changes: bool = typer.Option(
207
- True,
208
- help="Rebuild bundle and restart qontract-server when files changed in app-interface path",
209
- ),
210
- app_interface_monitor_file_extensions: str = typer.Option(
211
- ".json .yml .yaml",
212
- help="Monitor these file extensions",
213
- ),
214
- skip_initial_make_bundle: bool = typer.Option(
215
- False,
216
- help="Do not run 'make bundle' before starting the integration",
217
- ),
218
- no_dry_run: bool = typer.Option(
219
- False,
220
- help="Disable dry-run mode",
221
- ),
191
+ def run( # noqa: C901, PLR0912, PLR0913, PLR0915
192
+ env_name: Annotated[
193
+ str, typer.Argument(help="Environment to use.", autocompletion=complete_env)
194
+ ],
195
+ profile_name: Annotated[
196
+ str, typer.Argument(help="Profile to run.", autocompletion=complete_profile)
197
+ ],
198
+ *,
199
+ force_recreate: Annotated[
200
+ bool, typer.Option(help="Recreate all containers.")
201
+ ] = False,
202
+ force_build: Annotated[bool, typer.Option(help="Rebuild all containers.")] = False,
203
+ qontract_reconcile_monitor_file_changes: Annotated[
204
+ bool,
205
+ typer.Option(
206
+ help="Restart integration when files changed in qontract-reconcile path"
207
+ ),
208
+ ] = True,
209
+ qontract_reconcile_monitor_file_extensions: Annotated[
210
+ str, typer.Option(help="Monitor these file extensions")
211
+ ] = ".py .pyx .pyd",
212
+ qontract_schemas_monitor_file_changes: Annotated[
213
+ bool,
214
+ typer.Option(
215
+ help="Rebuild bundle and restart qontract-server when files changed in qontract-schemas path"
216
+ ),
217
+ ] = True,
218
+ qontract_schemas_monitor_file_extensions: Annotated[
219
+ str, typer.Option(help="Monitor these file extensions")
220
+ ] = ".json .yml .yaml",
221
+ app_interface_monitor_file_changes: Annotated[
222
+ bool,
223
+ typer.Option(
224
+ help="Rebuild bundle and restart qontract-server when files changed in app-interface path"
225
+ ),
226
+ ] = True,
227
+ app_interface_monitor_file_extensions: Annotated[
228
+ str, typer.Option(help="Monitor these file extensions")
229
+ ] = ".json .yml .yaml",
230
+ skip_initial_make_bundle: Annotated[
231
+ bool,
232
+ typer.Option(help="Do not run 'make bundle' before starting the integration"),
233
+ ] = False,
234
+ no_dry_run: Annotated[
235
+ bool,
236
+ typer.Option(help="Disable dry-run mode"),
237
+ ] = False,
222
238
  ) -> None:
223
239
  """Run a profile."""
224
240
  env = Env(name=env_name)
@@ -1,7 +1,4 @@
1
- from .models import (
2
- Env,
3
- Profile,
4
- )
1
+ from .models import Env, Profile
5
2
 
6
3
 
7
4
  def complete_env() -> list[str]:
@@ -1,10 +1,14 @@
1
1
  import os
2
2
  import platform
3
3
  from pathlib import Path
4
- from typing import Any
5
4
 
6
- from appdirs import AppDirs # type: ignore
7
- from pydantic import BaseSettings
5
+ from appdirs import AppDirs
6
+ from pydantic_settings import (
7
+ BaseSettings,
8
+ PydanticBaseSettingsSource,
9
+ SettingsConfigDict,
10
+ YamlConfigSettingsSource,
11
+ )
8
12
 
9
13
  from .utils import yaml
10
14
 
@@ -20,14 +24,6 @@ user_cache_dir.mkdir(parents=True, exist_ok=True)
20
24
  is_mac = platform.system().lower() == "darwin"
21
25
 
22
26
 
23
- def yaml_config_settings_source(settings: BaseSettings) -> dict[str, Any]:
24
- encoding = settings.__config__.env_file_encoding
25
- if user_config_file.exists():
26
- cfg = yaml.safe_load(user_config_file.read_text(encoding))
27
- return cfg if cfg else {}
28
- return {}
29
-
30
-
31
27
  class Config(BaseSettings):
32
28
  debug: bool = False
33
29
  defaults_profile: str = "defaults"
@@ -37,31 +33,41 @@ class Config(BaseSettings):
37
33
  profiles_dir: Path = user_config_dir / "profiles"
38
34
  worktrees_dir: Path = user_cache_dir / "worktrees"
39
35
 
40
- def __init__(self, *args: Any, **kwargs: Any) -> None:
41
- super().__init__(*args, **kwargs)
36
+ def __init__(self) -> None:
37
+ super().__init__()
42
38
  self.environments_dir.mkdir(parents=True, exist_ok=True)
43
39
  self.profiles_dir.mkdir(parents=True, exist_ok=True)
44
40
 
45
- class Config:
46
- extra = "ignore"
47
- env_file_encoding = "utf-8"
48
- env_prefix = "qontract_development_"
41
+ @classmethod
42
+ def settings_customise_sources(
43
+ cls,
44
+ settings_cls: type[BaseSettings],
45
+ init_settings: PydanticBaseSettingsSource,
46
+ env_settings: PydanticBaseSettingsSource,
47
+ dotenv_settings: PydanticBaseSettingsSource,
48
+ file_secret_settings: PydanticBaseSettingsSource,
49
+ ) -> tuple[PydanticBaseSettingsSource, ...]:
50
+ return (
51
+ init_settings,
52
+ env_settings,
53
+ YamlConfigSettingsSource(settings_cls),
54
+ )
49
55
 
50
- @classmethod
51
- def customise_sources(
52
- cls, init_settings: Any, env_settings: Any, file_secret_settings: Any
53
- ) -> tuple:
54
- return (init_settings, env_settings, yaml_config_settings_source)
56
+ model_config = SettingsConfigDict(
57
+ yaml_file=user_config_file,
58
+ yaml_file_encoding="utf-8",
59
+ env_prefix="qontract_development_",
60
+ )
55
61
 
56
62
  def save(self) -> None:
57
63
  user_config_file.write_text(
58
64
  yaml.dump(
59
- self.dict(),
65
+ self.model_dump(),
60
66
  explicit_start=True,
61
67
  indent=4,
62
68
  default_flow_style=False,
63
69
  ),
64
- encoding=self.__config__.env_file_encoding,
70
+ encoding=self.model_config["yaml_file_encoding"],
65
71
  )
66
72
 
67
73
 
@@ -1,12 +1,10 @@
1
+ import contextlib
1
2
  import copy
2
3
  import os
3
4
  from pathlib import Path
4
- from typing import Any
5
+ from typing import Any, ClassVar, Self
5
6
 
6
- from pydantic import (
7
- BaseModel,
8
- validator,
9
- )
7
+ from pydantic import BaseModel, model_validator
10
8
 
11
9
  from .config import config
12
10
  from .utils import yaml
@@ -24,12 +22,12 @@ class EnvSettings(BaseModel):
24
22
 
25
23
 
26
24
  class ProfileSettings(BaseModel):
27
- command: str = "dockerfiles/hack/run-integration.py"
25
+ command: str = "run-integration"
28
26
  command_extra_args: str = ""
29
27
  container_uid: int = os.getuid()
30
28
  debugger: str = "debugpy"
31
29
  dry_run: bool = True
32
- integration_extra_args: str = ""
30
+ integration_extra_args: str | int = ""
33
31
  integration_name: str = ""
34
32
  log_level: str = "INFO"
35
33
  app_interface_path: Path | None = None
@@ -55,39 +53,42 @@ class ProfileSettings(BaseModel):
55
53
  localstack_compose_file: Path | None
56
54
  skip_initial_make_bundle: bool = False
57
55
 
58
- @validator("localstack_compose_file", always=True)
59
- def default_localstack_compose_file(cls, v: Path | None, values: dict) -> Path:
60
- return (
61
- v
62
- if v
63
- else values["qontract_reconcile_path"] / "dev/localstack/docker-compose.yml"
56
+ @model_validator(mode="after")
57
+ def default_localstack_compose_file(self) -> Self:
58
+ self.localstack_compose_file = (
59
+ self.localstack_compose_file
60
+ or self.qontract_reconcile_path / "dev/localstack/docker-compose.yml"
64
61
  )
62
+ return self
65
63
 
66
64
 
67
65
  class Base(BaseModel):
68
66
  name: str
69
67
  default: bool = False
70
- _root: Path
68
+ root: ClassVar[Path]
71
69
 
72
70
  def __lt__(self, other: "Base") -> bool:
73
71
  return self.name < other.name
74
72
 
75
- @validator("name")
76
- def name_remove_suffix(cls, v: str) -> str:
77
- p = Path(v)
78
- return str(p.parent / p.stem)
73
+ @model_validator(mode="after")
74
+ def name_remove_suffix(self) -> Self:
75
+ p = Path(self.name)
76
+ self.name = str(p.parent / p.stem)
77
+ return self
79
78
 
80
79
  @property
81
80
  def file(self) -> Path:
82
- p = self._root / self.name
81
+ p = self.root / self.name
83
82
  p.parent.mkdir(parents=True, exist_ok=True)
84
83
  return p.with_suffix(".yml")
85
84
 
86
85
  @classmethod
87
86
  def list_all(cls) -> list["Base"]:
88
87
  items = []
89
- for f in [f for f in list(cls._root.glob("**/*")) if f.is_file()]:
90
- items.append(cls(name=str(f.relative_to(cls._root))))
88
+ for f in [f for f in list(cls.root.glob("**/*")) if f.is_file()]:
89
+ items.append( # noqa: PERF401
90
+ cls(name=str(f.relative_to(cls.root)))
91
+ )
91
92
  return items
92
93
 
93
94
  @property
@@ -101,18 +102,18 @@ class Base(BaseModel):
101
102
  @property
102
103
  def settings_as_dict(self) -> dict[str, Any]:
103
104
  values = self.default_settings_as_dict
104
- try:
105
+ with contextlib.suppress(FileNotFoundError):
105
106
  values.update(
106
107
  **yaml.safe_load(
107
- self.file.read_text(encoding=config.__config__.env_file_encoding)
108
+ self.file.read_text(
109
+ encoding=config.model_config["yaml_file_encoding"]
110
+ )
108
111
  )
109
112
  )
110
- except FileNotFoundError:
111
- pass
112
113
  return values
113
114
 
114
115
  def dump(self) -> None:
115
- values = self.settings.dict( # type: ignore
116
+ values = self.settings.dict( # type: ignore[attr-defined]
116
117
  exclude_defaults=not self.default, exclude_unset=not self.default
117
118
  )
118
119
  self.file.write_text(
@@ -126,7 +127,7 @@ class Base(BaseModel):
126
127
 
127
128
 
128
129
  class Env(Base):
129
- _root: Path = config.environments_dir
130
+ root: ClassVar[Path] = config.environments_dir
130
131
  default: bool = True
131
132
  settings: EnvSettings
132
133
 
@@ -141,7 +142,7 @@ class Env(Base):
141
142
 
142
143
 
143
144
  class Profile(Base):
144
- _root: Path = config.profiles_dir
145
+ root: ClassVar[Path] = config.profiles_dir
145
146
  settings: ProfileSettings
146
147
 
147
148
  def __init__(self, *args: Any, **kwargs: Any) -> None:
@@ -155,17 +156,17 @@ class Profile(Base):
155
156
  defaults = {
156
157
  "integration_name": "changeme",
157
158
  "integration_extra_args": "",
159
+ "localstack_compose_file": None,
158
160
  }
159
- try:
161
+ with contextlib.suppress(FileNotFoundError, AttributeError, NameError):
160
162
  defaults.update(
161
163
  yaml.safe_load(
162
164
  DEFAULT_PROFILE.file.read_text(
163
- encoding=config.__config__.env_file_encoding
165
+ encoding=config.model_config["yaml_file_encoding"]
164
166
  )
165
167
  )
166
168
  )
167
- except (FileNotFoundError, AttributeError, NameError):
168
- pass
169
+
169
170
  return defaults
170
171
 
171
172
  def dump(self) -> None:
@@ -12,10 +12,7 @@ from typing import Any
12
12
 
13
13
  from .models import Profile
14
14
  from .templates import template
15
- from .utils import (
16
- EndlessProcess,
17
- console,
18
- )
15
+ from .utils import EndlessProcess, console
19
16
 
20
17
  log = logging.getLogger(__name__)
21
18
 
@@ -26,12 +23,13 @@ _docker_compose_bin = (
26
23
 
27
24
  def compose_up(
28
25
  compose_file: Path,
26
+ *,
29
27
  force_recreate: bool = False,
30
28
  remove_orphan: bool = True,
31
29
  build: bool = False,
32
30
  ) -> None:
33
31
  log.info("Starting all containers")
34
- compose_cmd = _docker_compose_bin + ["-f", str(compose_file), "up", "-d"]
32
+ compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "up", "-d"]
35
33
  if force_recreate:
36
34
  compose_cmd.append("--force-recreate")
37
35
  if remove_orphan:
@@ -43,12 +41,7 @@ def compose_up(
43
41
 
44
42
  def compose_restart(compose_file: Path, container: str) -> None:
45
43
  log.info(f"Restarting {container} container")
46
- compose_cmd = _docker_compose_bin + [
47
- "-f",
48
- str(compose_file),
49
- "restart",
50
- container,
51
- ]
44
+ compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "restart", container]
52
45
  subprocess.run(compose_cmd, check=True)
53
46
 
54
47
 
@@ -60,12 +53,12 @@ def container_restart(container: str) -> None:
60
53
 
61
54
  def compose_down(compose_file: Path) -> None:
62
55
  log.info("Stopping all containers")
63
- compose_cmd = _docker_compose_bin + ["-f", str(compose_file), "down"]
56
+ compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "down"]
64
57
  subprocess.run(compose_cmd, check=True)
65
58
 
66
59
 
67
60
  def compose_log_tail(compose_file: Path) -> Process:
68
- compose_cmd = _docker_compose_bin + ["-f", str(compose_file), "logs", "--follow"]
61
+ compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "logs", "--follow"]
69
62
  p = EndlessProcess(target=subprocess.run, args=(compose_cmd,))
70
63
  p.start()
71
64
  return p
@@ -74,7 +67,7 @@ def compose_log_tail(compose_file: Path) -> Process:
74
67
  def compose_list_projects() -> list[dict[str, Any]]:
75
68
  return json.loads(
76
69
  subprocess.run(
77
- _docker_compose_bin + ["ls", "--format", "json"],
70
+ [*_docker_compose_bin, "ls", "--format", "json"],
78
71
  capture_output=True,
79
72
  check=True,
80
73
  ).stdout
@@ -86,7 +79,7 @@ def compose_stop_project(project_name: str) -> None:
86
79
  for p in compose_list_projects():
87
80
  if p["Name"] == project_name:
88
81
  subprocess.run(
89
- _docker_compose_bin + ["-f", p["ConfigFiles"], "down"], check=True
82
+ [*_docker_compose_bin, "-f", p["ConfigFiles"], "down"], check=True
90
83
  )
91
84
 
92
85
 
@@ -97,7 +90,9 @@ def make_bundle(app_interface_path: Path, qontract_server_path: Path) -> None:
97
90
  "APP_INTERFACE_PATH": str(app_interface_path.expanduser().absolute())
98
91
  })
99
92
  subprocess.run(
100
- ["make", "-C", str(qontract_server_path), "bundle"], env=shell_env, check=False
93
+ ["make", "-C", str(qontract_server_path), "bundle"],
94
+ env=shell_env,
95
+ check=False,
101
96
  )
102
97
 
103
98
 
@@ -1,16 +1,14 @@
1
1
  from pathlib import Path
2
2
  from typing import Any
3
3
 
4
- from jinja2 import (
5
- Environment,
6
- FileSystemLoader,
7
- )
4
+ from jinja2 import Environment, FileSystemLoader
8
5
 
9
6
  environment = Environment(
10
7
  loader=FileSystemLoader(str(Path(__file__).parent / "templates")),
11
8
  trim_blocks=True,
12
9
  lstrip_blocks=True,
13
10
  keep_trailing_newline=True,
11
+ autoescape=True,
14
12
  )
15
13
 
16
14
 
@@ -1,8 +1,5 @@
1
1
  from multiprocessing import Process
2
- from pathlib import (
3
- Path,
4
- PosixPath,
5
- )
2
+ from pathlib import Path, PosixPath
6
3
  from typing import Any
7
4
 
8
5
  import yaml
@@ -27,6 +24,6 @@ console = Console(record=True)
27
24
 
28
25
  class EndlessProcess(Process):
29
26
  def run(self) -> None:
30
- if self._target: # type: ignore
27
+ if self._target: # type: ignore[attr-defined]
31
28
  while True:
32
- self._target(*self._args, **self._kwargs) # type: ignore
29
+ self._target(*self._args, **self._kwargs) # type: ignore[attr-defined]
@@ -4,11 +4,7 @@ from multiprocessing import Process
4
4
  from pathlib import Path
5
5
 
6
6
  from rich.logging import RichHandler
7
- from watchfiles import (
8
- Change,
9
- DefaultFilter,
10
- watch,
11
- )
7
+ from watchfiles import Change, DefaultFilter, watch
12
8
 
13
9
  log = logging.getLogger(__name__)
14
10
 
@@ -1,89 +0,0 @@
1
- [tool.poetry]
2
- name = "qontract-development-cli"
3
- version = "0.14.5"
4
- description = "Helper tool for qontract-reconcile development"
5
- authors = ["Christian Assing <cassing@redhat.com>"]
6
- license = "MIT"
7
- readme = "README.md"
8
- repository = "http://github.com/chassing/qontract-development-cli"
9
- homepage = "http://github.com/chassing/qontract-development-cli"
10
-
11
- [tool.poetry.dependencies]
12
- python = ">=3.11,<4.0"
13
- Jinja2 = "^3.1.2"
14
- typer = "^0.9.0"
15
- rich = "^13.7.0"
16
- pydantic = "^1.10.2"
17
- appdirs = "^1.4.4"
18
- PyYAML = "^6.0.1"
19
- getkey = "^0.6.5"
20
- watchfiles = "^0.21.0"
21
-
22
- [tool.poetry.dev-dependencies]
23
- types-PyYAML = "^6.0.12"
24
-
25
- [tool.poetry.group.dev.dependencies]
26
- pytest = "^8.0.0"
27
- mypy = "^1.8.0"
28
- types-pyyaml = "^6.0.12.11"
29
- ruff = "^0.4.8"
30
-
31
- [build-system]
32
- requires = ["poetry-core>=1.0.0"]
33
- build-backend = "poetry.core.masonry.api"
34
-
35
- [tool.poetry.scripts]
36
- qd = "qontract_development_cli.__main__:app"
37
-
38
- [tool.mypy]
39
- plugins = "pydantic.mypy"
40
- enable_error_code = "truthy-bool, redundant-expr"
41
- no_implicit_optional = true
42
- check_untyped_defs = true
43
- warn_unused_ignores = true
44
- show_error_codes = true
45
- disallow_untyped_defs = true
46
- disallow_incomplete_defs = true
47
-
48
- [[tool.mypy.overrides]]
49
- module = ["getkey.*"]
50
- ignore_missing_imports = true
51
-
52
- [tool.commitizen]
53
- name = "cz_conventional_commits"
54
- version = "0.11.1"
55
- version_files = ["pyproject.toml:version"]
56
- update_changelog_on_bump = true
57
- major_version_zero = true
58
- change_type_map = { "feat" = "Features", "fix" = "Bug Fixes" }
59
- tag_format = "v$version"
60
-
61
- [tool.ruff]
62
- line-length = 88
63
- target-version = 'py311'
64
- required-version = "0.4.8"
65
- src = ["qontract_development_cli"]
66
- extend-exclude = [".local", ".cache"]
67
- fix = true
68
-
69
- [tool.ruff.lint]
70
- preview = true
71
- # defaults are ["E4", "E7", "E9", "F"]
72
- extend-select = [
73
- # flake8 default rules
74
- "E1", # preview rule
75
- "E2", # preview rule
76
- "W",
77
- # isort
78
- "I",
79
- # pylint
80
- "PL",
81
- # pyupgrade
82
- "UP",
83
- ]
84
-
85
- [tool.ruff.format]
86
- preview = true
87
-
88
- [tool.ruff.lint.isort]
89
- known-first-party = ["qontract_development_cli"]
@@ -1,41 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['qontract_development_cli', 'qontract_development_cli.commands']
6
-
7
- package_data = \
8
- {'': ['*'], 'qontract_development_cli': ['templates/*']}
9
-
10
- install_requires = \
11
- ['Jinja2>=3.1.2,<4.0.0',
12
- 'PyYAML>=6.0.1,<7.0.0',
13
- 'appdirs>=1.4.4,<2.0.0',
14
- 'getkey>=0.6.5,<0.7.0',
15
- 'pydantic>=1.10.2,<2.0.0',
16
- 'rich>=13.7.0,<14.0.0',
17
- 'typer>=0.9.0,<0.10.0',
18
- 'watchfiles>=0.21.0,<0.22.0']
19
-
20
- entry_points = \
21
- {'console_scripts': ['qd = qontract_development_cli.__main__:app']}
22
-
23
- setup_kwargs = {
24
- 'name': 'qontract-development-cli',
25
- 'version': '0.14.5',
26
- 'description': 'Helper tool for qontract-reconcile development',
27
- 'long_description': "# Qontract Development CLI\n\n[![PyPI](https://img.shields.io/pypi/v/qontract-development-cli)][pypi-link]\n[![PyPI platforms][pypi-platforms]][pypi-link]\n![PyPI - License](https://img.shields.io/pypi/l/qontract-development-cli)\n\nQontract Development CLI supports your daily [qontract-reconcile][qontract-reconcile] development work.\n\n[toc]\n\n## Installation\n\nYou can install this library from [PyPI][pypi-link] with `pip`:\n\n```shell\npython3 -m pip install qontract-development-cli\n```\n\nOr install it with `pipx`:\n\n```shell\npipx install qontract-development-cli\n```\n\nYou can also use `pipx` to run the library without installing it:\n\n```shell\npipx run qontract-development-cli\n```\n\n## Quickstart\n\n* Create initial configuration\n\n ```shell\n qd config init\n ```\n\n ![qd init](/demo/qd_config_init.gif)\n\n* Create a profile `sql-query` to run the `sql-query` integration\n\n ```shell\n qd profile create sql-query\n ```\n\n ![qd profile create](/demo/qd_profile_create.gif)\n\n* Run `sql-query` profile\n\n ```shell\n qd profile run dev sql-query\n ```\n\n ![qd sql_query](/images/profile_run_sql_query.svg)\n\n## Features\n\nQontract Development CLI currently provides the following features (get help with `-h` or `--help`):\n\n* Run `qontract-reconcile` and `qontract-server` as docker containers on your local machine\n* Support for different App-Interface environments (dev, prod, ...) via the `env` command\n* Configure your [qontract-reconcile integration][qontract-reconcile] with the `profile` command\n* Support pull request reviews (see `profile create`)\n* Bootstrap your initial configurations with the `config` command\n* Shell auto-completion (see `qd --help`)\n* Automatically restart `qontract-reconcile` container when files change\n* Automatically rebuild the App-Interface bundle and restart the `qontract-server` container when files change\n\n## Examples\n\nTake a look at the [examples](examples) directory for different profile examples.\n\n## Usage\n\nThe following sections describe all available commands and their options.\n\n### Config\n\nManage global qontract-development-cli configuration.\n\n`qd config [sub-cmd] --help`\n\n* **edit**: open the configuration file in your favorite editor\n* **init**: create a default configuration\n\n#### Global Settings\n\n| **Key** | **Description** | **Default** |\n| --------------------------- | ------------------------------------- | ------------------------------------ |\n| debug | Enable/disable debug mode | false |\n| defaults_profile | Name of defaults profile | defaults |\n| docker_compose_project_name | Docker compose project name | qontract-development |\n| editor | Your favorite editor | $EDITOR or vim |\n| environments_dir | Directory to store environment files | User config directory / environments |\n| profiles_dir | Directory to store profile files | User config directory / profiles |\n| worktrees_dir | Directory to store git repo worktrees | User cache directory / worktrees |\n\n### Environments\n\nAn environment specifies app-interface instance settings, e.g., **dev** vs. **prod** config and path to the actual app-interface instance.\n\n`qd env [sub-cmd] --help`\n\n* **edit**: Create/edit an environment file in your editor.\n* **ls**: List all available environments.\n* **rm**: Remove environment.\n* **show**: Display environment.\n\n#### Environment Settings\n\n| **Key** | **Description** | **Default** |\n| ---------------------------------- | ------------------------------------ | ---------------------------------------------- |\n| **app_interface_path** | Path to local app-interface instance | ~/workspace/app-interface |\n| app_interface_state_bucket | S3 bucket | empty |\n| app_interface_state_bucket_account | AWS S3 account | empty |\n| **config** | app-interface config | ~/workspace/qontract-reconcile/config.dev.toml |\n| gitlab_pr_submitter_queue_url | Gitlab pr submitter queue url | |\n| run_qontract_reconcile | Run qontract-reconcile container | true |\n| run_qontract_server | Run qontract-server container | true |\n| run_vault | Run vault container | false |\n\n> :point_right: **Bold keys** are mandatory or should be customized.\n\n### Profiles\n\nA profile specifies all settings to run a qontract-reconcile integration (e.g., *sql-query*).\n\n`qd profile [sub-cmd] --help`\n\n* **create**: Create a new profile to run an integration.\n\n Supports the creation of a new profile from an open PR/MR. See `$ qd profile create --help` for all available options.\n\n* **edit**: Edit a profile in your editor.\n* **ls**: List all available profiles.\n* **rm**: Remove profile.\n* **run**: Run a profile.\n* **show**: Display profile.\n\n#### --no-dry-run\n\nBy default, `qd` runs integrations in `dry-run` mode if not explicitly specified differently in the profile config.\nThere is also a `--no-dry-run` flag which lets you disable `dry-run` mode from the command line.\n\n```shell\nqd profile run dev my-integration --no-dry-run\n```\n\nThe command line flag takes precedence over any configuration you might have in the profile settings.\n\n#### Profile Settings\n\n| **Key** | **Description** | **Default** |\n| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |\n| additional_environment | Dictionary of additional environment variables to pass to the qontract-reconcile container | `{}` |\n| container_uid | Change ownership of /recconcile files in container to this user id | current UID |\n| command | Command to run in the qontract-reconcile container. | `dockerfiles/hack/run-integration.py` |\n| command_extra_args | Additional arguments to pass to the command. | |\n| debugger | Python debugger | `debugpy` |\n| dry_run | Run --dry-run mode | `true` |\n| extra_hosts | List of 'HOSTNAME:IP' mapping entries for qontract-reconcile `/etc/hosts`. See [extra_hosts](https://docs.docker.com/compose/compose-file/#extra_hosts) docker compose file. | `[]` |\n| **integration_name** | Intergration name | |\n| **integration_extra_args** | Intergration extra arguments | |\n| **internal_redhat_ca** | Inject Red Hat internal CAs and `REQUESTS_CA_BUNDLE` environment variable | `false` |\n| log_level | Log level | `info` |\n| app_interface_path | App-interface instance path. (Overrides *env.app_interface_path*) | |\n| app_interface_pr | App-interface PR/MR number | |\n| app_interface_upstream | Upstream remote name | `upstream` |\n| qontract_reconcile_build_image | Build qontract-reconcile image | `true` |\n| qontract_reconcile_image | Qontract-reconcile image | `quay.io/app-sre/qontract-reconcile:latest` |\n| qontract_reconcile_path | Qontract-reconcile path | `~/workspace/qontract-reconcile` |\n| qontract_reconcile_pr | Qontract-reconcile PR/MR number | |\n| qontract_reconcile_upstream | Upstream remote name | `upstream` |\n| qontract_server_build_image | Build qontract-server image | `true` |\n| qontract_server_image | Qontract-server image | `quay.io/app-sre/qontract-server:lates`t |\n| qontract_server_path | Qontract-server path | `~/workspace/qontract-server` |\n| qontract_schemas_path | Qontract-schemas path | `~/workspace/qontract-schemas` |\n| qontract_schemas_pr | Qontract-schemas PR/MR number | |\n| qontract_schemas_upstream | Upstream remote name | `upstream` |\n| run_once | If 'true', execute the integration once and exit | `true` |\n| sleep_duration_secs | If not *run_once*, sleep duration until integration runs again | `10` |\n| localstack | Run localstack container and set AWS related environment variables | `false` |\n| localstack_compose_file | Path to your Localstack docker-compose file | `qontract_server_path` / `dev/localstack/docker-compose.yml` |\n| skip_initial_make_bundle | Skip initial make bundle step | `false` |\n\n> :point_right: **Bold keys** are mandatory or should be customized.\n\n### PR/MR support\n\nIt's a pretty handy feature to create a profile from a pull request (merge request). E.g.:\n\n```shell\nqd profile create --app-interface PATH_TO_YOUR_LOCAL/app-interface-dev-data --app-interface-pr NUMBER --qontract-schemas-pr NUMBER --qontract-reconcile-pr NUMBER --integration-name glitchtip --integration-extra-args '' glitchtip-pr-check\n```\n\nWhich results in this profile:\n\n```shell\n$ qd profile show glitchtip-pr-check\n---\napp_interface_path: PATH_TO_YOUR_LOCAL/app-interface-dev-data\napp_interface_pr: NUMBER\nintegration_name: glitchtip\nqontract_reconcile_pr: NUMBER\nqontract_schemas_pr: NUMBER\n```\n\nRunning this profile will:\n\n* Create new git worktrees (see `config.worktrees_dir`) for app-interface-dev-data, qontract-schema, and qontract-reconcile PRs\n* Start the containers with the adapted path to these worktrees\n* Restarting the profile will fetch PR updates\n\n> :point_right: A git worktree cleanup isn't implemented yet\n\n## Development\n\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)\n\n### Release\n\n* Bump the version in `pyproject.toml`\n* Update the `CHANGELOG.md`\n* Commit the changes\n\n[pypi-link]: https://pypi.org/project/qontract-development-cli/\n[pypi-platforms]: https://img.shields.io/pypi/pyversions/qontract-development-cli\n[qontract-reconcile]: https://github.com/app-sre/qontract-reconcile\n",
28
- 'author': 'Christian Assing',
29
- 'author_email': 'cassing@redhat.com',
30
- 'maintainer': 'None',
31
- 'maintainer_email': 'None',
32
- 'url': 'http://github.com/chassing/qontract-development-cli',
33
- 'packages': packages,
34
- 'package_data': package_data,
35
- 'install_requires': install_requires,
36
- 'entry_points': entry_points,
37
- 'python_requires': '>=3.11,<4.0',
38
- }
39
-
40
-
41
- setup(**setup_kwargs)