Skip to content

Commit

Permalink
update CI to combine code coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
bckohan committed Jan 24, 2024
1 parent efb552f commit a543da8
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 53 deletions.
66 changes: 53 additions & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,22 @@ jobs:
poetry export --without-hashes --format=requirements.txt | poetry run safety check --stdin
poetry run python -m readme_renderer ./README.rst -o /tmp/README.html
build:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']
django-version:
- 'Django~=3.2.0' # LTS April 2024
- 'Django~=4.2.0' # LTS April 2026
- 'Django~=5.0.0' # April 2025
- '3.2' # LTS April 2024
- '4.2' # LTS April 2026
- '5.0' # April 2025
exclude:
- python-version: '3.9'
django-version: 'Django~=5.0.0'
django-version: '5.0'
- python-version: '3.11'
django-version: 'Django~=3.2.0'
django-version: '3.2'
- python-version: '3.12'
django-version: 'Django~=3.2.0'
django-version: '3.2'

steps:
- uses: actions/checkout@v3
Expand All @@ -85,14 +85,54 @@ jobs:
poetry config virtualenvs.in-project true
poetry run pip install --upgrade pip
poetry install
poetry run pip install -U "${{ matrix.django-version }}"
poetry run pip install -U "Django~=${{ matrix.django-version }}"
- name: Run Unit Tests
run: |
poetry run pytest
mv .coverage py${{ matrix.python-version }}-dj${{ matrix.django-version }}.coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
file: ./coverage.xml
name: coverage-py${{ matrix.python-version }}-dj${{ matrix.django-version }}
path: py${{ matrix.python-version }}-dj${{ matrix.django-version }}.coverage

coverage-combine:
needs: [test]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true

- name: Install Release Dependencies
run: |
poetry config virtualenvs.in-project true
poetry run pip install --upgrade pip
poetry install
- name: Get coverage files
uses: actions/download-artifact@v4
with:
pattern: coverage-*
merge-multiple: true

- run: ls -la *.coverage
- run: poetry run coverage combine *.coverage
- run: poetry run coverage report
- run: poetry run coverage xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml

15 changes: 15 additions & 0 deletions django_typer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from types import MethodType, SimpleNamespace

import click
from click.shell_completion import CompletionItem
from django.conf import settings
from django.core.management import get_commands
from django.core.management.base import BaseCommand
Expand Down Expand Up @@ -60,6 +61,7 @@
"command",
"group",
"get_command",
"COMPLETE_VAR"
]

