Reading the repo
Reading the repo
Saved setup from 4 h ago
Will it work on my device?
CONTRIBUTING.mdThank you for helping. This guide covers the development setup, the tests, the code conventions and the release process. By taking part you agree to follow the code of conduct. Report security problems privately as described in SECURITY.md, never in a public issue.
You need Python 3.9 or newer. The package has no runtime dependencies. Test your changes on the oldest supported Python (3.9) and on the newest.
git clone https://github.com/robco/bakalari.git
cd bakalari
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install -e ".[dev]"
mypy stays below version 2 because mypy 2 can no longer check code against Python 3.9 (python_version in
pyproject.toml).
The offline suite needs no network and no account. It drives the client through a scripted transport
(FakeTransport in unittests/support.py) and uses the synthetic fixtures in unittests/fixtures/.
python -m pytest # the whole offline suite
python -m unittest unittests.test_marks -v # one test module, no test dependencies
python -m pytest --cov=bakalari --cov-fail-under=90 # CI coverage threshold
The default pytest discovery path is unittests/, so it never selects live tests even when credentials are
present. Without third-party test tools, run python -m unittest discover -s unittests -t . -v.
On Python 3.10 or newer, install the optional reference client and run the offline interoperability test:
python -m pip install -e ".[dev,mcp-test]"
python -m pytest tests/test_mcp_sdk.py
CI runs this test alongside the unit tests under coverage. It uses synthetic school responses and requires no account. The installed library and MCP server still have no runtime dependencies.
tests/test_live.py runs against a real Bakaláři installation when all three variables are set and
BAKALARI_SKIP_LIVE is not:
export BAKALARI_URL=https://... # the address of your school's Bakaláři web
export BAKALARI_USERNAME=...
export BAKALARI_PASSWORD=...
python -m unittest tests.test_live -v
Set BAKALARI_SKIP_LIVE=1 to skip them even when the credentials are set. CI never runs them. The rules for
live tests:
skipTest) when the school or account has a module disabled (BakalariPermissionError).Fixtures are synthetic. Copy the shape of a real response (keys, nesting, value types, enum values,
space-padded ids, empty and null fields) but never its values. Invent the names, ids, texts, marks and dates, and
use https://school.example/bakaweb as the address. Never commit a real name, id, school, message text or token,
not even from your own account, and never commit a captured response with the plan to anonymize it later.
The package uses only the standard library and runs on Python 3.9. Keep from __future__ import annotations and
the typing style (Optional, Dict, List, Tuple, Mapping). Don't use match, X | Y at runtime,
dataclass(slots=..., kw_only=...), zip(strict=...) or datetime.UTC.
marks.py, komens.py, ...) holds the area's response models and its
XxxEndpoints(ClientCore) mixin, which BakalariClient combines. Endpoint methods are thin: they build the
path (ids go through quote_segment), call get_json, post_json or request, and parse the result with the
model's from_json.@dataclass(frozen=True) subclasses of Model with a from_json classmethod. Entity models end
with a raw field holding the original object, which is hidden from repr() and to_dict(). Field names are
the snake_case API names (school_class for Class), and collections are tuples.bakalari._parse helpers: ids exact (ident), display strings trimmed (text),
timestamps as aware datetime (parse_datetime) or date (parse_date), numbers and booleans via
to_int, to_float and to_bool, {Id, Abbrev, Name} objects as Item, and attachments as
AttachmentInfo. Parsing is lenient: a missing optional field gets a default.from_json(data, *, validate=True) checks the essential shape with
check_shape when it validates, and with require_mapping or require_list always. Endpoint methods take
validate: bool = True and pass it on. Counter endpoints use require_int.format_date. Date parameters are named since and until and
accept a date, a datetime or an ISO string.GET /api/3/marks) and notable server behaviour. Comments only explain non-obvious server quirks.BakalariPermissionError
propagate. Tokens and passwords never appear in messages, log records or repr().unittest), one test module per area module (unittests/test_marks.py),
with synthetic fixtures unittests/fixtures/<area>*.json. They cover model parsing (padded ids, nulls, missing
fields), to_dict() JSON round trips, the HTTP method, path (including percent-encoded ids), query and body of
every endpoint method, status handling, contract errors and lenient parsing.ruff check . # pycodestyle, pyflakes, isort, bugbear and comprehensions; line length 120
mypy bakalari # the settings in pyproject.toml, checked against Python 3.9 (needs mypy < 2)
Ruff's pyupgrade (UP) rules are off on purpose, so that the Optional[...] typing style stays.
To check the packaging the way CI does:
python -m build
twine check --strict dist/*
Keep a pull request focused on one change, add tests, update the documentation and add an entry to
CHANGELOG.md under "Unreleased". CI runs the offline tests on Python 3.9 to 3.14 on Linux and on Python 3.13 on
macOS and Windows, checks coverage (at least 90 %), runs ruff and mypy, and builds the package, checking the wheel
by installing it into a fresh virtual environment. Python 3.12 runs the combined test/build job, which also
checks the MCP SDK and uploads JUnit, HTML and coverage reports. The remaining matrix jobs use only the standard
library to check compatibility.
Versions follow Semantic Versioning. The version is set in one place,
bakalari/_version.py. Setuptools reads that value for the distribution, and the library, CLI and MCP server
use it at runtime. The filename is a project convention, not a GitHub Actions or PyPI requirement
Set __version__ = "X.Y.Z" in bakalari/_version.py.
In CHANGELOG.md, rename "Unreleased" to ## [X.Y.Z] - YYYY-MM-DD, add a new empty "Unreleased"
section above it, and update the link references at the bottom.
Commit the changes, push to main, and wait for Python Package CI to pass.
Create and push the release tag:
git tag -a vX.Y.Z -m "bakalari X.Y.Z"
git push origin vX.Y.Z
On GitHub, publish a release for that tag and use the changelog section as its release notes. The
Python Package Publish workflow starts on release: published.
It builds the wheel and source distribution, uploads them as release-dists, then publishes them to PyPI.
A tag push alone does not publish a package.
Publishing does not rerun CI or enforce tag naming or changelog formatting. The package version comes from
bakalari/_version.py in the selected checkout, regardless of the branch, tag or release name.
CI runs tests, lint, type and distribution checks separately; its python-package artifact and test and
coverage reports are retained for 30 days. The publish job uses the pypi environment and OpenID Connect
trusted publishing to upload the distributions from its own build job.
To publish manually, select a branch or tag in GitHub's Run workflow dialog, or use:
gh workflow run python-publish.yml --ref main
# Or publish a specific tag:
gh workflow run python-publish.yml --ref vX.Y.Z
A manual run also publishes to PyPI. PyPI accepts each distribution filename only once; bump the package
version before a new release and check a partially completed upload before retrying. For a local build
check without publishing, use python -m build, python -m twine check --strict dist/* and
python .github/scripts/check_distribution.py.
On GitHub, create the pypi environment (Settings, Environments). Allow the branches and tags you intend
to publish from; remove any tag-only restriction if you want manual publishing from main.
Configure a PyPI trusted publisher:
bakalarirobcobakalaripython-publish.ymlpypiIf the project does not exist, add a pending publisher in the account's Publishing settings. No API token
is needed. If migrating from the previous workflow, replace the publisher for release.yml with the one
above; PyPI matches the workflow filename exactly.
If branch protection requires the old CI job names, update those required checks to the checks reported by Python Package CI, including the compatibility matrix and Test & Build (Python 3.12).
The old tag-push publisher and manual TestPyPI rehearsal are replaced by this release-based workflow.
The testpypi environment and its publisher are no longer used.
Enable private vulnerability reporting (Settings, Advanced Security, Private vulnerability reporting), which SECURITY.md and the issue forms link to.
Then run bash setup.sh. It stops at the first error and is safe to run again. Servers and tests aren't started; it lists them at the end. Read it first.
git clone https://github.com/robco/bakalari.git cd bakalari
Python >=3.9
sudo apt install python3 python3.9 python3-venv # Debian/Ubuntu
python -m venv .venv source .venv/bin/activate pip install -e .
pytest
Install from PyPI in a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U bakalari
On Windows, create the environment with py -m venv .venv and activate it in PowerShell with
.\.venv\Scripts\Activate.ps1.
To install from source (including before a release is available on PyPI):
git clone https://github.com/robco/bakalari.git
cd bakalari
python -m pip install .
Both install the library and the bakalari and bakalari-mcp commands. Check the installation with
python -m bakalari --version or python -m bakalari --help.
No reports for Linux yet.
Opens a chat with this manual already filled in, for Linux. It uses your own account; gitmanual sends nothing.