mirror of
https://git.zavage.net/Zavage-Software/smileyface.git
synced 2026-06-25 18:12:48 -06:00
Merge branch 'python-version-support'
Add Python 3.8-3.13 multi-version support: pytest smoke-test suite, uv+nox version matrix, importlib_resources backport for 3.8, declared the previously-undeclared selenium dependency, Gitea Actions CI matrix, and supporting docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
a2d4b334d7
21
.gitea/workflows/test.yml
Normal file
21
.gitea/workflows/test.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
|
- name: Run smoke tests
|
||||||
|
run: uv run --python ${{ matrix.python-version }} --with . --with pytest pytest -v
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ idea
|
|||||||
.env
|
.env
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
.claude/worktrees/
|
.claude/worktrees/
|
||||||
|
uv.lock
|
||||||
|
|||||||
10
CLAUDE.md
10
CLAUDE.md
@ -24,13 +24,19 @@ flake8 smileyface/
|
|||||||
|
|
||||||
# Run pre-commit hooks manually
|
# Run pre-commit hooks manually
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
|
||||||
|
# Run the smoke-test suite on the current interpreter
|
||||||
|
uv run --with . --with pytest pytest -v
|
||||||
|
|
||||||
|
# Run the full Python version matrix (3.8-3.13)
|
||||||
|
uv run --with nox nox -s tests
|
||||||
```
|
```
|
||||||
|
|
||||||
There is no test suite.
|
Smoke tests live in `tests/` (import, CLI `--help`, and settings checks) and run across the supported Python 3.8-3.13 matrix via `nox`.
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
- **Black** formatter: 120 char line length, target Python 3.13
|
- **Black** formatter: 120 char line length, targets Python 3.8–3.13
|
||||||
- **isort**: profile black, multi_line_output=3, trailing commas, force_grid_wrap=3
|
- **isort**: profile black, multi_line_output=3, trailing commas, force_grid_wrap=3
|
||||||
- **flake8**: 120 char max, ignores E121/E123/E126/E226/E24/E704/W605
|
- **flake8**: 120 char max, ignores E121/E123/E126/E226/E24/E704/W605
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@ -15,6 +15,16 @@ Activate your desired python environment, then:
|
|||||||
|
|
||||||
poetry install
|
poetry install
|
||||||
|
|
||||||
|
Supported Python Versions
|
||||||
|
==========================
|
||||||
|
SmileyFace is tested against CPython 3.8 – 3.13. The supported range is
|
||||||
|
enforced by a smoke-test matrix.
|
||||||
|
|
||||||
|
To run the matrix locally (requires [uv](https://docs.astral.sh/uv/)):
|
||||||
|
|
||||||
|
uv python install 3.8 3.9 3.10 3.11 3.12 3.13
|
||||||
|
uv run --with nox nox -s tests
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|||||||
15
noxfile.py
Normal file
15
noxfile.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import nox
|
||||||
|
|
||||||
|
nox.options.default_venv_backend = "uv"
|
||||||
|
nox.options.reuse_existing_virtualenvs = False
|
||||||
|
|
||||||
|
# Newest first. 3.8 is a stretch goal (see the importlib_resources backport).
|
||||||
|
PYTHON_VERSIONS = ["3.13", "3.12", "3.11", "3.10", "3.9", "3.8"]
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(python=PYTHON_VERSIONS)
|
||||||
|
def tests(session):
|
||||||
|
"""Install the project + pytest and run the smoke suite on each interpreter."""
|
||||||
|
session.install(".")
|
||||||
|
session.install("pytest")
|
||||||
|
session.run("pytest", "-v")
|
||||||
1397
poetry.lock
generated
1397
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,14 @@ homepage = "https://zavage-software.com/portfolio/smileyface"
|
|||||||
repository = "https://git-mirror.zavage.net/zavage-software/smileyface"
|
repository = "https://git-mirror.zavage.net/zavage-software/smileyface"
|
||||||
documentation = "https://git-mirror.zavage.net/zavage-software/smileyface"
|
documentation = "https://git-mirror.zavage.net/zavage-software/smileyface"
|
||||||
keywords = ["cas"]
|
keywords = ["cas"]
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
]
|
||||||
|
|
||||||
packages = [{ include = "smileyface" }]
|
packages = [{ include = "smileyface" }]
|
||||||
include = [
|
include = [
|
||||||
@ -21,17 +29,21 @@ include = [
|
|||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.13"
|
python = ">=3.8,<4.0"
|
||||||
pydantic-settings = ">=2.0"
|
pydantic-settings = ">=2.0"
|
||||||
click = ">=8.0"
|
click = ">=8.0"
|
||||||
platformdirs = ">=3.0"
|
platformdirs = ">=3.0"
|
||||||
sqlparse = "*"
|
sqlparse = "*"
|
||||||
|
selenium = ">=4.0"
|
||||||
|
importlib-resources = { version = ">=5.0", python = "<3.9" }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "*"
|
black = "*"
|
||||||
pre-commit = "*"
|
pre-commit = "*"
|
||||||
isort = "*"
|
isort = "*"
|
||||||
flake8 = "*"
|
flake8 = "*"
|
||||||
|
pytest = "*"
|
||||||
|
nox = "*"
|
||||||
#Sphinx = "^5.3.0"
|
#Sphinx = "^5.3.0"
|
||||||
#sphinx-rtd-theme = "^1.3.0"
|
#sphinx-rtd-theme = "^1.3.0"
|
||||||
|
|
||||||
@ -41,7 +53,7 @@ build-backend = "poetry.core.masonry.api"
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
target-version = ['py313']
|
target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313']
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
multi_line_output = 3
|
multi_line_output = 3
|
||||||
@ -49,3 +61,7 @@ combine_as_imports = true
|
|||||||
include_trailing_comma = true
|
include_trailing_comma = true
|
||||||
force_grid_wrap = 3
|
force_grid_wrap = 3
|
||||||
ensure_newline_before_comments = true
|
ensure_newline_before_comments = true
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
addopts = "-ra"
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from importlib.resources import files
|
try:
|
||||||
|
from importlib.resources import files # Python 3.9+
|
||||||
|
except ImportError: # Python 3.8
|
||||||
|
from importlib_resources import files
|
||||||
|
|
||||||
import sqlparse
|
import sqlparse
|
||||||
|
|
||||||
|
|||||||
24
tests/test_cli.py
Normal file
24
tests/test_cli.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from smileyface.cli import cli
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_help():
|
||||||
|
result = CliRunner().invoke(cli, ["--help"])
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert "SmileyFace" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_command_help():
|
||||||
|
runner = CliRunner()
|
||||||
|
for name in cli.commands:
|
||||||
|
result = runner.invoke(cli, [name, "--help"])
|
||||||
|
assert result.exit_code == 0, f"`{name} --help` failed:\n{result.output}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_scrape_subcommands_help():
|
||||||
|
runner = CliRunner()
|
||||||
|
scrape_group = cli.commands["scrape"]
|
||||||
|
for name in scrape_group.commands:
|
||||||
|
result = runner.invoke(cli, ["scrape", name, "--help"])
|
||||||
|
assert result.exit_code == 0, f"`scrape {name} --help` failed:\n{result.output}"
|
||||||
34
tests/test_imports.py
Normal file
34
tests/test_imports.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import importlib
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
import smileyface
|
||||||
|
|
||||||
|
|
||||||
|
def test_top_level_package_imports():
|
||||||
|
importlib.import_module("smileyface")
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_submodules_import():
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
def _on_walk_error(name):
|
||||||
|
errors.append(f"{name}: failed during package walk")
|
||||||
|
|
||||||
|
module_names = [
|
||||||
|
info.name
|
||||||
|
for info in pkgutil.walk_packages(
|
||||||
|
smileyface.__path__,
|
||||||
|
prefix="smileyface.",
|
||||||
|
onerror=_on_walk_error,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
assert module_names, "walk_packages found no submodules - check smileyface.__path__"
|
||||||
|
|
||||||
|
for name in module_names:
|
||||||
|
try:
|
||||||
|
importlib.import_module(name)
|
||||||
|
except Exception as exc: # noqa: BLE001 - we want every failure, not the first
|
||||||
|
errors.append(f"{name}: {exc!r}")
|
||||||
|
|
||||||
|
assert not errors, "Modules failed to import:\n" + "\n".join(errors)
|
||||||
16
tests/test_settings.py
Normal file
16
tests/test_settings.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from smileyface.settings import AppSettings
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults():
|
||||||
|
settings = AppSettings(_env_file=None)
|
||||||
|
assert settings.sqlite_filename == "smiles.db"
|
||||||
|
assert settings.skip_validate is False
|
||||||
|
assert settings.project_dir == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_overrides(monkeypatch):
|
||||||
|
monkeypatch.setenv("SMILEYFACE_PROJECT_DIR", "/srv/ut4")
|
||||||
|
monkeypatch.setenv("SMILEYFACE_SKIP_VALIDATE", "true")
|
||||||
|
settings = AppSettings(_env_file=None)
|
||||||
|
assert settings.project_dir == "/srv/ut4"
|
||||||
|
assert settings.skip_validate is True
|
||||||
Loading…
Reference in New Issue
Block a user