Skip to content
This repository was archived by the owner on Jun 2, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/change_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

VERSION_PY = Path(__file__).parent.parent / 'defectdojo_api_generated' / '__init__.py'
VERSION_RE = re.compile(r"__version__ = '(.*?)'")
CLI_VERSION_PY = (
Path(__file__).parent.parent / 'packages' / 'cli' / 'src' / 'defectdojo_api_generated_cli' / '__init__.py'
)
CLI_DEPENDENCY_PYPROJECT = Path(__file__).parent.parent / 'packages' / 'cli' / 'pyproject.toml'
CLI_DEPENDENCY_RE = re.compile(r'("defectdojo-api-generated==)([^"]+)(")')


def main():
Expand All @@ -22,6 +27,10 @@ def main():

if args.set:
VERSION_PY.write_text(VERSION_RE.sub(f"__version__ = '{args.set}'", data))
cli_version_data = CLI_VERSION_PY.read_text()
CLI_VERSION_PY.write_text(VERSION_RE.sub(f"__version__ = '{args.set}'", cli_version_data))
cli_pyproject_data = CLI_DEPENDENCY_PYPROJECT.read_text()
CLI_DEPENDENCY_PYPROJECT.write_text(CLI_DEPENDENCY_RE.sub(rf'\g<1>{args.set}\g<3>', cli_pyproject_data))
print(f'New version: {args.set}')


Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,25 @@ jobs:
run: |
.github/change_version.py --set '${{ inputs.version }}'
- name: Build
run: uv run pyproject-build
run: |
rm -rf dist packages/cli/dist
uv run pyproject-build
uv run pyproject-build packages/cli
- name: Publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.twine_token }}
run: uv run twine upload --verbose --repository ${{ inputs.repository }} dist/*
run: uv run twine upload --verbose --repository ${{ inputs.repository }} dist/* packages/cli/dist/*
- name: highlight
run: |
INDEX=""
if [ "${{ inputs.repository }}" != "pypi" ]; then
INDEX=" -i ${{ inputs.repository }}"
fi
cat <<EOF >> ${GITHUB_STEP_SUMMARY}
\`example\` uploaded to ${{ inputs.repository }}
Packages uploaded to ${{ inputs.repository }}
\`\`\`
pip install${INDEX} defectdojo-api-generated==${{ inputs.version }}
pip install${INDEX} defectdojo-api-generated-cli==${{ inputs.version }}
\`\`\`
EOF
10 changes: 7 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ jobs:
run: |
make lint-check

- name: Build CLI package
run: |
uv run pyproject-build packages/cli

unit-tests:
runs-on: ubuntu-latest
strategy:
Expand All @@ -50,7 +54,7 @@ jobs:
- name: Install dependencies
run: |
pip install uv
uv sync --dev --extra cli
uv sync --dev

- name: Run unit tests
run: |
Expand All @@ -76,7 +80,7 @@ jobs:
- name: Install dependencies
run: |
pip install uv
uv sync --dev --extra cli
uv sync --dev

- name: Run e2e tests
run: |
Expand All @@ -96,7 +100,7 @@ jobs:
- name: Install dependencies
run: |
pip install uv
uv sync --dev --extra cli
uv sync --dev

- name: build docs
run: |
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ build:
jobs:
post_install:
- pip install uv
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --dev --extra cli
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --dev --all-extras

# Build documentation with Mkdocs
mkdocs:
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ Formatting and lint:
- `make lint`
- `make lint-check`

Repo-specific expectation:

- After any code, template, packaging, or GitHub Actions change, run `make lint`.
- Do not treat `make lint` as optional or defer it unless the user explicitly asks not to run it or you are blocked.

Tests:

- `make test`
Expand Down
11 changes: 10 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@
This project uses `uv`, so set up the virtualenv by running

```
uv sync --dev --extra cli
uv sync --dev
```

Use `make test` to make sure all tests pass before pushing.

Use `make lint` to make sure lint check passes before pushing.

## Packaging layout

The main package, `defectdojo-api-generated`, is intentionally library-only.
It does not publish console scripts and should remain installable without CLI-only dependencies.

The `dojo` entrypoint lives in the thin wrapper package under `packages/cli/`.
That package depends on the main library plus the CLI stack and should stay small:
keep CLI packaging, entrypoints, and CLI-only dependency wiring there, while the actual CLI implementation can continue to live under `defectdojo_api_generated/cli/`.

## Guidelines

### Update openapi-generator version
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sync:

.venv39: export VIRTUAL_ENV=.venv39
.venv39:
uv sync --dev --extra cli --python 3.9 --active
uv sync --dev --all-extras --python 3.9 --active

test39: .venv39
test39: export VIRTUAL_ENV=.venv39
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Multiple changes done on top of default openapi-generator:
* Remove most of pydantic/schema validations due to inconsistencies with actual database schema/requirements (tracked in https://github.com/fopina/defectdojo-api-generated/issues/39)
* *Iterator* methods for every *list* API method to handle pagination automatically
* A nice CLI exposing all the API methods <3
* installed only as an extra, to keep everything clean when package is used as library only
* published as a separate package, to keep library-only installs free of console-script conflicts

## Example

Expand Down Expand Up @@ -49,7 +49,7 @@ pip install defectojo-api-generated
> [uv](https://docs.astral.sh/uv/) recommended or [pipx](https://github.com/pypa/pipx)

```
uv tool install 'defectojo-api-generated[cli]'
uv tool install defectdojo-api-generated-cli
```

```
Expand All @@ -69,7 +69,7 @@ Commands:
You can also skip tool install and just run it with:

```
$ uvx 'defectojo-api-generated[cli]'
$ uvx defectdojo-api-generated-cli
Usage: dojo [OPTIONS] COMMAND [ARGS]...
...
```
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.venv/
dist/
src/*.egg-info/
uv.lock
5 changes: 5 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# defectdojo-api-generated-cli

Thin CLI wrapper distribution for [`defectdojo-api-generated`](https://pypi.org/project/defectdojo-api-generated/).

This package owns the `dojo` console entrypoint and pulls in the optional CLI dependencies.
29 changes: 29 additions & 0 deletions packages/cli/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[build-system]
requires = ["setuptools>=69.5.0,<80.0.0"]
build-backend = "setuptools.build_meta"

[project]
name = "defectdojo-api-generated-cli"
version = "1.0.0"
description = "CLI wrapper for defectdojo-api-generated"
readme = "README.md"
requires-python = ">=3.9,<4"
dependencies = [
"classyclick==1.0.0",
"defectdojo-api-generated==1.0.0",
"jmespath>=1.0.1",
]

[project.scripts]
dojo = "defectdojo_api_generated_cli.__main__:main"
defectdojo-api-generated = "defectdojo_api_generated_cli.__main__:main"

[project.urls]
Homepage = "https://github.com/fopina/defectdojo-api-generated"

[tool.uv.sources]
defectdojo-api-generated = { path = "../.." }

[tool.setuptools.packages.find]
where = ["src"]
include = ["defectdojo_api_generated_cli"]
3 changes: 3 additions & 0 deletions packages/cli/src/defectdojo_api_generated_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Thin wrapper distribution for the DefectDojo CLI."""

__version__ = '1.0.0'
6 changes: 6 additions & 0 deletions packages/cli/src/defectdojo_api_generated_cli/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Entrypoint owned by the separate CLI distribution."""

from defectdojo_api_generated.cli.__main__ import main

if __name__ == '__main__':
main()
15 changes: 3 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,11 @@ dependencies = [
"typing-extensions>= 4.7.1",
]

[project.optional-dependencies]
cli = [
"classyclick==1.0.0",
"jmespath>=1.0.1",
]

[project.scripts]
dojo = "defectdojo_api_generated.cli.__main__:main"
# duplicated entrypoint to be able to use uvx without `--from`
defectdojo-api-generated = "defectdojo_api_generated.cli.__main__:main"

[dependency-groups]
dev = [
"build",
"classyclick==1.0.0",
"jmespath>=1.0.1",
"mkdocs>=1.6.1",
"mkdocs-click>=0.9.0",
"pytest",
Expand All @@ -44,7 +35,7 @@ dev = [
Homepage = "https://github.com/fopina/defectdojo-api-generated"

[tool.setuptools.packages.find]
include = ["defectdojo_api_generated*"]
include = ["defectdojo_api_generated", "defectdojo_api_generated.*"]

[tool.setuptools.dynamic]
version = {attr = "defectdojo_api_generated.__version__"}
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/test_packaging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pathlib
import re
import unittest

try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python < 3.11
import tomli as tomllib

import defectdojo_api_generated

ROOT = pathlib.Path(__file__).resolve().parents[2]


class TestPackagingLayout(unittest.TestCase):
def test_library_package_has_no_console_scripts(self):
pyproject = tomllib.loads((ROOT / 'pyproject.toml').read_text())

self.assertNotIn('scripts', pyproject['project'])

def test_cli_wrapper_package_owns_console_scripts(self):
pyproject = tomllib.loads((ROOT / 'packages' / 'cli' / 'pyproject.toml').read_text())

self.assertEqual(
pyproject['project']['scripts'],
{
'dojo': 'defectdojo_api_generated_cli.__main__:main',
'defectdojo-api-generated': 'defectdojo_api_generated_cli.__main__:main',
},
)

def test_cli_wrapper_version_matches_library_version(self):
wrapper_init = (ROOT / 'packages' / 'cli' / 'src' / 'defectdojo_api_generated_cli' / '__init__.py').read_text()
match = re.search(r"__version__ = '([^']+)'", wrapper_init)

self.assertIsNotNone(match)
self.assertEqual(match.group(1), defectdojo_api_generated.__version__)

def test_cli_wrapper_dependency_matches_library_version(self):
pyproject = tomllib.loads((ROOT / 'packages' / 'cli' / 'pyproject.toml').read_text())

self.assertIn(
f'defectdojo-api-generated=={defectdojo_api_generated.__version__}',
pyproject['project']['dependencies'],
)
Loading
Loading