diff --git a/README.rst b/README.rst index 6b1d3fd0fbce32a9403b8be932f122514e2ede3c..034e5bdb06e03706b470fe113567f69bb7675a46 100644 --- a/README.rst +++ b/README.rst @@ -2,100 +2,47 @@ schilder2000 ============ -Schildergenerator reloaded – generate signs to print out with a simple web -interface. A rewrite from scratch of the old schildergenerator_. +Schildergenerator reloaded – generate and print signs using a common design from +a simple web interface. A rewrite from scratch of the old schildergenerator_. +Adapted from its README [#orig-authors]_: -Development setup ------------------ +You might find it useful in the following scenarios: -Please configure your editor to respect the EditorConfig_ settings. +* You need to set up an event where visitors need signs to point them around. +* You often need to change some signs in your organization. +* You’re bored in the Fachschaftsbüro. -Code and build process is split between frontend and backend part. Note that -the frontend must be build before the backend, as its results will be -incorporated as static files into the Python package. +What you need for this tool to unleash its maximum potential: -Frontend -~~~~~~~~ +* A printer on premise. +* Some kind of server, preferrably running a flavor of Linux, capable of + printing on said printer via IPP. A RaspberryPi would suffice, even if it’s + slow. +* Web clients of any type being able to connect to said server. +* The knowledge to modify a HTML template to fit your organizations design. -The frontend support code is written in TypeScript and Less, and compiled and -bundled using Webpack. To install the dependencies and start a development -server: +After you put in the initial work of setting this up, you’ll have a service +where everyone on your team can quickly create a new sign as soon as they see a +need for it (provided they’ve got a mobile web client) just by typing a headline +and some text, selecting or uploading an image -- and by the time they arrive at +the printer, the sign is already printed and ready to be posted on the wall. +All while heeding the event’s design. -.. code:: shell-session +This tool frees the event organisators of the responsibility to pre-think all +the details of sign-making. Instead, this part can simply be delegated to the +helpers who are setting up the event by showing them the web frontend of this +tool. - % npm install # may update package-lock.json - % npm clean-install # uses package-lock.json exactly - % npm run start -This starts a development server that watches for changes and automatically -rebuilds the bundle, and instructs the browser to reload using a WebSocket -connection. To build for production: - -.. code:: shell-session - - % npm run build - -Backend -~~~~~~~ - -Dependencies are managed using PDM_, the build process is handled by -PDM-Backend_. - -You may want to create a virtualenv explicitly in order to install pip_ as well, -and/or activate the virtualenv: - -.. code:: shell-session - - % pdm venv create --with-pip - % eval $(pdm venv activate) - -To install dependencies, including those for development: - -.. code:: shell-session - - % pdm install --dev - -Create a directory ``instance`` for your instance configuration, and edit the -configuration: - -.. code:: shell-session - - % cp -r examples instance - % $EDITOR instance/config.py - -Do replace ``SECRET_KEY`` with a unique, random value; and customise -``SQLALCHEMY_DATABASE_URI`` according to your environment. See `SQLAlchemy -documentation`_ for details. You may need to create a database in your database -server beforehand. - -Run database migrations: - -.. code:: shell-session - - % pdm run migrate - -To start the development server: - -.. code:: shell-session - - % pdm run serve - -If you want to use the ``flask`` command-line tool without specifying the app -every time, you can use a ``.env`` file: - -.. code:: shell-session - - % echo FLASK_APP=schilder2000 >> .env - -Production setup ----------------- - -Don’t. This is not ready yet. +.. _schildergenerator: https://git.fsmpi.rwth-aachen.de/schilder/schildergenerator +.. [#orig-authors] Thanks to Dave Kliczbor and Felix Schäfer for their work + there. +.. SEPARATOR +.. The line above is to separate content that should be included in + ``docs/index.rst`` (which includes this file) as well from content that + should only appear here. -.. _schildergenerator: https://git.fsmpi.rwth-aachen.de/schilder/schildergenerator -.. _EditorConfig: https://editorconfig.org/ -.. _PDM: https://pdm-project.org/ -.. _PDM-Backend: https://backend.pdm-project.org/ -.. _pip: https://pip.pypa.io/ -.. _`SQLAlchemy documentation`: https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls +See ``docs/installation.rst`` for installation instructions. To build the +documentation as HTML locally, run ``pdm run docs`` and open +``docs/_build/index.html``. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..6e37e540da6fe15af1d1710f4215d48fc89e53aa --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,44 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +from pathlib import Path + +try: + import tomllib +except ImportError: + import tomli as tomllib + +with (Path(__file__).parent.parent / "pyproject.toml").open("rb") as f: + _pyproject = tomllib.load(f) + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = _pyproject["project"]["name"] +author = ", ".join([a["name"] for a in _pyproject["project"]["authors"]]) +copyright = f"2024, {author}" +version = str(_pyproject["project"].get("version", "DEV")) +release = version + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["sphinx.ext.intersphinx"] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" + +intersphinx_mapping = dict( + flask=("https://flask.palletsprojects.com/en/3.0.x/", None), + flask_multipass=("https://flask-multipass.readthedocs.io/en/latest/", None), + flask_sqlalchemy=("https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/", None), + gunicorn=("https://docs.gunicorn.org/en/stable/", None), +) diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000000000000000000000000000000000000..ada9c3590d2c3edef9b70d378c336d06b994b060 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,87 @@ +Development setup +================= + +Please configure your editor to respect the EditorConfig_ settings. + +Code and build process is split between frontend and backend part. Note that +the frontend must be build before the backend, as its results will be +incorporated as static files into the Python package. + +Frontend +-------- + +The frontend support code is written in TypeScript and Less, and compiled and +bundled using Webpack. To install the dependencies and start a development +server: + +.. code:: shell-session + + % npm install # may update package-lock.json, or instead: + % npm clean-install # uses package-lock.json exactly + % npm run start + +This starts a development server that watches for changes and automatically +rebuilds the bundle, and instructs the browser to reload using a WebSocket +connection. To build for production: + +.. code:: shell-session + + % npm run build + +Backend +------- + +Dependencies are managed using PDM_, the build process is handled by +PDM-Backend_. + +You may want to create a virtualenv explicitly in order to install pip_ as well, +and/or activate the virtualenv: + +.. code:: shell-session + + % pdm venv create --with-pip + % eval $(pdm venv activate) + +To install dependencies, including those for development: + +.. code:: shell-session + + % pdm install --dev + +Create a directory ``instance`` for your instance configuration, and edit the +configuration: + +.. code:: shell-session + + % cp -r examples instance + % $EDITOR instance/config/config.py + +Do replace ``SECRET_KEY`` with a unique, random value; and customise +``SQLALCHEMY_DATABASE_URI`` according to your environment. See `SQLAlchemy +documentation`_ for details. You may need to create a database in your database +server beforehand. + +Run database migrations: + +.. code:: shell-session + + % pdm run migrate + +To start the development server: + +.. code:: shell-session + + % pdm run serve + +If you want to use the ``flask`` command-line tool without specifying the app +every time, you can use a ``.env`` file: + +.. code:: shell-session + + % echo FLASK_APP=schilder2000 >> .env + +.. _EditorConfig: https://editorconfig.org/ +.. _PDM: https://pdm-project.org/ +.. _PDM-Backend: https://backend.pdm-project.org/ +.. _pip: https://pip.pypa.io/ +.. _`SQLAlchemy documentation`: https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..a8bd4e24596e3159ada752fbc0e87a91549d7f72 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,10 @@ +.. include:: ../README.rst + :end-before: .. SEPARATOR + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + development diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000000000000000000000000000000000000..09d3ea1e8e18be7af4f8e6da4575971b9bb1ceff --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,269 @@ +Installation +============ + +To install this software, you can use a container-based setup, or run it +directly in a Python environment. As there are many ways (and opinions!) how to +configure a web server and deploy a Python WSGI application, only some details +are outlined here and the reader is expected to be familiar with at least web +server administration. + +See also the :external+flask:std:doc:`Flask documentation on production +deployment <deploying/index>`. + +Container setup (Podman, Kubernetes, …) +--------------------------------------- + +Pull the `container image`_ (or build it yourself using the provided +``Containerfile``): + +.. code:: shell-session + + % podman pull registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}} + +Config and data is expected in ``/usr/local/var/schilder2000-instance/``. For +starters, you can use the ``examples/`` directory in the `source repository`_, +which is also included in the container image. To create a volume based on it: + +.. code:: shell-session + + % [[ $(id -u) -eq 0 ]] || podman unshare + # pushd $(podman image mount registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}}) + # cd usr/local/var/schilder2000-instance + # tar cf - . | podman volume import schilder2000-instance + # popd + # podman image unmount registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}} + +You can also use a volume just for ``/usr/local/var/schilder2000-instance/data`` +and mount ``/usr/local/var/schilder2000-instance/config/config.py`` separately. + +Continue with :ref:`configuration` and :ref:`database-migration` before starting +the container. + +Configure your inbound proxy to pass HTTP to port 8080, and your container +management to launch the container as required. For example, as plain +invocation: + +.. code:: shell-session + + % podman run --rm --detach --publish-all \ + --volume schilder2000-instance:/usr/local/var/schilder2000-instance \ + registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}} + +Without container +----------------- + +You can use our pre-built `Python package`_ (wheel) or build it yourself. + +Dependencies +~~~~~~~~~~~~ + +You need Python 3.9 or later and the dependencies specified in +``pyproject.toml``. It is suggested to use a virtualenv, as distribution +packages are often missing or outdated. + +Some optional dependencies need additional native libraries, namely +MySQL/MariaDB Connector/C for MySQL/MariaDB support (both should work with +either database), and OpenLDAP and Cyrus SASL for LDAP authentication. If +binary wheels for those are not available, you need the development versions of +those as well as C compiler and Python development infrastructure. +Additionally, Git is required to get the sources of some dependencies. + +.. code:: shell-session + + # apt-get install python3-dev pkg-config gcc libmariadb-dev libldap-dev libsasl2-dev git + # dnf install python3-devel gcc 'pkgconfig(libmariadb)' 'pkgconfig(ldap)' git-core + # apk add -t .schilder2000 python3 mariadb-connector-c libldap # Runtime + # apk add -t .schilder2000-build build-base git mariadb-connector-c-dev \ + openldap-dev python3-dev # Build only, can be removed later + +To use OS packages as much as possible (note that the versions your distribution +provides may be too outdated and you may need to install the development +dependencies from above anyway): + +.. code:: shell-session + + # apt-get install python3 weasyprint python3-jinja2 python3-flask \ + python3-asgiref python3-flask-sqlalchemy python3-flaskext.wtf alembic \ + python3-qrcode python3-ldap python3-authlib python3-psycopg \ + python3-mysqldb git + # dnf install python3 \ + python3dist\({weasyprint,jinja2,flask\\[async\\],flask-sqlalchemy,flask-wtf,alembic,qrcode}\) \ + python3dist\({python-ldap,python3-saml,authlib,psycopg,mysqlclient}\) + # apk add -t .schilder2000 python3 weasyprint py3-jinja2 py3-flask \ + py3-asgiref py3-flask-sqlalchemy py3-flask-wtf py3-alembic py3-ldap \ + py3-python3-saml py3-authlib py3-psycopg py3-mysqlclient git + +If you are not using the pre-built `Python package`_ (wheel), you also need +Node.js and npm to build the frontend: + +.. code:: shell-session + + # apt-get install npm + # dnf install nodejs-npm + # apk add npm + +You will also likely want a WSGI server. If in doubt, choose Gunicorn_: + +.. code:: shell-session + + (venv) % pip install gunicorn + # apt-get install gunicorn + # dnf install python3-gunicorn + # apk add py3-gunicorn + + +Pre-built package +~~~~~~~~~~~~~~~~~ + +In your chosen deployment location (e. g., virtualenv), install the package with +your desired optional extras. These are ``auth-ldap``, ``auth-saml``, +``auth-oauth``, ``all-auth``, ``db-postgres``, ``db-mysql``, ``all-db``, +``all``. For example, to install with support for SAML login and Postgres +database: + +.. code:: shell-session + + (venv) % pip install "schilder2000[auth-saml,db-postgres]" \ + --index-url https://git.fsmpi.rwth-aachen.de/api/v4/projects/305/packages/pypi/simple + +Continue with :ref:`configuration`. + +Building from source +~~~~~~~~~~~~~~~~~~~~ + +This package follows `PEP 517`_ conventions. You can use `build`_ to generate +SDist and wheel packages, or run ``pip install .`` in your local source tree, … +if you are at this point, you probably know your choices anyway. + +The default build process will automatically invoke Node.js/npm to build the +frontend. To disable this behaviour, pass ``without-npm`` as build config +setting. This requires the files to already exist. For example: + +.. code:: shell-session + + % npm run build + % python -m build --config-setting without-npm + +.. _configuration: + +Configuration +------------- + +Configuration and runtime data is stored in the instance directory. For +container installs, this is ``/usr/local/var/schilder2000-instance/``. +For package installs, this is ``{{ python prefix }}/var/schilder2000-instance``; +if in doubt, try to run ``flask -A schilder2000``, the error should tell you +where it expects the instance directory. + +Example config and data is located in the ``examples`` directory. The templates +there get their footer text and logo from the application config and should also +be useful as an example to write your own templates. + +The main application config is located in ``config/config.py``. Available +options: + +.. py:data:: SQLALCHEMY_DATABASE_URI + :type: str + + **Required**. Database connection URI. See + :external:py:data:`Flask-SQLAlchemy documentation + <flask_sqlalchemy.config.SQLALCHEMY_DATABASE_URI>` for details and + additional options. Note that the ``db-postgres`` optional dependency + install the ``psycopg`` driver (i. e., version 3), not ``psycopg2``. + +.. py:data:: SECRET_KEY + :type: str | bytes + + **Required**. Secret key for signing cookies and other security related + needs. See :external+flask:py:data:`Flask documentation <SECRET_KEY>` + for details. + +.. py:data:: SCHILD_FOOTER + :type: str + + Footer text used by the templates shipped in ``examples/``. + +.. py:data:: SCHILD_LOGO + :type: str + + Logo used by the templates shipped in ``examples/``. Expects a file + relative to ``{{ instance path }}/data/static``. + +.. py:data:: TEMPLATES_AUTO_RELOAD + :type: bool + + Reload templates when they are changed. See + :external+flask:py:data:`Flask documentation <TEMPLATES_AUTO_RELOAD>` + for details. + +.. py:data:: PRINTERS + :type: dict[str, str] + + **Required**. Available printers. Maps display names to IPP(S) URLs. + +.. py:data:: REQUIRE_LOGIN + :type: bool + + **Required**. Whether authentication is required to access the service. + If enabled, requires additional configuration for + :external+flask_multipass:std:doc:`index`. + +.. py:data:: MULTIPASS_AUTH_PROVIDERS + :type: dict[str, dict] + +.. py:data:: MULTIPASS_IDENTITY_PROVIDERS + :type: dict[str, dict] + +.. py:data:: MULTIPASS_PROVIDER_MAP + :type: dict[str, str] + + See :external+flask_multipass:std:doc:`Flask-Multipass documentation + <index>` for details. + +.. py:data:: MULTIPASS_IDENTITY_INFO_KEYS + :type: list + + Required by Flask-Multipass, but can be empty, as identity information + is not used. + +See also :external+flask:std:doc:`Flask documentation <config>` for additional +options and information. + +.. _database-migration: + +Database migration +~~~~~~~~~~~~~~~~~~ + +Unless you use SQLite, create the database in your database server. In all +cases, run the migrations: + +.. code:: shell-session + + % flask -A schilder2000 alembic upgrade head # without container + % podman run --rm --volume <...> <image> flask alembic upgrade head # with container + +WSGI and webserver setup +~~~~~~~~~~~~~~~~~~~~~~~~ + +An example config for Gunicorn_ is provided in ``gunicorn.conf.py``. This +will listen on ``[::]:8080`` (port 8080, all interfaces), and write the access +log to stdout. + +See :external+flask:std:doc:`Flask documentation on Gunicorn +<deploying/gunicorn>` and :external+gunicorn:std:doc:`Gunicorn documentation +<index>` for further information. + +If you want to use another WSGI server, configure it to use +``schilder2000:create_app()`` as application object. Note that this is a +factory function that returns the application callable, you have to call it! + +To use your webserver to directly serve static files, route ``/static`` to ``{{ +python packages directory }}/schilder2000/static`` and ``/instance/static`` to +``{{ instance path }}/data/static``. + +.. _`Python package`: https://git.fsmpi.rwth-aachen.de/schilder/schilder2000/-/packages +.. _`container image`: https://git.fsmpi.rwth-aachen.de/schilder/schilder2000/container_registry/33 +.. _Gunicorn: https://gunicorn.org/ +.. _`source repository`: https://git.fsmpi.rwth-aachen.de/schilder/schilder2000 +.. _`PEP 517`: https://peps.python.org/pep-0517/ +.. _`build`: https://github.com/pypa/build diff --git a/examples/config/config.py b/examples/config/config.py index f792cc1af3773f537ed57df93e66ebe4b7b2acaa..3e025edbb6ddfe048673accf212fe0243330675f 100644 --- a/examples/config/config.py +++ b/examples/config/config.py @@ -7,12 +7,12 @@ from pathlib import Path SQLALCHEMY_DATABASE_URI = "postgresql+psycopg:///schilder2000" -# SQLALCHEMY_DATABASE_URI = "sqlite:///schilder2000.db" # Relative to instance directory +# SQLALCHEMY_DATABASE_URI = "sqlite:///data/schilder2000.db" # Relative to instance directory # SQLALCHEMY_DATABASE_URI = "mysql:///schilder2000" # To generate a secret key: # % python -c 'import secrets; print(secrets.token_hex())' -SECRET_KEY = "abc123" # Replace me! +#SECRET_KEY = ... # Replace me! TEMPLATES_AUTO_RELOAD = True SCHILD_FOOTER = "Organization Without a Cool Acronym – O.W.C.A." diff --git a/examples/data/templates/_layout.html.j2 b/examples/data/templates/_layout.html.j2 index 9056e07520d54ff3e8746d259bcbabb3d4ced800..9d2e639836c53379a27c12ee402059f55061fb95 100644 --- a/examples/data/templates/_layout.html.j2 +++ b/examples/data/templates/_layout.html.j2 @@ -20,7 +20,7 @@ </div> <div id="logo"> - <img src="{{ url_for('instance.static', filename='geier.png') }}" /> + <img src="{{ url_for('instance.static', filename=config['SCHILD_LOGO']) }}" /> </div> </footer> </body> diff --git a/pdm.lock b/pdm.lock index 6ca98eb79ce973d139ecf2298a9bbe9c464d85ec..c0f968a97bad7d71f93317a42bded338744a6efe 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "auth-ldap", "auth-oauth", "auth-saml", "db-mysql", "db-postgres", "dev"] +groups = ["default", "auth-ldap", "auth-oauth", "auth-saml", "db-mysql", "db-postgres", "dev", "docs"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:426cc9921b084477e1e6775780cf783ca6941201253b4c20f888b7fa19717a71" +content_hash = "sha256:cb72c38a190b2e922bfba2ca29390a8bf6832123ae43187cdd433f5dde03d422" [[metadata.targets]] requires_python = ">=3.9" @@ -129,6 +129,17 @@ files = [ {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] +[[package]] +name = "alabaster" +version = "0.7.16" +requires_python = ">=3.9" +summary = "A light, configurable Sphinx theme" +groups = ["docs"] +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + [[package]] name = "alembic" version = "1.13.2" @@ -244,6 +255,20 @@ files = [ {file = "awesomeversion-24.6.0.tar.gz", hash = "sha256:aee7ccbaed6f8d84e0f0364080c7734a0166d77ea6ccfcc4900b38917f1efc71"}, ] +[[package]] +name = "babel" +version = "2.16.0" +requires_python = ">=3.8" +summary = "Internationalization utilities" +groups = ["docs"] +dependencies = [ + "pytz>=2015.7; python_version < \"3.9\"", +] +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + [[package]] name = "backoff" version = "2.2.1" @@ -388,7 +413,7 @@ name = "certifi" version = "2024.8.30" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." -groups = ["default"] +groups = ["default", "docs"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -481,7 +506,7 @@ name = "charset-normalizer" version = "3.3.2" requires_python = ">=3.7.0" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -groups = ["default"] +groups = ["default", "docs"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -567,8 +592,8 @@ name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["default", "dev"] -marker = "platform_system == \"Windows\" or sys_platform == \"win32\"" +groups = ["default", "dev", "docs"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -685,6 +710,17 @@ files = [ {file = "docstring_to_markdown-0.15-py3-none-any.whl", hash = "sha256:27afb3faedba81e34c33521c32bbd258d7fbb79eedf7d29bc4e81080e854aec0"}, ] +[[package]] +name = "docutils" +version = "0.21.2" +requires_python = ">=3.9" +summary = "Docutils -- Python Documentation Utilities" +groups = ["docs"] +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -1061,18 +1097,29 @@ name = "idna" version = "3.10" requires_python = ">=3.6" summary = "Internationalized Domain Names in Applications (IDNA)" -groups = ["default"] +groups = ["default", "docs"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[[package]] +name = "imagesize" +version = "1.4.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Getting image size from png/jpeg/jpeg2000/gif file" +groups = ["docs"] +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "importlib-metadata" version = "8.5.0" requires_python = ">=3.8" summary = "Read metadata from Python packages" -groups = ["default", "dev"] +groups = ["default", "dev", "docs"] marker = "python_version < \"3.10\"" dependencies = [ "typing-extensions>=3.6.4; python_version < \"3.8\"", @@ -1150,7 +1197,7 @@ name = "jinja2" version = "3.1.4" requires_python = ">=3.7" summary = "A very fast and expressive template engine." -groups = ["default", "dev"] +groups = ["default", "dev", "docs"] dependencies = [ "MarkupSafe>=2.0", ] @@ -1314,7 +1361,7 @@ name = "markupsafe" version = "2.1.5" requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." -groups = ["default", "dev"] +groups = ["default", "dev", "docs"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -1494,7 +1541,7 @@ name = "packaging" version = "24.1" requires_python = ">=3.8" summary = "Core utilities for Python packages" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -1729,7 +1776,7 @@ name = "pygments" version = "2.18.0" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." -groups = ["default", "dev"] +groups = ["default", "dev", "docs"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -2003,7 +2050,7 @@ name = "requests" version = "2.32.3" requires_python = ">=3.8" summary = "Python HTTP for Humans." -groups = ["default"] +groups = ["default", "docs"] dependencies = [ "certifi>=2017.4.17", "charset-normalizer<4,>=2", @@ -2083,6 +2130,113 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +groups = ["docs"] +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +requires_python = ">=3.9" +summary = "Python documentation generator" +groups = ["docs"] +dependencies = [ + "Jinja2>=3.1", + "Pygments>=2.17", + "alabaster~=0.7.14", + "babel>=2.13", + "colorama>=0.4.6; sys_platform == \"win32\"", + "docutils<0.22,>=0.20", + "imagesize>=1.3", + "importlib-metadata>=6.0; python_version < \"3.10\"", + "packaging>=23.0", + "requests>=2.30.0", + "snowballstemmer>=2.2", + "sphinxcontrib-applehelp", + "sphinxcontrib-devhelp", + "sphinxcontrib-htmlhelp>=2.0.0", + "sphinxcontrib-jsmath", + "sphinxcontrib-qthelp", + "sphinxcontrib-serializinghtml>=1.1.9", + "tomli>=2; python_version < \"3.11\"", +] +files = [ + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +groups = ["docs"] +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +groups = ["docs"] +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +groups = ["docs"] +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +requires_python = ">=3.5" +summary = "A sphinx extension which renders display math in HTML via JavaScript" +groups = ["docs"] +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +groups = ["docs"] +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +groups = ["docs"] +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + [[package]] name = "sqlalchemy" version = "2.0.35" @@ -2175,7 +2329,7 @@ name = "tomli" version = "2.0.1" requires_python = ">=3.7" summary = "A lil' TOML parser" -groups = ["dev"] +groups = ["dev", "docs"] marker = "python_version < \"3.11\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, @@ -2304,7 +2458,7 @@ name = "urllib3" version = "2.2.3" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." -groups = ["default"] +groups = ["default", "docs"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -2537,7 +2691,7 @@ name = "zipp" version = "3.20.2" requires_python = ">=3.8" summary = "Backport of pathlib-compatible object wrapper for zip files" -groups = ["default", "dev"] +groups = ["default", "dev", "docs"] marker = "python_version < \"3.10\"" files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, diff --git a/pyproject.toml b/pyproject.toml index 336fc497e04fff197bcd36a081415483a596d183..9c32596421581f62377d8052e89099601b1ddde3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,10 +48,15 @@ dev = [ "pylsp-rope~=0.1", "flask-shell-ipython~=0.5", ] +docs = [ + "sphinx~=7.4", + "tomli~=2.0; python_version < '3.11'", +] [tool.pdm.scripts] serve = "flask -A schilder2000 run --debug" migrate = "flask -A schilder2000 alembic upgrade head" +docs = "sphinx-build docs docs/_build" [tool.pdm.build] includes = [ @@ -59,6 +64,7 @@ includes = [ "schilder2000/templates", "schilder2000/migrations", # Referenced files in manifest.json included via pdm_build.py + "docs", ] [tool.pdm.version]