MIL-001 Project scaffold and tooling #26
@@ -0,0 +1,46 @@
|
|||||||
|
# Continuous integration for the squirrel census project: runs the pytest tests
|
||||||
|
# on Python 3.13 for every push and pull request. Read by Gitea Actions (a
|
||||||
|
# runner must be registered for the repository). It changes nothing outside the
|
||||||
|
# repository and uses no secret. Decision: MIL-001 task "Add CI workflow".
|
||||||
|
# Host: Gitea Actions; uses only github.* contexts, so it also runs on GitHub.
|
||||||
|
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.13"
|
||||||
|
PIP_VERSION: "26.2.1"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: tests-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pytest:
|
||||||
|
name: Run the tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- name: Check out the code
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5.6.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Create the virtual environment and install
|
||||||
|
run: |
|
||||||
|
python -m venv .venv
|
||||||
|
.venv/bin/python -m pip install --upgrade "pip==${PIP_VERSION}"
|
||||||
|
.venv/bin/python -m pip install -e ".[dev]"
|
||||||
|
|
||||||
|
- name: Run the tests
|
||||||
|
run: .venv/bin/python -m pytest
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Packaging and builds
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# Test and tool caches
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# Secrets: personal tokens used only by the maintainer, never by the project
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Generated source documentation (Doxygen)
|
||||||
|
docs/doxygen/
|
||||||
|
|
||||||
|
# Editors and operating systems
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# yamllint configuration for the workflow files in .gitea/workflows/.
|
||||||
|
extends: default
|
||||||
|
rules:
|
||||||
|
document-start: disable
|
||||||
|
line-length:
|
||||||
|
max: 100
|
||||||
|
truthy:
|
||||||
|
check-keys: false
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Doxygen configuration for the squirrel census project.
|
||||||
|
# Build the source documentation with: doxygen Doxyfile
|
||||||
|
# Output goes to docs/doxygen/ (ignored by git). Everything not listed here
|
||||||
|
# keeps the Doxygen default.
|
||||||
|
|
||||||
|
PROJECT_NAME = "Squirrel Census"
|
||||||
|
PROJECT_BRIEF = "Count squirrels by primary fur colour"
|
||||||
|
PROJECT_NUMBER = 0.1.0
|
||||||
|
OUTPUT_DIRECTORY = docs/doxygen
|
||||||
|
INPUT = src README.md
|
||||||
|
FILE_PATTERNS = *.py *.md
|
||||||
|
RECURSIVE = YES
|
||||||
|
USE_MDFILE_AS_MAINPAGE = README.md
|
||||||
|
OPTIMIZE_OUTPUT_JAVA = YES
|
||||||
|
EXTRACT_ALL = YES
|
||||||
|
GENERATE_HTML = YES
|
||||||
|
GENERATE_LATEX = NO
|
||||||
|
QUIET = YES
|
||||||
|
WARN_IF_UNDOCUMENTED = YES
|
||||||
|
WARN_AS_ERROR = NO
|
||||||
@@ -31,10 +31,10 @@ This plan schedules the four phases that deliver the squirrel census assignment:
|
|||||||
|
|
||||||
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
|
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
| G1 Project scaffold and tooling | [MIL-001] | 2026-10-13 to 2026-10-14 | 2026-10-14 | S01 | none | `.gitignore`, `pyproject.toml`, constants, Doxyfile, continuous integration (CI), repository metadata | |
|
| G1 Project scaffold and tooling | [MIL-001] | 2026-10-13 to 2026-10-14 | 2026-10-14 | S01 | none | `.gitignore`, `pyproject.toml`, constants, Doxyfile, continuous integration (CI), repository metadata | [Milestone 124][MS-124] |
|
||||||
| G2 Squirrel census analysis | [MIL-002] | 2026-10-14 to 2026-10-15 | 2026-10-15 | S01 | US-001 | Package, tests, census file, `squirrel_count.csv` | |
|
| G2 Squirrel census analysis | [MIL-002] | 2026-10-14 to 2026-10-15 | 2026-10-15 | S01 | US-001 | Package, tests, census file, `squirrel_count.csv` | [Milestone 125][MS-125] |
|
||||||
| G3 README, source documentation and screenshots | [MIL-003] | 2026-10-15 to 2026-10-16 | 2026-10-16 | S01 | none | README, Doxygen comments, screenshots | |
|
| G3 README, source documentation and screenshots | [MIL-003] | 2026-10-15 to 2026-10-16 | 2026-10-16 | S01 | none | README, Doxygen comments, screenshots | [Milestone 126][MS-126] |
|
||||||
| G4 Wiki and publication | [MIL-004] | 2026-10-16 | 2026-10-16 | S01 | none | Wiki in en, da and ms | |
|
| G4 Wiki and publication | [MIL-004] | 2026-10-16 | 2026-10-16 | S01 | none | Wiki in en, da and ms | [Milestone 127][MS-127] |
|
||||||
|
|
||||||
```plantuml
|
```plantuml
|
||||||
@startgantt
|
@startgantt
|
||||||
@@ -93,3 +93,7 @@ A No-Go at a gateway stops the later gateways; their dates move by the days the
|
|||||||
[MIL-003]: ./milestones/mil-003-documentation.md
|
[MIL-003]: ./milestones/mil-003-documentation.md
|
||||||
[MIL-004]: ./milestones/mil-004-wiki-publication.md
|
[MIL-004]: ./milestones/mil-004-wiki-publication.md
|
||||||
[f1b3c03]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/f1b3c034dc3880b7884646b4a4ed28e5f98fbcd3
|
[f1b3c03]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/commit/f1b3c034dc3880b7884646b4a4ed28e5f98fbcd3
|
||||||
|
[MS-124]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/124
|
||||||
|
[MS-125]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/125
|
||||||
|
[MS-126]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/126
|
||||||
|
[MS-127]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-squirrel/milestone/127
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Project configuration for the squirrel census assignment (day 25).
|
||||||
|
# Read by pip, setuptools and pytest. The decision to use the standard library
|
||||||
|
# only is recorded in docs/adr/adr-0001-standard-library-instead-of-pandas.md.
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "squirrel"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Count the squirrels of the 2018 Central Park Squirrel Census by primary fur colour."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
license-files = ["LICENSE"]
|
||||||
|
dependencies = []
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Intended Audience :: Education",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest==9.1.1", "ruff==0.17.0", "mypy==2.4.0"]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = ["src"]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
src = ["src", "tests"]
|
||||||
|
extend-exclude = ["docs"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B", "D"]
|
||||||
|
ignore = ["D203", "D213"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
strict = true
|
||||||
|
files = ["src", "tests"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**" = ["D"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
##
|
||||||
|
# @file __init__.py
|
||||||
|
# @brief Package root of the squirrel census analysis.
|
||||||
|
|
||||||
|
"""Count the squirrels of the 2018 Central Park Squirrel Census by fur colour."""
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
##
|
||||||
|
# @file constants.py
|
||||||
|
# @brief Literals used by the squirrel census analysis, each defined once.
|
||||||
|
|
||||||
|
"""Literals used by the squirrel census analysis, each defined once."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
## @brief Default location of the census CSV file, relative to the working directory.
|
||||||
|
DEFAULT_INPUT_PATH: pathlib.Path = (
|
||||||
|
pathlib.Path("data") / "2018_Central_Park_Squirrel_Census_-_Squirrel_Data.csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
## @brief Default location of the summary CSV file that is written.
|
||||||
|
DEFAULT_OUTPUT_PATH: pathlib.Path = pathlib.Path("squirrel_count.csv")
|
||||||
|
|
||||||
|
## @brief Text encoding of the input and output files.
|
||||||
|
FILE_ENCODING: str = "utf-8"
|
||||||
|
|
||||||
|
## @brief Name of the census column that holds the primary fur colour.
|
||||||
|
FUR_COLOR_COLUMN: str = "Primary Fur Color"
|
||||||
|
|
||||||
|
## @brief Fur colours that are counted, in the order they appear in the summary.
|
||||||
|
FUR_COLORS: tuple[str, ...] = ("Gray", "Cinnamon", "Black")
|
||||||
|
|
||||||
|
## @brief Header of the summary file: the colour column, then the count column.
|
||||||
|
SUMMARY_COLUMNS: tuple[str, str] = ("Fur Color", "Count")
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Tests for the squirrel.constants module."""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from squirrel import constants
|
||||||
|
|
||||||
|
|
||||||
|
def test_fur_colors_are_gray_cinnamon_black_in_order() -> None:
|
||||||
|
assert constants.FUR_COLORS == ("Gray", "Cinnamon", "Black")
|
||||||
|
|
||||||
|
|
||||||
|
def test_summary_columns_are_fur_color_and_count() -> None:
|
||||||
|
assert constants.SUMMARY_COLUMNS == ("Fur Color", "Count")
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_input_path_has_census_file_name() -> None:
|
||||||
|
assert constants.DEFAULT_INPUT_PATH.name == (
|
||||||
|
"2018_Central_Park_Squirrel_Census_-_Squirrel_Data.csv"
|
||||||
|
)
|
||||||
|
assert constants.DEFAULT_INPUT_PATH.parent == pathlib.Path("data")
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_output_path_has_summary_file_name() -> None:
|
||||||
|
assert constants.DEFAULT_OUTPUT_PATH == pathlib.Path("squirrel_count.csv")
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_encoding_and_column_name_values() -> None:
|
||||||
|
assert constants.FILE_ENCODING == "utf-8"
|
||||||
|
assert constants.FUR_COLOR_COLUMN == "Primary Fur Color"
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_imports_only_standard_library() -> None:
|
||||||
|
source = pathlib.Path(constants.__file__).read_text(encoding="utf-8")
|
||||||
|
imported: set[str] = set()
|
||||||
|
for node in ast.walk(ast.parse(source)):
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
imported.update(alias.name.split(".")[0] for alias in node.names)
|
||||||
|
elif isinstance(node, ast.ImportFrom) and node.module is not None:
|
||||||
|
imported.add(node.module.split(".")[0])
|
||||||
|
|
||||||
|
assert imported <= sys.stdlib_module_names
|
||||||
Reference in New Issue
Block a user