Skip to content

Commit

Permalink
Update tooling
Browse files Browse the repository at this point in the history
Update ruff, black, isort, mypy, pytest, and coverage configuration
Add workspace settings and recommended extensions
Add Continuous Integration reports for ruff, mypy and pytest
  • Loading branch information
pre-commit-ci[bot] authored and JonathanPlasse committed Jan 21, 2023
1 parent 1ce727f commit ef37ffb
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 96 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: lint

on:
pull_request:
paths:
- "**.py"

jobs:
test:
strategy:
matrix:
os: [ubuntu-latest]
python-version: ["3.7"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: pip-test-${{ matrix.python-version }}-${{ matrix.os }}
- name: Install dependencies
run: pip install -e .[lint,tests]
- name: Run Ruff
run: ruff --format=github .
- name: Run mypy
run: mypy .
- name: Publish mypy report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
check_name: mypy
report_paths: "./reports/mypy.xml"
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ jobs:
run: pip install -e .[tests]
- name: Test with pytest
run: pytest --cov=asyncio_mqtt --cov=tests --cov-report=xml
- name: Publish pytest report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
check_name: pytest
report_paths: "./reports/pytest.xml"
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
files: ./reports/coverage.xml
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ coverage.xml
asyncio_mqtt/_version.py
*.DS_Store
docs/_build
reports
31 changes: 15 additions & 16 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.215
rev: v0.0.228
hooks:
- id: ruff
args:
- --fix
- --target-version
- py37
- --force-exclude

- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
hooks:
- id: mypy
additional_dependencies:
- anyio
- pytest
- types-paho-mqtt == 1.6.0.1

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.4
hooks:
Expand All @@ -46,8 +37,16 @@ repos:
args: ["--fix=lf"]
- id: trailing-whitespace

- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
hooks:
- id: python-check-blanket-type-ignore
- id: python-check-blanket-noqa
- id: mypy
additional_dependencies:
- anyio == 3.6.2
- pytest == 7.2.0
- types-paho-mqtt == 1.6.0.1

ci:
skip:
- ruff
- mypy
8 changes: 8 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"recommendations": [
"ms-python.python",
"littlefoxteam.vscode-python-test-adapter",
"charliermarsh.ruff",
"tamasfe.even-better-toml"
]
}
13 changes: 13 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"python.formatting.provider": "black",
"python.linting.mypyEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"ruff.importStrategy": "fromEnvironment"
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Update Ruff and fix new lint errors. Contributed by [pre-commit-ci](https://github.com/apps/pre-commit-ci) and [Jonathan Plasse (@JonathanPlasse)](https://github.com/JonathanPlasse) in [#161](https://github.com/sbtinstruments/asyncio-mqtt/pull/161)
- Update tooling. Contributed by [pre-commit-ci](https://github.com/apps/pre-commit-ci) and [Jonathan Plasse (@JonathanPlasse)](https://github.com/JonathanPlasse) in [#184](https://github.com/sbtinstruments/asyncio-mqtt/pull/184)

### Fixed

Expand Down
19 changes: 1 addition & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,7 @@ pre-commit install

### Visual Studio Code

If you are using VSCode, these are the settings to activate on save:

- `black` to format.
- `mypy` to lint.
- Install the [charliermarsh.ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) extension to lint, sort imports, and auto-fix lint errors (`ruff` is a fast equivalent to `flake8`)

```json
{
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"python.formatting.provider": "black",
"python.linting.mypyEnabled": true
}
```
If you are using VSCode, these are workspace settings and highly recommended extensions to improve developer experience.

## Testing

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<a href="https://pypi.org/project/asyncio-mqtt"><img alt="PyPI version" src="https://img.shields.io/pypi/v/asyncio-mqtt"></a>
<a href="https://pypi.org/project/asyncio-mqtt"><img alt="Supported Python versions" src="https://img.shields.io/pypi/pyversions/asyncio-mqtt.svg"></a>
<a href="https://pypi.org/project/asyncio-mqtt"><img alt="PyPI downloads" src="https://img.shields.io/pypi/dm/asyncio-mqtt"></a>
<a href="https://github.com/sbtinstruments/asyncio-mqtt/actions/workflows/test.yml"><img alt="Coverage" src="https://github.com/sbtinstruments/asyncio-mqtt/actions/workflows/test.yml/badge.svg"></a>
<a href="https://github.com/sbtinstruments/asyncio-mqtt/actions/workflows/test.yml"><img alt="test suite" src="https://github.com/sbtinstruments/asyncio-mqtt/actions/workflows/test.yml/badge.svg"></a>
<a href="https://github.com/sbtinstruments/asyncio-mqtt/actions/workflows/lint.yml"><img alt="lint" src="https://github.com/sbtinstruments/asyncio-mqtt/actions/workflows/lint.yml/badge.svg"></a>
<a href="https://codecov.io/gh/sbtinstruments/asyncio-mqtt"><img alt="Coverage" src="https://img.shields.io/codecov/c/github/sbtinstruments/asyncio-mqtt"></a>
<a href="https://results.pre-commit.ci/latest/github/sbtinstruments/asyncio-mqtt/main"><img alt="pre-commit.ci status" src="https://results.pre-commit.ci/badge/github/sbtinstruments/asyncio-mqtt/main.svg"></a>
<a href="https://github.com/sbtinstruments/asyncio-mqtt"><img alt="Typing: strict" src="https://img.shields.io/badge/typing-strict-green.svg"></a>
Expand Down
40 changes: 24 additions & 16 deletions asyncio_mqtt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ async def decorated(self: Client, *args: P.args, **kwargs: P.kwargs) -> T:
return decorated


MAX_TOPIC_LENGTH = 65535


@dataclass(frozen=True)
class Wildcard:
"""A topic, optionally with wildcards (+ and #). Can only be subscribed to."""
Expand All @@ -131,18 +134,20 @@ def __str__(self) -> str:
def __post_init__(self) -> None:
"""Validate the wildcard."""
if not isinstance(self.value, str):
raise TypeError("wildcard must be a string")
msg = "wildcard must be a string"
raise TypeError(msg)
if (
len(self.value) == 0
or len(self.value) > 65535
or len(self.value) > MAX_TOPIC_LENGTH
or "#/" in self.value
or any(
"+" in level or "#" in level
for level in self.value.split("/")
if len(level) > 1
)
):
raise ValueError(f"Invalid wildcard: {self.value}")
msg = f"Invalid wildcard: {self.value}"
raise ValueError(msg)


WildcardLike: TypeAlias = "str | Wildcard"
Expand All @@ -155,14 +160,16 @@ class Topic(Wildcard):
def __post_init__(self) -> None:
"""Validate the topic."""
if not isinstance(self.value, str):
raise TypeError("topic must be a string")
msg = "topic must be a string"
raise TypeError(msg)
if (
len(self.value) == 0
or len(self.value) > 65535
or len(self.value) > MAX_TOPIC_LENGTH
or "+" in self.value
or "#" in self.value
):
raise ValueError(f"Invalid topic: {self.value}")
msg = f"Invalid topic: {self.value}"
raise ValueError(msg)

def matches(self, wildcard: WildcardLike) -> bool:
"""Check if the topic is matched by a given wildcard."""
Expand Down Expand Up @@ -511,9 +518,8 @@ async def unfiltered_messages(
)
# Early out
if self._unfiltered_messages_callback is not None:
raise RuntimeError(
"Only a single unfiltered_messages generator can be used at a time."
)
msg = "Only a single unfiltered_messages generator can be used at a time."
raise RuntimeError(msg)
callback, generator = self._deprecated_callback_and_generator(
log_context="unfiltered", queue_maxsize=queue_maxsize
)
Expand Down Expand Up @@ -590,7 +596,8 @@ async def _message_generator() -> AsyncGenerator[mqtt.MQTTMessage, None]:
# We got disconnected from the broker. Cancel the "get" task.
get.cancel()
# Stop the generator with the following exception
raise MqttError("Disconnected during message iteration")
msg = "Disconnected during message iteration"
raise MqttError(msg)

return _put_in_queue, _message_generator()

Expand Down Expand Up @@ -630,7 +637,8 @@ async def _generator() -> AsyncGenerator[Message, None]:
# We got disconnected from the broker. Cancel the "get" task.
get.cancel()
# Stop the generator with the following exception
raise MqttError("Disconnected during message iteration")
msg = "Disconnected during message iteration"
raise MqttError(msg)

return _callback, _generator()

Expand All @@ -640,16 +648,16 @@ async def _wait_for(
try:
return await asyncio.wait_for(fut, timeout=timeout, **kwargs)
except asyncio.TimeoutError:
raise MqttError("Operation timed out") from None
msg = "Operation timed out"
raise MqttError(msg) from None

@contextmanager
def _pending_call(
self, mid: int, value: T, pending_dict: dict[int, T]
) -> Iterator[None]:
if mid in self._pending_calls:
raise RuntimeError(
f'There already exists a pending call for message ID "{mid}"'
)
msg = f'There already exists a pending call for message ID "{mid}"'
raise RuntimeError(msg)
pending_dict[mid] = value # [1]
try:
# Log a warning if there is a concerning number of pending calls
Expand Down Expand Up @@ -709,7 +717,7 @@ def _on_disconnect(
# fails). In turn, this avoids asyncio debug messages like the
# following:
#
# "[asyncio] Future exception was never retrieved"
# `[asyncio] Future exception was never retrieved`
#
# See also: https://docs.python.org/3/library/asyncio-dev.html#detect-never-retrieved-exceptions
if not self._connected.done() or self._connected.exception() is not None:
Expand Down
2 changes: 0 additions & 2 deletions asyncio_mqtt/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
class MqttError(Exception):
"""Base exception for all asyncio-mqtt exceptions."""

pass


class MqttCodeError(MqttError):
def __init__(self, rc: int | mqtt.ReasonCodes | None, *args: Any):
Expand Down
Loading

0 comments on commit ef37ffb

Please sign in to comment.