Skip to content

Commit

Permalink
feat: Add option to skip duties based on condition
Browse files Browse the repository at this point in the history
issue #6: #6
  • Loading branch information
pawamoy committed Feb 11, 2023
1 parent a884929 commit 629b988
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 34 deletions.
22 changes: 22 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
hide:
- navigation
---

# Usage

## Writing duties
Expand Down Expand Up @@ -418,6 +423,23 @@ duty start
duty up
```

### Skipping duties

You can tell duty to always skip a duty if a certain condition is met.
This feature is inspired by pytest's `skip_if` marker.

```python
@duty(
skip_if=sys.version_info < (3, 8),
skip_reason="Building docs is not supported on Python 3.7",
)
def docs(ctx):
ctx.run("mkdocs build")
```

By default, `skip_reason` will be "duty: skipped" where "duty" is replaced
by the name of the duty.

## Running duties

To run a duty, simply use:
Expand Down
37 changes: 5 additions & 32 deletions duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import importlib
import os
import sys
from functools import wraps
from io import StringIO
from pathlib import Path

Expand All @@ -26,30 +25,6 @@ def pyprefix(title: str) -> str: # noqa: D103
return title


def skip_if(condition: bool, reason: str):
"""Decorator allowing to skip a duty if a condition is met.
Parameters:
condition: The condition to meet.
reason: The reason to skip.
Returns:
A decorator.
"""

def decorator(func):
@wraps(func)
def wrapper(ctx, *args, **kwargs):
if condition:
ctx.run(["true"], title=reason)
else:
func(ctx, *args, **kwargs)

return wrapper

return decorator


@duty
def changelog(ctx):
"""Update the changelog in-place with latest commits.
Expand Down Expand Up @@ -156,15 +131,13 @@ def check_docs(ctx):
ctx.run("mkdocs build -s", title=pyprefix("Building documentation"), pty=PTY)


@duty # noqa: WPS231
@skip_if(
sys.version_info < (3, 8),
reason=pyprefix(
"Checking types is not supported on Python 3.7 because of a mypy issue, "
"see https://github.com/python/mypy/issues/14670"
@duty(
skip_if=sys.version_info < (3, 8),
skip_reason=pyprefix(
"Type-checking: skipped: not supported on Python 3.7, see https://github.com/python/mypy/issues/14670"
),
)
def check_types(ctx): # noqa: WPS231
def check_types(ctx):
"""
Check that the code is correctly typed.
Expand Down
19 changes: 17 additions & 2 deletions src/duty/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@
from __future__ import annotations

import inspect
from functools import wraps
from typing import Any, Callable, Iterable, overload

from duty.collection import Duty, DutyListType


def _skip(func: Callable, reason: str):
@wraps(func)
def wrapper(ctx, *args, **kwargs):
ctx.run(lambda: True, title=reason)

return wrapper


def create_duty(
func: Callable,
name: str | None = None,
aliases: Iterable[str] | None = None,
pre: DutyListType | None = None,
post: DutyListType | None = None,
skip_if: bool = False,
skip_reason: str | None = None,
**opts: Any,
) -> Duty:
"""
Expand All @@ -24,6 +35,8 @@ def create_duty(
aliases: A set of aliases for this duty.
pre: Pre-duties.
post: Post-duties.
skip_if: Skip running the duty if the given condition is met.
skip_reason: Custom message when skipping.
opts: Options passed to the context.
Returns:
Expand All @@ -36,6 +49,8 @@ def create_duty(
aliases.add(name)
name = dash_name
description = inspect.getdoc(func) or ""
if skip_if:
func = _skip(func, skip_reason or f"{dash_name}: skipped")
duty = Duty(name, description, func, aliases=aliases, pre=pre, post=post, opts=opts)
duty.__name__ = name # type: ignore
duty.__doc__ = description
Expand All @@ -45,12 +60,12 @@ def create_duty(

@overload
def duty(**kwargs: Any) -> Callable[[Callable], Duty]: # type: ignore[misc]
...
... # pragma: no cover


@overload
def duty(func: Callable) -> Duty:
...
... # pragma: no cover


def duty(*args: Any, **kwargs: Any) -> Callable | Duty:
Expand Down
13 changes: 13 additions & 0 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
"""Tests for the `decorator` module."""

import inspect

import pytest

from duty.context import Context
from duty.decorator import duty as decorate
from duty.exceptions import DutyFailure


def test_accept_one_posarg_when_decorating():
"""Accept only one positional argument when decorating."""
with pytest.raises(ValueError, match="accepts only one positional argument"):
decorate(0, 1)


def test_skipping():
"""Wrap function that must be skipped."""
duty = decorate(lambda ctx: ctx.run("false"), skip_if=True)
# no DutyFailure raised
assert duty.run() is None
with pytest.raises(DutyFailure):
assert inspect.unwrap(duty)(Context({}))

0 comments on commit 629b988

Please sign in to comment.