"""
Expand Down Expand Up @@ -87,6 +89,7 @@
# COLOR_SYSTEM = lazy(get_color_system, str)
# rich_utils.COLOR_SYSTEM = COLOR_SYSTEM(rich_utils.COLOR_SYSTEM)

COMPLETE_VAR = "_COMPLETE_INSTRUCTION"

def traceback_config():
cfg = getattr(settings, "DT_RICH_TRACEBACK_CONFIG", {"show_locals": True})
Expand Down Expand Up @@ -234,6 +237,17 @@ class DjangoAdapterMixin: # pylint: disable=too-few-public-methods
callback_is_method: bool = True
param_converters: t.Dict[str, t.Callable[..., t.Any]] = {}

def shell_complete(self, ctx: Context, incomplete: str) -> t.List[CompletionItem]:
"""
By default if the incomplete string is a space and there are no completions
the click infrastructure will return _files. We'd rather return parameters
for the command if there are any available.
"""
completions = super().shell_complete(ctx, incomplete)
if not completions and (incomplete.isspace() or not incomplete) and getattr(ctx, '_opt_prefixes', None):
completions = super().shell_complete(ctx, min(ctx._opt_prefixes))
return completions

def common_params(self):
return []

Expand Down Expand Up @@ -657,6 +671,7 @@ def handle(self, *args, **options):
standalone_mode=False,
supplied_params=options,
django_command=self,
complete_var=None,
prog_name=f"{sys.argv[0]} {self.typer_app.info.name}",
)

Expand Down
64 changes: 37 additions & 27 deletions django_typer/management/commands/shellcompletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from typer import Argument, Option, echo
from typer.completion import Shells, completion_init

from django_typer import TyperCommand, command, get_command
from django_typer import TyperCommand, command, get_command, COMPLETE_VAR

try:
from shellingham import detect_shell
Expand Down Expand Up @@ -69,8 +69,6 @@ class Command(TyperCommand):

_shell: Shells

COMPLETE_VAR = "_COMPLETE_INSTRUCTION"

@cached_property
def manage_script(self) -> t.Union[str, Path]:
"""
Expand Down Expand Up @@ -137,8 +135,8 @@ def shell(self):
self,
"_shell",
Shells(
os.environ[self.COMPLETE_VAR].partition("_")[2]
if self.COMPLETE_VAR in os.environ
os.environ[COMPLETE_VAR].partition("_")[2]
if COMPLETE_VAR in os.environ
else detect_shell()[0]
),
)
Expand Down Expand Up @@ -279,7 +277,7 @@ def install(
install_path = install(
shell=self.shell.value,
prog_name=manage_script or self.manage_script_name,
complete_var=self.COMPLETE_VAR,
complete_var=COMPLETE_VAR,
)[1]
self.stdout.write(
self.style.SUCCESS(
Expand Down Expand Up @@ -388,18 +386,27 @@ def get_completion_args(self) -> t.Tuple[t.List[str], str]:
except Exception:
pass
return (
cwords,
cwords[-1] if len(cwords) and not command[-1].isspace() else "",
cwords[:-1],
cwords[-1] if len(cwords) and not command[-1].isspace() else ' ',
)

CompletionClass.get_completion_args = get_completion_args
add_completion_class(self.shell.value, CompletionClass)

_get_completions = CompletionClass.get_completions
def get_completions(self, args, incomplete):
"""
need to remove the django command name from the arg completions
"""
return _get_completions(self, args[1:], incomplete)
CompletionClass.get_completions = get_completions

add_completion_class(self.shell.value, CompletionClass)

args, incomplete = CompletionClass(
cli=self.noop_command,
ctx_args=self.noop_command,
cli=self.noop_command.command,
ctx_args={},
prog_name=sys.argv[0],
complete_var=self.COMPLETE_VAR,
complete_var=COMPLETE_VAR,
).get_completion_args()

def call_fallback(fb):
Expand All @@ -413,21 +420,24 @@ def call_fallback(fb):
call_fallback(fallback)
else:
try:
os.environ[self.COMPLETE_VAR] = os.environ.get(
self.COMPLETE_VAR, f"complete_{self.shell.value}"
os.environ[COMPLETE_VAR] = os.environ.get(
COMPLETE_VAR, f"complete_{self.shell.value}"
)
cmd = get_command(args[0])
if isinstance(cmd, TyperCommand):
# invoking the command will trigger the autocompletion?
cmd.command_tree.command._main_shell_completion(
ctx_args={},
prog_name=f"{sys.argv[0]} {cmd.command_tree.command.name}",
complete_var=self.COMPLETE_VAR,
)
return
else:
call_fallback(fallback)
except Exception as e:
except Exception:
call_fallback(fallback)
return

if isinstance(cmd, TyperCommand):
cmd.typer_app(
args=args[1:],
standalone_mode=True,
django_command=cmd,
complete_var=COMPLETE_VAR,
prog_name=f"{sys.argv[0]} {self.typer_app.info.name}",
)
return
else:
call_fallback(fallback)

def django_fallback(self):
Expand Down Expand Up @@ -462,10 +472,10 @@ def get_completions(self, args, incomplete):
CompletionClass.get_completions = get_completions
echo(
CompletionClass(
cli=self.noop_command,
cli=self.noop_command.command,
ctx_args={},
prog_name=self.manage_script_name,
complete_var=self.COMPLETE_VAR,
complete_var=COMPLETE_VAR,
).complete()
)

Expand Down
4 changes: 0 additions & 4 deletions django_typer/tests/test_app/management/commands/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@


def parse_app_label(label: t.Union[str, AppConfig]):
if label == "django_apps":
import ipdb

ipdb.set_trace()
if isinstance(label, AppConfig):
return label
return apps.get_app_config(label)
Expand Down
14 changes: 7 additions & 7 deletions django_typer/tests/typer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ def create(username: str, flag: bool = False):
print(f"flag: {flag}")


# @app.command(epilog="Delete Epilog")
# def delete(username: str):
# if state["verbose"]:
# print("About to delete a user")
# print(f"Deleting user: {username}")
# if state["verbose"]:
# print("Just deleted a user")
@app.command(epilog="Delete Epilog")
def delete(username: str):
if state["verbose"]:
print("About to delete a user")
print(f"Deleting user: {username}")
if state["verbose"]:
print("Just deleted a user")


@app.callback(epilog="Main Epilog")
Expand Down
2 changes: 0 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ addopts = [
"--cov=django_typer",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html",
"--cov-report=xml",
"--cov-fail-under=90"
]

Expand Down

0 comments on commit a543da8

Please sign in to comment.