diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d2a5984..d6dd602 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,35 +1,35 @@ name: Test on: push: pull_request: jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: - - '3.11' + - '3.13' steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - run: pip install --upgrade pip - run: pip install -r requirements.txt -r dev-requirements.txt - run: make check prod-requirements: runs-on: ubuntu-latest strategy: matrix: python-version: - - '3.11' + - '3.13' steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - run: pip install --upgrade pip # only install prod requirements - run: pip install -r requirements.txt # check that app.py runs without crashing on a missing import - run: python app.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 19a3c0e..ced2766 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,27 +1,27 @@ default: - image: python:3.11 + image: python:3.13 cache: - - key: pip-python-3.11 + - key: pip-python-3.13 paths: - .cache/pip variables: PYTHONDONTWRITEBYTECODE: "1" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" stages: - test test-job: stage: test script: - python3 -m pip install -r requirements.txt -r dev-requirements.txt - make check test-prod-requirements-job: stage: test script: # only install prod requirements - python3 -m pip install -r requirements.txt # check that app.py runs without crashing on a missing import - python3 app.py diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..7252c4a --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn --workers=4 app:app diff --git a/README.md b/README.md index d80af61..cc4e925 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,108 @@ # Wikidata Image Positions [This tool](https://wd-image-positions.toolforge.org/) shows [relative position within image](https://www.wikidata.org/wiki/Property:P2677) qualifiers of [depicts](https://www.wikidata.org/wiki/Property:P180) statements on [Wikidata](https://www.wikidata.org/) items as areas on the item’s [image](https://www.wikidata.org/wiki/Property:P18) (or other property). It also supports [Wikimedia Commons](https://commons.wikimedia.org/) files, where the [named place on map](https://www.wikidata.org/wiki/Property:P9664) property is used in a similar way. Examples: * [The Coronation of Napoleon](https://wd-image-positions.toolforge.org/item/Q1231009) * [Situation Room](https://wd-image-positions.toolforge.org/item/Q2915674) For more usage information, please see the tool’s [on-wiki documentation page](https://www.wikidata.org/wiki/User:Lucas_Werkmeister/Wikidata_Image_Positions). ## Toolforge setup -On Wikimedia Toolforge, this tool runs under the `wd-image-positions` tool name. -Source code resides in `~/www/python/src/`, -a virtual environment is set up in `~/www/python/venv/`, -logs end up in `~/uwsgi.log`. +On Wikimedia Toolforge, this tool runs under the `wd-image-positions` tool name, +from a container built using the [Toolforge Build Service](https://wikitech.wikimedia.org/wiki/Help:Toolforge/Building_container_images). + +### Image build + +To build a new version of the image, +run the following command on Toolforge after becoming the tool account: + +```sh +toolforge build start --use-latest-versions https://gitlab.wikimedia.org/toolforge-repos/wd-image-positions +``` + +The image will contain all the dependencies listed in `requirements.txt`, +as well as the commands specified in the `Procfile`. + +### Webservice + +The web frontend of the tool runs as a webservice using the `buildpack` type. +The web service runs the first command in the `Procfile` (`web`), +which runs the Flask WSGI app using gunicorn. -If the web service is not running for some reason, run the following command: ``` webservice start ``` -If it’s acting up, try the same command with `restart` instead of `start`. -Both should pull their config from the `service.template` file in the source code directory. -To update the service, run the following commands after becoming the tool account: +Or, if the `~/service.template` file went missing: + ``` -cd ~/www/python/src -git fetch -git log -p @..@{u} # inspect changes -git rebase -webservice restart +webservice --mount=none buildservice start ``` -If there were any changes in the Python environment (e.g. new dependencies), -add the following steps before the `webservice restart`: +If it’s acting up, try the same command with `restart` instead of `start`. + +### Configuration + +The tool reads configuration from both the `config.yaml` file (if it exists) +and from any environment variables starting with `TOOL_*`. +The config file is more convenient for local development; +the environment variables are used on Toolforge: +list them with `toolforge envvars list`. +Nested dicts are specified with envvar names where `__` separates the key components, +and the tool lowercases keys in nested dicts, +so that e.g. the following are equivalent: + +```sh +toolforge envvars create TOOL_OAUTH__CONSUMER_KEY 271b735e0cf895694f2ee7a3ae7a2dbc +``` + +```yaml +OAUTH: + CONSUMER_KEY: 271b735e0cf895694f2ee7a3ae7a2dbc ``` -webservice shell -source ~/www/python/venv/bin/activate -pip-sync ~/www/python/src/requirements.txt + +For the available configuration variables, see the `config.yaml.example` file. + +### Update + +To update the tool, build a new version of the image as described above, +then restart the webservice: + +```sh +toolforge build start --use-latest-versions https://gitlab.wikimedia.org/toolforge-repos/wd-image-positions +webservice restart ``` ## Local development setup You can also run the tool locally, which is much more convenient for development (for example, Flask will automatically reload the application any time you save a file). ``` git clone https://gitlab.wikimedia.org/toolforge-repos/wd-image-positions.git cd wd-image-positions pip3 install -r requirements.txt -r dev-requirements.txt FLASK_ENV=development flask run ``` If you want, you can do this inside some virtualenv too. ## Contributing To send a patch, you can submit a [pull request on GitHub](https://github.com/lucaswerkmeister/tool-wd-image-positions) or a [merge request on GitLab](https://gitlab.wikimedia.org/toolforge-repos/wd-image-positions). (E-mail / patch-based workflows are also acceptable.) ## License The code in this repository is released under the AGPL v3, as provided in the `LICENSE` file. diff --git a/requirements.in b/requirements.in index 32a7797..de4952b 100644 --- a/requirements.in +++ b/requirements.in @@ -1,13 +1,15 @@ babel cachetools decorator flask >= 2.0.0 +gunicorn iiif-prezi MarkupSafe mwapi mwoauth +pymysql @ git+https://github.com/PyMySQL/PyMySQL@main pyyaml requests requests_oauthlib toolforge >= 6.1.0 toolforge_i18n[Flask] >= 0.1.0 diff --git a/requirements.txt b/requirements.txt index cdeb0d9..45b3431 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,125 +1,131 @@ # # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile # aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.12.14 # via mwapi aiosignal==1.4.0 # via aiohttp attrs==25.3.0 # via aiohttp babel==2.17.0 # via # -r requirements.in # toolforge-i18n beautifulsoup4==4.13.4 # via toolforge-i18n blinker==1.9.0 # via flask cachetools==6.1.0 # via # -r requirements.in # pyld certifi==2025.7.9 # via requests charset-normalizer==3.4.2 # via requests click==8.2.1 # via flask decorator==5.2.1 # via # -r requirements.in # toolforge flask==3.1.1 # via # -r requirements.in # toolforge-i18n frozendict==2.4.6 # via pyld frozenlist==1.7.0 # via # aiohttp # aiosignal +gunicorn==23.0.0 + # via -r requirements.in idna==3.10 # via # requests # yarl iiif-prezi==0.3.0 # via -r requirements.in itsdangerous==2.2.0 # via flask jinja2==3.1.6 # via flask lxml==6.0.0 # via # iiif-prezi # pyld markupsafe==3.0.2 # via # -r requirements.in # flask # jinja2 # toolforge-i18n # werkzeug multidict==6.6.3 # via # aiohttp # yarl mwapi==0.6.1 # via # -r requirements.in # toolforge-i18n mwoauth==0.4.0 # via -r requirements.in oauthlib==3.3.1 # via # mwoauth # requests-oauthlib +packaging==25.0 + # via gunicorn pillow==11.3.0 # via iiif-prezi propcache==0.3.2 # via # aiohttp # yarl pyjwt==2.10.1 # via mwoauth pyld==2.0.4 # via iiif-prezi -pymysql==1.1.1 - # via toolforge +pymysql @ git+https://github.com/PyMySQL/PyMySQL@main + # via + # -r requirements.in + # toolforge pyyaml==6.0.2 # via -r requirements.in requests==2.32.4 # via # -r requirements.in # mwapi # mwoauth # requests-oauthlib # toolforge # toolforge-i18n requests-oauthlib==2.0.0 # via # -r requirements.in # mwoauth soupsieve==2.7 # via beautifulsoup4 toolforge==6.1.0 # via -r requirements.in toolforge-i18n[flask]==0.1.2 # via # -r requirements.in # toolforge-i18n typing-extensions==4.14.1 # via beautifulsoup4 urllib3==2.5.0 # via requests werkzeug==3.1.3 # via # flask # toolforge-i18n yarl==1.20.1 # via aiohttp diff --git a/service.template b/service.template index 15c0ad7..c113691 100644 --- a/service.template +++ b/service.template @@ -1,4 +1,5 @@ # Toolforge webservice template # Provide default arguments for `webservice` commands for this tool. -type: python3.11 +type: buildservice health-check-path: /healthz +mount: none