From 665af429627ab4e277bc8ec177625cb2b9d64edc Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:26:38 -0400 Subject: [PATCH 01/47] build: Update to latest profile template (cherry picked from commit 914f1c1b44c50d1850d9495378b553363a2a3775) --- .github/workflows/ci.yml | 5 +--- .gitignore | 4 ---- common-requirements.txt | 52 +++++++++++++++++++++------------------- include/config.mk | 4 +++- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34f17b27b..ac16da1c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: submodules: true - uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: '3.10' cache: pip cache-dependency-path: '**/requirements*.txt' # Don't install editable projects in the current working directory. @@ -23,9 +23,6 @@ jobs: # - run: | # make linkcheck # rm -f output.json output.txt - # Google Chrome and ChromeDriver are already included. - # https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners - # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md - run: pytest -W error # Deploy the built documentation to the staging directory. - if: github.event_name == 'push' diff --git a/.gitignore b/.gitignore index 16ebca1ad..2a40f9487 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,6 @@ /*.pdf /build /cache.sqlite -/chromedriver -/chromedriver_linux64.zip -/chromedriver_mac64.zip -/chromedriver_win32.zip /docs/_static/patched /src *.pyc diff --git a/common-requirements.txt b/common-requirements.txt index 4e586605b..7780d73bc 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -12,10 +12,12 @@ async-generator==1.10 # via # trio # trio-websocket -attrs==20.3.0 +attrs==23.1.0 # via + # cattrs # outcome # pytest + # requests-cache # trio babel==2.9.1 # via @@ -23,13 +25,13 @@ babel==2.9.1 # sphinx-intl build==0.10.0 # via pip-tools -certifi==2022.12.7 +cattrs==23.1.2 + # via requests-cache +certifi==2023.7.22 # via # elastic-transport # requests - # urllib3 -cffi==1.15.0 - # via cryptography + # selenium charset-normalizer==3.1.0 # via requests click==8.1.3 @@ -40,10 +42,6 @@ click==8.1.3 # sphinx-intl colorama==0.4.4 # via sphinx-autobuild -cryptography==41.0.1 - # via - # pyopenssl - # urllib3 docutils==0.18 # via # myst-parser @@ -53,14 +51,15 @@ elastic-transport==8.4.0 elasticsearch[requests]==8.6.2 # via ocdsindex exceptiongroup==1.0.0 - # via pytest + # via + # cattrs + # pytest h11==0.13.0 # via wsproto idna==2.10 # via # requests # trio - # urllib3 imagesize==1.4.1 # via sphinx iniconfig==1.1.1 @@ -93,7 +92,7 @@ myst-parser==0.18.1 # via -r common-requirements.in ocds-babel==0.3.1 # via -r common-requirements.in -ocdsextensionregistry==0.1.12 +ocdsextensionregistry==0.3.8 # via -r common-requirements.in ocdsindex==0.2.0 # via -r common-requirements.in @@ -106,14 +105,12 @@ packaging==21.3 # sphinx pip-tools==6.13.0 # via -r common-requirements.in +platformdirs==3.9.1 + # via requests-cache pluggy==0.13.1 # via pytest -pycparser==2.21 - # via cffi -pygments==2.13.0 +pygments==2.15.1 # via sphinx -pyopenssl==22.0.0 - # via urllib3 pyparsing==2.4.7 # via packaging pyproject-hooks==1.0.0 @@ -124,7 +121,7 @@ pytest==7.2.0 # via -r common-requirements.in pytz==2021.1 # via babel -pyyaml==5.4.1 +pyyaml==6.0.1 # via myst-parser requests==2.31.0 # via @@ -132,12 +129,14 @@ requests==2.31.0 # ocdsextensionregistry # requests-cache # sphinx -requests-cache==0.5.2 +requests-cache==1.1.0 # via ocdsextensionregistry -selenium==4.1.3 +selenium==4.11.2 # via -r common-requirements.in six==1.15.0 - # via livereload + # via + # livereload + # url-normalize sniffio==1.2.0 # via trio snowballstemmer==2.1.0 @@ -171,7 +170,7 @@ tomli==2.0.1 # build # pyproject-hooks # pytest -tornado==6.3.2 +tornado==6.3.3 # via livereload trio==0.20.0 # via @@ -180,13 +179,18 @@ trio==0.20.0 trio-websocket==0.9.2 # via selenium typing-extensions==4.4.0 - # via myst-parser + # via + # cattrs + # myst-parser uc-micro-py==1.0.1 # via linkify-it-py -urllib3[secure,socks]==1.26.5 +url-normalize==1.4.3 + # via requests-cache +urllib3[socks]==1.26.5 # via # elastic-transport # requests + # requests-cache # selenium wheel==0.38.4 # via pip-tools diff --git a/include/config.mk b/include/config.mk index ca85dfda9..8be5c3709 100644 --- a/include/config.mk +++ b/include/config.mk @@ -12,13 +12,15 @@ LOCALE_DIR=docs/locale # Directory in which to build documentation files. BUILD_DIR=build # Extra build files or directories. (These should match paths in .gitignore.) -EXTRA_BUILD_FILES=chromedriver* +EXTRA_BUILD_FILES= # Files that are built and distributed (you may use Bash extended globbing). DIST_FILES= # Directory in which to build .pot files. POT_DIR=$(BUILD_DIR)/locale # The prefix, if any, to the schema and codelists domains. DOMAIN_PREFIX= +# The Transifex organization name. +TRANSIFEX_ORGANIZATION=open-contracting-partnership-1 # The Transifex project name. TRANSIFEX_PROJECT=open-contracting-standard-1-1 # Any additional extract targets. From 159ae05e951d07afe8af538ab01ced16b2b0dff1 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:03:20 -0400 Subject: [PATCH 02/47] test: Ignore "ResourceWarning: unclosed file <_io.BufferedWriter name='/dev/null'>" (unknown source) (cherry picked from commit bdcaaefd882e220f83b56a4da1e8a118392fbbe0) --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac16da1c8..6f2618a9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,8 @@ jobs: # - run: | # make linkcheck # rm -f output.json output.txt - - run: pytest -W error + # "ResourceWarning: unclosed file <_io.BufferedWriter name='/dev/null'>" + - run: pytest -W error -W ignore::ResourceWarning # Deploy the built documentation to the staging directory. - if: github.event_name == 'push' uses: shimataro/ssh-key-action@v2 From eb365706ea070b2fe7f922489d4f4d0bfe87c770 Mon Sep 17 00:00:00 2001 From: Duncan Dewhurst Date: Thu, 12 Oct 2023 10:59:54 +1300 Subject: [PATCH 03/47] .gitignore: Add python-version (used by pyenv) (cherry picked from commit cbc010dfde266a38343137087602205cbd5744d9) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2a40f9487..f1d29bd7b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /docs/_static/patched /src *.pyc +.python-version From 8780606368675a95eca48865d5ee2eb1208f391d Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Fri, 3 Nov 2023 02:31:12 -0400 Subject: [PATCH 04/47] ci: Run lint on both push and pull_request events (only pull_request events set GITHUB_BASE_REF, needed by test_json.py) (cherry picked from commit caf95de7a7527dba4cb628e36561874e32b4e946) --- .github/workflows/lint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9a312dfbf..b74d3731a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,7 +4,6 @@ env: BASEDIR: https://raw.githubusercontent.com/open-contracting/standard-maintenance-scripts/main jobs: build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 19173f091c53e9fedc67387f936185ff98cb5dfa Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Fri, 3 Nov 2023 03:57:03 -0400 Subject: [PATCH 05/47] ci: Run lint on only pushes to protected branches or tags (cherry picked from commit df4e7d9792a688aa1948ac743d4400dd974f2b45) --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b74d3731a..73b7bf56e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,6 +4,7 @@ env: BASEDIR: https://raw.githubusercontent.com/open-contracting/standard-maintenance-scripts/main jobs: build: + if: github.event_name == 'pull_request' || startsWith(github.event.ref, 'refs/tags/') || startsWith(github.event.ref, 'refs/heads/1.1') || startsWith(github.event.ref, 'refs/heads/1.2') runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From df72e0e3a08ae6fbc6392e16da59e674171eeba3 Mon Sep 17 00:00:00 2001 From: Duncan Dewhurst Date: Tue, 14 Nov 2023 10:11:35 +1300 Subject: [PATCH 06/47] requirements.txt: Upgrade sphinxcontrib-opendataservices-jsonschema (cherry picked from commit 34e7631bb579658470badeca2de7ebe6e8854257) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3399e19cf..9c2ea5690 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,5 @@ ocdskit==1.1.3 sphinx-design==0.4.1 sphinxcontrib-opencontracting==0.0.8 -sphinxcontrib-opendataservices-jsonschema==0.5.1 +sphinxcontrib-opendataservices-jsonschema==0.6.1 sphinxcontrib-opendataservices==0.5.0 From da11e1501de05af0d2c154ba2a24afa08c9546a7 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:23:55 -0500 Subject: [PATCH 07/47] build: Update to latest profile template (cherry picked from commit 8dccac8d93eecfccdd1479617b30c7091abea8fe) --- .github/dependabot.yml | 6 ++++++ .github/workflows/ci.yml | 4 ++-- .github/workflows/js.yml | 4 ++-- .github/workflows/lint.yml | 6 +++--- .github/workflows/shell.yml | 2 +- .github/workflows/spellcheck.yml | 4 ++-- .gitignore | 1 - common-requirements.txt | 7 ++++--- include/common.mk | 10 +++++----- include/prologue.mk | 2 +- script/update | 2 +- 11 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..123014908 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f2618a9e..384eee48a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,10 +5,10 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' cache: pip diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index 47d8e09f7..f60649c7a 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -5,8 +5,8 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 18 - run: npm install standard diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 73b7bf56e..68a36675d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,11 +4,11 @@ env: BASEDIR: https://raw.githubusercontent.com/open-contracting/standard-maintenance-scripts/main jobs: build: - if: github.event_name == 'pull_request' || startsWith(github.event.ref, 'refs/tags/') || startsWith(github.event.ref, 'refs/heads/1.1') || startsWith(github.event.ref, 'refs/heads/1.2') + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' cache: pip diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index 182176554..ad37d1558 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -5,7 +5,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install shellcheck run: | sudo apt update diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 5d16e5fe0..300eefc34 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -5,8 +5,8 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - run: pip install codespell diff --git a/.gitignore b/.gitignore index f1d29bd7b..2a40f9487 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ /docs/_static/patched /src *.pyc -.python-version diff --git a/common-requirements.txt b/common-requirements.txt index 7780d73bc..1fab18073 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -64,7 +64,7 @@ imagesize==1.4.1 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.1.2 +jinja2==3.1.3 # via # myst-parser # sphinx @@ -103,7 +103,7 @@ packaging==21.3 # build # pytest # sphinx -pip-tools==6.13.0 +pip-tools==7.3.0 # via -r common-requirements.in platformdirs==3.9.1 # via requests-cache @@ -168,6 +168,7 @@ sphinxcontrib-serializinghtml==1.1.5 tomli==2.0.1 # via # build + # pip-tools # pyproject-hooks # pytest tornado==6.3.3 @@ -186,7 +187,7 @@ uc-micro-py==1.0.1 # via linkify-it-py url-normalize==1.4.3 # via requests-cache -urllib3[socks]==1.26.5 +urllib3[socks]==1.26.18 # via # elastic-transport # requests diff --git a/include/common.mk b/include/common.mk index aafd8e0ee..424e82269 100644 --- a/include/common.mk +++ b/include/common.mk @@ -1,15 +1,15 @@ # See https://github.com/datamade/data-making-guidelines -# See http://clarkgrubb.com/makefile-style-guide#phony-target-arg +# See https://clarkgrubb.com/makefile-style-guide#phony-target-arg FORCE: -# http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html +# https://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html COMMA := , SPACE := SPACE += COMMA_SEPARATED_TRANSLATIONS=$(subst $(SPACE),$(COMMA),$(TRANSLATIONS:.%=%)) -# See http://clarkgrubb.com/makefile-style-guide#phony-targets +# See https://clarkgrubb.com/makefile-style-guide#phony-targets .PHONY: clean clean: rm -rf $(BUILD_DIR) @@ -45,7 +45,7 @@ extract_schema: $(POT_DIR) # The codelist CSV files and JSON Schema files must be present for the `csv-table-no-translate` and `jsonschema` # directives to succeed, but the contents of the files have no effect on the generated .pot files. -# See http://www.sphinx-doc.org/en/stable/builders.html#sphinx.builders.gettext.MessageCatalogBuilder +# See https://www.sphinx-doc.org/en/master/usage/builders/index.html#sphinx.builders.gettext.MessageCatalogBuilder .PHONY: extract_markdown extract_markdown: current_lang.en sphinx-build -nW --keep-going -q -b gettext $(DOCS_DIR) $(POT_DIR) @@ -110,7 +110,7 @@ clean_current_lang: ### Build # Build the source documentation. -# See http://www.sphinx-doc.org/en/stable/builders.html#sphinx.builders.html.DirectoryHTMLBuilder +# See https://www.sphinx-doc.org/en/master/usage/builders/index.html#sphinx.builders.html.DirectoryHTMLBuilder .PHONY: build_source build_source: current_lang.en sphinx-build -nW --keep-going -q -b dirhtml $(DOCS_DIR) $(BUILD_DIR)/en diff --git a/include/prologue.mk b/include/prologue.mk index b512b7b3e..166f3ab5d 100644 --- a/include/prologue.mk +++ b/include/prologue.mk @@ -1,4 +1,4 @@ -# See http://clarkgrubb.com/makefile-style-guide#prologue +# See https://clarkgrubb.com/makefile-style-guide#prologue MAKEFLAGS += --warn-undefined-variables SHELL := bash .SHELLFLAGS := -eu -o pipefail -c diff --git a/script/update b/script/update index 5c9d03fc6..ada17caa4 100755 --- a/script/update +++ b/script/update @@ -4,7 +4,7 @@ set -euo pipefail function main { mkdir -p script include tests - for f in Makefile common-requirements.in common-requirements.txt .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk include/header.html script/diff script/update tests/conftest.py tests/test_common.py; do + for f in Makefile common-requirements.in common-requirements.txt .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk include/header.html script/diff script/update tests/conftest.py tests/test_common.py; do curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/$f done From 465dd353f2a86f5591dea1f1c256682a40f19145 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:14:33 +0000 Subject: [PATCH 08/47] build(deps): bump bcomnes/netrc-creds from 2 to 3 Bumps [bcomnes/netrc-creds](https://github.com/bcomnes/netrc-creds) from 2 to 3. - [Release notes](https://github.com/bcomnes/netrc-creds/releases) - [Changelog](https://github.com/bcomnes/netrc-creds/blob/master/CHANGELOG.md) - [Commits](https://github.com/bcomnes/netrc-creds/compare/v2...v3) --- updated-dependencies: - dependency-name: bcomnes/netrc-creds dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] (cherry picked from commit 55909da4c27348166e816d8bf6dd18e033a9ba5d) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 384eee48a..966087019 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: key: ${{ secrets.PRIVATE_KEY }} known_hosts: standard.open-contracting.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGveFGTJ9yyObNGDUCUyzyFkm6Kzh3YqIt1qB7B/KU6E - if: github.event_name == 'push' - uses: bcomnes/netrc-creds@v2 + uses: bcomnes/netrc-creds@v3 with: machine: standard.open-contracting.org login: manage From 3fcc1cc05b0502d65ecbca6f0d853f7c0d7e81e3 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:00:18 -0400 Subject: [PATCH 09/47] build: Update to latest profile template (cherry picked from commit 1bf6a0c3d6367e9fb8604f3d532946e4194962ed) --- .github/workflows/shell.yml | 6 ++---- common-requirements.txt | 2 +- docs/conf.py | 1 + docs/guidance/index.md | 2 +- docs/locale/es/LC_MESSAGES/guidance/index.po | 4 ++-- docs/locale/fr/LC_MESSAGES/guidance/index.po | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index ad37d1558..e87989085 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -6,10 +6,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install shellcheck - run: | + - run: | sudo apt update - sudo apt install shellcheck - - run: sudo snap install shfmt + sudo apt install shellcheck shfmt - run: shellcheck $(shfmt -f .) - run: shfmt -d -i 4 -sr $(shfmt -f .) diff --git a/common-requirements.txt b/common-requirements.txt index 1fab18073..089b9bfb1 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -133,7 +133,7 @@ requests-cache==1.1.0 # via ocdsextensionregistry selenium==4.11.2 # via -r common-requirements.in -six==1.15.0 +six==1.16.0 # via # livereload # url-normalize diff --git a/docs/conf.py b/docs/conf.py index 1d3cc89bc..7ae7ca563 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -111,6 +111,7 @@ 'license_url': f'{repository_url}/blob/HEAD/LICENSE', 'repository_url': repository_url, } +html_short_title = f'{html_theme_options["short_project"]} v{release}' # List the extension identifiers and versions that should be part of this specification. The extensions must be in # the extension registry: https://github.com/open-contracting/extension_registry/blob/main/extension_versions.csv diff --git a/docs/guidance/index.md b/docs/guidance/index.md index 10bfe575a..d2692297b 100644 --- a/docs/guidance/index.md +++ b/docs/guidance/index.md @@ -6,7 +6,7 @@ The four phases of implementation described in this guide have helped implemente Read the guidance to understand the main steps to implement OCDS and to find supporting resources. Detailed guidance on specific topics and tasks is provided in sub-pages. -Use the [OCDS Implementation Checklist](https://www.open-contracting.org/resources/ocds-implementation-checklist) to keep track of your progress. +Use the [OCDS Implementation Checklist](https://www.open-contracting.org/resources/ocds-implementation-checklist/) to keep track of your progress. ```{toctree} :maxdepth: 2 diff --git a/docs/locale/es/LC_MESSAGES/guidance/index.po b/docs/locale/es/LC_MESSAGES/guidance/index.po index 457625b13..06d357115 100644 --- a/docs/locale/es/LC_MESSAGES/guidance/index.po +++ b/docs/locale/es/LC_MESSAGES/guidance/index.po @@ -55,11 +55,11 @@ msgstr "" #: ../../docs/guidance/index.md:9 msgid "" "Use the [OCDS Implementation Checklist](https://www.open-" -"contracting.org/resources/ocds-implementation-checklist) to keep track of " +"contracting.org/resources/ocds-implementation-checklist/) to keep track of " "your progress." msgstr "" "Utilice la [Lista de Verificación de Implementación](https://www.open-" -"contracting.org/resources/ocds-implementation-checklist) para hacer un " +"contracting.org/resources/ocds-implementation-checklist/) para hacer un " "seguimiento de su progreso." #: ../../docs/guidance/index.md:20 diff --git a/docs/locale/fr/LC_MESSAGES/guidance/index.po b/docs/locale/fr/LC_MESSAGES/guidance/index.po index 736c5359a..21f566677 100644 --- a/docs/locale/fr/LC_MESSAGES/guidance/index.po +++ b/docs/locale/fr/LC_MESSAGES/guidance/index.po @@ -46,7 +46,7 @@ msgstr "" #: ../../docs/guidance/index.md:9 msgid "" "Use the [OCDS Implementation Checklist](https://www.open-" -"contracting.org/resources/ocds-implementation-checklist) to keep track of " +"contracting.org/resources/ocds-implementation-checklist/) to keep track of " "your progress." msgstr "" From 7340679a320f7755cb840d1dfc7936639da7d315 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:15:10 -0400 Subject: [PATCH 10/47] chore: Update JS style to latest profile template (cherry picked from commit f049f111d13611bbf6cc32b269cb5202755b1c70) --- .github/workflows/js.yml | 7 +- docs/_static/script.js | 138 ++++++++++++++++++++------------------- 2 files changed, 74 insertions(+), 71 deletions(-) diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index f60649c7a..4c8147e26 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -6,8 +6,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - run: npm install standard - - run: npx standard docs/_static/script.js + - uses: biomejs/setup-biome@v2 + - run: biome ci --indent-style=space --line-width=119 docs/_static/script.js diff --git a/docs/_static/script.js b/docs/_static/script.js index e46cd638a..ffd653486 100644 --- a/docs/_static/script.js +++ b/docs/_static/script.js @@ -1,35 +1,40 @@ /* global XMLHttpRequest, location, renderjson */ -const pathComponents = window.location.pathname.split('/') -if (pathComponents[pathComponents.length - 2] === 'codelists') { - document.querySelector('.wy-nav-content').style.maxWidth = 'none' +const pathComponents = window.location.pathname.split("/"); +if (pathComponents[pathComponents.length - 2] === "codelists") { + document.querySelector(".wy-nav-content").style.maxWidth = "none"; } // FYI: extensionlist appears many times on /schema/reference/ -if (document.querySelector('.extension_list')) { - const language = location.pathname.split('/')[2] +if (document.querySelector(".extension_list")) { + const language = location.pathname.split("/")[2]; // Append an empty list for community extensions. - document.querySelectorAll('.extension_list .hide').forEach(element => { - const dl = document.createElement('dl') - dl.className = 'simple community-list hide' - element.insertAdjacentElement('afterend', dl) - element.innerHTML = `${element.innerHTML}` - }) + for (const element of document.querySelectorAll(".extension_list .hide")) { + const dl = document.createElement("dl"); + dl.className = "simple community-list hide"; + element.insertAdjacentElement("afterend", dl); + element.innerHTML = `${element.innerHTML}`; + } // Get the community extensions to add to the documentation. - const request = new XMLHttpRequest() - request.open('GET', 'https://raw.githubusercontent.com/open-contracting/extension_registry/main/build/extensions.json') - request.responseType = 'json' + const request = new XMLHttpRequest(); + request.open( + "GET", + "https://raw.githubusercontent.com/open-contracting/extension_registry/main/build/extensions.json", + ); + request.responseType = "json"; request.onload = () => { if (request.status >= 200 && request.status < 400) { // Add community extensions. - request.response.extensions.forEach(extension => { + for (const extension of request.response.extensions) { if (!extension.core) { - const div = document.getElementById(`extensionlist-${extension.category}`) + const div = document.getElementById(`extensionlist-${extension.category}`); if (div) { - div.querySelector('.community-list').insertAdjacentHTML('beforeend', ` + div.querySelector(".community-list").insertAdjacentHTML( + "beforeend", + `
${extension.name[language] || extension.name.en} @@ -38,79 +43,80 @@ if (document.querySelector('.extension_list')) {
${extension.description[language] || extension.description.en}
- `) - div.querySelectorAll('.hide').forEach(element => { - element.classList.remove('hide') - }) + `, + ); + for (const element of div.querySelectorAll(".hide")) { + element.classList.remove("hide"); + } } } - }) + } // Remove empty extension lists. - document.querySelectorAll('.extension_list').forEach(element => { - if (!element.querySelector('a')) { - element.remove() + for (const element of document.querySelectorAll(".extension_list")) { + if (!element.querySelector("a")) { + element.remove(); } - }) + } } - } + }; - request.send() + request.send(); } -document.querySelectorAll('.expandjson').forEach(element => { - const defaultOpen = [] - let fileClassName - element.classList.forEach(className => { - if (className.substring(0, 7) === 'expand-' && className.length > 7) { - defaultOpen.push(className.substring(7)) +for (const element of document.querySelectorAll(".expandjson")) { + const defaultOpen = []; + let fileClassName; + for (const className of element.classList) { + if (className.substring(0, 7) === "expand-" && className.length > 7) { + defaultOpen.push(className.substring(7)); } - if (className.substring(0, 5) === 'file-') { - fileClassName = className + if (className.substring(0, 5) === "file-") { + fileClassName = className; } - }) + } - let data = JSON.parse(element.textContent) + let data = JSON.parse(element.textContent); // If the jsoninclude directive indexed to a JSON array (a common mistake), only display the first entry. if (data.length) { - data = data[0] + data = data[0]; } - const replacement = renderjson.set_show_to_level(1).set_max_string_length(100).set_default_open(defaultOpen)(data) + const replacement = renderjson.set_show_to_level(1).set_max_string_length(100).set_default_open(defaultOpen)(data); // element.firstElementChild.replaceWith(replacement) // https://caniuse.com/mdn-api_parentnode_firstelementchild - element.replaceChild(replacement, element.querySelector('.highlight-json')) + element.replaceChild(replacement, element.querySelector(".highlight-json")); - const container = element.previousElementSibling - let select - if (container && container.classList.contains('selection-container')) { + const container = element.previousElementSibling; + let select; + if (container?.classList.contains("selection-container")) { // Hide additional examples. - element.style.display = 'none' - container.appendChild(element) + element.style.display = "none"; + container.appendChild(element); // Display the select element if there are multiple options. - select = container.querySelector('select') - select.style.display = '' + select = container.querySelector("select"); + select.style.display = ""; } else { - const div = document.createElement('div') - element.insertAdjacentElement('beforebegin', div) - div.className = 'selection-container' - div.appendChild(element) + const div = document.createElement("div"); + element.insertAdjacentElement("beforebegin", div); + div.className = "selection-container"; + div.appendChild(element); - select = document.createElement('select') - div.insertAdjacentElement('afterbegin', select) + select = document.createElement("select"); + div.insertAdjacentElement("afterbegin", select); // Hide the select element if there is one option. - select.style.display = 'none' - select.addEventListener('change', () => { - div.querySelectorAll('.expandjson').forEach(child => { - child.style.display = 'none' - }) - div.querySelector(`.${select.value}`).style.display = '' - }) + select.style.display = "none"; + select.addEventListener("change", () => { + for (const child of div.querySelectorAll(".expandjson")) { + child.style.display = "none"; + } + div.querySelector(`.${select.value}`).style.display = ""; + }); } - const option = document.createElement('option') - option.value = fileClassName - option.textContent = fileClassName.replace('file-', '') - select.appendChild(option) -}) + const option = document.createElement("option"); + option.value = fileClassName; + option.textContent = fileClassName.replace("file-", ""); + select.appendChild(option); +} From 930b38adb5fb4ba1b2a9d0025a16a95802f00e3b Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:15:51 -0400 Subject: [PATCH 11/47] chore: Update to latest profile template (cherry picked from commit 796405a8fa335ba37f018e9e9703962b15b19be7) --- .github/workflows/shell.yml | 3 ++- common-requirements.txt | 10 +++++----- script/diff | 7 ++++--- script/update | 7 ++++--- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index e87989085..fe96765b8 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -8,6 +8,7 @@ jobs: - uses: actions/checkout@v4 - run: | sudo apt update - sudo apt install shellcheck shfmt + sudo apt install devscripts shellcheck shfmt + - run: checkbashisms $(shfmt -f .) - run: shellcheck $(shfmt -f .) - run: shfmt -d -i 4 -sr $(shfmt -f .) diff --git a/common-requirements.txt b/common-requirements.txt index 089b9bfb1..ff4de52f3 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -56,7 +56,7 @@ exceptiongroup==1.0.0 # pytest h11==0.13.0 # via wsproto -idna==2.10 +idna==3.7 # via # requests # trio @@ -64,7 +64,7 @@ imagesize==1.4.1 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.1.3 +jinja2==3.1.4 # via # myst-parser # sphinx @@ -123,7 +123,7 @@ pytz==2021.1 # via babel pyyaml==6.0.1 # via myst-parser -requests==2.31.0 +requests==2.32.2 # via # elasticsearch # ocdsextensionregistry @@ -151,7 +151,7 @@ sphinx==5.3.0 # sphinx-intl sphinx-autobuild==2021.3.14 # via -r common-requirements.in -sphinx-intl==2.1.0 +sphinx-intl==2.2.0 # via -r common-requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -171,7 +171,7 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -tornado==6.3.3 +tornado==6.4.1 # via livereload trio==0.20.0 # via diff --git a/script/diff b/script/diff index 988c0fff9..cf7e85060 100755 --- a/script/diff +++ b/script/diff @@ -1,6 +1,7 @@ -#!/usr/bin/env bash -# No -e, because diff fails. -set -uo pipefail +#!/bin/sh + +# No -e, because diff uses exit status 1 when differences are found. +set -u curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/docs/conf.py | diff -u - docs/conf.py diff --git a/script/update b/script/update index ada17caa4..cba8c6fc2 100755 --- a/script/update +++ b/script/update @@ -1,7 +1,8 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/bin/sh -function main { +set -eu + +main() { mkdir -p script include tests for f in Makefile common-requirements.in common-requirements.txt .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk include/header.html script/diff script/update tests/conftest.py tests/test_common.py; do From 5491ce41725e2acf66762e917d5e29566c7cd7cb Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:22:45 -0400 Subject: [PATCH 12/47] ci: Add sme exception (cherry picked from commit b95e9ccbc5ec552ff182f9e7349a9a9fd94a9b1a) --- .github/workflows/spellcheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 300eefc34..b60b28a2b 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -10,4 +10,4 @@ jobs: with: python-version: '3.10' - run: pip install codespell - - run: codespell -S .git,docson,locale,examples,country.csv,currency.csv,language.csv,mediaType.csv -L fo,zar . + - run: codespell -S .git,docson,locale,examples,country.csv,currency.csv,language.csv,mediaType.csv -L fo,sme,zar,SME . From 2055d3facf061a99388dfcfb0bdf8e87cc618b9f Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:30:36 -0400 Subject: [PATCH 13/47] chore: ruff check (cherry picked from commit 987f40e58399341b34f77eba5b45f4e20c0bffb3) --- docs/conf.py | 5 +- manage.py | 152 +++++++++++++-------------------- pyproject.toml | 35 ++++++++ tests/__init__.py | 4 +- tests/test_common.py | 4 +- tests/test_schema_integrity.py | 2 +- 6 files changed, 100 insertions(+), 102 deletions(-) create mode 100644 pyproject.toml diff --git a/docs/conf.py b/docs/conf.py index 7ae7ca563..448261356 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,10 +9,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) + import csv import json import os diff --git a/manage.py b/manage.py index 0b5a40d45..7da07066a 100755 --- a/manage.py +++ b/manage.py @@ -29,7 +29,7 @@ sys.path.append(str(basedir / 'docs')) -from conf import release # noqa isort:skip +from conf import release # noqa: E402 def custom_warning_formatter(message, category, filename, lineno, line=None): @@ -38,7 +38,7 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): warnings.formatwarning = custom_warning_formatter -versioned_template = json.loads(''' +versioned_template = json.loads(""" { "type": "array", "items": { @@ -61,7 +61,7 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): } } } -''') +""") common_versioned_definitions = { 'StringNullUriVersioned': { @@ -116,35 +116,26 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): def json_load(filename, library=json, **kwargs): - """ - Loads JSON data from the given filename. - """ + """Load JSON data from the given filename.""" with (schemadir / filename).open() as f: return library.load(f, **kwargs) def json_dump(filename, data): - """ - Writes JSON data to the given filename. - """ + """Write JSON data to the given filename.""" with (schemadir / filename).open('w') as f: json.dump(data, f, indent=2) f.write('\n') def csv_load(url, delimiter=','): - """ - Loads CSV data into a ``csv.DictReader`` from the given URL. - """ - reader = csv.DictReader(StringIO(get(url).text), delimiter=delimiter) - return reader + """Load CSV data into a ``csv.DictReader`` from the given URL.""" + return csv.DictReader(StringIO(get(url).text), delimiter=delimiter) @contextmanager def csv_dump(filename, fieldnames): - """ - Writes CSV headers to the given filename, and yields a ``csv.writer``. - """ + """Write CSV headers to the given filename, and yield a ``csv.writer``.""" f = (schemadir / 'codelists' / filename).open('w') writer = csv.writer(f, lineterminator='\n') writer.writerow(fieldnames) @@ -155,18 +146,14 @@ def csv_dump(filename, fieldnames): def get(url): - """ - GETs a URL and returns the response. Raises an exception if the status code is not successful. - """ - response = requests.get(url) + """GET a URL and returns the response. Raise an exception if the status code is not successful.""" + response = requests.get(url, timeout=10) response.raise_for_status() return response def coerce_to_list(data, key): - """ - Returns the value of the ``key`` key in the ``data`` mapping. If the value is a string, wraps it in an array. - """ + """Return the value of the ``key`` key in the ``data`` mapping. If the value is a string, wrap it in an array.""" item = data.get(key, []) if isinstance(item, str): return [item] @@ -174,16 +161,14 @@ def coerce_to_list(data, key): def get_metaschema(): - """ - Patches and returns the JSON Schema Draft 4 metaschema. - """ + """Patches and returns the JSON Schema Draft 4 metaschema.""" return json_merge_patch.merge(json_load('metaschema/json-schema-draft-4.json'), json_load('metaschema/meta-schema-patch.json')) def get_common_definition_ref(item): """ - Returns a schema that references the common definition that the ``item`` matches: "StringNullUriVersioned", + Return a schema that references the common definition that the ``item`` matches: "StringNullUriVersioned", "StringNullDateTimeVersioned" or "StringNullVersioned". """ for name, keywords in common_versioned_definitions.items(): @@ -191,15 +176,14 @@ def get_common_definition_ref(item): if any(item.get(keyword) != value for keyword, value in keywords.items()): continue # And adds no keywords to the definition. - if any(keyword not in (*keywords, *keywords_to_remove) for keyword in item): + if any(keyword not in {*keywords, *keywords_to_remove} for keyword in item): continue return {'$ref': f'#/definitions/{name}'} + return None def add_versioned(schema, unversioned_pointers, pointer=''): - """ - An outer function that calls ``_add_versioned`` on each field. - """ + """Call ``_add_versioned`` on each field.""" for key, value in schema['properties'].items(): new_pointer = f'{pointer}/properties/{key}' _add_versioned(schema, unversioned_pointers, new_pointer, key, value) @@ -211,7 +195,7 @@ def add_versioned(schema, unversioned_pointers, pointer=''): def _add_versioned(schema, unversioned_pointers, pointer, key, value): """ - An inner function that performs the changes to the schema to refer to versioned/unversioned definitions. + Perform the changes to the schema to refer to versioned/unversioned definitions. :param schema dict: the schema of the object on which the field is defined :param unversioned_pointers set: JSON Pointers to ``id`` fields to leave unversioned if the object is in an array @@ -280,9 +264,7 @@ def _add_versioned(schema, unversioned_pointers, pointer, key, value): def update_refs_to_unversioned_definitions(schema): - """ - Replaces ``$ref`` values with unversioned definitions. - """ + """Replace ``$ref`` values with unversioned definitions.""" for key, value in schema.items(): if key == '$ref': schema[key] = value + 'Unversioned' @@ -291,9 +273,7 @@ def update_refs_to_unversioned_definitions(schema): def get_unversioned_pointers(schema, fields, pointer=''): - """ - Returns the JSON Pointers to ``id`` fields that must not be versioned if the object is in an array. - """ + """Return the JSON Pointers to ``id`` fields that must not be versioned if the object is in an array.""" if isinstance(schema, list): for index, item in enumerate(schema): get_unversioned_pointers(item, fields, pointer=f'{pointer}/{index}') @@ -322,9 +302,7 @@ def get_unversioned_pointers(schema, fields, pointer=''): def remove_omit_when_merged(schema): - """ - Removes properties that set ``omitWhenMerged``. - """ + """Remove properties that set ``omitWhenMerged``.""" if isinstance(schema, list): for item in schema: remove_omit_when_merged(item) @@ -340,15 +318,13 @@ def remove_omit_when_merged(schema): def remove_metadata_and_extended_keywords(schema): - """ - Removes metadata and extended keywords from properties and definitions. - """ + """Remove metadata and extended keywords from properties and definitions.""" if isinstance(schema, list): for item in schema: remove_metadata_and_extended_keywords(item) elif isinstance(schema, dict): for key, value in schema.items(): - if key in ('definitions', 'properties'): + if key in {'definitions', 'properties'}: for subschema in value.values(): for keyword in keywords_to_remove: subschema.pop(keyword, None) @@ -356,12 +332,10 @@ def remove_metadata_and_extended_keywords(schema): def get_versioned_release_schema(schema): - """ - Returns the versioned release schema. - """ + """Return the versioned release schema.""" # Update schema metadata. release_with_underscores = release.replace('.', '__') - schema['id'] = f'https://standard.open-contracting.org/schema/{release_with_underscores}/versioned-release-validation-schema.json' # noqa + schema['id'] = f'https://standard.open-contracting.org/schema/{release_with_underscores}/versioned-release-validation-schema.json' schema['title'] = 'Schema for a compiled, versioned Open Contracting Release.' # Release IDs, dates and tags appear alongside values in the versioned release schema. @@ -447,10 +421,7 @@ def unused_terms(filename): @cli.command() @click.option('--ignore-base', help='A base branch to ignore, e.g. 1.2-dev') def missing_changelog(ignore_base): - """ - Print pull requests not mentioned in the changelog. - """ - + """Print pull requests not mentioned in the changelog.""" # Ignore PRs to the ppp-extension branch, which became OCDS for PPPs. ignore = ['ppp-extension'] if ignore_base: @@ -491,7 +462,7 @@ def missing_changelog(ignore_base): base_ref = pr['base']['ref'] # Include merged PRs, not in the "Minor:" or "1.0-RC" milestones, not syncing branches, and not ignored. - if not merged_at or milestone_number in (26, 27, 28, 29, 2) or pattern.search(title) or base_ref in ignore: + if not merged_at or milestone_number in {26, 27, 28, 29, 2} or pattern.search(title) or base_ref in ignore: if number in prs: click.echo(f'WARNING: #{number} should not be in changelog', file=sys.stderr) continue @@ -553,7 +524,8 @@ def update_country(file): # Clean "Western Sahara*", "United Arab Emirates (the)", etc. codes[d[str(offset + 9)]] = re.sub(r' \(the\)|\*', '', d[str(offset + 13)]) # The country code appears at offsets 9 and 15. Check that they are always the same. - assert d[str(offset + 9)] == d[str(offset + 15)] + if d[str(offset + 9)] != d[str(offset + 15)]: + raise AssertionError with open(schemadir / 'codelists' / 'country.csv', 'w') as f: writer = csv.writer(f, lineterminator='\n') @@ -564,16 +536,14 @@ def update_country(file): @cli.command() def update_currency(): - """ - Update currency.csv from ISO 4217. - """ + """Update currency.csv from ISO 4217.""" # https://www.iso.org/iso-4217-currency-codes.html # https://www.six-group.com/en/products-services/financial-information/data-standards.html#scrollTo=currency-codes - # "List One: Current Currency & Funds" + # List One: Current Currency & Funds current_codes = {} - url = 'https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/amendments/lists/list_one.xml' # noqa: E501 - tree = etree.fromstring(get(url).content) + url = 'https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/amendments/lists/list_one.xml' + tree = etree.fromstring(get(url).content) # noqa: S320 # trusted external for node in tree.xpath('//CcyNtry'): match = node.xpath('./Ccy') # Entries like Antarctica have no universal currency. @@ -584,23 +554,23 @@ def update_currency(): current_codes[code] = title # We should expect currency titles to be consistent across countries. elif current_codes[code] != title: - raise Exception(f'expected {current_codes[code]}, got {title}') + raise click.ClickException(f'expected {current_codes[code]}, got {title}') - # "List Three: Historic Denominations (Currencies & Funds)" + # List Three: Historic Denominations (Currencies & Funds) historic_codes = {} - url = 'https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/amendments/lists/list_three.xml' # noqa: E501 - tree = etree.fromstring(get(url).content) + url = 'https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/amendments/lists/list_three.xml' + tree = etree.fromstring(get(url).content) # noqa: S320 # trusted external for node in tree.xpath('//HstrcCcyNtry'): code = node.xpath('./Ccy')[0].text title = node.xpath('./CcyNm')[0].text.strip() valid_until = node.xpath('./WthdrwlDt')[0].text # Use ISO8601 interval notation. valid_until = re.sub(r'^(\d{4})-(\d{4})$', r'\1/\2', valid_until.replace(' to ', '/')) - if code not in current_codes: - if code not in historic_codes: - historic_codes[code] = {'Title': title, 'Valid Until': valid_until} - # If the code is historical, use the most recent title and valid date. - elif valid_until > historic_codes[code]['Valid Until']: + if ( + code not in current_codes + # Last condition: If the code is historical, use the most recent title and valid date. + and (code not in historic_codes or valid_until > historic_codes[code]['Valid Until']) + ): historic_codes[code] = {'Title': title, 'Valid Until': valid_until} with csv_dump('currency.csv', ['Code', 'Title', 'Valid Until']) as writer: @@ -610,23 +580,20 @@ def update_currency(): writer.writerow([code, historic_codes[code]['Title'], historic_codes[code]['Valid Until']]) release_schema = json_load('release-schema.json') - codes = sorted(list(current_codes) + list(historic_codes)) - release_schema['definitions']['Value']['properties']['currency']['enum'] = codes + [None] + codes = sorted([*current_codes, historic_codes]) + release_schema['definitions']['Value']['properties']['currency']['enum'] = [*codes, None] json_dump('release-schema.json', release_schema) @cli.command() def update_language(): - """ - Update language.csv from ISO 639-1. - """ + """Update language.csv from ISO 639-1.""" # https://www.iso.org/iso-639-language-codes.html # https://id.loc.gov/vocabulary/iso639-1.html with csv_dump('language.csv', ['Code', 'Title']) as writer: - reader = csv_load('https://id.loc.gov/vocabulary/iso639-1.tsv', delimiter='\t') - for row in reader: + for row in csv_load('https://id.loc.gov/vocabulary/iso639-1.tsv', delimiter='\t'): # Remove parentheses, like "Greek, Modern (1453-)", and split alternatives. titles = re.split(r' *\| *', re.sub(r' \(.+\)', '', row['Label (English)'])) # Remove duplication like "Ndebele, North | North Ndebele" and join alternatives using a comma instead of @@ -660,8 +627,7 @@ def update_media_type(): with csv_dump('mediaType.csv', ['Code', 'Title']) as writer: for registry in registries: # See "Available Formats" under each heading. - reader = csv_load(f'https://www.iana.org/assignments/media-types/{registry}.csv') - for row in reader: + for row in csv_load(f'https://www.iana.org/assignments/media-types/{registry}.csv'): if ' ' in row['Name']: name, message = row['Name'].split(' ', 1) else: @@ -673,7 +639,7 @@ def update_media_type(): logging.warning('%s: %s', message, code) # "x-emf" has "image/emf" in its "Template" value (but it is deprecated). elif template and template != code: - raise Exception(f"expected {code}, got {template}") + raise click.ClickException(f"expected {code}, got {template}") else: writer.writerow([code, name]) @@ -683,9 +649,7 @@ def update_media_type(): @cli.command() @click.pass_context def update(ctx): - """ - Update codelists except country.csv. - """ + """Update codelists except country.csv.""" ctx.invoke(update_currency) ctx.invoke(update_language) ctx.invoke(update_media_type) @@ -737,9 +701,7 @@ def text(node, xpath): def add_translation_note(path, language, domain): - """ - Adds a translation note to a file. - """ + """Add a translation note to a file.""" base_url = 'https://standard.open-contracting.org/1.1' with open(path) as f: @@ -749,10 +711,10 @@ def add_translation_note(path, language, domain): _ = translator.gettext pattern = f'{base_url}/{{}}/{domain}/' - response = requests.get(pattern.format(language)) + response = requests.get(pattern.format(language), timeout=10) # If it's a new page, add the note to the current version of the page. - if response.status_code == 404: + if response.status_code == requests.codes.not_found: message = _('This page was recently added to the English documentation. ' 'It has not yet been translated.') @@ -772,11 +734,15 @@ def add_translation_note(path, language, domain): element = document.xpath(xpath)[0] element.getparent().replace(element, replacement) - message = _('This page was recently changed in the English documentation. ' - 'The changes have not yet been translated.') + message = _( + 'This page was recently changed in the English documentation. ' + 'The changes have not yet been translated.' + ) - template = '

%(note)s

' \ - '%(message)s

' + template = ( + '

%(note)s

' + '%(message)s

' + ) document.xpath('//h1')[0].addnext(lxml.etree.XML(template % { 'note': _('Note'), 'message': message % {'url': pattern.format('en')}})) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..8cd10b2a3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "profile" +version = "0.0.0" + +[tool.ruff] +line-length = 119 +target-version = "py310" + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000", + "D1", "D205", + "PTH", + "D200", # https://github.com/astral-sh/ruff/issues/6269 +] +allowed-confusables = ["’"] + +[tool.ruff.lint.flake8-builtins] +builtins-ignorelist = ["copyright"] + +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true + +[tool.ruff.lint.per-file-ignores] +"docs/conf.py" = ["D100", "INP001"] +"tests/*" = [ + "ARG001", "D", "FBT003", "INP001", "PLR2004", "S", "TRY003", +] +"manage.py" = [ + "ARG001", # click + "B028", # warnings + "D301", # click escapes + "TRY003", # errors +] diff --git a/tests/__init__.py b/tests/__init__.py index 07f837aa2..fc301d0a9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,6 +13,6 @@ test_search_params = [ ('en', r'found \d+ page\(s\) matching'), # See https://github.com/sphinx-doc/sphinx/issues/11008 - # ('es', r'encontraron \d+ páginas que coinciden'), - # ('fr', r'\d+ page\(s\) correspondant'), + # ('es', r'encontraron \d+ páginas que coinciden'), # noqa: ERA001 + # ('fr', r'\d+ page\(s\) correspondant'), # noqa: ERA001 ] diff --git a/tests/test_common.py b/tests/test_common.py index 803bb96a9..40e77f5a9 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -8,13 +8,13 @@ from tests import languages, test_basic_params, test_search_params -@pytest.mark.parametrize('lang,text', test_basic_params.items()) +@pytest.mark.parametrize(('lang', 'text'), test_basic_params.items()) def test_basic(browser, server, lang, text): browser.get(f'{server}{lang}') assert text in browser.find_element(By.TAG_NAME, 'body').text -@pytest.mark.parametrize('lang,regex', test_search_params) +@pytest.mark.parametrize(('lang', 'regex'), test_search_params) def test_search(browser, server, lang, regex): browser.get(f'{server}{lang}') search_box = browser.find_element(By.ID, 'rtd-search-form').find_element(By.TAG_NAME, 'input') diff --git a/tests/test_schema_integrity.py b/tests/test_schema_integrity.py index 6c75b7884..39ed3bd6e 100644 --- a/tests/test_schema_integrity.py +++ b/tests/test_schema_integrity.py @@ -10,7 +10,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) -from manage import get_metaschema, get_versioned_release_schema # noqa isort:skip +from manage import get_metaschema, get_versioned_release_schema def test_versioned_release_schema_is_in_sync(): From bd03bb270f9b0d4c882045c23f493ef2dc111510 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:31:23 -0400 Subject: [PATCH 14/47] build: Update to latest profile template (cherry picked from commit a1c73ce3e17488fda8918ab15a19d0bf725a5368) --- .github/workflows/lint.yml | 14 ++++++ .pre-commit-config.yaml | 18 ++++++++ .python-version | 1 + common-requirements.in | 3 +- common-requirements.txt | 87 ++++++++++++++++++-------------------- script/update | 2 +- tests/conftest.py | 14 +++--- tests/test_common.py | 24 +++++------ 8 files changed, 94 insertions(+), 69 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 .python-version diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 68a36675d..621563ce9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,13 +6,27 @@ jobs: build: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-latest + env: + PAT: ${{ secrets.PAT }} steps: - uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT || github.token }} - uses: actions/setup-python@v5 with: python-version: '3.10' cache: pip cache-dependency-path: '**/requirements*.txt' + - id: changed-files + uses: tj-actions/changed-files@v45 + - uses: pre-commit/action@v3.0.1 + continue-on-error: true + with: + extra_args: pip-compile --files ${{ steps.changed-files.outputs.all_changed_files }} + - if: ${{ env.PAT }} + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[github-actions] pre-commit autoupdate' - shell: bash run: curl -s -S --retry 3 $BASEDIR/tests/install.sh | bash - - shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..8c81d5c10 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +ci: + autoupdate_schedule: quarterly + skip: [pip-compile] +default_language_version: + python: python3.10 +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff + - id: ruff-format + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.4.18 + hooks: + - id: pip-compile + name: pip-compile common-requirements.in + args: [common-requirements.in, -o, common-requirements.txt] + files: ^common-requirements\.(in|txt)$ diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..c8cfe3959 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/common-requirements.in b/common-requirements.in index 9832fc75d..bc75f3234 100644 --- a/common-requirements.in +++ b/common-requirements.in @@ -2,7 +2,7 @@ linkify-it-py myst-parser ocds-babel Sphinx --e git+https://github.com/open-contracting/standard_theme.git@open_contracting#egg=standard_theme +git+https://github.com/open-contracting/standard_theme.git@open_contracting#egg=standard_theme # Profile ocdsextensionregistry @@ -19,5 +19,4 @@ selenium # Development click -pip-tools sphinx-autobuild diff --git a/common-requirements.txt b/common-requirements.txt index ff4de52f3..9dd5cffd2 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -1,13 +1,11 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile common-requirements.in -# --e git+https://github.com/open-contracting/standard_theme.git@open_contracting#egg=standard_theme - # via -r common-requirements.in +# This file was autogenerated by uv via the following command: +# uv pip compile common-requirements.in -o common-requirements.txt alabaster==0.7.12 # via sphinx +anyio==4.4.0 + # via + # starlette + # watchfiles async-generator==1.10 # via # trio @@ -23,11 +21,9 @@ babel==2.9.1 # via # sphinx # sphinx-intl -build==0.10.0 - # via pip-tools cattrs==23.1.2 # via requests-cache -certifi==2023.7.22 +certifi==2024.7.4 # via # elastic-transport # requests @@ -38,9 +34,9 @@ click==8.1.3 # via # -r common-requirements.in # ocdsindex - # pip-tools # sphinx-intl -colorama==0.4.4 + # uvicorn +colorama==0.4.6 # via sphinx-autobuild docutils==0.18 # via @@ -48,16 +44,20 @@ docutils==0.18 # sphinx elastic-transport==8.4.0 # via elasticsearch -elasticsearch[requests]==8.6.2 +elasticsearch==8.6.2 # via ocdsindex -exceptiongroup==1.0.0 +exceptiongroup==1.2.2 # via + # anyio # cattrs # pytest h11==0.13.0 - # via wsproto + # via + # uvicorn + # wsproto idna==3.7 # via + # anyio # requests # trio imagesize==1.4.1 @@ -74,8 +74,6 @@ jsonref==1.0.0.post1 # via ocdsextensionregistry linkify-it-py==1.0.1 # via -r common-requirements.in -livereload==2.6.3 - # via sphinx-autobuild lxml==4.9.1 # via ocdsindex markdown-it-py==2.2.0 @@ -90,31 +88,24 @@ mdurl==0.1.2 # via markdown-it-py myst-parser==0.18.1 # via -r common-requirements.in -ocds-babel==0.3.1 +ocds-babel==0.3.6 # via -r common-requirements.in -ocdsextensionregistry==0.3.8 +ocdsextensionregistry==0.5.0 # via -r common-requirements.in ocdsindex==0.2.0 # via -r common-requirements.in outcome==1.1.0 # via trio -packaging==21.3 +packaging==24.1 # via - # build # pytest # sphinx -pip-tools==7.3.0 - # via -r common-requirements.in platformdirs==3.9.1 # via requests-cache pluggy==0.13.1 # via pytest pygments==2.15.1 # via sphinx -pyparsing==2.4.7 - # via packaging -pyproject-hooks==1.0.0 - # via build pysocks==1.7.1 # via urllib3 pytest==7.2.0 @@ -133,12 +124,14 @@ requests-cache==1.1.0 # via ocdsextensionregistry selenium==4.11.2 # via -r common-requirements.in +setuptools==75.2.0 + # via sphinx-intl six==1.16.0 - # via - # livereload - # url-normalize + # via url-normalize sniffio==1.2.0 - # via trio + # via + # anyio + # trio snowballstemmer==2.1.0 # via sphinx sortedcontainers==2.4.0 @@ -149,7 +142,7 @@ sphinx==5.3.0 # myst-parser # sphinx-autobuild # sphinx-intl -sphinx-autobuild==2021.3.14 +sphinx-autobuild==2024.9.3 # via -r common-requirements.in sphinx-intl==2.2.0 # via -r common-requirements.in @@ -165,14 +158,12 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@07ca0e39979a244656dd6df0658f2ead428184b9#egg=standard_theme + # via -r common-requirements.in +starlette==0.40.0 + # via sphinx-autobuild tomli==2.0.1 - # via - # build - # pip-tools - # pyproject-hooks - # pytest -tornado==6.4.1 - # via livereload + # via pytest trio==0.20.0 # via # selenium @@ -181,23 +172,25 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.4.0 # via + # anyio # cattrs # myst-parser + # uvicorn uc-micro-py==1.0.1 # via linkify-it-py url-normalize==1.4.3 # via requests-cache -urllib3[socks]==1.26.18 +urllib3==1.26.19 # via # elastic-transport # requests # requests-cache # selenium -wheel==0.38.4 - # via pip-tools +uvicorn==0.30.6 + # via sphinx-autobuild +watchfiles==0.24.0 + # via sphinx-autobuild +websockets==13.0.1 + # via sphinx-autobuild wsproto==1.1.0 # via trio-websocket - -# The following packages are considered to be unsafe in a requirements file: -# pip -# setuptools diff --git a/script/update b/script/update index cba8c6fc2..bd2170e08 100755 --- a/script/update +++ b/script/update @@ -5,7 +5,7 @@ set -eu main() { mkdir -p script include tests - for f in Makefile common-requirements.in common-requirements.txt .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk include/header.html script/diff script/update tests/conftest.py tests/test_common.py; do + for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk include/header.html script/diff script/update tests/conftest.py tests/test_common.py; do curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/$f done diff --git a/tests/conftest.py b/tests/conftest.py index 1a96b5cdc..f5a6035f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,11 +7,11 @@ from selenium.webdriver.chrome.options import Options -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def browser(request): options = Options() - options.add_argument('--headless') - options.add_argument('--no-sandbox') + options.add_argument("--headless") + options.add_argument("--no-sandbox") browser = webdriver.Chrome(options=options) browser.implicitly_wait(3) @@ -21,17 +21,17 @@ def browser(request): browser.quit() -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def server(request): - host = 'localhost' + host = "localhost" port_number = 8331 - server = HTTPServer((host, port_number), partial(SimpleHTTPRequestHandler, directory='build')) + server = HTTPServer((host, port_number), partial(SimpleHTTPRequestHandler, directory="build")) thread = threading.Thread(target=server.serve_forever) thread.start() - yield f'http://{host}:{port_number}/' + yield f"http://{host}:{port_number}/" server.shutdown() thread.join() diff --git a/tests/test_common.py b/tests/test_common.py index 40e77f5a9..417759f17 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -8,31 +8,31 @@ from tests import languages, test_basic_params, test_search_params -@pytest.mark.parametrize(('lang', 'text'), test_basic_params.items()) +@pytest.mark.parametrize(("lang", "text"), test_basic_params.items()) def test_basic(browser, server, lang, text): - browser.get(f'{server}{lang}') - assert text in browser.find_element(By.TAG_NAME, 'body').text + browser.get(f"{server}{lang}") + assert text in browser.find_element(By.TAG_NAME, "body").text -@pytest.mark.parametrize(('lang', 'regex'), test_search_params) +@pytest.mark.parametrize(("lang", "regex"), test_search_params) def test_search(browser, server, lang, regex): - browser.get(f'{server}{lang}') - search_box = browser.find_element(By.ID, 'rtd-search-form').find_element(By.TAG_NAME, 'input') - search_box.send_keys('tender\n') + browser.get(f"{server}{lang}") + search_box = browser.find_element(By.ID, "rtd-search-form").find_element(By.TAG_NAME, "input") + search_box.send_keys("tender\n") time.sleep(3) - assert re.search(regex, browser.find_element(By.TAG_NAME, 'body').text) + assert re.search(regex, browser.find_element(By.TAG_NAME, "body").text) # This seems to be an issue in Selenium and/or ChromeDriver. @pytest.mark.filterwarnings("ignore:unclosed Date: Tue, 26 Nov 2024 23:13:10 -0500 Subject: [PATCH 15/47] chore: Use double-quotes to reduce diff with profile template (cherry picked from commit 61bedba61f7c019ed7d8714df20bd48d3ec79a0a) --- docs/conf.py | 120 +++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 448261356..bacca8720 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,10 +6,6 @@ # -- Path setup -------------------------------------------------------------- -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - import csv import json import os @@ -24,12 +20,12 @@ # -- Project information ----------------------------------------------------- -project = 'Open Contracting Data Standard' -copyright = 'Open Contracting Partnership' -author = 'Open Contracting Partnership' +project = "Open Contracting Data Standard" +copyright = "Open Contracting Partnership" +author = "Open Contracting Partnership" -version = '1.1' -release = '1.1.5' +version = "1.1" +release = "1.1.5" # -- General configuration --------------------------------------------------- @@ -38,21 +34,21 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'myst_parser', - 'sphinx.ext.ifconfig', - 'sphinxcontrib.jsonschema', - 'sphinxcontrib.opencontracting', - 'sphinxcontrib.opendataservices', - 'sphinx_design', + "myst_parser", + "sphinx.ext.ifconfig", + "sphinxcontrib.jsonschema", + "sphinxcontrib.opencontracting", + "sphinxcontrib.opendataservices", + "sphinx_design", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**/docson/[!p]**', '**/docson/package*.json'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**/docson/[!p]**", "**/docson/package*.json"] # -- Options for HTML output ------------------------------------------------- @@ -60,84 +56,84 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'standard_theme' # 'pydata_sphinx_theme' +html_theme = "standard_theme" # 'pydata_sphinx_theme' html_theme_path = [standard_theme.get_html_theme_path()] -html_favicon = '_static/favicon-16x16.ico' +html_favicon = "_static/favicon-16x16.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # -- Local configuration ----------------------------------------------------- -_ = get_translation('theme') +_ = get_translation("theme") -profile_identifier = '' -repository_url = 'https://github.com/open-contracting/standard' +profile_identifier = "" +repository_url = "https://github.com/open-contracting/standard" # Internationalization. gettext_compact = False # `DOMAIN_PREFIX` from `config.mk`. -gettext_domain_prefix = f'{profile_identifier}-' if profile_identifier else '' -locale_dirs = ['locale/', os.path.join(standard_theme.get_html_theme_path(), 'locale')] +gettext_domain_prefix = f"{profile_identifier}-" if profile_identifier else "" +locale_dirs = ["locale/", os.path.join(standard_theme.get_html_theme_path(), "locale")] # We use single quotes for codes, which docutils will change to double quotes. # https://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/utils/smartquotes.py smartquotes = False # MyST configuration. -myst_enable_extensions = ['linkify'] +myst_enable_extensions = ["linkify"] myst_heading_anchors = 6 myst_heading_slug_func = make_id # https://github.com/executablebooks/MyST-Parser/issues/357 -suppress_warnings = ['myst.anchor'] +suppress_warnings = ["myst.anchor"] # Theme customization. navigation_with_keys = False # restore the Sphinx default html_context = { - 'analytics_id': 'HTWZHRIZ', + "analytics_id": "HTWZHRIZ", } html_theme_options = { - 'analytics_id': 'HTWZHRIZ', - 'display_version': True, - 'root_url': f'/profiles/{profile_identifier}' if profile_identifier else '', - 'short_project': project.replace('Open Contracting Data Standard', 'OCDS'), - 'copyright': copyright, - 'license_name': 'Apache License 2.0', - 'license_url': f'{repository_url}/blob/HEAD/LICENSE', - 'repository_url': repository_url, + "analytics_id": "HTWZHRIZ", + "display_version": True, + "root_url": f"/profiles/{profile_identifier}" if profile_identifier else "", + "short_project": project.replace("Open Contracting Data Standard", "OCDS"), + "copyright": copyright, + "license_name": "Apache License 2.0", + "license_url": f"{repository_url}/blob/HEAD/LICENSE", + "repository_url": repository_url, } -html_short_title = f'{html_theme_options["short_project"]} v{release}' +html_short_title = f"{html_theme_options["short_project"]} v{release}" # List the extension identifiers and versions that should be part of this specification. The extensions must be in # the extension registry: https://github.com/open-contracting/extension_registry/blob/main/extension_versions.csv -default_extension_version = f'v{release}' +default_extension_version = f"v{release}" extension_versions = { - 'bids': default_extension_version, - 'enquiries': default_extension_version, - 'location': default_extension_version, - 'lots': default_extension_version, - 'milestone_documents': default_extension_version, - 'participation_fee': default_extension_version, - 'process_title': default_extension_version, + "bids": default_extension_version, + "enquiries": default_extension_version, + "location": default_extension_version, + "lots": default_extension_version, + "milestone_documents": default_extension_version, + "participation_fee": default_extension_version, + "process_title": default_extension_version, } # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder # Ignore Google Sheets. -linkcheck_anchors_ignore = [r'^gid='] +linkcheck_anchors_ignore = [r"^gid="] linkcheck_ignore = [ # Avoid GitHub.com rate limiting. - r'^https://github.com/open-contracting/standard/(?:issues|pull)/\d+$', + r"^https://github.com/open-contracting/standard/(?:issues|pull)/\d+$", # Ignore irreproducible false positives. - r'^https://www.fcny.org/fcny/$', - r'^http://www.eprocurementtoolkit.org/sites/default/files/2016-11/OCDS_Implemetation_Methodology_0.pdf#page=27$', + r"^https://www.fcny.org/fcny/$", + r"^http://www.eprocurementtoolkit.org/sites/default/files/2016-11/OCDS_Implemetation_Methodology_0.pdf#page=27$", # Ignore unwanted links created by linkify. r'^http://buyandsell.gc.ca$', r'^http://(?:identifier|release).id$', r'^http://vnd\.', # Ignore expected redirects. - r'^https://docs.google.com/spreadsheets/d/[^/]+/pub?gid=\d+&single=true&output=csv$', + r"^https://docs.google.com/spreadsheets/d/[^/]+/pub?gid=\d+&single=true&output=csv$", ] @@ -145,32 +141,32 @@ def setup(app): # The root of the repository. basedir = Path(__file__).resolve().parents[1] # `LOCALE_DIR` from `config.mk`. - localedir = basedir / 'docs' / 'locale' + localedir = basedir / "docs" / "locale" - language = app.config.overrides.get('language', 'en') + language = app.config.overrides.get("language", "en") - headers = ['Title', 'Description', 'Extension'] + headers = ["Title", "Description", "Extension"] # The gettext domain for schema translations. Should match the domain in the `pybabel compile` command. - schema_domain = f'{gettext_domain_prefix}schema' + schema_domain = f"{gettext_domain_prefix}schema" # The gettext domain for codelist translations. Should match the domain in the `pybabel compile` command. - codelists_domain = f'{gettext_domain_prefix}codelists' + codelists_domain = f"{gettext_domain_prefix}codelists" - standard_dir = basedir / 'schema' - standard_build_dir = basedir / 'build' / language + standard_dir = basedir / "schema" + standard_build_dir = basedir / "build" / language - branch = os.getenv('GITHUB_REF_NAME', 'latest') + branch = os.getenv("GITHUB_REF_NAME", "latest") translate([ # The glob patterns in `babel_ocds_schema.cfg` should match these filenames. - (glob(str(standard_dir / '*-schema.json')), standard_build_dir, schema_domain), + (glob(str(standard_dir / "*-schema.json")), standard_build_dir, schema_domain), # The glob patterns in `babel_ocds_codelist.cfg` should match these. - (glob(str(standard_dir / 'codelists' / '*.csv')), standard_build_dir / 'codelists', codelists_domain), + (glob(str(standard_dir / "codelists" / "*.csv")), standard_build_dir / "codelists", codelists_domain), ], localedir, language, headers, version=branch) - with (standard_build_dir / 'release-schema.json').open() as f: + with (standard_build_dir / "release-schema.json").open() as f: fieldnames, rows = mapping_sheet(json.load(f), infer_required=True) - with (standard_build_dir / 'release-schema.csv').open('w') as f: + with (standard_build_dir / "release-schema.csv").open("w") as f: writer = csv.DictWriter(f, fieldnames) writer.writeheader() writer.writerows(rows) From e354e82e7dbb931e343f73beb56b8cf4c6ca3aa1 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:13:33 -0500 Subject: [PATCH 16/47] build: Update to latest profile template (cherry picked from commit 0bd578120b25cd638dd0cdade00344a83b80b51b) --- .github/workflows/ci.yml | 53 +++++++------------------------- .github/workflows/js.yml | 11 +++---- .github/workflows/lint.yml | 36 ++++------------------ .github/workflows/shell.yml | 13 ++------ .github/workflows/spellcheck.yml | 15 +++------ docs/conf.py | 2 +- script/diff | 2 +- 7 files changed, 30 insertions(+), 102 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 966087019..b92694c15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,46 +2,15 @@ name: CI on: [push, pull_request] jobs: build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - cache: pip - cache-dependency-path: '**/requirements*.txt' - # Don't install editable projects in the current working directory. - # https://pip.pypa.io/en/latest/reference/pip_install/#install-src - - run: pip install --src $GITHUB_WORKSPACE/../src -r requirements.txt - - if: github.repository == 'open-contracting/standard_profile_template' - run: make update extract - - run: make - # Disable linkcheck as it is too slow for development. - # - run: | - # make linkcheck - # rm -f output.json output.txt + uses: open-contracting/.github/.github/workflows/ci-profile.yml@main + secrets: + private-key: ${{ secrets.PRIVATE_KEY }} + elasticsearch-password: ${{ secrets.ELASTICSEARCH_PASSWORD }} + with: + path-prefix: '' + version: '1.1' + production-refs: '["refs/heads/1.0", "refs/heads/1.1"]' + # Linkcheck is slow for development. + linkcheck: false # "ResourceWarning: unclosed file <_io.BufferedWriter name='/dev/null'>" - - run: pytest -W error -W ignore::ResourceWarning - # Deploy the built documentation to the staging directory. - - if: github.event_name == 'push' - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.PRIVATE_KEY }} - known_hosts: standard.open-contracting.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGveFGTJ9yyObNGDUCUyzyFkm6Kzh3YqIt1qB7B/KU6E - - if: github.event_name == 'push' - uses: bcomnes/netrc-creds@v3 - with: - machine: standard.open-contracting.org - login: manage - password: ${{ secrets.ELASTICSEARCH_PASSWORD }} - - if: github.event_name == 'push' && success() && github.repository != 'open-contracting/standard_profile_template' - env: - PATH_PREFIX: "" - PRODUCTION: ${{ startsWith(github.ref, 'refs/tags') || github.ref == 'refs/heads/latest' || github.ref == 'refs/heads/1.0' || github.ref == 'refs/heads/1.1' }} - RELEASE: ${{ startsWith(github.ref, 'refs/tags') }} - VERSION: "1.1" - shell: bash - run: curl -sS https://raw.githubusercontent.com/open-contracting/deploy/main/deploy-docs.sh | bash - + pytest-options: -W ignore::ResourceWarning diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index 4c8147e26..f0d0e38b5 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -1,10 +1,7 @@ name: Lint JavaScript on: [push, pull_request] jobs: - build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: biomejs/setup-biome@v2 - - run: biome ci --indent-style=space --line-width=119 docs/_static/script.js + lint: + uses: open-contracting/.github/.github/workflows/js.yml@main + with: + filenames: docs/_static/script.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 621563ce9..2f31fb710 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,33 +1,9 @@ name: Lint on: [push, pull_request] -env: - BASEDIR: https://raw.githubusercontent.com/open-contracting/standard-maintenance-scripts/main jobs: - build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest - env: - PAT: ${{ secrets.PAT }} - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PAT || github.token }} - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - cache: pip - cache-dependency-path: '**/requirements*.txt' - - id: changed-files - uses: tj-actions/changed-files@v45 - - uses: pre-commit/action@v3.0.1 - continue-on-error: true - with: - extra_args: pip-compile --files ${{ steps.changed-files.outputs.all_changed_files }} - - if: ${{ env.PAT }} - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: '[github-actions] pre-commit autoupdate' - - shell: bash - run: curl -s -S --retry 3 $BASEDIR/tests/install.sh | bash - - - shell: bash - run: curl -s -S --retry 3 $BASEDIR/tests/script.sh | bash - + lint: + uses: open-contracting/.github/.github/workflows/lint.yml@main + permissions: + contents: write + with: + python-version: '3.10' diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index fe96765b8..dfdb0acfb 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -1,14 +1,5 @@ name: Lint Shell on: [push, pull_request] jobs: - build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: | - sudo apt update - sudo apt install devscripts shellcheck shfmt - - run: checkbashisms $(shfmt -f .) - - run: shellcheck $(shfmt -f .) - - run: shfmt -d -i 4 -sr $(shfmt -f .) + lint: + uses: open-contracting/.github/.github/workflows/shell.yml@main diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index b60b28a2b..d67a6d01e 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -1,13 +1,8 @@ name: Spell-check on: [push, pull_request] jobs: - build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - run: pip install codespell - - run: codespell -S .git,docson,locale,examples,country.csv,currency.csv,language.csv,mediaType.csv -L fo,sme,zar,SME . + lint: + uses: open-contracting/.github/.github/workflows/spellcheck.yml@main + with: + ignore: fo,sme,zar + skip: docson,locale,examples,country.csv,currency.csv,language.csv,mediaType.csv diff --git a/docs/conf.py b/docs/conf.py index bacca8720..76273021a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -104,7 +104,7 @@ "license_url": f"{repository_url}/blob/HEAD/LICENSE", "repository_url": repository_url, } -html_short_title = f"{html_theme_options["short_project"]} v{release}" +html_short_title = f"{html_theme_options['short_project']} v{release}" # List the extension identifiers and versions that should be part of this specification. The extensions must be in # the extension registry: https://github.com/open-contracting/extension_registry/blob/main/extension_versions.csv diff --git a/script/diff b/script/diff index cf7e85060..85fb10c99 100755 --- a/script/diff +++ b/script/diff @@ -5,7 +5,7 @@ set -u curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/docs/conf.py | diff -u - docs/conf.py -for f in .github/workflows/ci.yml .github/workflows/js.yml .github/workflows/spellcheck.yml include/config.mk; do +for f in .github/workflows/ci.yml .github/workflows/spellcheck.yml include/config.mk; do curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/$f | diff -u - $f done From b875146e59583c811f0f31ea3fd451bb91305df8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 04:17:14 +0000 Subject: [PATCH 17/47] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci (cherry picked from commit 260368738b55d7d2e870c7c3618ce512cdca0c17) --- docs/conf.py | 18 ++++++++++++------ tests/__init__.py | 14 +++++++------- tests/test_schema_integrity.py | 16 ++++++++-------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 76273021a..5522f2259 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -156,12 +156,18 @@ def setup(app): branch = os.getenv("GITHUB_REF_NAME", "latest") - translate([ - # The glob patterns in `babel_ocds_schema.cfg` should match these filenames. - (glob(str(standard_dir / "*-schema.json")), standard_build_dir, schema_domain), - # The glob patterns in `babel_ocds_codelist.cfg` should match these. - (glob(str(standard_dir / "codelists" / "*.csv")), standard_build_dir / "codelists", codelists_domain), - ], localedir, language, headers, version=branch) + translate( + [ + # The glob patterns in `babel_ocds_schema.cfg` should match these filenames. + (glob(str(standard_dir / "*-schema.json")), standard_build_dir, schema_domain), + # The glob patterns in `babel_ocds_codelist.cfg` should match these. + (glob(str(standard_dir / "codelists" / "*.csv")), standard_build_dir / "codelists", codelists_domain), + ], + localedir, + language, + headers, + version=branch, + ) with (standard_build_dir / "release-schema.json").open() as f: fieldnames, rows = mapping_sheet(json.load(f), infer_required=True) diff --git a/tests/__init__.py b/tests/__init__.py index fc301d0a9..29bd6731d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,17 +1,17 @@ languages = { - 'en': 'English', - 'es': 'Español', - 'fr': 'Français', + "en": "English", + "es": "Español", + "fr": "Français", } test_basic_params = { - 'en': 'Open Contracting Data Standard', - 'es': 'Estándar de Datos para las Contrataciones Abiertas', - 'fr': 'Standard de Données sur la Commande Publique Ouverte', + "en": "Open Contracting Data Standard", + "es": "Estándar de Datos para las Contrataciones Abiertas", + "fr": "Standard de Données sur la Commande Publique Ouverte", } test_search_params = [ - ('en', r'found \d+ page\(s\) matching'), + ("en", r"found \d+ page\(s\) matching"), # See https://github.com/sphinx-doc/sphinx/issues/11008 # ('es', r'encontraron \d+ páginas que coinciden'), # noqa: ERA001 # ('fr', r'\d+ page\(s\) correspondant'), # noqa: ERA001 diff --git a/tests/test_schema_integrity.py b/tests/test_schema_integrity.py index 39ed3bd6e..c6fdb0f86 100644 --- a/tests/test_schema_integrity.py +++ b/tests/test_schema_integrity.py @@ -14,27 +14,27 @@ def test_versioned_release_schema_is_in_sync(): - with open('schema/versioned-release-validation-schema.json') as f: + with open("schema/versioned-release-validation-schema.json") as f: actual = json.load(f) - with open('schema/release-schema.json') as f: + with open("schema/release-schema.json") as f: expected = get_versioned_release_schema(json.load(f)) - assert actual == expected, 'Run: python manage.py pre-commit' + assert actual == expected, "Run: python manage.py pre-commit" def test_dereferenced_release_schema_is_in_sync(): - with open('schema/dereferenced-release-schema.json') as f: + with open("schema/dereferenced-release-schema.json") as f: actual = json.load(f) - with open('schema/release-schema.json') as f: + with open("schema/release-schema.json") as f: expected = jsonref.load(f, merge_props=True) - assert actual == expected, 'Run: python manage.py pre-commit' + assert actual == expected, "Run: python manage.py pre-commit" def test_meta_schema_is_in_sync(): - with open('schema/meta-schema.json') as f: + with open("schema/meta-schema.json") as f: actual = json.load(f) - assert actual == get_metaschema(), 'Run: python manage.py pre-commit' + assert actual == get_metaschema(), "Run: python manage.py pre-commit" From 768820a98347587cd312437630639f82c8b178d6 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:17:58 -0500 Subject: [PATCH 18/47] chore: Run pre-commit run --all-files (cherry picked from commit b7aaa4b03175d59121525f32b50ae635619a8698) --- common-requirements.txt | 2 +- manage.py | 439 ++++++++++++++++++++-------------------- 2 files changed, 223 insertions(+), 218 deletions(-) diff --git a/common-requirements.txt b/common-requirements.txt index 9dd5cffd2..3076effd9 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -158,7 +158,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@07ca0e39979a244656dd6df0658f2ead428184b9#egg=standard_theme +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@c3cbd1b8ba6db24624e5d838ed18147db63f5d1b#egg=standard_theme # via -r common-requirements.in starlette==0.40.0 # via sphinx-autobuild diff --git a/manage.py b/manage.py index 7da07066a..6a31fc1e1 100755 --- a/manage.py +++ b/manage.py @@ -24,16 +24,16 @@ from lxml import etree basedir = Path(__file__).resolve().parent -schemadir = basedir / 'schema' -localedir = basedir / 'docs' / 'locale' +schemadir = basedir / "schema" +localedir = basedir / "docs" / "locale" -sys.path.append(str(basedir / 'docs')) +sys.path.append(str(basedir / "docs")) from conf import release # noqa: E402 def custom_warning_formatter(message, category, filename, lineno, line=None): - return str(message) + '\n' + return str(message) + "\n" warnings.formatwarning = custom_warning_formatter @@ -64,54 +64,49 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): """) common_versioned_definitions = { - 'StringNullUriVersioned': { - 'type': ['string', 'null'], - 'format': 'uri', + "StringNullUriVersioned": { + "type": ["string", "null"], + "format": "uri", }, - 'StringNullDateTimeVersioned': { - 'type': ['string', 'null'], - 'format': 'date-time', + "StringNullDateTimeVersioned": { + "type": ["string", "null"], + "format": "date-time", }, - 'StringNullVersioned': { - 'type': ['string', 'null'], - 'format': None, + "StringNullVersioned": { + "type": ["string", "null"], + "format": None, }, } recognized_types = ( # Array - ['array'], - ['array', 'null'], # optional string arrays - + ["array"], + ["array", "null"], # optional string arrays # Object - ['object'], - ['object', 'null'], # /Organization/details - + ["object"], + ["object", "null"], # /Organization/details # String - ['string'], - ['string', 'null'], - + ["string"], + ["string", "null"], # Literal - ['boolean', 'null'], - ['integer', 'null'], - ['number', 'null'], - + ["boolean", "null"], + ["integer", "null"], + ["number", "null"], # Mixed - ['string', 'integer'], - ['string', 'integer', 'null'], + ["string", "integer"], + ["string", "integer", "null"], ) keywords_to_remove = ( # Metadata keywords # https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6 - 'title', - 'description', - 'default', - + "title", + "description", + "default", # Extended keywords # http://os4d.opendataservices.coop/development/schema/#extended-json-schema - 'omitWhenMerged', - 'wholeListMerge', + "omitWhenMerged", + "wholeListMerge", ) @@ -123,12 +118,12 @@ def json_load(filename, library=json, **kwargs): def json_dump(filename, data): """Write JSON data to the given filename.""" - with (schemadir / filename).open('w') as f: + with (schemadir / filename).open("w") as f: json.dump(data, f, indent=2) - f.write('\n') + f.write("\n") -def csv_load(url, delimiter=','): +def csv_load(url, delimiter=","): """Load CSV data into a ``csv.DictReader`` from the given URL.""" return csv.DictReader(StringIO(get(url).text), delimiter=delimiter) @@ -136,8 +131,8 @@ def csv_load(url, delimiter=','): @contextmanager def csv_dump(filename, fieldnames): """Write CSV headers to the given filename, and yield a ``csv.writer``.""" - f = (schemadir / 'codelists' / filename).open('w') - writer = csv.writer(f, lineterminator='\n') + f = (schemadir / "codelists" / filename).open("w") + writer = csv.writer(f, lineterminator="\n") writer.writerow(fieldnames) try: yield writer @@ -162,8 +157,9 @@ def coerce_to_list(data, key): def get_metaschema(): """Patches and returns the JSON Schema Draft 4 metaschema.""" - return json_merge_patch.merge(json_load('metaschema/json-schema-draft-4.json'), - json_load('metaschema/meta-schema-patch.json')) + return json_merge_patch.merge( + json_load("metaschema/json-schema-draft-4.json"), json_load("metaschema/meta-schema-patch.json") + ) def get_common_definition_ref(item): @@ -178,18 +174,18 @@ def get_common_definition_ref(item): # And adds no keywords to the definition. if any(keyword not in {*keywords, *keywords_to_remove} for keyword in item): continue - return {'$ref': f'#/definitions/{name}'} + return {"$ref": f"#/definitions/{name}"} return None -def add_versioned(schema, unversioned_pointers, pointer=''): +def add_versioned(schema, unversioned_pointers, pointer=""): """Call ``_add_versioned`` on each field.""" - for key, value in schema['properties'].items(): - new_pointer = f'{pointer}/properties/{key}' + for key, value in schema["properties"].items(): + new_pointer = f"{pointer}/properties/{key}" _add_versioned(schema, unversioned_pointers, new_pointer, key, value) - for key, value in schema.get('definitions', {}).items(): - new_pointer = f'{pointer}/definitions/{key}' + for key, value in schema.get("definitions", {}).items(): + new_pointer = f"{pointer}/definitions/{key}" add_versioned(value, unversioned_pointers, pointer=new_pointer) @@ -207,98 +203,98 @@ def _add_versioned(schema, unversioned_pointers, pointer, key, value): if pointer in unversioned_pointers: return - types = coerce_to_list(value, 'type') + types = coerce_to_list(value, "type") # If a type is unrecognized, we might need to update this script. - if '$ref' not in value and types not in recognized_types: - warnings.warn(f'{pointer} has unrecognized type {types}') + if "$ref" not in value and types not in recognized_types: + warnings.warn(f"{pointer} has unrecognized type {types}") # For example, if $ref is used. if not types: # Ignore the `amendment` field, which had no `id` field in OCDS 1.0. - if 'deprecated' not in value: + if "deprecated" not in value: versioned_pointer = f"{value['$ref'][1:]}/properties/id" # If the `id` field is on an object not in an array, it needs to be versioned (e.g. buyer/properties/id). if versioned_pointer in unversioned_pointers: - value['$ref'] = value['$ref'] + 'VersionedId' + value["$ref"] = value["$ref"] + "VersionedId" return # Reference a common versioned definition if possible, to limit the size of the schema. ref = get_common_definition_ref(value) if ref: - schema['properties'][key] = ref + schema["properties"][key] = ref # Iterate into objects with properties like `Item.unit`. Otherwise, version objects with no properties as a # whole, like `Organization.details`. - elif types == ['object'] and 'properties' in value: + elif types == ["object"] and "properties" in value: add_versioned(value, unversioned_pointers, pointer=pointer) else: new_value = deepcopy(value) - if types == ['array']: - item_types = coerce_to_list(value['items'], 'type') + if types == ["array"]: + item_types = coerce_to_list(value["items"], "type") # See https://standard.open-contracting.org/latest/en/schema/merging/#whole-list-merge - if value.get('wholeListMerge'): + if value.get("wholeListMerge"): # Update `$ref` to the unversioned definition. - if '$ref' in value['items']: - new_value['items']['$ref'] = value['items']['$ref'] + 'Unversioned' + if "$ref" in value["items"]: + new_value["items"]["$ref"] = value["items"]["$ref"] + "Unversioned" # Otherwise, similarly, don't iterate over item properties. # See https://standard.open-contracting.org/latest/en/schema/merging/#lists - elif '$ref' in value['items']: + elif "$ref" in value["items"]: # Leave `$ref` to the versioned definition. return # Exceptional case for deprecated `Amendment.changes`. - elif item_types == ['object'] and pointer == '/definitions/Amendment/properties/changes': + elif item_types == ["object"] and pointer == "/definitions/Amendment/properties/changes": return # Warn in case new combinations are added to the release schema. - elif item_types != ['string']: + elif item_types != ["string"]: # Note: Versioning the properties of un-$ref'erenced objects in arrays isn't implemented. However, # this combination hasn't occurred, with the exception of `Amendment/changes`. warnings.warn(f"{pointer}/items has unexpected type {item_types}") versioned = deepcopy(versioned_template) - versioned['items']['properties']['value'] = new_value - schema['properties'][key] = versioned + versioned["items"]["properties"]["value"] = new_value + schema["properties"][key] = versioned def update_refs_to_unversioned_definitions(schema): """Replace ``$ref`` values with unversioned definitions.""" for key, value in schema.items(): - if key == '$ref': - schema[key] = value + 'Unversioned' + if key == "$ref": + schema[key] = value + "Unversioned" elif isinstance(value, dict): update_refs_to_unversioned_definitions(value) -def get_unversioned_pointers(schema, fields, pointer=''): +def get_unversioned_pointers(schema, fields, pointer=""): """Return the JSON Pointers to ``id`` fields that must not be versioned if the object is in an array.""" if isinstance(schema, list): for index, item in enumerate(schema): - get_unversioned_pointers(item, fields, pointer=f'{pointer}/{index}') + get_unversioned_pointers(item, fields, pointer=f"{pointer}/{index}") elif isinstance(schema, dict): # Follows the logic of _get_merge_rules in merge.py from ocds-merge. - types = coerce_to_list(schema, 'type') + types = coerce_to_list(schema, "type") # If an array is whole list merge, its items are unversioned. - if 'array' in types and schema.get('wholeListMerge'): + if "array" in types and schema.get("wholeListMerge"): return - if 'array' in types and 'items' in schema: - item_types = coerce_to_list(schema['items'], 'type') + if "array" in types and "items" in schema: + item_types = coerce_to_list(schema["items"], "type") # If an array mixes objects and non-objects, it is whole list merge. - if any(item_type != 'object' for item_type in item_types): + if any(item_type != "object" for item_type in item_types): return # If it is an array of objects, any `id` fields are unversioned. - if 'id' in schema['items']['properties']: - if hasattr(schema['items'], '__reference__'): - reference = schema['items'].__reference__['$ref'][1:] + if "id" in schema["items"]["properties"]: + if hasattr(schema["items"], "__reference__"): + reference = schema["items"].__reference__["$ref"][1:] else: reference = pointer - fields.add(f'{reference}/properties/id') + fields.add(f"{reference}/properties/id") for key, value in schema.items(): - get_unversioned_pointers(value, fields, pointer=f'{pointer}/{key}') + get_unversioned_pointers(value, fields, pointer=f"{pointer}/{key}") def remove_omit_when_merged(schema): @@ -308,12 +304,12 @@ def remove_omit_when_merged(schema): remove_omit_when_merged(item) elif isinstance(schema, dict): for key, value in schema.items(): - if key == 'properties': + if key == "properties": for prop in list(value): - if value[prop].get('omitWhenMerged'): + if value[prop].get("omitWhenMerged"): del value[prop] - if prop in schema['required']: - schema['required'].remove(prop) + if prop in schema["required"]: + schema["required"].remove(prop) remove_omit_when_merged(value) @@ -324,7 +320,7 @@ def remove_metadata_and_extended_keywords(schema): remove_metadata_and_extended_keywords(item) elif isinstance(schema, dict): for key, value in schema.items(): - if key in {'definitions', 'properties'}: + if key in {"definitions", "properties"}: for subschema in value.values(): for keyword in keywords_to_remove: subschema.pop(keyword, None) @@ -334,15 +330,17 @@ def remove_metadata_and_extended_keywords(schema): def get_versioned_release_schema(schema): """Return the versioned release schema.""" # Update schema metadata. - release_with_underscores = release.replace('.', '__') - schema['id'] = f'https://standard.open-contracting.org/schema/{release_with_underscores}/versioned-release-validation-schema.json' - schema['title'] = 'Schema for a compiled, versioned Open Contracting Release.' + release_with_underscores = release.replace(".", "__") + schema["id"] = ( + f"https://standard.open-contracting.org/schema/{release_with_underscores}/versioned-release-validation-schema.json" + ) + schema["title"] = "Schema for a compiled, versioned Open Contracting Release." # Release IDs, dates and tags appear alongside values in the versioned release schema. remove_omit_when_merged(schema) # Create unversioned copies of all definitions. - unversioned_definitions = {k + 'Unversioned': deepcopy(v) for k, v in schema['definitions'].items()} + unversioned_definitions = {k + "Unversioned": deepcopy(v) for k, v in schema["definitions"].items()} update_refs_to_unversioned_definitions(unversioned_definitions) # Determine which `id` fields occur on objects in arrays. @@ -350,17 +348,17 @@ def get_versioned_release_schema(schema): get_unversioned_pointers(jsonref.replace_refs(schema), unversioned_pointers) # Omit `ocid` from versioning. - ocid = schema['properties'].pop('ocid') + ocid = schema["properties"].pop("ocid") add_versioned(schema, unversioned_pointers) - schema['properties']['ocid'] = ocid + schema["properties"]["ocid"] = ocid # Add the common versioned definitions. for name, keywords in common_versioned_definitions.items(): versioned = deepcopy(versioned_template) for keyword, value in keywords.items(): if value: - versioned['items']['properties']['value'][keyword] = value - schema['definitions'][name] = versioned + versioned["items"]["properties"]["value"][keyword] = value + schema["definitions"][name] = versioned # Add missing definitions. while True: @@ -370,17 +368,17 @@ def get_versioned_release_schema(schema): except jsonref.JsonRefError as e: name = e.cause.args[0] - if name.endswith('VersionedId'): + if name.endswith("VersionedId"): # Add a copy of an definition with a versioned `id` field, using the same logic as before. - definition = deepcopy(schema['definitions'][name[:-11]]) - pointer = f'/definitions/{name[:-11]}/properties/id' + definition = deepcopy(schema["definitions"][name[:-11]]) + pointer = f"/definitions/{name[:-11]}/properties/id" pointers = unversioned_pointers - {pointer} - _add_versioned(definition, pointers, pointer, 'id', definition['properties']['id']) + _add_versioned(definition, pointers, pointer, "id", definition["properties"]["id"]) else: # Add a copy of an definition with no versioned fields. definition = unversioned_definitions[name] - schema['definitions'][name] = definition + schema["definitions"][name] = definition # Remove all metadata and extended keywords. remove_metadata_and_extended_keywords(schema) @@ -394,7 +392,7 @@ def cli(): @cli.command() -@click.argument('filename') +@click.argument("filename") def unused_terms(filename): """ Print terms in FILENAME that don't occur in the documentation. @@ -402,75 +400,80 @@ def unused_terms(filename): Can be used to remove unused terms from a glossary. """ paths = [] - for extension in ('csv', 'json', 'md'): - paths.extend(glob(str(basedir / 'docs' / '**' / f'*.{extension}'), recursive=True)) + for extension in ("csv", "json", "md"): + paths.extend(glob(str(basedir / "docs" / "**" / f"*.{extension}"), recursive=True)) corpus = [] for path in paths: with open(path) as f: # Replace punctuation with whitespace, except in abbreviations like "e.g.". - corpus.append(re.sub(r'(? historic_codes[code]['Valid Until']) + and (code not in historic_codes or valid_until > historic_codes[code]["Valid Until"]) ): - historic_codes[code] = {'Title': title, 'Valid Until': valid_until} + historic_codes[code] = {"Title": title, "Valid Until": valid_until} - with csv_dump('currency.csv', ['Code', 'Title', 'Valid Until']) as writer: + with csv_dump("currency.csv", ["Code", "Title", "Valid Until"]) as writer: for code in sorted(current_codes): writer.writerow([code, current_codes[code], None]) for code in sorted(historic_codes): - writer.writerow([code, historic_codes[code]['Title'], historic_codes[code]['Valid Until']]) + writer.writerow([code, historic_codes[code]["Title"], historic_codes[code]["Valid Until"]]) - release_schema = json_load('release-schema.json') + release_schema = json_load("release-schema.json") codes = sorted([*current_codes, historic_codes]) - release_schema['definitions']['Value']['properties']['currency']['enum'] = [*codes, None] + release_schema["definitions"]["Value"]["properties"]["currency"]["enum"] = [*codes, None] - json_dump('release-schema.json', release_schema) + json_dump("release-schema.json", release_schema) @cli.command() @@ -592,14 +594,14 @@ def update_language(): # https://www.iso.org/iso-639-language-codes.html # https://id.loc.gov/vocabulary/iso639-1.html - with csv_dump('language.csv', ['Code', 'Title']) as writer: - for row in csv_load('https://id.loc.gov/vocabulary/iso639-1.tsv', delimiter='\t'): + with csv_dump("language.csv", ["Code", "Title"]) as writer: + for row in csv_load("https://id.loc.gov/vocabulary/iso639-1.tsv", delimiter="\t"): # Remove parentheses, like "Greek, Modern (1453-)", and split alternatives. - titles = re.split(r' *\| *', re.sub(r' \(.+\)', '', row['Label (English)'])) + titles = re.split(r" *\| *", re.sub(r" \(.+\)", "", row["Label (English)"])) # Remove duplication like "Ndebele, North | North Ndebele" and join alternatives using a comma instead of # a pipe. To preserve order, a dict without values is used instead of a set. - titles = ', '.join({' '.join(reversed(title.split(', '))): None for title in titles}) - writer.writerow([row['code'], titles]) + titles = ", ".join({" ".join(reversed(title.split(", "))): None for title in titles}) + writer.writerow([row["code"], titles]) @cli.command() @@ -613,37 +615,37 @@ def update_media_type(): # See "Registries included below". registries = [ - 'application', - 'audio', - 'font', - 'image', - 'message', - 'model', - 'multipart', - 'text', - 'video', + "application", + "audio", + "font", + "image", + "message", + "model", + "multipart", + "text", + "video", ] - with csv_dump('mediaType.csv', ['Code', 'Title']) as writer: + with csv_dump("mediaType.csv", ["Code", "Title"]) as writer: for registry in registries: # See "Available Formats" under each heading. - for row in csv_load(f'https://www.iana.org/assignments/media-types/{registry}.csv'): - if ' ' in row['Name']: - name, message = row['Name'].split(' ', 1) + for row in csv_load(f"https://www.iana.org/assignments/media-types/{registry}.csv"): + if " " in row["Name"]: + name, message = row["Name"].split(" ", 1) else: - name, message = row['Name'], None + name, message = row["Name"], None code = f"{registry}/{name}" - template = row['Template'] + template = row["Template"] # All messages are expected to be about deprecation and obsoletion. if message: - logging.warning('%s: %s', message, code) + logging.warning("%s: %s", message, code) # "x-emf" has "image/emf" in its "Template" value (but it is deprecated). elif template and template != code: raise click.ClickException(f"expected {code}, got {template}") else: writer.writerow([code, name]) - writer.writerow(['offline/print', 'print']) + writer.writerow(["offline/print", "print"]) @cli.command() @@ -659,7 +661,7 @@ def update(ctx): @click.pass_context def check_iso_6523(ctx): def text(node, xpath): - return re.sub(r'\s+', ' ', node.xpath(xpath)[0]) + return re.sub(r"\s+", " ", node.xpath(xpath)[0]) """ Checks PEPPOL BIS Billing 3.0's ISO 6523 ICD codelist for new codes. @@ -671,52 +673,54 @@ def text(node, xpath): maximum = 213 skipped = {92, 103, 181, 182} - response = get('https://docs.peppol.eu/poacc/billing/3.0/codelist/ICD/') + response = get("https://docs.peppol.eu/poacc/billing/3.0/codelist/ICD/") - divs = lxml.html.fromstring(response.content).xpath('//dd/div[@id]') + divs = lxml.html.fromstring(response.content).xpath("//dd/div[@id]") if not divs: - raise click.ClickException('The HTML markup of the data source has changed. Please update the script.') + raise click.ClickException("The HTML markup of the data source has changed. Please update the script.") rows = [] for div in divs: - identifier = div.attrib['id'] + identifier = div.attrib["id"] number = int(identifier) if number < minimum or number > maximum or number in skipped: - name = text(div, './strong/text()') - notes = text(div, './p/text()') - issuer = '' + name = text(div, "./strong/text()") + notes = text(div, "./p/text()") + issuer = "" # "Issuing agency: " appears at the end of the paragraph. The rest of the paragraph contains either a # purpose ("Intended Purpose/App. Area") or notes ("Notes on Use of Code"), with or without the label. - notes = re.sub(r'(Notes on Use of Code|Intended Purpose/App. Area)[: ]+', '', notes) - if 'Issuing agency: ' in notes: - notes, issuer = notes.split('Issuing agency: ') + notes = re.sub(r"(Notes on Use of Code|Intended Purpose/App. Area)[: ]+", "", notes) + if "Issuing agency: " in notes: + notes, issuer = notes.split("Issuing agency: ") rows.append([identifier, name, issuer, notes]) if rows: - csv.writer(sys.stdout, delimiter='\t').writerows(rows) + csv.writer(sys.stdout, delimiter="\t").writerows(rows) else: - click.echo('No new codes found.') + click.echo("No new codes found.") def add_translation_note(path, language, domain): """Add a translation note to a file.""" - base_url = 'https://standard.open-contracting.org/1.1' + base_url = "https://standard.open-contracting.org/1.1" with open(path) as f: document = lxml.html.fromstring(f.read()) - translator = gettext.translation('theme', localedir, languages=[language]) + translator = gettext.translation("theme", localedir, languages=[language]) _ = translator.gettext - pattern = f'{base_url}/{{}}/{domain}/' + pattern = f"{base_url}/{{}}/{domain}/" response = requests.get(pattern.format(language), timeout=10) # If it's a new page, add the note to the current version of the page. if response.status_code == requests.codes.not_found: - message = _('This page was recently added to the English documentation. ' - 'It has not yet been translated.') + message = _( + 'This page was recently added to the English documentation. ' + "It has not yet been translated." + ) # If it's an existing page, add the note the last version of the page. else: @@ -724,10 +728,10 @@ def add_translation_note(path, language, domain): xpath = '//div[@itemprop="articleBody"]' replacement = lxml.html.fromstring(response.content).xpath(xpath)[0] - replacement.make_links_absolute(f'{base_url}/{language}') + replacement.make_links_absolute(f"{base_url}/{language}") # Remove any existing translation notes. - parent = replacement.xpath('//h1')[0].getparent() + parent = replacement.xpath("//h1")[0].getparent() for div in replacement.xpath('//h1/following-sibling::div[@class="admonition note"]'): parent.remove(div) @@ -736,19 +740,20 @@ def add_translation_note(path, language, domain): message = _( 'This page was recently changed in the English documentation. ' - 'The changes have not yet been translated.' + "The changes have not yet been translated." ) template = ( '

%(note)s

' - '%(message)s

' + "%(message)s

" ) - document.xpath('//h1')[0].addnext(lxml.etree.XML(template % { - 'note': _('Note'), 'message': message % {'url': pattern.format('en')}})) + document.xpath("//h1")[0].addnext( + lxml.etree.XML(template % {"note": _("Note"), "message": message % {"url": pattern.format("en")}}) + ) - with open(path, 'wb') as f: - f.write(lxml.html.tostring(document, encoding='utf-8')) + with open(path, "wb") as f: + f.write(lxml.html.tostring(document, encoding="utf-8")) @cli.command() @@ -762,11 +767,11 @@ def add_translation_notes(): https://standard.open-contracting.org/1.1/en/governance/#translation-and-localization-policy """ - excluded = ('.doctrees', '_downloads', '_images', '_sources', '_static', 'codelists', 'genindex', 'search') + excluded = (".doctrees", "_downloads", "_images", "_sources", "_static", "codelists", "genindex", "search") - for language in ('es', 'fr'): - build_dir = basedir / 'build' / language - language_dir = localedir / language / 'LC_MESSAGES' + for language in ("es", "fr"): + build_dir = basedir / "build" / language + language_dir = localedir / language / "LC_MESSAGES" for root, dirs, files in os.walk(build_dir): # Skip Sphinx directories. @@ -783,9 +788,9 @@ def add_translation_notes(): source = os.path.join(root, os.path.dirname(name)) domain = relative_path(build_dir, source) - path = language_dir / domain / 'index.po' + path = language_dir / domain / "index.po" if not path.is_file(): - path = language_dir / f'{domain}.po' + path = language_dir / f"{domain}.po" if not path.is_file(): add_translation_note(os.path.join(root, name), language, domain) continue @@ -798,5 +803,5 @@ def add_translation_notes(): break -if __name__ == '__main__': +if __name__ == "__main__": cli() From a3472ce96d4653a0b2dc0a9f14440abaae5e944a Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:09:03 -0500 Subject: [PATCH 19/47] build: Upgrade ocdsextensionregistry. Remove milestone documents extension (changing OCDS validations is prohibited). (cherry picked from commit be321ae26790f006ae622280707a664ee3688fb5) --- common-requirements.txt | 2 +- docs/conf.py | 1 - docs/history/changelog.md | 2 +- .../es/LC_MESSAGES/history/changelog.po | 6 +- .../fr/LC_MESSAGES/history/changelog.po | 3 +- docs/schema/reference.md | 4 - manage.py | 310 +----------------- tests/test_schema_integrity.py | 13 +- 8 files changed, 19 insertions(+), 322 deletions(-) diff --git a/common-requirements.txt b/common-requirements.txt index 3076effd9..c81779f3c 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -90,7 +90,7 @@ myst-parser==0.18.1 # via -r common-requirements.in ocds-babel==0.3.6 # via -r common-requirements.in -ocdsextensionregistry==0.5.0 +ocdsextensionregistry==0.6.0 # via -r common-requirements.in ocdsindex==0.2.0 # via -r common-requirements.in diff --git a/docs/conf.py b/docs/conf.py index 5522f2259..ede1ba262 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -114,7 +114,6 @@ "enquiries": default_extension_version, "location": default_extension_version, "lots": default_extension_version, - "milestone_documents": default_extension_version, "participation_fee": default_extension_version, "process_title": default_extension_version, } diff --git a/docs/history/changelog.md b/docs/history/changelog.md index de29b17fd..186e72a16 100644 --- a/docs/history/changelog.md +++ b/docs/history/changelog.md @@ -109,7 +109,7 @@ See the changelogs for: * [Enquiries](https://extensions.open-contracting.org/en/extensions/enquiries/master/#changelog) * [Location](https://extensions.open-contracting.org/en/extensions/location/master/#changelog) * [Lots](https://extensions.open-contracting.org/en/extensions/lots/master/#changelog) -* [Milestone documents](https://extensions.open-contracting.org/en/extensions/milestone_documents/master/#changelog) +* [Milestone documents](https://github.com/open-contracting-extensions/ocds_milestone_documents_extension#changelog) * [Participation fees](https://extensions.open-contracting.org/en/extensions/participation_fee/master/#changelog) * [Process level title and description](https://extensions.open-contracting.org/en/extensions/process_title/master/#changelog) diff --git a/docs/locale/es/LC_MESSAGES/history/changelog.po b/docs/locale/es/LC_MESSAGES/history/changelog.po index 30c2aa5a8..8aa20aada 100644 --- a/docs/locale/es/LC_MESSAGES/history/changelog.po +++ b/docs/locale/es/LC_MESSAGES/history/changelog.po @@ -792,11 +792,9 @@ msgstr "" #: ../../docs/history/changelog.md:111 msgid "" -"[Milestone documents](https://extensions.open-" -"contracting.org/en/extensions/milestone_documents/master/#changelog)" +"[Milestone documents](https://github.com/open-contracting-extensions/ocds_milestone_documents_extension#changelog)" msgstr "" -"[Documentos de hito](https://extensions.open-" -"contracting.org/en/extensions/milestone_documents/master/#changelog)" +"[Documentos de hito](https://github.com/open-contracting-extensions/ocds_milestone_documents_extension#changelog)" #: ../../docs/history/changelog.md:112 ../../docs/history/changelog.md:190 msgid "" diff --git a/docs/locale/fr/LC_MESSAGES/history/changelog.po b/docs/locale/fr/LC_MESSAGES/history/changelog.po index 84975e6b1..7e457580e 100644 --- a/docs/locale/fr/LC_MESSAGES/history/changelog.po +++ b/docs/locale/fr/LC_MESSAGES/history/changelog.po @@ -601,8 +601,7 @@ msgstr "" #: ../../docs/history/changelog.md:110 msgid "" -"[Milestone documents](https://extensions.open-" -"contracting.org/en/extensions/milestone_documents/master/#changelog)" +"[Milestone documents](https://github.com/open-contracting-extensions/ocds_milestone_documents_extension#changelog)" msgstr "" #: ../../docs/history/changelog.md:111 ../../docs/history/changelog.md:189 diff --git a/docs/schema/reference.md b/docs/schema/reference.md index 9d7926037..334cc758c 100644 --- a/docs/schema/reference.md +++ b/docs/schema/reference.md @@ -402,10 +402,6 @@ Notes: * The `dateModified` field should be changed whenever the progress towards a milestone is reviewed, and the `status` either updated, or re-confirmed. -```{extensionlist} The following extensions to milestone are available -:list: milestones -``` - ### Value Financial values should be published with a currency attached. diff --git a/manage.py b/manage.py index 6a31fc1e1..2cd49e3a9 100755 --- a/manage.py +++ b/manage.py @@ -8,7 +8,6 @@ import sys import warnings from contextlib import contextmanager -from copy import deepcopy from glob import glob from io import StringIO from pathlib import Path @@ -22,6 +21,7 @@ from babel.messages.pofile import read_po from docutils.utils import relative_path from lxml import etree +from ocdsextensionregistry import get_versioned_release_schema basedir = Path(__file__).resolve().parent schemadir = basedir / "schema" @@ -38,77 +38,6 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): warnings.formatwarning = custom_warning_formatter -versioned_template = json.loads(""" -{ - "type": "array", - "items": { - "type": "object", - "properties": { - "releaseDate": { - "format": "date-time", - "type": "string" - }, - "releaseID": { - "type": "string" - }, - "value": {}, - "releaseTag": { - "type": "array", - "items": { - "type": "string" - } - } - } - } -} -""") - -common_versioned_definitions = { - "StringNullUriVersioned": { - "type": ["string", "null"], - "format": "uri", - }, - "StringNullDateTimeVersioned": { - "type": ["string", "null"], - "format": "date-time", - }, - "StringNullVersioned": { - "type": ["string", "null"], - "format": None, - }, -} - -recognized_types = ( - # Array - ["array"], - ["array", "null"], # optional string arrays - # Object - ["object"], - ["object", "null"], # /Organization/details - # String - ["string"], - ["string", "null"], - # Literal - ["boolean", "null"], - ["integer", "null"], - ["number", "null"], - # Mixed - ["string", "integer"], - ["string", "integer", "null"], -) - -keywords_to_remove = ( - # Metadata keywords - # https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6 - "title", - "description", - "default", - # Extended keywords - # http://os4d.opendataservices.coop/development/schema/#extended-json-schema - "omitWhenMerged", - "wholeListMerge", -) - def json_load(filename, library=json, **kwargs): """Load JSON data from the given filename.""" @@ -147,14 +76,6 @@ def get(url): return response -def coerce_to_list(data, key): - """Return the value of the ``key`` key in the ``data`` mapping. If the value is a string, wrap it in an array.""" - item = data.get(key, []) - if isinstance(item, str): - return [item] - return item - - def get_metaschema(): """Patches and returns the JSON Schema Draft 4 metaschema.""" return json_merge_patch.merge( @@ -162,230 +83,6 @@ def get_metaschema(): ) -def get_common_definition_ref(item): - """ - Return a schema that references the common definition that the ``item`` matches: "StringNullUriVersioned", - "StringNullDateTimeVersioned" or "StringNullVersioned". - """ - for name, keywords in common_versioned_definitions.items(): - # If the item matches the definition. - if any(item.get(keyword) != value for keyword, value in keywords.items()): - continue - # And adds no keywords to the definition. - if any(keyword not in {*keywords, *keywords_to_remove} for keyword in item): - continue - return {"$ref": f"#/definitions/{name}"} - return None - - -def add_versioned(schema, unversioned_pointers, pointer=""): - """Call ``_add_versioned`` on each field.""" - for key, value in schema["properties"].items(): - new_pointer = f"{pointer}/properties/{key}" - _add_versioned(schema, unversioned_pointers, new_pointer, key, value) - - for key, value in schema.get("definitions", {}).items(): - new_pointer = f"{pointer}/definitions/{key}" - add_versioned(value, unversioned_pointers, pointer=new_pointer) - - -def _add_versioned(schema, unversioned_pointers, pointer, key, value): - """ - Perform the changes to the schema to refer to versioned/unversioned definitions. - - :param schema dict: the schema of the object on which the field is defined - :param unversioned_pointers set: JSON Pointers to ``id`` fields to leave unversioned if the object is in an array - :param pointer str: the field's pointer - :param key str: the field's name - :param value str: the field's schema - """ - # Skip unversioned fields. - if pointer in unversioned_pointers: - return - - types = coerce_to_list(value, "type") - - # If a type is unrecognized, we might need to update this script. - if "$ref" not in value and types not in recognized_types: - warnings.warn(f"{pointer} has unrecognized type {types}") - - # For example, if $ref is used. - if not types: - # Ignore the `amendment` field, which had no `id` field in OCDS 1.0. - if "deprecated" not in value: - versioned_pointer = f"{value['$ref'][1:]}/properties/id" - # If the `id` field is on an object not in an array, it needs to be versioned (e.g. buyer/properties/id). - if versioned_pointer in unversioned_pointers: - value["$ref"] = value["$ref"] + "VersionedId" - return - - # Reference a common versioned definition if possible, to limit the size of the schema. - ref = get_common_definition_ref(value) - if ref: - schema["properties"][key] = ref - - # Iterate into objects with properties like `Item.unit`. Otherwise, version objects with no properties as a - # whole, like `Organization.details`. - elif types == ["object"] and "properties" in value: - add_versioned(value, unversioned_pointers, pointer=pointer) - - else: - new_value = deepcopy(value) - - if types == ["array"]: - item_types = coerce_to_list(value["items"], "type") - - # See https://standard.open-contracting.org/latest/en/schema/merging/#whole-list-merge - if value.get("wholeListMerge"): - # Update `$ref` to the unversioned definition. - if "$ref" in value["items"]: - new_value["items"]["$ref"] = value["items"]["$ref"] + "Unversioned" - # Otherwise, similarly, don't iterate over item properties. - # See https://standard.open-contracting.org/latest/en/schema/merging/#lists - elif "$ref" in value["items"]: - # Leave `$ref` to the versioned definition. - return - # Exceptional case for deprecated `Amendment.changes`. - elif item_types == ["object"] and pointer == "/definitions/Amendment/properties/changes": - return - # Warn in case new combinations are added to the release schema. - elif item_types != ["string"]: - # Note: Versioning the properties of un-$ref'erenced objects in arrays isn't implemented. However, - # this combination hasn't occurred, with the exception of `Amendment/changes`. - warnings.warn(f"{pointer}/items has unexpected type {item_types}") - - versioned = deepcopy(versioned_template) - versioned["items"]["properties"]["value"] = new_value - schema["properties"][key] = versioned - - -def update_refs_to_unversioned_definitions(schema): - """Replace ``$ref`` values with unversioned definitions.""" - for key, value in schema.items(): - if key == "$ref": - schema[key] = value + "Unversioned" - elif isinstance(value, dict): - update_refs_to_unversioned_definitions(value) - - -def get_unversioned_pointers(schema, fields, pointer=""): - """Return the JSON Pointers to ``id`` fields that must not be versioned if the object is in an array.""" - if isinstance(schema, list): - for index, item in enumerate(schema): - get_unversioned_pointers(item, fields, pointer=f"{pointer}/{index}") - elif isinstance(schema, dict): - # Follows the logic of _get_merge_rules in merge.py from ocds-merge. - types = coerce_to_list(schema, "type") - - # If an array is whole list merge, its items are unversioned. - if "array" in types and schema.get("wholeListMerge"): - return - if "array" in types and "items" in schema: - item_types = coerce_to_list(schema["items"], "type") - # If an array mixes objects and non-objects, it is whole list merge. - if any(item_type != "object" for item_type in item_types): - return - # If it is an array of objects, any `id` fields are unversioned. - if "id" in schema["items"]["properties"]: - if hasattr(schema["items"], "__reference__"): - reference = schema["items"].__reference__["$ref"][1:] - else: - reference = pointer - fields.add(f"{reference}/properties/id") - - for key, value in schema.items(): - get_unversioned_pointers(value, fields, pointer=f"{pointer}/{key}") - - -def remove_omit_when_merged(schema): - """Remove properties that set ``omitWhenMerged``.""" - if isinstance(schema, list): - for item in schema: - remove_omit_when_merged(item) - elif isinstance(schema, dict): - for key, value in schema.items(): - if key == "properties": - for prop in list(value): - if value[prop].get("omitWhenMerged"): - del value[prop] - if prop in schema["required"]: - schema["required"].remove(prop) - remove_omit_when_merged(value) - - -def remove_metadata_and_extended_keywords(schema): - """Remove metadata and extended keywords from properties and definitions.""" - if isinstance(schema, list): - for item in schema: - remove_metadata_and_extended_keywords(item) - elif isinstance(schema, dict): - for key, value in schema.items(): - if key in {"definitions", "properties"}: - for subschema in value.values(): - for keyword in keywords_to_remove: - subschema.pop(keyword, None) - remove_metadata_and_extended_keywords(value) - - -def get_versioned_release_schema(schema): - """Return the versioned release schema.""" - # Update schema metadata. - release_with_underscores = release.replace(".", "__") - schema["id"] = ( - f"https://standard.open-contracting.org/schema/{release_with_underscores}/versioned-release-validation-schema.json" - ) - schema["title"] = "Schema for a compiled, versioned Open Contracting Release." - - # Release IDs, dates and tags appear alongside values in the versioned release schema. - remove_omit_when_merged(schema) - - # Create unversioned copies of all definitions. - unversioned_definitions = {k + "Unversioned": deepcopy(v) for k, v in schema["definitions"].items()} - update_refs_to_unversioned_definitions(unversioned_definitions) - - # Determine which `id` fields occur on objects in arrays. - unversioned_pointers = set() - get_unversioned_pointers(jsonref.replace_refs(schema), unversioned_pointers) - - # Omit `ocid` from versioning. - ocid = schema["properties"].pop("ocid") - add_versioned(schema, unversioned_pointers) - schema["properties"]["ocid"] = ocid - - # Add the common versioned definitions. - for name, keywords in common_versioned_definitions.items(): - versioned = deepcopy(versioned_template) - for keyword, value in keywords.items(): - if value: - versioned["items"]["properties"]["value"][keyword] = value - schema["definitions"][name] = versioned - - # Add missing definitions. - while True: - try: - jsonref.replace_refs(schema, lazy_load=False) - break - except jsonref.JsonRefError as e: - name = e.cause.args[0] - - if name.endswith("VersionedId"): - # Add a copy of an definition with a versioned `id` field, using the same logic as before. - definition = deepcopy(schema["definitions"][name[:-11]]) - pointer = f"/definitions/{name[:-11]}/properties/id" - pointers = unversioned_pointers - {pointer} - _add_versioned(definition, pointers, pointer, "id", definition["properties"]["id"]) - else: - # Add a copy of an definition with no versioned fields. - definition = unversioned_definitions[name] - - schema["definitions"][name] = definition - - # Remove all metadata and extended keywords. - remove_metadata_and_extended_keywords(schema) - - return schema - - @click.group() def cli(): pass @@ -494,7 +191,10 @@ def pre_commit(): json_dump("meta-schema.json", get_metaschema()) json_dump("dereferenced-release-schema.json", jsonref_release_schema) - json_dump("versioned-release-validation-schema.json", get_versioned_release_schema(release_schema)) + json_dump( + "versioned-release-validation-schema.json", + get_versioned_release_schema(release_schema, release.replace(".", "__")), + ) @cli.command() diff --git a/tests/test_schema_integrity.py b/tests/test_schema_integrity.py index c6fdb0f86..789a27688 100644 --- a/tests/test_schema_integrity.py +++ b/tests/test_schema_integrity.py @@ -3,14 +3,19 @@ """ import json -import os.path import sys +from pathlib import Path import jsonref +from ocdsextensionregistry import get_versioned_release_schema -sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +basedir = Path(__file__).resolve().parent.parent -from manage import get_metaschema, get_versioned_release_schema +sys.path.extend([str(basedir), str(basedir / "docs")]) + +from conf import release + +from manage import get_metaschema def test_versioned_release_schema_is_in_sync(): @@ -18,7 +23,7 @@ def test_versioned_release_schema_is_in_sync(): actual = json.load(f) with open("schema/release-schema.json") as f: - expected = get_versioned_release_schema(json.load(f)) + expected = get_versioned_release_schema(json.load(f), release.replace(".", "__")) assert actual == expected, "Run: python manage.py pre-commit" From 3869581ef6bb3cc1e82b21e3032ecfe4079d80b6 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:12:24 -0500 Subject: [PATCH 20/47] build: Upgrade sphinxcontrib-opendataservices-jsonschema (cherry picked from commit 39a7163f781ca15e8d555c78365c72ac9a976131) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9c2ea5690..1c7a3ab40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,5 @@ ocdskit==1.1.3 sphinx-design==0.4.1 sphinxcontrib-opencontracting==0.0.8 -sphinxcontrib-opendataservices-jsonschema==0.6.1 +sphinxcontrib-opendataservices-jsonschema==0.7.1 sphinxcontrib-opendataservices==0.5.0 From 80c2efa31bd4dc68752aae96ecb1108fc5add2a6 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:14:20 -0500 Subject: [PATCH 21/47] chore: Add noqa for local imports (cherry picked from commit 311c9d2fa22f15ce68c964704e4959170a23876d) --- tests/test_schema_integrity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_schema_integrity.py b/tests/test_schema_integrity.py index 789a27688..fdbab6b76 100644 --- a/tests/test_schema_integrity.py +++ b/tests/test_schema_integrity.py @@ -13,9 +13,9 @@ sys.path.extend([str(basedir), str(basedir / "docs")]) -from conf import release +from conf import release # noqa: E402 -from manage import get_metaschema +from manage import get_metaschema # noqa: E402 def test_versioned_release_schema_is_in_sync(): From 51170f85c245e305675fb56c50957140d7def92c Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:19:25 -0500 Subject: [PATCH 22/47] build: Upgrade ocdsextensionregistry (cherry picked from commit 561a0520508107c3d230ec4c5c206559d281be4d) --- common-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common-requirements.txt b/common-requirements.txt index c81779f3c..fb836f0ad 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -90,7 +90,7 @@ myst-parser==0.18.1 # via -r common-requirements.in ocds-babel==0.3.6 # via -r common-requirements.in -ocdsextensionregistry==0.6.0 +ocdsextensionregistry==0.6.1 # via -r common-requirements.in ocdsindex==0.2.0 # via -r common-requirements.in @@ -158,7 +158,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@c3cbd1b8ba6db24624e5d838ed18147db63f5d1b#egg=standard_theme +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@5de343d1d8e342b5f2a42c6132db37aebe382e36#egg=standard_theme # via -r common-requirements.in starlette==0.40.0 # via sphinx-autobuild From a68bf426c99a4dc70f23006192bde09f6d47c49e Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Sun, 15 Dec 2024 02:10:15 -0500 Subject: [PATCH 23/47] build: Upgrade code for latest ocdskit and ocdsextensionregistry (cherry picked from commit d2f4a956de5a07398843431a1345a4d8a69f3792) --- .github/workflows/lint.yml | 2 ++ common-requirements.txt | 2 +- manage.py | 7 ++++--- requirements.txt | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2f31fb710..600ad9d5a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,5 +5,7 @@ jobs: uses: open-contracting/.github/.github/workflows/lint.yml@main permissions: contents: write + secrets: + personal-access-token: ${{ secrets.PAT }} with: python-version: '3.10' diff --git a/common-requirements.txt b/common-requirements.txt index fb836f0ad..3ca01e41a 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -90,7 +90,7 @@ myst-parser==0.18.1 # via -r common-requirements.in ocds-babel==0.3.6 # via -r common-requirements.in -ocdsextensionregistry==0.6.1 +ocdsextensionregistry==0.6.5 # via -r common-requirements.in ocdsindex==0.2.0 # via -r common-requirements.in diff --git a/manage.py b/manage.py index 2cd49e3a9..f0e6de6b5 100755 --- a/manage.py +++ b/manage.py @@ -22,6 +22,7 @@ from docutils.utils import relative_path from lxml import etree from ocdsextensionregistry import get_versioned_release_schema +from ocdsextensionregistry.util import replace_refs basedir = Path(__file__).resolve().parent schemadir = basedir / "schema" @@ -39,10 +40,10 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): warnings.formatwarning = custom_warning_formatter -def json_load(filename, library=json, **kwargs): +def json_load(filename): """Load JSON data from the given filename.""" with (schemadir / filename).open() as f: - return library.load(f, **kwargs) + return json.load(f) def json_dump(filename, data): @@ -187,7 +188,7 @@ def pre_commit(): - versioned-release-validation-schema.json """ release_schema = json_load("release-schema.json") - jsonref_release_schema = json_load("release-schema.json", jsonref, merge_props=True) + jsonref_release_schema = replace_refs(json_load("release-schema.json"), keep_defs=True) json_dump("meta-schema.json", get_metaschema()) json_dump("dereferenced-release-schema.json", jsonref_release_schema) diff --git a/requirements.txt b/requirements.txt index 1c7a3ab40..33d3d98dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # Add your own requirements below. -ocdskit==1.1.3 +ocdskit==1.3.0 sphinx-design==0.4.1 sphinxcontrib-opencontracting==0.0.8 sphinxcontrib-opendataservices-jsonschema==0.7.1 From 2f528df253d6ad795ea3ce5a08acd2e18ae4d630 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Sun, 15 Dec 2024 02:20:13 -0500 Subject: [PATCH 24/47] chore: Run ruff check . (cherry picked from commit 08c8ad03a471335203255b3d439823fb3f00f125) --- manage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/manage.py b/manage.py index f0e6de6b5..b361e3e13 100755 --- a/manage.py +++ b/manage.py @@ -14,7 +14,6 @@ import click import json_merge_patch -import jsonref import lxml.etree import lxml.html import requests From 5a7fa46d263f1d1d8a3a97803a7970da7d1210a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:40:03 +0000 Subject: [PATCH 25/47] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.8.6) - [github.com/astral-sh/uv-pre-commit: 0.4.18 → 0.5.14](https://github.com/astral-sh/uv-pre-commit/compare/0.4.18...0.5.14) (cherry picked from commit 4a0a66158562c7d3ce5c799b8d295c1341d830cf) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c81d5c10..6286a0316 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,12 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 + rev: v0.8.6 hooks: - id: ruff - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.4.18 + rev: 0.5.14 hooks: - id: pip-compile name: pip-compile common-requirements.in From 4c57cb3e4af9fbbf3eaaf8a34ff34a0d802809ad Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:52:48 -0500 Subject: [PATCH 26/47] ui: Update logo (cherry picked from commit 05e9626a048ef7255208495bf4a68a451908b55c) --- common-requirements.txt | 4 ++-- docs/_static/basic.css | 9 +++++++++ docs/_static/favicon-16x16.ico | Bin 1406 -> 93062 bytes docs/_templates/layout.html | 14 +++++--------- include/common.mk | 1 - include/header.html | 12 ------------ script/update | 2 +- 7 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 include/header.html diff --git a/common-requirements.txt b/common-requirements.txt index 3ca01e41a..2d408c71d 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -64,7 +64,7 @@ imagesize==1.4.1 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.1.4 +jinja2==3.1.5 # via # myst-parser # sphinx @@ -158,7 +158,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@5de343d1d8e342b5f2a42c6132db37aebe382e36#egg=standard_theme +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@d56505d69fceeb43535a3387172b2024eead536e#egg=standard_theme # via -r common-requirements.in starlette==0.40.0 # via sphinx-autobuild diff --git a/docs/_static/basic.css b/docs/_static/basic.css index 86fdf2087..355bdc65a 100644 --- a/docs/_static/basic.css +++ b/docs/_static/basic.css @@ -1,3 +1,12 @@ +.wy-side-nav-search { + margin: 0; + padding: 0; +} +.wy-side-nav-search > a, .wy-side-nav-search .wy-dropdown > a { + margin: 0; + padding: 0 6px; +} + #build-your-data-pipeline li { margin-bottom: 1em; } diff --git a/docs/_static/favicon-16x16.ico b/docs/_static/favicon-16x16.ico index 40159444df4205bf4b951f36bf7677998df5f7ba..b0139d067154e59e1f327fccf005e2c21e7ae3e1 100644 GIT binary patch literal 93062 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 literal 1406 zcmeH{-A|HX7{-4H6n=$Tni`2f0z!tpydqc$scA-l#jCcJ9eZN6va+)_w>oOM)ojDQ z*E*?;YT`$!o2GNknsZ`SYW1J=(7C5=ZQjp*&+}Z@!M)wr!3`>La&i#2j!rEU0Cs_} zC>5mqjmj%Yc-?NQWf^Z(71cf;9)B&KfFG%$9#4vFgOn2bQi*D$jROdEp$cE0-wkxrpm(H^yt-xOy&A(R-bWo4pj?x`Fe~ zZJc-eF!%T2i1p(bxQF9m4Ev)Pna>AMy}Xa=)dN)V$ATxMy?KiI-80e?FR%^8DI9){ zZgdb^B98j~AlBix=spaRIUc8IVhHQ_2%4!OK?3W9_)jO$P7kB`I70E%7@C<;a%aaV zo0%lz(u)2hKHA%@@5~IMhwoJ+TGRF0< zn0~Hc`t=Rd@9!JMz3Fcz@V_UJSFA~mpefw`cM^SF%O0^`be2$Et}wE^iV~G_^scC) z9LOfqrHG1F`%{l1Wt9dMr8RQ6;!x8`p@SxEfgC#KDR=k+4aYL1r1;qi%H diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 971c158b6..c68368339 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -4,19 +4,15 @@ {% block mobile_nav %} -{% trans %} Open Contracting {% endtrans %} {% trans %} Data Standard {% endtrans %} v{{ version }} +{% trans %}Open Contracting Data Standard{% endtrans %} v{{ version }} {% endblock %} {% block sidebartitle %} - {% endblock %} diff --git a/include/common.mk b/include/common.mk index 424e82269..63cb5b5cb 100644 --- a/include/common.mk +++ b/include/common.mk @@ -159,7 +159,6 @@ $(LANGUAGES:.%=pdf.%): pdf.%: FORCE --javascript-delay $(PDF_DELAY) \ --disable-smart-shrinking \ --print-media-type \ - --header-html include/header.html \ toc https://standard.open-contracting.org$(PDF_ROOT)/$*/$(PDF_PAGES) $*.pdf .PHONY: pdf diff --git a/include/header.html b/include/header.html deleted file mode 100644 index 948eff988..000000000 --- a/include/header.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - ocp-logo-only - - - - - - diff --git a/script/update b/script/update index bd2170e08..a1ec03c34 100755 --- a/script/update +++ b/script/update @@ -5,7 +5,7 @@ set -eu main() { mkdir -p script include tests - for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk include/header.html script/diff script/update tests/conftest.py tests/test_common.py; do + for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py; do curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/$f done From eef77e715b76be0dda7f6fe28e5662dc029319f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 16:49:44 -0400 Subject: [PATCH 27/47] [pre-commit.ci] pre-commit autoupdate (#1735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.6...v0.11.4) - [github.com/astral-sh/uv-pre-commit: 0.5.14 → 0.6.12](https://github.com/astral-sh/uv-pre-commit/compare/0.5.14...0.6.12) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore: Fix LOG015. build: Upgrade jinja. * chore: Fix RUF100 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: James McKinney <26463+jpmckinney@users.noreply.github.com> (cherry picked from commit 817760814adb4bda908342ae0979cd2bd78e659b) --- .pre-commit-config.yaml | 4 ++-- common-requirements.txt | 2 +- docs/_static/favicon-16x16.ico | Bin 93062 -> 1406 bytes manage.py | 7 ++++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6286a0316..39ef46bc2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,12 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 + rev: v0.11.4 hooks: - id: ruff - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.5.14 + rev: 0.6.12 hooks: - id: pip-compile name: pip-compile common-requirements.in diff --git a/common-requirements.txt b/common-requirements.txt index 2d408c71d..62a2917b5 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -64,7 +64,7 @@ imagesize==1.4.1 # via sphinx iniconfig==1.1.1 # via pytest -jinja2==3.1.5 +jinja2==3.1.6 # via # myst-parser # sphinx diff --git a/docs/_static/favicon-16x16.ico b/docs/_static/favicon-16x16.ico index b0139d067154e59e1f327fccf005e2c21e7ae3e1..40159444df4205bf4b951f36bf7677998df5f7ba 100644 GIT binary patch literal 1406 zcmeH{-A|HX7{-4H6n=$Tni`2f0z!tpydqc$scA-l#jCcJ9eZN6va+)_w>oOM)ojDQ z*E*?;YT`$!o2GNknsZ`SYW1J=(7C5=ZQjp*&+}Z@!M)wr!3`>La&i#2j!rEU0Cs_} zC>5mqjmj%Yc-?NQWf^Z(71cf;9)B&KfFG%$9#4vFgOn2bQi*D$jROdEp$cE0-wkxrpm(H^yt-xOy&A(R-bWo4pj?x`Fe~ zZJc-eF!%T2i1p(bxQF9m4Ev)Pna>AMy}Xa=)dN)V$ATxMy?KiI-80e?FR%^8DI9){ zZgdb^B98j~AlBix=spaRIUc8IVhHQ_2%4!OK?3W9_)jO$P7kB`I70E%7@C<;a%aaV zo0%lz(u)2hKHA%@@5~IMhwoJ+TGRF0< zn0~Hc`t=Rd@9!JMz3Fcz@V_UJSFA~mpefw`cM^SF%O0^`be2$Et}wE^iV~G_^scC) z9LOfqrHG1F`%{l1Wt9dMr8RQ6;!x8`p@SxEfgC#KDR=k+4aYL1r1;qi%H literal 93062 zcmeI5_up(;S>@Y*Vt$?<=|JanJKpZTw;MBzN)iPXBnO2d3IZ~sNRx9EB#59OqP7V~ z1w>TBj59h0^gpTje%Id5u3e{2z4yHx=%#&}&!Os8)mveowb!$r^*no@3oiH`{`anT zUBLhU*9%_o?C-hYzg%#^1&`q1p$jhgp7uC*FL=w7`N!hFi~jjY@={_|&gfBC)q_rviVJY0X%+WA|aWBxV0*>UXVS%g5I66^tFg*&6ot9$Udtea@lpQQ!0A_Pdj_ z51rTL3-Q!~ZedO^^Jb$iZ8gPT0x!P11G;OEPmOa6C1{@0f_m+TyWwYlV5i{pRs@A)wf*-liPh#h93$<6-{_a{*k;-uqMvnxjl@H7+Lr-E-qu@{v2B8uM=Dys|L2F*Jmsh zesbISt>$RQ(nS^T)HiZdVaeELJo9|dI1)qRN8Ehr4>yckd*j)ZU zJ$|npf6i?k^%&1z#_N22{*J%Dl=lhV3TJYf{0D{y#%2zS#-Fv<5@Qo~j*5YfNsYzH z?fzH{Y<1k!zsFdW?roeTFI`l;WNxOW`QKeHsd>awIH~hd;pj`?NDTc27!p5!{`t-2 zfA+hZ%m4Iqn=Ah0vzsga*yDFLSNzd$A3l7Z<8of_-(CJi{vPaICfMOzSiTKd?XkBr8p8pa1R)|LJSpl{_r!KEC1lPHdp@sr#4r9;Wsx|{vOBY zKe@Te<99!?cU<}TPvrT$&e!vISNy@JH&@*Anc#4XwaT#Kv0X7!%o~&8 z)9C)rW#$zvr!1WL`XFj^Q`qLlTT>V?WzPb8SA8yBQerR(w$2GtC z!Obp%06%~iksQQqfco2x$e@y(UL`^kpQ-~X-66@Mt+;5|6- z@;~jI)^TS1jXqP`ea#$Ti8J%c8DrpV&MA|hEXVIZ*}vB~+{bo!ykjb3H*r()xE|l& zq^6U=k>|ttuRfBG3P;8=V_0EG-Vr~)4Sotc;^h+`*j)3m_iwKGjrVP?{pfo(*Z%su zH`jjnUG4bLJ2%&T@ExniwLE?;&tLmtj$h~XA9?rY8vfq*x#r{V-&`FGf;%vG6?koU zljGzzu?DXhL&ez_;m?6H^MD>RgE4fOJY1U0c_mlN*=m6DTIVrb=G8Us^J^Y1_v^PE zx~<>I{A+#_)-Ll&8fHl+UC^T-mp3KR*zp@ zIe47sujh5XK7YsGU&s4g_ujW|u6zI6HrIYY?14G3dJUg@&Bs3g-Wt}f{>*PQoP+l* z&fqgOp8Cv~DeoFD)&1s{g)cQ^eAffmIyA;UUgP|5xSfAz^1CUIuIKVQJ6BEm>5^#h zp35=5m;McNIoyQaHonPAY9nJB@h5!rDdLhil7r-*4}u}^bG`TxH)2M-y!lr)H@xYW zH#fZTe{62}wbyNK_|;$9-1sZ6-Q4*4*KBV5E;DAD z2dK-$mwb#?l%t~`_1(|{(U<0v@|N+?F)XecxX8|Bk=EiTAnb)i39Le_?YY?;l)((_rq6zZ@)|dK(<~PPkE=$!TKW z_$kJS(a~qLp#PBle^TG%(=o=^5TIl#&kK(16MrvaKqJ@Lkqq-h^Zd; z%E&u%m zn_FM_{LQUD|GdquKlj|ttuJ`a=JfNQy*d57XSL(G&)l3obUcsec-^hM-oMM=^FFt{ z==r?w&uwmb$4F3|G2ge0xcf)D$W;j%g!K=pUY z=C-H(^yc=b{?z97pYeFg=JucVc=G=7Gd$+=PkqYfHeT=Foa~fI6nP6xJ_;pW5&@}zO=<+wHo-6XQRc)Da=pxH1t5!kIt#f|Lk+|;Nt6} zHJy9WTxf>WAkkZUF|o$J&c(y|Hh3cUQ=^mHB>^VlEToc*yM-JJc= zAK9G!5s&}2Is4x{9y1+}=DtUN>_@oubwBFyBbzh)z3+48agWXW26JNdC!f@C1_uUX z&je%g$ZMh_K$h_7$bp_t7+A?vGdO_kM+S9q{oJ_qT zKE&ifEIs>&8?Mxq#ZUi;e?05+ihKH+;VAq}y#qN)@5#8Q-;}tACMUl-^%nBEaMY_3 z?>s-ZqL&(;p1H*naV(s~IS!UM?`I#|F(gJF16CgW=*^u!{KFhSw7K(#IDYU4x5p3s zz~;_6evtd);59$g5B~1#qklN>bM`TXH?Stoz^?okPL#`ttuwx&%U%qBssGV{#&G9K za>@6+Iddp+5x-=YyPBq#yY%sP;-X$^@YVUNe6D^DzH8!Pac$ksb7Buqa#ZgzV2-68 zY(GJ1b9EHCkoA=0diqc34ZWc2-iud&PjJKCXmawb!BJ>%Yc{(&C(=hRVn2YA=<4TuTX>lx@dG2Uukb1ZeR=ffQCHqMXd$@7(Xhu#_d6g&~H z;HJh&ocqGkjG;$?qenezbJtyU+{OI^$D?@M*Lr-fhd3*&b&SDhXW_wcA~AD|uf|`| zVa7-FzIlXsr8%npg?@!Th2EOneEqxbZhkA?zJ9(wzFgjP7u>bf^xtScePW|NYU%pI z)e=*6{_4Cvn78#^>Q*nI?)$xFOsS)+$H-Ar+%tbxFId-ze_*YST<;pZLw#%W@ZiZj z4{!3OUwm1{JsyD?%Dj&AVh(o)dRabb+KG6lW>N!*C*oA%)()OJjs}*%%AIxGxw-4? z;<%H4&&OT-yKq-H6JNnt=P~2v8h<_hu_Hd-{!{4ur;<~eOQOAujkgdR_0n#7O^=P@ z3-AB>cfSd*_Z|4Y?@cWs+|_lLbMsPqHR5%ZrdM}`yE?W8uBLNU=d5lYOicON^*xr} zl#ds#x|SN4O1{^9PPAw9q=nH)a0B8eSsZ3sh`&H zHCFvkhxAw!FWCui~%;cM%`Y9>SM7rTU&6HJ$~zM(~9%vWG9U zms+57eYne<5l?R?F6wp7aWR@};dAYrw+Uwt8C*?Xryf(z?o;pF_#eRzT5&HD;x+-6s zya?;o#zQ&FbF_o2e%^``9!zZc9rFEFaAgdXtJLWDD~Wwok3kCzpL2;R{it7~=0ePi zZ-Cd+@YMCwjvmJ+&?C?vAh#Q_ZeWS?sJ2%3M1yxOS;ds`ugAG1mg@1NZfDLKv9Wz# zu(hJU&JGT1{>2&n3i69P@XO3M;)SX23t#dV+FmVSJ<2+yxv{>Bb?vR@vIeA&+OcI! zM9(H3qH!-WKdgAO^r|1Kxa!~QV*Ake4b7YC{<$?iV_)~qucAM0ho^$6*EL;^wumNw z4xWMfNjwoN;#rPZ2bM~UcP!mC$2s-17#c_Cr#kP0r^M9q=omW+OFkDo)$QW_>vKAP z-O2YDM|7B&ZyG-s)34C?AZI;8u5m^VGe+Xw#@jbOlB0yXUiH#=5=UZ7-m-RWZPD6K zJT5t_*Q$4T;UO3Y!&&`%Rc%Jk)B3SGo!{hd3#RDTHHQhN@cYzL_Bk2*;&Hx?T(2;t zCfC!{FVM>rPjb`1l6l?tZ-OQHDEOJrZJqDw_FOpmjy-?Z=1%Z-C%NaHv6?D)te`W+N&oP#vUp?599lQ zt68g?{~7<#RC)=?`>Y#-Df8p-l$iPum?DR^XT?4PYr|@()AAIUlB423sm1Yw;uFYC zct+9R150;q9t9>Ib@ptIySP2~0p6A|Z|&dbi?yRZ`wZV}K74K7zs-m6nOvqX=I<0= z#P~CKxz4S^mi2CH+bt%(COLETR$?O9(uc7AL!I7Q*ri{luBbPU|FDgRQ;*Gqsi)5M z+2t$r_@}0Fy@36;@e=eqgDJczHM%?nrcQ||eCC^(e{y{4d3sUulyy2eN>5zRQ9Knl z8DAzWJ?hMvO^!Rbz2lDDzl+D#kHz_&&vo5D3LocsZtZjVUU#h>^XC=DoG-x_aZ;U6 z4M{C&Ez(-J^{Mz>>Mgvk8}NzsW1_d{jW7;0Y?&v1HvKlK`_P9Pt!01U+`sjmuJ^=0 z(^Kfj8n(pK0#oUovL^u_-5$vJQ!}QL_gOP;n3B_Pc?lX_j}x8hyiDv1PjyV0&pF-) z@dTE{)8e>`e{YWi=XQC%*Klz@kB#@6Kb!lvuN?K-ynp9e@#XJCBgW4Uw%{$afF7Ox z?`irm;&pXwS%Zk)dZ!*&*YecB>`ApQ79GE%wZxXb)?81V{CLmx9FOY$8V1hiu{}?A zfi1Aqyw9Hd$x};BMeb)0k#!l@7lfz8)H|rh*lTC(n=xgb*qWa87jr%HJpBTBYQmH; zZtxT5V+Tu|yGPB(4z3pGdF8q7d-=Z0V~g7@R?q#Ot9UAmtWV0;y>U+r=GZ1t40r3@2TfCIGMD3VJf%a z;Cyg2zjhB(^%?y=XO7ZOm@zeLDsn&9P+2>UMi*1o`0$T!Qls0`@?yLxv}k${=xws+ zM6DXH9DNZ#)7aN}D!Pf-aS&IWryX3?`8(ov`TBGI+s~+D!tfS9x5G_!tZ-C)mtaa< zE^)=bOM5Y&;+LSe%vsD8<3$v(IIyJ$fxl(V-rATw&ejv7wOr3QW2@JC z2DZe)4wmZt_uCmO=X3u!xN_Z$d0%0w`&5_W^STcG@;@j3@ms7l*aw;Zmfx7AOV(A#K-D0UQ6wpy^qL-UBlL$!R@TEP}NKD^apqP2%xsb8nQ$XpG!@V8tSVol5*BXztr zSbLi<{YvxWUB76~p7mrsCh8Fle@ko)k88r#`CzGDJ7X%ItnXd#DPO6l)-jcyJbfxW zxhwwUbNMa)CVPeL>$Ddkbz?Af>kH{YupXmES5Kiy<*CAyzI5^;eW;nM3S0ASv@hpq zK0kZsP+mCBv5bBD|32RP9Qv%q_blDMyv^?eS9)v3S=2XE4-#8D9t2w6`Ze{%U`yW8 zJEf**KZNTvj2Xs`^v#!N3I}BUwf;k|^)$}ve09i^7jK1^Sj!IV9mPowA8p3NU8?^93N$6_y2*HrO!tm%fU zCcc_6wRV5T*E#R^+;HuEj{Dd-KVt<;$9ivZ)i^6Ym-Pc{=jwRJ$vz45L_G-lU!9}& z3z=6ZPo%zTPi?f8{ilhC^q$V1Y0nW`zkJj2=3%R~l)*3e0aw9l=c>97=92pv|MaM+ zl?PLLpYf^6Q}`Wv|M97Q^TUbp_PJO?)^Bk>g{Qz&Jaqef?PakaQ#}=bit#hgUTF0Z z2j_F*xX;J@+}iy+!O{GBxv&mD@4SxbZ}r~i_g(w!`LX%6-XGps#XxWCJ^h^WP#GIz z;@~d*dEz4dUBMQ-rQUMQkn4iX5fcyTi_|wPU#qRhp#LF#fvxAX#Y4_*w7chiQKPH+ z?|tE`!cyM5;|tDfG0@zv_r0jo^{L`1RQ~s=tW9?flWXVvZK?U$*QuBAbp0uKN=(H= z?;NGyoO+&P*w0zUd>-rhevSKq+fip}@8x?o?~(Txx1(oD9`IbX^A&81??Z>2k%QNJ zj`cC+65msAz_^uT#+~;Me9yvIur>d_j;-LU-mAa2pXFy;XDrP{Z|d#zMd(4?LT{S6 zw*AenD{+lUYSg{|!93kPEA)c0FA095UUt^Knj0G5cJx)pn2+6OToqm?Tp0t^>Z~)0 z_o+@tQ(dm7fKQV6_t~b^UB7hd?QhJwhVEk!Xx{vc5 zYYxr(;vH`bL!RFq42d1`b}-X=3#z}f)qm5&Zr@fv)O*o<<`Uy^;3D!rA*d~U{Me-D3~crI)i6Y;mKRYb?@ZP7cg zu1)V2>lL#egY)Bg8ori&HHn8^!|Q2%r*IXFqS^oHtNT7xuP0y0`eS4G)vPC|k>UPFfIIVEz{OQbz2>XVbBTlSmDefiakaEnbk(v>qo<&z`mK*-4X^8t?f-XudgCf|6)OqZC(9hCu5JO9ib)Rw7 z@icJNHM=p8J*U#AVEn@maUY&5f3In(k3zeDX}`|P1e`sCV53Xi2>L9fs5Phae_nB2Mq4bDY6!;)-$6A z(>csFKgLP19&AngT)Jw;%zVFo=Xk8f zppO~j=X)B5{hiJiao;%WdFc^R-zFa7#ao+A4RNmX*i(Z}PHkGxVQ8$)3?gMvku?q?KvdQ*w1OLT-}&nG5M_b+UK{5FFwCKl)~2N$H<&Au7Qz(n|{B3 z-+Y_b6t4O>@%zKkaMP2 z2luUyOkDZ}9U{wD}?D$8~ejT6zy(pzi^G z$a+NeyLsWPvHs;ga#eDGwfyq$HW%}p7)~CjU!|{rzZR~t{-^&LO-23h4b=ZQH_q{_ z?Q<^6RjDU4cO6^LKVz}SRr1l^xONP!Jua@~Jh>{EYI%3oAzS}9=Gk-U80#DIT|EC| z%^r0R*WZg9djixU#&2VW{)qi2b;!X!o?dD9HnIobb!YbB(~l_6<@dr7+5dsh+wFDL z`z>s>d?h*Jk=Ww*`5Sj)AkK~8^M)&zjJapZdeyQsb z@!{QDB)v3ftc#X4oqMaTc5nqp6=(5&(d?zI^cJX%$5*ibMXrKZ?f-Gy<5k(Crw8hK zQO7s^RQgqnYw2~zGjnidoP6BzoR1k-^|_!N72VizhLVBu0wDg z72IS`h3gW7BiB;li`Wm4{np^#>|KQi=e``-vxa(<&u9NN*InC}0S7dE;rq&8#z@v{ zr9aViL#{&`eXk8$SsRN6%sN=-S7EDu*V=9U{#gsmxN;mS7N!>vou!7vFSHK3tsnB6 z=~23#HPx;ow^z%ahr~j9Xy;+5)x}D?O`P{F+Hz)MIT%~X2ItaBD`zW{; z(zVI%dy-xS*85qH%35URZ{nY8XsEYm&Q|Y>@yQs-7{n(mTvZ)5W7hd;ZEWi?&x7Y9 zm?Hjl4`&Bcjwu?$b*noZWxRuDvL}MPy&F|U6bZ|5A;iT+W1J^Nsm7JePkWB{jchJ^`&_Wyp{gf!j|Jv_&S8K8CTTWm!{gewZ2$ypX!Iv5+{Fe)x)o#aVS-sPn4(hBYG9HY7;vw2k9cV9!JZ*eWUE1|L#KQEJk{4#}Vf=<^oy8I( z%YPSEIu9Mx_IVu~$lq^cARar~Dm5GPKzc76fBO@y6(kO7Fn+!+Hhka&Ue#wMlii=RJELfCKp{ zc!HC}N^nF>uwHJSls%L2)HKVqiKJ`8~69YT0!slShKBeNV;`XXt6rLF8jN9<1D(^#2i77oPF-7b>{er$G z$~_v5d1@$ixcW)m#9T1Gg`40cvE+V0=0^6DWZz=)@9eEiyw5HFwx(cjuX8Fm<9t|O z2#1L;y*v5K{>8iTTGU?Tk8+oL6M0^fFV=iBKTeI5erx?xeK)n@92c_=P3>M>bpTi3 zuf;-jft~}LWlsErrr}*LHaxBF!zUK%IoP9R&$7LL)^*^IRxfPpS>uz6tsE1+3P)?V zODxsD#Z>Bn=7Hrepsn;5?xAL59>^YG(d+hrG6&?U?bvnBH^-K%Dh66Bvj*!p&A2LT zB^L5raMfeqz!YPyKI6Tbm!L+Uc&hp*$<5@b#Js!N`-k%!JQ?rAllx)2r>C_t*RBxL z#g4si=G^AGsrlpUWM2}-+kfYA_Q=k4Da4r=bFWi$X4V<$;qm?hUud`1AG=*^JI04t z-Fg`x*Y>+%jOH~*JJ@n8%44YcSGP3}gDXAKmK&e_VYpmRxHLTaE*jpoVlR6k`4#;R zuAN3xx+dRsEvXs0|B>r=(9(^wdjE`CYl*GGQ`Kkc-`)-!fy4249aHIvaozIdv2*PY z$1^>9=7F3u^}2n<%~wFH>njkG;yce=RdpM=eayekSA{FF<`@>P2B!3<4q&Q#HSU2G zKgwKBuLcbfO%9J-%YGTxsGE#+a+BAJV15Kk_9TfRa_y|=WNcIGx87d|>!tL9ecrhh zoPj}m1G0t!=`Rhb^q^r{Xh3nm!G^Y1as1S!#JMaNwMVq zcj8F?cg>3Z-}X{^;1jl^|J&!#ANDZX&+EF+V3GIn{e0hedf+zrQh&jb!-El9&cE_t z^iacF&cW&%GA5$8Djuo>mbe;oa0OQxuf|#By)+H4?*L~#^QqA))}-}V^i9-=?(^V& zcJA}-^~$o>WO3Hi1E2n^S{o*=3R6|{D<7datK%xMugAaIV7I7d7a3&B!w#GZFzNKIi){~PeX<7w~re0-CC`?#8xxg5Ot zcVI0zlh1fx-#;}|I6XbHU^?|J)?mq7<`26d-kdLiS|Icu*6 zKDg>Wl(_o0ueYNxRypc%3mKxa(LGm%*6hRC`e{ z|KstYyP~;j?FjXV>|dEYvHLOMc>FE(sQdj@?(94~A6x}nQ;zdPUBg#>2S4ldbDx1; zV9oTTvAh<0+%(d(p&$ui(OHZqCbWU8kCc^kw$id_e=Y+bd<c8Y_$T*X)!2jqPBDt3Q8eJk{7YBwKxNAuWSUrK%R#wmP?mv$nX3wqk6v4yALIdt%>SnkpU@@vpCM(wnsAVh^;}2r>4Br@)l4 zuH`tr-xf~Z!BFy-zuS0S-wUa=?q2VgfBV?X?~8n=`7yokzxvbA_|cs^zMm+SRS<$CuIFc#`LI9_YGa_o}h^f;JUD7HGjjE9bWd2!+ zFC|~4H%lLjye9kOs)2X2|fO0U(Sku ze24ga)B2Oa>73j278$P`8}a0P^EVexm|p`&%)JZiU=CjBQUCUKer?T9Fqik@eVx3wh?T|KNBSIGy()C#V| zR^iKeEVlGYIUmvQ{hThU*bwWc3ueDp)rp>Sz>tXv0=Mo0{VTN+n2t-gb) zfh&Ih@ERDOkvsRXVl1Qc=l*T`JKe8(_*C?`nE$yyf&CxyIb4E`=%a zBtGEq9n6Ru$LGjn!r20MTWm2ejfrq%`qa#!<$3jk{;A$6-cfv0eLw4ocqG;d5$14sNrs`JS0$a(M^s|`nuB(gRfRB}$j(u6y3Zvbv6{@kw zmt9w3UTAJ&uA*)*7B0rn7&ykO=HC)m(N>dQPYfjA(O)pnxd%VgdFP%m)WhTXabD$A zxi#9#x~efy{}K+~!_>SknRFsOoofX>`EI3Bgs{H)2Iw6_#>qTyJC09=?~ja!iefY6*1&xYkRJ z&gz^cr)JM*>pj)4=GqcDOHYgW6W({lG0DQ(+)9W58a`>Ck>iP=wuw?S zCic}>1XmMQ3R}m$9k}ec@_oe>;~AaxEo$|SA$f!QZdi9zN2FelhEF`?`|tE@)7KNP z6t6~}R%2zYh2w_Cs(o$_Ypaf_fi3>d+)#~W%@7|eIpMR(3DL#b5BE*KoF3Bcyu`=i zd}nWXuRE~Zr?lfLTGQXWm|K~neH?ahRoY5_A=-+Xpqf2-P3z+`CVO7vF}Np;{7vjO zhL^T#G4KGU7JM~ul{r{r(R;Shv5A51aY5fnxQcmZ-;-;__5JN}$$nyRLdFD+Sc`%A z&B4`#C+FAs9y_sg%U1(e&QW>4^n2w!d_TXZ-`D-vQ|Gb1s<+Jb$I=U#v3z1`(vD@r z)m`kP)A!Ob9v;%{g{N94AZL}vQoBEn_0dnEcjj47LrXlrc@5Nas?RUl{R`Ag(O6l# z8jYovV19aiIy{*+NB?#^M`v6)w$95LS3|R-Io0g;FEH0?e>6QtubXXeTh?*8*B-vD z{fMavW{!2u9&JSoocU>ae~qt}xFQE|@4nm7v-U}tBg9vitE`)%f#ZQv`)hmh(C+~^ zBnIkzqMtJs=73^n$wmHt4|)514&Q5jzvXzURn33!@8g%c&V<;U>%3Y=w4bXqyqsl? z$9@oFp+2DXRWyaQ==7DM3$oWG<5PR-G|j%4w;fk`jo9kAaxd09#g%nvd$P#0>?3l2 zgLn<^LZ6xw>brL@Ir~U{rPrb5{iAU8AK%>7?~ZB3!NQffjPuOi(9-PohZ1Adc6!I@ z55>E(HXiTFIv4d*>w!bFs~LkOWBuay`|seY=3w7fN?iGzH6MK!Vu5R?#H~F{u2Zll zy{(BOKQ69v&sO+!?0skq#G}+lzo=`g9UK(CmY8xZjyfjnwf9?KHRB0J^?Ae<^`FGT z&ij5pbHi{Jdp3GK=44!MlPhtOHmNNL>(IsR`AF;vw8!xbnAEelm~4IdN6F@e|_h zxIX-5a`NnHVZFvVMo&e%_CnvA4)69-V_@%3i?)J0E~4%*a8=w=d>yQu15Y#7rgcku zUPa+ly=o4mrU2iM{3_nE`rZ08er>M%YF{MZ-`a2VyFJ|WX{C2r&xYJOd($T;T=ErT znsHssS096?iqm#*Wo~UfM-Pks0DI*|{aNo%uYvEB-UfXdw7Y(I?Wuh4aK+e%uL@V@ zf~@D#XEzrdwJvaFKReuQE@2JD+`^v3mc!^1+gGb*@0^t1Dz;X}U}%OFzG57b3*uYZ z)7={=xZjN(bEP|nds~M|4XWVr> z)mUl@@su2K%_Hey_(&O+DkO?=G(NmB|T*#?mJ}gsUl5J}R-0 zahLzal^#~%D&8nw4f)sTcenmkIU#f4vM=G)fP+i8-u8^E3z=J$13dzLb(pWjRreUo z19s#g+5az6NncKA*f= zZxnqKuYvyATuZb2?!i^OhHV^N#g+K#eoMwXad3gF*<-LSNWJ-L`xt_&x2CT*<4`r6 z&fCMwbAH_~xI$;5VfA(5Tj_=Buj^Trwj#e$vsh<}zwp;;E9aN_i*+03R=9;cr8L0w z`=i&7!%_2V*7#}L53bNyzu)&Xv?gY))w&Mx z-I`A2LH3a@an*92?uQb-uWo=2!SiT*2E94z5_IWbdB+pz!vXU-m96#$n`w4=Ap>&#F$7s}AwijH|9I zqTPu%uIXvGV*TZVg)4g@>BSHK&-3&Qng{#AH5FFfR-M)q^ zeJymmF;jh|*C4J+S9HFLUOfk{;@enjo#NoJ`AV)jCtpo^^}KOawTrIV|KnSUgH_Ku zmcC*QihQt(tFALg93+3SkG5*14}`ul4leZ-de7c<_0{NgL|>)XG5V_2oA>qA1C1;4 z!QhH~Fg3AWyO{VtYnqn&$~f5d)qTR1n*F%ATE@W@{?B+$aZpWK`E}D*hjEp@E%V}K zZ7{mM^nf~Z%3p?VFI-J|@%^IP^;^_eN?E z!49&~i;q9Kk z4F4*5@$6sq8od2z*5UpA2giWCIGlYB|EhcjY8~189-W)sQ2OkxJ&gViYdEZ7@H{#{ z;)~*akNM?qE*&)EO0RRy_l~+he9?-7<+*o1tK+JTXYXU{{#E*osEarM%6_c(Qa5M) zNqX6+5!O7lchvqqYQ^adu}4pDSw9NgOL)1hzwa=vE=)f7t!=JdJH|?>6g6^oZ@d zntK_}>9?baj)Sejl>E2ni#A;4eY}1Gxl>zvY3;)6Fm}J)z6N@+($_#AhP`9yWwXc6 zHDL6g@8;Lk_6k>yPaOwwHG5bSuHuV!{&$|r|E_(ehLm*y)O}t1=y%IldhJzl1rBO$ zPWss9c`8Tk;A+wrYq+vLD6XnL$ogK_UAi72y|nm}BY&Z87r$k$588V*>NpEL6}CFA z?2Xy&XJ9PQXCp7R$I<$r*HpmY*O&G@xZg%{!bxMr!$QM&9<%2TaXsrHc5tOP+JC=s zOCM8?IiCY<-n@4HzH-65r|;G0skn0g0k3a1_k|b(T`z7fXkT*nx9IgjdL&QVH%e_d zd(TiSZu_vx{cRrZ;;Q3jXG|wt1$+Fh^+9n}K68AVo?p9;koB|Hlrl#-&)a=%`#$gB zq4kU!2honst8jnA)rIKx<_#`!)q1m&zqr1c_i42U`x)pRvIjZ){nO9DdKdH3)Zpny zv$tdPGoNhWMYn*0WaX;fHuIz13KLb6E_=NiU*5Iv`#v^vms(GDYdar6< zLp0V}-MKVCZnt9)Y-K$-_&Ud}`QG5FfvLjkgsaq@_1dfFwq4)Tn4PsJ_A}_U>xtUW zkabOcFIM{*T-R$a+uVy>T;;9f7u-zqHeclJDIT@IQ;UJ3WRw$ePv`bFXDUm3s7R~O=6ZE@vX?K!bMj_S;Cch>}J&6;Pa zJ+Q8X&lT6{zj6;z^p5Mo-M`Gf?DV2BFESS9gvD7)O!@DDs~!XAeo=dd(u+oqqy1O- zt{IE!70p^f`i7|47H7rJ!@o_hsN-Qj8ohPa>C{faLdL;4CJqV<;;ZpiUSoVa>PYWZ z@l<~=-_L{R!h7lp*IyEg!~6PH_(ADqD6V3DrskafD?DcV+}#@_y~w>sGGpl+oyM!M zbuVz0J{k8P9epyjw+x!XTB|j&%7Ic7>spoHku?J8zv_JD7!|IFg@d;pgEd@@eo=6R zcddt={rTkYtQ|xX5QEY$>Ru`Mw)(gB3cEi__OGDdC$)$3!BwqIb-egH`G53__O-fV z*qY?cuYOtcM`zBmcZk0K#6r$v=9${vv8Y#HxVObqg{Qg=t`_@dIydcc z@4ZHYtFnHg?MEjE=Q_k$*VNZexQ@_u0(j-ooP{UnU)^`C<~iH3mU>S;r#4If75$=F z>%|;J!?iVpXbN(M)bPnoM{lIH3wv()?$s~4gR8zSwc*M+wSp`4K=Bplz`u_!u=Y^e zy=sJbMc39oGOPn#_P5Kk>Fr2QgX<>vO{>`I9PaoyPV;(89}`!un}EAhd$zw_jb$v9 zzlj-H-~7f&XH|dpJ@ocD*LM1l?P*|s4NcJbrSRe7b<|_?Z9P}F^<3d;V3fHEH;U!> zp>V!y@v`<(576}idREjnotx3?_zU(&nxAD&4&2=K_|+2ySLFrPSRDsfU@AQ_VlJA3 z^#*tr*)N%13CFSZA^$>Z-dW3pPTkD~ry7p)6I{e+HVzi99FO8G$Ao{kegAtNB(9PZ zG9L1G*I2H-+iG|67(HTeZQrV6;ySBC>v4AWlNMWYdCjdpuQKn}aK-D?Xs*X`{jVNj zVxjwGnitAhS>yHU^j7$N8jr(S@T%w0z7&0|)!x3sMIVcy!R4#@xw;=biOYV}sTe-^pE!?jG-oIUKm32*%$KZI)xKfAu?;6MD zxYpy2yFD_|s;-;pS{3irxSO6M`);k_e*%x;bF6tLUsX%EMlHEuc~|JgC9cLguRgDW zDLLCXSbQa}{*hi8$AnmzUgYF5Xm|H!kjMGl^fXw@(Q_d;R!bYN-9t0I9cp)Vfw9oM zuyodptuanBUvVwSnv3G9YNf6tk-r)5tbGe-vFAE-JbnM}!x#U>v2kC+wjSKR|JL6( z`B>3elkQ&XsKU)rxBks|zId)QMboKj+|-)s<4ymXTxDOoKB)d5b>17?*Y7o~O?52} zx-tHOeYaWjJgq-*+{SphW~m=*xDsE5DewEYl?!H_SI@2Ok>qo;_Xpp_eHX=i>p{j} zE^P&`xW#QmbDY+$Hqc)8TCQMLw{%e_RPP+Rlm;YjH}My(OB8H!an!f;}_22I==B= z+}D`?iY;d?_a#@qQ;TO@;yGkoi2eF2@uS*pdOysc8IyxYyomZc=VU*8FL{b_S4TJ} z<5^{oUvp`=3XdlH7LjY%$0t|#{o`55Rd`Bc?`XBv(Ci&oa#Ufe#%aco<2oOXwR2i+ zWiIG?IeHn~FGWqm3-*GQLe3A6TP&>&r8O;It9pNhUWA^Yf2KIjLtih~YaK==P zQ*qXeC+`=g<#zOF>Z)_DQ)_vvAJTOJ#B0x`-fpzIJ}6qp zK0Ebe+vABCX#F0(-{ykot2Mq_;z|q_rqoq(RmP9C&dy6dV|WbAN5?F=plf>VKPKm~ z))Z};7})DK(N_F+@o^Nc#9Fwj>#Oip*Y1p8@)`BJez9W(Cq`rOxz^!ZPIz~C;^tSm z2PA8r)mZF<QLvP+-_Y$Z%YqZUq+m{)}(Oe7#42k+m4~Eg%C&X4aK^;c*yEdI4WyU zi6P1VsIhsj)D1iF&-@R6Ci_|#|6Jqjz6{juqSu*|ZEY{(=9oEN*7=sW^51*7GWKz( zP4z;Z*BLwZP2s$hzdUk5#|uq!H5{Kc0;%0kF)&=EAKJCm5?3>46{ZST#r5DY*F>E; zOZ{0j9Xz427c#lBd%=o#_nz|HMq|xfPV92cT7H}JW~a6@jne_X3a*mNFuu+?^JMV4 z-Tzo@v8Q(Sfb999J%9E(kXN|(Th_x_m*5=7*V03_&O>cF^;G91m`J_8Fb0=}f5aNU zKlgpCW!$IuESBY zQ@^?DGaqT%$~A-huGSBt$G*l@9an{|=qmT7F!t3k;YzI_|N&;Vj+Hm8q0ke z*kf_K?pclX;?3zE3p4-1>FO-^!a^&^$MW)$*O!=@xxMRzd7P5hfGsuXmbXgB<2^7B z)h^NTuGw)t^oOXY!msYN9^cP81s;t!82B=?>x@B@^BN}Z7y!S zlb_^6=bN=8=YISIcp!Bea#dsW&H7aI!QAS;152KoG1WEc9>2>?Ggp;%HzyQ(j90uF z$63E>%tN@!JkT1e`OaA9?6m>7PL6x3sjcvl;<5AlJ>Qi>hpUWX;VLj94;6j}XTe+b zSZ)hf-ugHFFedt5@!!?&z1Kq@i+&X6qwB66H?QLmf7Bii_O6V6@0^wWWv1NloVarA zvNq0f;uu=NISA)D=6$^M_8Yd?7m}Kq*TT^U3bwpXmof3p^l9-O?d|becJ>f>UDx!2 za6Qi8i`wVq^n_%;NMe~fEIfvBe4%X^#rd`h!!RPi>3;u_?-v!=mJ|)988`Qu40dmrEbXeOx!o6_k`dN$)WMt zyS9o>Fa|o8)C6)>;vccAbd@o14Oil8;A+Oy%6+k=hDXOH*D)_FjinwXhp?X@{-|pS zUq&n>9vPR?-=J@t+z`%kzf5ZqYOLk{bH!ORS2_Rs9E{%Arzn2+eQM9`p|={gQs)s{ zFNL>QKkQn=8_zLz`NOMl*LNrZQZ^TF5``?UbXkOy&2EIIbh>+lo2 z=RCjq)6+F4tk3AB6NgiyiAPmDC8ijg!W3hZYn(Cf;#K*()S+slL%z(CvkF(t!T2&= zt0(^H&FCi+JN(S(1w4)Hr)G|$ANrxvR@5k5-)s#0`|kJcY4r&sg`wiB;ELymu9{vO zowfVi;4EVy`ZhJ4S!0d*W}ddsZi z+kYF3(Qg*~h#~La1MZAp zG`5&<-l-LakKE^1{C479a8;Z&jaOn|)9T?WYZUrb=74$&`Z97A=O=r8)Se7||Htfk zWe>M8kk~Z1s(7lfQMZLFZwp)X+}dsY=dQhczH?SQWb;D(2Ig+lSnkEz_7}LZ_S!+; zcutK;>77B7%2{oXUwhQRgX7ls3ESq*j+NuY*p*%=&MMwo;4AMnu%(ut_a6mY>}lja zA)Z_7C4;-*l5iJ2b?SO!qt{Won!Q7=`faYe^jWUE1drK=bY%|i`){zM{+FM;j;(wx zKF!n3_ncpHl%ErSN-x2A=5?qR93Gx3-C4Qv4xaj4sxc^EU0fBm>OQX-ULSE(uFC!c zYIS{N>-F77wss@ltxxFDX3r~r*A;(^wjvgJjpg_*dM(B(_4X=GN}u;zv9=Wh^A-rZ%C*y2l!{>%NSI)C$otvo_6Gs!jD}QtvuqkKgcCAFo5Ws_`w1c@Dak zNK8zg$lmUJmfm0Xb_d(1xn{W>x^rDG>T)-{iEE+Ob(q8#n&{e(zOP~I6CX_9WVlSc zQ8x>Y&>`x7{fmJoW1joL=plIhVL6Ikx$MvGwU%Z~ImgON*um5aKh3|_`-gDV=U`$W z_{?>2tOL&RkMm%zAa9yGv~|60ee>0Q-}bCQt)4ag6?GL{)wGl`Y~mWRQvaTB3qxzS z^}3NWcFw}Xk4IV>3!TySE$aJWv47v_`LHL8oFhK6_4({e4riIi$@ywhxyLanOogkC z;Hw(f(I?+>>Z;YdpO3j`#gdO_Bc;&<{neqQ<$)KhwT=;r37&owUN z9+(Ob_jtF&Q~lfD@0=^HTI7Ps1JMTIDs;ha{G)flK3nP{uGKe}aBuB+F!&4BP|Wk= z8SAa6*Ndy>_*K{lu2!(MhNpVWzm2~IuSJXTZ(|`nm#NXazH+M3Coe=_m{&w+F~-J# z%va;#GreYNan`m*KQz`Drv+aXe!Lx+>M_!PJKmlTc`I5K-xLmL*rHw`w#Z36r~291 zmmBU7@A_N=U-bXVGw%K8wcSp=`BxiW#F__n7jM%`a!M%Fu1Dl(D_Py%vdR`&5zgfbz9gh9agyFdGR|m7TPY?G_h|c zd#$mLlRC?oU_4~J^&RA0z0%aai~-hD^&HgP;=wW7!&M)vH7xBs*3ZRVyw8Y-&67{P z$2g$gvbTmFh;f}~)}-BPY~=bhxlYB)U);1kzQj$hd=Ec&_~>HF`b^aymsh^O*15ib$4(xmzZ);qx~VZxT_qj{$J`fO^}n5Q<@<^&^MdX- zq}Pw{ks4TfAp1Tu}UTEIqJasHBv z&o%yoyhZM5Ow>E|`h;?q`qi=4=aRqFUaa?UPnBSdzBdd)@m+MUMLU&>y=YGaQaZ~Y7-5z>;`TYD|owLMm zJW}vn`x@51qvqZAIK*Sde>8Sf&E9jBnuY!E{N9W)zGi$H$F+FA#;nHhoVUeSlg=_8 z4r~z<^%|{J2VcZS`AaXy_((nxjM4Mtx~kwze2|O#eRCK$>&Roz%=4Zv=gMjHuv~nb zSOT-^NZ&s(ukfUopvU5GKID1U7@X@j*BI5UulH}!Xe|b!*U1ao8WZ*p)vJ2B`Cna+ zgS}Ed(DoxA`_uj@dpns28Uyu0tp!#L99%VNqNCzn;b?hVxLd*1&{+1YvF69`k^Um< zVt9(VrX%$W`y8@%$$fW-ALiZi@6v6kyusL6f?UX$C#;bY(jtr!fMbE?IeE8#=)JYui&CLV%0 zO5rK~)Y9|pdcA(f9Pf_$-Ahb`tEP1T)N8_3Ub72cHC}tIl2dP^riBLf{8(pFzf@0* zdlTzTrfy@8RbR)o!&RMMk_RqvbFP0Mg)9EndbZc1i}$X6$IG(UH??Tvp2-o7KYw`W<#~2Y`Mv8m zc)RvnYmP0S_uu^ej$Y?GHLk)}P^+8&*|R_|OnPZu8_xOhJS7LL>)=>(HxIPe*fsCb zRy_u$r}zQlt65{|H>7t4&Z3TFtu||zYM)=%n@}UmT6^;F;luMg2WL6AJr~iijQg@~ z5u8leS&sL7F6w?^t8U2);^`P8g0sRJpU3%D$1P@meq<-^g+5L683L#8&b1 z81U0E6z@d-9T)=7`AvMLK7u()w3PE_I_kUE?ecf}?|QCo)nmSgzprvYzDL$9Sz9&- zl&ie%{A*krNIxib%B->cMD4xP_DIP!aOA4Km&Ld)mHZ>e$Wad{u6Tde^)!t|UTD9c z`$yaFSMkukBl5#&E%FS#k?YJ4@k+9<-%Hh5a8~@R?!gCJ@D}@C7I%$0M#jHm>p0_G z<9_J2pU+^;b5fjEanslte(}3-h?7Hy_}Rly!|@hB)4VwJo#w}n`i+If;IrdN4wQ$* zlzr=!1EM#=RqnfE4bZhVxgHh0Xw(VXbw1l3+B0i621wC!{jAY+r_O!sW&h-@VeQuzyAZwZQO7(S^Q|4#uwfr{MkClV3 zos0Rg_=qd{Wu4FId=0!6=EU93Ar`mCE(aBc7N1q0Up0jJyB+I(e_y|6eJ}AO?v3ll zb8DksOUZr(a}a%Xa@MRLug)Z=AoZwW)ewY4#D5;tK-X9y9eEx6L&!^|M&hvck=lk{iy}6y=r|#GL*oW%p7rwfVPfT1~ zW5_+TcJf2|X+~|@n5f=T%a7h=_7cN`$a-nxAvs#pS;bkNf5&`r?46ehN6Xiae=jg{ zBtISFwc~x)z8jD2@!5gb<$e4-HC^W_1@{x6=8?WC z_w!{fLe|*CQ?I`D;wiL@aYsz4*UyEk!oYpKweEQ6J%TTu_xsG)V$Jm6vk^ugQ*n02ic5A}d%4L@Vx(D__GKL5?mea@e}aIcOd z@V5N?ga76|mVD`VDNGsvW-kF=Pu@pu)1JZX<7@8={BK&JqJ5DIpSQ`>+ThXj)A>;|Kr}yUV6t;=8yTGI(@=a>VB2? zdA+>SQ(}tP7fjWWsc zo6g!g@{njP`%A6!*i%!yHTtz!b3vVS^neFjtP|5aRd2~%=80+z$2{H=8d|&yALe-IUUjUo3%1+?G1!8)YQH*tQ+h4+ z^Rl;C@s@q2_%q?I)ZlvkO^l&AhUdM9F|@vM^Pz;b_+RaN4J=uQah~YitD*Q^sST;m zt&ww`h3F}2;)#7TrqB>0_7$eA{V{jdRIZI2*Jn=r(@!Vw6H~6`t6sZ0mk%Ufj{DyA zT}B=%z9!DnkN29{cn_`4+dlclUU~2NI(xhUh?wVe<0lk=nSM6S1kC-Z8Ov2*=7V`MK!*5Ofu_gdEOOMA2H&*}e-&qYnuKF##Y<29#ueCV&f z7F~QrD4c595r%2YBSWv;(1bob^mAA&6;drcV=$0qv(yZ=h|Hh61z-nEm?QTu4^ z(N2G$wMBabU7JX(aLr9(quR?l5FDU~ICIy|{<*~^a7)A2w1>X@Q(TnW!qoKm#8Ig$ z6-RmNdxxXIlz5I;&@}mx9Q8E%Qt59|L+RzYA3&9PUedFD%b5NUG zKbE8Dm#ny#b?^2jxDScF37wHgcEsA9X#t!?iTY8_~DnE_)+f zn^Cb*d~rVATeI<3Y5TsPE-@-Ns932M$^qUAD|OrRx%Pb1;p1=RrO7Wy9HtjG{ipJ0 z&+)R(-~H3nQTENlxv3e$!MD)&Xw1)^^mT@@uNjv%uIkHZrHuyVc{Od)=|0NqSE8BFGnuyP~@o z<5TQsm7LMF>aRq1>36v%gZSZo5UF1q8-p+U2JOFrAF_`weo#2W7+D%0?=cuKmy92# zC)Ry2HTK@opZB#M>NpScQQkAX$*utkmh_+Ko44n>uQR&?Ud^7n^dPvmq3c)GjqVrj zdP_Okexl}`%TZik>*Hv1_8Pm2^`kA{o8q2*>3CDEFK?@(_OMjv;Gv9{`}*Ddjru6e zsnK(4Um)5bn#+FX#6{L&(BCNUXHO}{I6i9PqI+wQGcMPy8yoFMReRanrS~PqYAv5U zW*qW-n}^vuA>Q;{#llV>``pt$-S;)V*1yraG`7h{_yqcx!4aR6^?CYn9x z&2P;^;vL9Q=6ZO;iF+S3?%_$5&zXFWestH)*poMV2<~@^ZcPj@-epfbwbSHDdH-DJ z>AUIq`M1S?)9dQLw_~gI22Q*sw&3>WMYKJISK}gkaiqS;IuqCJXK$d_n>Y5o63iWm zEy*ACztAu7z*x7DbFAJc|H^qcCdnn@psy7S{>0$osOR%qUytV2yOf8rzlgC-J~FR! z9knt)?D_Sn;U!&*BCj zJb&+A58}u7b)CI>iQM95bvS;ec+VaW)T$bm+#`woL|CuX_NN3-+5Z$BWi6&SihM6U zo?l`;GWtCGGg|ALJ%qK|%pR^DMx0f1r|`A>t%I|~*23qa_K<5q8W-XH^hCJU$38)Q zdhzA{W_l7ICvS8=st>up8U4<+hmiZf5Gy^8YW*AGv+U;~r=e%Wn;wzzC>Wp*tnJ4D zCh(6uf)Q}yo(%0Vo)a_f-5^ehz1a^C&7JonS2dqg`-PAAT)hHwyS5k7-Qn5fc=!g+ z4dZ*RPfea;%y+#^bQC&MpEJGa>T|d}Ya9~$b~XC&59YD1`?+Nvf4A|{QQvpxx6LuX z|IXqp=Uic{`&{PDjk|ijXnoQBtl3{Sx?b(&{+{fk7mVrIQ_I%3w+`!`Rn~^GR~2;+ z^>Fs9B5s~SADefHx9nYoR}?IW2lSdf;^UBO#=mQ?8D8f;GsZGG2z?a%^1cm2><7r_ zMK9q!4IJTV8t1Hwx}SyI6ixmG>U#I|{h13->L}(_d`>xv@v#3Wda8T``Q9-mS%?UJ|r?e+T{lYy-s6YYgCF48NMKCT&G#b0_7V~=a~{b#~s&Ud|4d+Oyf*Yt}s zul=I0XiY6x^IQ{yu74N&;rVAhl=~N)dMlWdTj05IylJ{GCVk$0u;4>I%ly5##ly9? zKn%J6xIJdsA6FdlJ;F(FY}W9(kE!_`wT5sLUR2h}=$Y$rTHDi;lDCa{g(ufyIscr8 zVv1vMRgLKmwjO3YRlo5#w}mZkGgf-Toxd8sTuVmJ4da_}wuWsjC>Uc6zdfn)nCHft zBe8PqFH5`}ID^m3vCQ2E*2v}ch=MoqAis$X53#p%^vBV2d7XYG*H#N&>B+FKAbUjW z4TO*QJo?MCSE=#MnhW@sYpvy__F|kHSlV*a@Hnl1+20v{+Qn0Ns>bd(eK37_8y# zCqBt-9|Iri9&uva%F+Mr<9Q7nB!1)}F(hU`)8iU>oNLwXGu!1Pums2I(ZwrJhZiqd zd#E@!uv9ge;-)q(;V1F*pBX3T^V@g$dmAf*vB70chq+IoI%CwcD^`jzc+6NL4@B>G zjE#NQ)x~K4;LMtlIQT5_Nvwel@u3%+L%h_H$G{HH=e24hV=jLmy)BM--*6Ijmc%!C z$u)c7CG^rBmMX@{OT@UHdS1uTJcjca&5xhl-=AmmSb4wtxvE#No@eYXhR+(8t;I@u z>c7!@CiL&+Fwdj;q}PwIKC9o_F8>qoCguuvVn)rzzr|vE z4X^X{@=@VP+#2J2f3eiLDSIGT!=n}&-zj+N++>VDl+!JL|MK69@i@WPl%vdJ6Mt*! zKV6RF_;=0Y_C8yDwHO(XwdaxpU!GU}pI{6gqZXOG%UoU@hzDbphqV$9wb1g&bNzM8 z>*$Chc}boOH}QFXE-`xGiP+sV_=G1lrJPe^F@9$si(D&fWnAj@-kxyQ{us>TvmDda ze>09^Fn@OdTXL6sbWQk5-iH#iajhwSQ#`9YY`S*Dr-_JOw z2DeW`JjtE%lUi`;5zN}V#$fgLo&!@SxZ20mJWt2iikp?O%wsdA#8#i{)LRE{8CQ%i zJvKJ}>KfmEw;m_sGkWd|YlSy?tq!qLM?EHXf}g?=`zQK7#Y@4`0#8GaAHh@p{)4}J z8jl*Mx;^2lbzH4stH$ieTVqGz%G(ue4SY5ICB||+XZb@elS@3rLLF-ABai8|!BBsX zyr;a>xk=Ay#*@BWX{j}?I`X$VhgOagOdZD5JT_}%Hy-OaihsY4sa!WvKcWuLVIAeQ z24|hi!e`>GV@@0dcgMUf+~n_h58p2b@9*d2n7%{hdNZcRZ(qU8_}wedjlY}c)ye$_ zMrZ03PDkNuaeEQxcY9C=0ekny5syIZ)Uo{B(&s0bI3CA5SmQMF)}j03yD{fogLWRr zg}in?&gXC2YvqrX&s_4*c+CmlJTQE7%yB)0p*%jtjOxodLJMEWa~J;W*Ebjb+r2#U z_mieR^jWUoUE=A`zsFoS=6IaE{{G>=L%13^0$Z~e7XOE9rGuS1f}?MLV{_sCdTign zJ}0*au{OSOu-H1B~DnnXo;bJ2Uq|8&AsCh|MAVuQOAWmzyEi; z81=I*`t}OWmKdAyG~Yj;F*$kdeaGP%u9iHt#FXcE#?%r^;^aTR1(we5@Ne3`~w v!`2GEj)Sf5QmkBX^54k_oQ%N92%L<-$q1Z`z{v=ljKIkVoQ%LjegytMmuDj3 diff --git a/manage.py b/manage.py index b361e3e13..c930fc2e0 100755 --- a/manage.py +++ b/manage.py @@ -37,6 +37,7 @@ def custom_warning_formatter(message, category, filename, lineno, line=None): warnings.formatwarning = custom_warning_formatter +logger = logging.getLogger(__name__) def json_load(filename): @@ -246,7 +247,7 @@ def update_currency(): # List One: Current Currency & Funds current_codes = {} url = "https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/amendments/lists/list_one.xml" - tree = etree.fromstring(get(url).content) # noqa: S320 # trusted external + tree = etree.fromstring(get(url).content) # trusted external for node in tree.xpath("//CcyNtry"): # Entries like Antarctica have no universal currency. if node.xpath("./Ccy"): @@ -261,7 +262,7 @@ def update_currency(): # List Three: Historic Denominations (Currencies & Funds) historic_codes = {} url = "https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/amendments/lists/list_three.xml" - tree = etree.fromstring(get(url).content) # noqa: S320 # trusted external + tree = etree.fromstring(get(url).content) # trusted external for node in tree.xpath("//HstrcCcyNtry"): code = node.xpath("./Ccy")[0].text title = node.xpath("./CcyNm")[0].text.strip() @@ -338,7 +339,7 @@ def update_media_type(): template = row["Template"] # All messages are expected to be about deprecation and obsoletion. if message: - logging.warning("%s: %s", message, code) + logger.warning("%s: %s", message, code) # "x-emf" has "image/emf" in its "Template" value (but it is deprecated). elif template and template != code: raise click.ClickException(f"expected {code}, got {template}") From e4175d06a681145e52ac4195864e9b643c5b5cab Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:41:42 -0400 Subject: [PATCH 28/47] build: Update to latest profile template (cherry picked from commit 3fd068f36cbdef634c6c76a5264da7f369786d4e) --- .github/workflows/ci.yml | 2 ++ .github/workflows/js.yml | 2 ++ .github/workflows/shell.yml | 2 ++ .github/workflows/spellcheck.yml | 2 ++ common-requirements.txt | 14 +++++++------- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b92694c15..8be55fd53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,8 @@ on: [push, pull_request] jobs: build: uses: open-contracting/.github/.github/workflows/ci-profile.yml@main + permissions: + contents: read secrets: private-key: ${{ secrets.PRIVATE_KEY }} elasticsearch-password: ${{ secrets.ELASTICSEARCH_PASSWORD }} diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index f0d0e38b5..bc34612db 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -3,5 +3,7 @@ on: [push, pull_request] jobs: lint: uses: open-contracting/.github/.github/workflows/js.yml@main + permissions: + contents: read with: filenames: docs/_static/script.js diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index dfdb0acfb..67df87a17 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -3,3 +3,5 @@ on: [push, pull_request] jobs: lint: uses: open-contracting/.github/.github/workflows/shell.yml@main + permissions: + contents: read diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index d67a6d01e..8e72d55ff 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -3,6 +3,8 @@ on: [push, pull_request] jobs: lint: uses: open-contracting/.github/.github/workflows/spellcheck.yml@main + permissions: + contents: read with: ignore: fo,sme,zar skip: docson,locale,examples,country.csv,currency.csv,language.csv,mediaType.csv diff --git a/common-requirements.txt b/common-requirements.txt index 62a2917b5..c91b9ee37 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -42,7 +42,7 @@ docutils==0.18 # via # myst-parser # sphinx -elastic-transport==8.4.0 +elastic-transport==8.17.1 # via elasticsearch elasticsearch==8.6.2 # via ocdsindex @@ -51,7 +51,7 @@ exceptiongroup==1.2.2 # anyio # cattrs # pytest -h11==0.13.0 +h11==0.16.0 # via # uvicorn # wsproto @@ -88,7 +88,7 @@ mdurl==0.1.2 # via markdown-it-py myst-parser==0.18.1 # via -r common-requirements.in -ocds-babel==0.3.6 +ocds-babel==0.3.7 # via -r common-requirements.in ocdsextensionregistry==0.6.5 # via -r common-requirements.in @@ -114,7 +114,7 @@ pytz==2021.1 # via babel pyyaml==6.0.1 # via myst-parser -requests==2.32.2 +requests==2.32.4 # via # elasticsearch # ocdsextensionregistry @@ -124,7 +124,7 @@ requests-cache==1.1.0 # via ocdsextensionregistry selenium==4.11.2 # via -r common-requirements.in -setuptools==75.2.0 +setuptools==80.9.0 # via sphinx-intl six==1.16.0 # via url-normalize @@ -158,7 +158,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@d56505d69fceeb43535a3387172b2024eead536e#egg=standard_theme +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@fe5c0879e00db6d00b1d10a7df98826ba46abdbe#egg=standard_theme # via -r common-requirements.in starlette==0.40.0 # via sphinx-autobuild @@ -180,7 +180,7 @@ uc-micro-py==1.0.1 # via linkify-it-py url-normalize==1.4.3 # via requests-cache -urllib3==1.26.19 +urllib3==2.5.0 # via # elastic-transport # requests From 9d9dbd1e55aa1aae6cbbf5ed319e25413c25de01 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:22:14 -0400 Subject: [PATCH 29/47] build: Normalize conf.py. Use html_css_files and html_js_files. (cherry picked from commit bc1297d5986b64fd6c05e895ff6147bdddced239) --- docs/_static/{basic.css => custom.css} | 0 docs/_templates/layout.html | 2 -- docs/conf.py | 27 ++++++-------------------- 3 files changed, 6 insertions(+), 23 deletions(-) rename docs/_static/{basic.css => custom.css} (100%) diff --git a/docs/_static/basic.css b/docs/_static/custom.css similarity index 100% rename from docs/_static/basic.css rename to docs/_static/custom.css diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index c68368339..5310a83dc 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -1,5 +1,3 @@ -{% set css_files = css_files + ["_static/basic.css"] %} -{% set script_files = script_files + ["_static/renderjson.js", "_static/script.js"] %} {% extends "!layout.html" %} {% block mobile_nav %} diff --git a/docs/conf.py b/docs/conf.py index ede1ba262..73b47c0e4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,11 +1,8 @@ # Configuration file for the Sphinx documentation builder. # -# This file only contains a selection of the most common options. For a full -# list see the documentation: +# For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -# -- Path setup -------------------------------------------------------------- - import csv import json import os @@ -19,6 +16,7 @@ from sphinx.locale import get_translation # -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "Open Contracting Data Standard" copyright = "Open Contracting Partnership" @@ -27,12 +25,9 @@ version = "1.1" release = "1.1.5" - # -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ "myst_parser", "sphinx.ext.ifconfig", @@ -42,28 +37,18 @@ "sphinx_design", ] -# Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**/docson/[!p]**", "**/docson/package*.json"] - # -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = "standard_theme" # 'pydata_sphinx_theme' html_theme_path = [standard_theme.get_html_theme_path()] html_favicon = "_static/favicon-16x16.ico" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] +html_css_files = ["custom.css"] +html_js_files = ["renderjson.js", "script.js"] # -- Local configuration ----------------------------------------------------- From 74d55cdc7c86a20a0a5c92a9b3fd29874b0c1555 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:12:00 +0000 Subject: [PATCH 30/47] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.12.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.12.2) - [github.com/astral-sh/uv-pre-commit: 0.6.12 → 0.7.19](https://github.com/astral-sh/uv-pre-commit/compare/0.6.12...0.7.19) (cherry picked from commit 68db85199bea30e22d4d936abcb7564d213c82e5) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39ef46bc2..6921fb7ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,12 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.12.2 hooks: - id: ruff - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.6.12 + rev: 0.7.19 hooks: - id: pip-compile name: pip-compile common-requirements.in From 2c059b3fedda5c6950380c0291c9eb3ba33a3891 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:44:11 -0400 Subject: [PATCH 31/47] build: Update to latest profile template (cherry picked from commit 4da118fdc624d42dbf954b386c2c66cb0e9e45f2) --- .pre-commit-config.yaml | 2 +- common-requirements.txt | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6921fb7ec..abdd165fb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.12.2 hooks: - - id: ruff + - id: ruff-check - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit rev: 0.7.19 diff --git a/common-requirements.txt b/common-requirements.txt index c91b9ee37..9901c62d5 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -158,9 +158,9 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@fe5c0879e00db6d00b1d10a7df98826ba46abdbe#egg=standard_theme +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@1c192073bbc326445fbd0092d0cefcdea9962465#egg=standard_theme # via -r common-requirements.in -starlette==0.40.0 +starlette==0.47.2 # via sphinx-autobuild tomli==2.0.1 # via pytest @@ -170,11 +170,12 @@ trio==0.20.0 # trio-websocket trio-websocket==0.9.2 # via selenium -typing-extensions==4.4.0 +typing-extensions==4.14.1 # via # anyio # cattrs # myst-parser + # starlette # uvicorn uc-micro-py==1.0.1 # via linkify-it-py From 2754132d21d39066bf2303896f06157f536c723e Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:44:16 -0400 Subject: [PATCH 32/47] build: Remove Transifex (cherry picked from commit c9e259151c3480bc0409b79304229b11d62f6dfb) --- .tx/config | 351 ---------------------------------------------- include/common.mk | 31 ---- include/config.mk | 4 - 3 files changed, 386 deletions(-) delete mode 100644 .tx/config diff --git a/.tx/config b/.tx/config deleted file mode 100644 index da5197060..000000000 --- a/.tx/config +++ /dev/null @@ -1,351 +0,0 @@ -[main] -host = https://www.transifex.com - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:404] -file_filter = docs/locale//LC_MESSAGES/404.po -source_file = build/locale/404.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:codelists] -file_filter = docs/locale//LC_MESSAGES/codelists.po -source_file = build/locale/codelists.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:governance--deprecation] -file_filter = docs/locale//LC_MESSAGES/governance/deprecation.po -source_file = build/locale/governance/deprecation.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:governance--index] -file_filter = docs/locale//LC_MESSAGES/governance/index.po -source_file = build/locale/governance/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build] -file_filter = docs/locale//LC_MESSAGES/guidance/build.po -source_file = build/locale/guidance/build.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--change_history] -file_filter = docs/locale//LC_MESSAGES/guidance/build/change_history.po -source_file = build/locale/guidance/build/change_history.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--data_collection_tools] -file_filter = docs/locale//LC_MESSAGES/guidance/build/data_collection_tools.po -source_file = build/locale/guidance/build/data_collection_tools.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--easy_releases] -file_filter = docs/locale//LC_MESSAGES/guidance/build/easy_releases.po -source_file = build/locale/guidance/build/easy_releases.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--hosting] -file_filter = docs/locale//LC_MESSAGES/guidance/build/hosting.po -source_file = build/locale/guidance/build/hosting.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--merging] -file_filter = docs/locale//LC_MESSAGES/guidance/build/merging.po -source_file = build/locale/guidance/build/merging.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--serialization] -file_filter = docs/locale//LC_MESSAGES/guidance/build/serialization.po -source_file = build/locale/guidance/build/serialization.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--build--system_architectures] -file_filter = docs/locale//LC_MESSAGES/guidance/build/system_architectures.po -source_file = build/locale/guidance/build/system_architectures.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--design] -file_filter = docs/locale//LC_MESSAGES/guidance/design.po -source_file = build/locale/guidance/design.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--design--user_needs] -file_filter = docs/locale//LC_MESSAGES/guidance/design/user_needs.po -source_file = build/locale/guidance/design/user_needs.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--index] -file_filter = docs/locale//LC_MESSAGES/guidance/index.po -source_file = build/locale/guidance/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map] -file_filter = docs/locale//LC_MESSAGES/guidance/map.po -source_file = build/locale/guidance/map.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--amendments] -file_filter = docs/locale//LC_MESSAGES/guidance/map/amendments.po -source_file = build/locale/guidance/map/amendments.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--awards_contracts] -file_filter = docs/locale//LC_MESSAGES/guidance/map/awards_contracts.po -source_file = build/locale/guidance/map/awards_contracts.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--beneficial_ownership] -file_filter = docs/locale//LC_MESSAGES/guidance/map/beneficial_ownership.po -source_file = build/locale/guidance/map/beneficial_ownership.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--buyers_suppliers] -file_filter = docs/locale//LC_MESSAGES/guidance/map/buyers_suppliers.po -source_file = build/locale/guidance/map/buyers_suppliers.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--electronic_catalogues] -file_filter = docs/locale//LC_MESSAGES/guidance/map/electronic_catalogues.po -source_file = build/locale/guidance/map/electronic_catalogues.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--extensions] -file_filter = docs/locale//LC_MESSAGES/guidance/map/extensions.po -source_file = build/locale/guidance/map/extensions.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--framework_agreements] -file_filter = docs/locale//LC_MESSAGES/guidance/map/framework_agreements.po -source_file = build/locale/guidance/map/framework_agreements.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--linked_standards] -file_filter = docs/locale//LC_MESSAGES/guidance/map/linked_standards.po -source_file = build/locale/guidance/map/linked_standards.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--localization] -file_filter = docs/locale//LC_MESSAGES/guidance/map/localization.po -source_file = build/locale/guidance/map/localization.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--milestones] -file_filter = docs/locale//LC_MESSAGES/guidance/map/milestones.po -source_file = build/locale/guidance/map/milestones.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--organization_classifications] -file_filter = docs/locale//LC_MESSAGES/guidance/map/organization_classifications.po -source_file = build/locale/guidance/map/organization_classifications.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--organization_identifiers] -file_filter = docs/locale//LC_MESSAGES/guidance/map/organization_identifiers.po -source_file = build/locale/guidance/map/organization_identifiers.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--organization_personal_identifiers] -file_filter = docs/locale//LC_MESSAGES/guidance/map/organization_personal_identifiers.po -source_file = build/locale/guidance/map/organization_personal_identifiers.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--organization_reference] -file_filter = docs/locale//LC_MESSAGES/guidance/map/organization_reference.po -source_file = build/locale/guidance/map/organization_reference.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--organizational_units] -file_filter = docs/locale//LC_MESSAGES/guidance/map/organizational_units.po -source_file = build/locale/guidance/map/organizational_units.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--pre-qualification] -file_filter = docs/locale//LC_MESSAGES/guidance/map/pre-qualification.po -source_file = build/locale/guidance/map/pre-qualification.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--map--unsuccessful_processes] -file_filter = docs/locale//LC_MESSAGES/guidance/map/unsuccessful_processes.po -source_file = build/locale/guidance/map/unsuccessful_processes.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--publish] -file_filter = docs/locale//LC_MESSAGES/guidance/publish.po -source_file = build/locale/guidance/publish.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:guidance--publish--quality] -file_filter = docs/locale//LC_MESSAGES/guidance/publish/quality.po -source_file = build/locale/guidance/publish/quality.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:history--changelog] -file_filter = docs/locale//LC_MESSAGES/history/changelog.po -source_file = build/locale/history/changelog.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:history--history_and_development] -file_filter = docs/locale//LC_MESSAGES/history/history_and_development.po -source_file = build/locale/history/history_and_development.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:history--index] -file_filter = docs/locale//LC_MESSAGES/history/index.po -source_file = build/locale/history/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:index] -file_filter = docs/locale//LC_MESSAGES/index.po -source_file = build/locale/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:primer--how] -file_filter = docs/locale//LC_MESSAGES/primer/how.po -source_file = build/locale/primer/how.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:primer--index] -file_filter = docs/locale//LC_MESSAGES/primer/index.po -source_file = build/locale/primer/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:primer--next] -file_filter = docs/locale//LC_MESSAGES/primer/next.po -source_file = build/locale/primer/next.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:primer--releases_and_records] -file_filter = docs/locale//LC_MESSAGES/primer/releases_and_records.po -source_file = build/locale/primer/releases_and_records.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:primer--what] -file_filter = docs/locale//LC_MESSAGES/primer/what.po -source_file = build/locale/primer/what.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema] -file_filter = docs/locale//LC_MESSAGES/schema.po -source_file = build/locale/schema.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--codelists] -file_filter = docs/locale//LC_MESSAGES/schema/codelists.po -source_file = build/locale/schema/codelists.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--conformance_and_extensions] -file_filter = docs/locale//LC_MESSAGES/schema/conformance_and_extensions.po -source_file = build/locale/schema/conformance_and_extensions.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--identifiers] -file_filter = docs/locale//LC_MESSAGES/schema/identifiers.po -source_file = build/locale/schema/identifiers.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--index] -file_filter = docs/locale//LC_MESSAGES/schema/index.po -source_file = build/locale/schema/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--merging] -file_filter = docs/locale//LC_MESSAGES/schema/merging.po -source_file = build/locale/schema/merging.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--record_package] -file_filter = docs/locale//LC_MESSAGES/schema/record_package.po -source_file = build/locale/schema/record_package.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--records_reference] -file_filter = docs/locale//LC_MESSAGES/schema/records_reference.po -source_file = build/locale/schema/records_reference.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--reference] -file_filter = docs/locale//LC_MESSAGES/schema/reference.po -source_file = build/locale/schema/reference.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--release] -file_filter = docs/locale//LC_MESSAGES/schema/release.po -source_file = build/locale/schema/release.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:schema--release_package] -file_filter = docs/locale//LC_MESSAGES/schema/release_package.po -source_file = build/locale/schema/release_package.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:sphinx] -file_filter = docs/locale//LC_MESSAGES/sphinx.po -source_file = build/locale/sphinx.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:support--index] -file_filter = docs/locale//LC_MESSAGES/support/index.po -source_file = build/locale/support/index.pot -source_lang = en -type = PO - -[o:open-contracting-partnership-1:p:open-contracting-standard-1-1:r:theme] -file_filter = docs/locale//LC_MESSAGES/theme.po -source_file = build/locale/theme.pot -source_lang = en -type = PO - diff --git a/include/common.mk b/include/common.mk index 63cb5b5cb..12a9c619b 100644 --- a/include/common.mk +++ b/include/common.mk @@ -63,37 +63,6 @@ docs/locale: $(TRANSLATIONS:.%=docs/locale/%) pocount: find $(LOCALE_DIR) -name LC_MESSAGES -exec pocount --incomplete --short "{}" + -### Transifex - -.PHONY: clean_txconfig -clean_txconfig: - rm -f .tx/config - sphinx-intl create-txconfig - -.PHONY: update_txconfig -update_txconfig: - sphinx-intl update-txconfig-resources --transifex-organization-name $(TRANSIFEX_ORGANIZATION) --transifex-project-name $(TRANSIFEX_PROJECT) --pot-dir $(POT_DIR) --locale-dir $(LOCALE_DIR) - -# Builds and pushes the .pot files (`source_file` in .tx/config) to Transifex. -.PHONY: push -push: extract - tx push -s - -force_push.%: extract - tx push -f -s -t -l $* - -# Also pushes the translation .po files (`file_filter` in .tx/config) to Transifex. -.PHONY: force_push -force_push: extract - tx push -f -s -t -l $(COMMA_SEPARATED_TRANSLATIONS) - -pull.%: FORCE - tx pull -f -l $* - -.PHONY: pull -pull: - tx pull -f -l $(COMMA_SEPARATED_TRANSLATIONS) - ### Current language # Create a symlink for the language, so that file paths in `jsonschema` directives resolve. diff --git a/include/config.mk b/include/config.mk index 8be5c3709..9bca0278f 100644 --- a/include/config.mk +++ b/include/config.mk @@ -19,10 +19,6 @@ DIST_FILES= POT_DIR=$(BUILD_DIR)/locale # The prefix, if any, to the schema and codelists domains. DOMAIN_PREFIX= -# The Transifex organization name. -TRANSIFEX_ORGANIZATION=open-contracting-partnership-1 -# The Transifex project name. -TRANSIFEX_PROJECT=open-contracting-standard-1-1 # Any additional extract targets. EXTRACT_TARGETS= # Extra arguments for sphinx-autobuild. From b8df33b985adef87c840eb38c645db9f2f71bf9f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:56:22 +0000 Subject: [PATCH 33/47] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.2 → v0.13.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.2...v0.13.3) - [github.com/astral-sh/uv-pre-commit: 0.7.19 → 0.8.23](https://github.com/astral-sh/uv-pre-commit/compare/0.7.19...0.8.23) (cherry picked from commit d8ba5b3793f87cc68f8e2b12c8dcb4cc08e801ca) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abdd165fb..410bf121e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,12 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 + rev: v0.13.3 hooks: - id: ruff-check - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.7.19 + rev: 0.8.23 hooks: - id: pip-compile name: pip-compile common-requirements.in From 3ac576cefbeadd0d934441582820a59df2899d57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:28:54 +0000 Subject: [PATCH 34/47] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.3 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.10) - [github.com/astral-sh/uv-pre-commit: 0.8.23 → 0.9.21](https://github.com/astral-sh/uv-pre-commit/compare/0.8.23...0.9.21) (cherry picked from commit b9a24d06b243a023cffbe49190379a0966a2834b) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 410bf121e..f989312e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,12 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.10 hooks: - id: ruff-check - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.8.23 + rev: 0.9.21 hooks: - id: pip-compile name: pip-compile common-requirements.in From dd7b0b4052e44752103ada27a2f971f6e4da8589 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 17 Sep 2026 21:50:07 -0400 Subject: [PATCH 35/47] build: Update to latest profile template (cherry picked from commit 229314eee9420fdd56f5fd720774ee2adddcd4b0) --- .github/dependabot.yml | 2 + .github/workflows/js.yml | 4 +- .github/workflows/lint.yml | 6 +- .github/workflows/shell.yml | 2 +- .github/workflows/spellcheck.yml | 2 +- .pre-commit-config.yaml | 10 ++-- .python-version | 2 +- common-requirements.txt | 100 ++++++++++++++++++------------- pyproject.toml | 6 +- script/diff | 10 ++-- script/update | 6 +- tests/conftest.py | 1 - 12 files changed, 81 insertions(+), 70 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 123014908..5e4251f20 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,5 @@ updates: directory: "/" schedule: interval: "daily" + cooldown: + default-days: 7 diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index bc34612db..ab79551b1 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -1,9 +1,7 @@ name: Lint JavaScript on: [push, pull_request] jobs: - lint: + js: uses: open-contracting/.github/.github/workflows/js.yml@main permissions: contents: read - with: - filenames: docs/_static/script.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 600ad9d5a..2b658c540 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,8 +4,4 @@ jobs: lint: uses: open-contracting/.github/.github/workflows/lint.yml@main permissions: - contents: write - secrets: - personal-access-token: ${{ secrets.PAT }} - with: - python-version: '3.10' + contents: read diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml index 67df87a17..db657bed6 100644 --- a/.github/workflows/shell.yml +++ b/.github/workflows/shell.yml @@ -1,7 +1,7 @@ name: Lint Shell on: [push, pull_request] jobs: - lint: + shell: uses: open-contracting/.github/.github/workflows/shell.yml@main permissions: contents: read diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 8e72d55ff..0e4f3cca8 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -1,7 +1,7 @@ name: Spell-check on: [push, pull_request] jobs: - lint: + spellcheck: uses: open-contracting/.github/.github/workflows/spellcheck.yml@main permissions: contents: read diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f989312e2..fa2a642f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,20 @@ ci: autoupdate_schedule: quarterly skip: [pip-compile] -default_language_version: - python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.10 + rev: v0.15.20 hooks: - id: ruff-check - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.9.21 + rev: 0.11.26 hooks: - id: pip-compile name: pip-compile common-requirements.in args: [common-requirements.in, -o, common-requirements.txt] files: ^common-requirements\.(in|txt)$ + - repo: https://github.com/biomejs/pre-commit + rev: v2.5.2 + hooks: + - id: biome-check diff --git a/.python-version b/.python-version index c8cfe3959..e4fba2183 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10 +3.12 diff --git a/common-requirements.txt b/common-requirements.txt index 9901c62d5..cc4e2accf 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -4,26 +4,24 @@ alabaster==0.7.12 # via sphinx anyio==4.4.0 # via + # elasticsearch # starlette # watchfiles async-generator==1.10 - # via - # trio - # trio-websocket -attrs==23.1.0 + # via trio-typing +attrs==25.4.0 # via # cattrs # outcome - # pytest # requests-cache # trio -babel==2.9.1 +babel==2.18.0 # via # sphinx # sphinx-intl cattrs==23.1.2 # via requests-cache -certifi==2024.7.4 +certifi==2026.1.4 # via # elastic-transport # requests @@ -42,26 +40,23 @@ docutils==0.18 # via # myst-parser # sphinx -elastic-transport==8.17.1 +elastic-transport==9.2.1 # via elasticsearch -elasticsearch==8.6.2 +elasticsearch==9.3.0 # via ocdsindex -exceptiongroup==1.2.2 - # via - # anyio - # cattrs - # pytest h11==0.16.0 # via # uvicorn # wsproto -idna==3.7 +idna==3.15 # via # anyio # requests # trio imagesize==1.4.1 # via sphinx +importlib-metadata==8.7.1 + # via trio-typing iniconfig==1.1.1 # via pytest jinja2==3.1.6 @@ -74,7 +69,7 @@ jsonref==1.0.0.post1 # via ocdsextensionregistry linkify-it-py==1.0.1 # via -r common-requirements.in -lxml==4.9.1 +lxml==6.1.0 # via ocdsindex markdown-it-py==2.2.0 # via @@ -86,35 +81,42 @@ mdit-py-plugins==0.3.1 # via myst-parser mdurl==0.1.2 # via markdown-it-py +mypy-extensions==1.1.0 + # via trio-typing myst-parser==0.18.1 # via -r common-requirements.in ocds-babel==0.3.7 # via -r common-requirements.in ocdsextensionregistry==0.6.5 # via -r common-requirements.in -ocdsindex==0.2.0 +ocdsindex==0.4.1 # via -r common-requirements.in -outcome==1.1.0 - # via trio +outcome==1.3.0.post0 + # via + # trio + # trio-websocket packaging==24.1 # via # pytest # sphinx + # trio-typing platformdirs==3.9.1 # via requests-cache -pluggy==0.13.1 +pluggy==1.6.0 # via pytest -pygments==2.15.1 - # via sphinx +pygments==2.20.0 + # via + # pytest + # sphinx pysocks==1.7.1 # via urllib3 -pytest==7.2.0 +pytest==9.0.3 # via -r common-requirements.in -pytz==2021.1 - # via babel +python-dateutil==2.9.0.post0 + # via elasticsearch pyyaml==6.0.1 # via myst-parser -requests==2.32.4 +requests==2.33.0 # via # elasticsearch # ocdsextensionregistry @@ -122,15 +124,19 @@ requests==2.32.4 # sphinx requests-cache==1.1.0 # via ocdsextensionregistry -selenium==4.11.2 +selenium==4.40.0 # via -r common-requirements.in -setuptools==80.9.0 +setuptools==84.0.0 # via sphinx-intl six==1.16.0 - # via url-normalize -sniffio==1.2.0 + # via + # python-dateutil + # url-normalize +sniffio==1.3.1 # via # anyio + # elastic-transport + # elasticsearch # trio snowballstemmer==2.1.0 # via sphinx @@ -158,30 +164,34 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@1c192073bbc326445fbd0092d0cefcdea9962465#egg=standard_theme +standard-theme @ git+https://github.com/open-contracting/standard_theme.git@c1594b82ed4f9d75b85b3cd0dfb86b3a0d9ef965#egg=standard_theme # via -r common-requirements.in -starlette==0.47.2 +starlette==1.3.1 # via sphinx-autobuild -tomli==2.0.1 - # via pytest -trio==0.20.0 +trio==0.32.0 # via # selenium + # trio-typing # trio-websocket -trio-websocket==0.9.2 +trio-typing==0.10.0 + # via selenium +trio-websocket==0.12.2 + # via selenium +types-certifi==2021.10.8.3 # via selenium -typing-extensions==4.14.1 +types-urllib3==1.26.25.14 + # via selenium +typing-extensions==4.15.0 # via - # anyio - # cattrs + # elasticsearch # myst-parser - # starlette - # uvicorn + # selenium + # trio-typing uc-micro-py==1.0.1 # via linkify-it-py url-normalize==1.4.3 # via requests-cache -urllib3==2.5.0 +urllib3==2.7.0 # via # elastic-transport # requests @@ -189,9 +199,13 @@ urllib3==2.5.0 # selenium uvicorn==0.30.6 # via sphinx-autobuild -watchfiles==0.24.0 +watchfiles==1.1.1 # via sphinx-autobuild +websocket-client==1.9.0 + # via selenium websockets==13.0.1 # via sphinx-autobuild wsproto==1.1.0 # via trio-websocket +zipp==3.23.0 + # via importlib-metadata diff --git a/pyproject.toml b/pyproject.toml index 8cd10b2a3..66170be1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,11 @@ version = "0.0.0" [tool.ruff] line-length = 119 -target-version = "py310" [tool.ruff.lint] select = ["ALL"] ignore = [ - "ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000", + "ANN", "C901", "COM812", "CPY001", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000", "D1", "D205", "PTH", "D200", # https://github.com/astral-sh/ruff/issues/6269 @@ -33,3 +32,6 @@ ignore-variadic-names = true "D301", # click escapes "TRY003", # errors ] + +[tool.uv.pip] +exclude-newer = "1 week" diff --git a/script/diff b/script/diff index 85fb10c99..18f5fbdcc 100755 --- a/script/diff +++ b/script/diff @@ -3,14 +3,12 @@ # No -e, because diff uses exit status 1 when differences are found. set -u -curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/docs/conf.py | diff -u - docs/conf.py - -for f in .github/workflows/ci.yml .github/workflows/spellcheck.yml include/config.mk; do - curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/$f | diff -u - $f +for f in biome.json .github/workflows/ci.yml .github/workflows/lint.yml .github/workflows/spellcheck.yml docs/conf.py include/config.mk; do + curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/$f | diff -u - $f done if [ -d util ] || [ -d schema/project-level ]; then - curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/.gitignore | diff -u - .gitignore + curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/.gitignore | diff -u - .gitignore else - curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/manage.py | diff -u - manage.py + curl -sS https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/manage.py | diff -u - manage.py fi diff --git a/script/update b/script/update index a1ec03c34..ede828d36 100755 --- a/script/update +++ b/script/update @@ -5,12 +5,12 @@ set -eu main() { mkdir -p script include tests - for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/lint.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py; do - curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/$f + for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/js.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py; do + curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/$f done if [ ! -d schema/project-level ]; then - curl -sS -o .gitignore https://raw.githubusercontent.com/open-contracting/standard_profile_template/latest/.gitignore + curl -sS -o .gitignore https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/.gitignore fi chmod +x script/* diff --git a/tests/conftest.py b/tests/conftest.py index f5a6035f0..2a393883b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,6 @@ def browser(request): options = Options() options.add_argument("--headless") - options.add_argument("--no-sandbox") browser = webdriver.Chrome(options=options) browser.implicitly_wait(3) From b6031252b41e45716bd8bc65469db82516c5c6ff Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:09:10 -0400 Subject: [PATCH 36/47] chore: Run biome format . (cherry picked from commit 08a31a023d5c0b1d10164936d698e8ca50458656) --- docs/_static/custom.css | 54 ++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 355bdc65a..975421a52 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -2,7 +2,8 @@ margin: 0; padding: 0; } -.wy-side-nav-search > a, .wy-side-nav-search .wy-dropdown > a { +.wy-side-nav-search > a, +.wy-side-nav-search .wy-dropdown > a { margin: 0; padding: 0 6px; } @@ -21,8 +22,8 @@ } img[alt="org-id.guide"] { - width: 200px; - float: right; + width: 200px; + float: right; } .process-table { @@ -82,14 +83,39 @@ img[alt="org-id.guide"] { } /* renderjson.js */ -pre.renderjson { overflow: scroll; font-size:smaller; border: 1px solid grey;} -.renderjson a { text-decoration: none; } -.renderjson .disclosure { color: crimson; font-size: 150%; } -.renderjson .syntax { color: grey; } -.renderjson .string { color: darkred; } -.renderjson .number { color: darkcyan; } -.renderjson .boolean { color: blueviolet; } -.renderjson .key { color: darkblue; } -.renderjson .keyword { color: blue; } -.renderjson .object.syntax { color: lightseagreen; } -.renderjson .array.syntax { color: orange; } +pre.renderjson { + overflow: scroll; + font-size: smaller; + border: 1px solid grey; +} +.renderjson a { + text-decoration: none; +} +.renderjson .disclosure { + color: crimson; + font-size: 150%; +} +.renderjson .syntax { + color: grey; +} +.renderjson .string { + color: darkred; +} +.renderjson .number { + color: darkcyan; +} +.renderjson .boolean { + color: blueviolet; +} +.renderjson .key { + color: darkblue; +} +.renderjson .keyword { + color: blue; +} +.renderjson .object.syntax { + color: lightseagreen; +} +.renderjson .array.syntax { + color: orange; +} From b2c91b7b1b4a636dff8e8c9082c6d927dc0e1d7b Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:18:49 -0400 Subject: [PATCH 37/47] chore(css): Remove unnecessary !important (cherry picked from commit 283a307677a2a84bbe91bd5c4f05d9755efd8334) --- docs/_static/custom.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 975421a52..a5153f1f2 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -66,9 +66,9 @@ img[alt="org-id.guide"] { /* Media */ @media only print { - .wy-table-responsive { - overflow: visible !important; - } + .wy-table-responsive { + overflow: visible; + } } /* sphinx-design */ @@ -78,7 +78,7 @@ img[alt="org-id.guide"] { } .rst-content img.sd-card-img-top { width: auto; - height: 3rem !important; + height: 3rem; margin: 1rem auto 0; } From 7d056525aa90527f5537aafce1357fcacd729e69 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:20:39 -0400 Subject: [PATCH 38/47] chore: Configure biome (cherry picked from commit 8df80cde91a725f30ea2c02d21a6e826caed3880) --- biome.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 biome.json diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..4c0f77229 --- /dev/null +++ b/biome.json @@ -0,0 +1,16 @@ +{ + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true, + "defaultBranch": "main" + }, + "formatter": { + "indentStyle": "space", + "lineWidth": 119, + "includes": ["**", "!docs/examples/**/*.json", "!schema/**/*.json"] + }, + "files": { + "includes": ["**", "!docs/_static/docson", "!docs/_templates", "!docs/_static/renderjson.*"] + } +} From a546f3fb9eb1e260ff6ce3a7395e5953a09897df Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 17 Sep 2026 23:11:38 -0400 Subject: [PATCH 39/47] build: Update to latest profile template (cherry picked from commit 747ffb6813e4141aca1cd4c10462a3c61f0d1f6a) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8be55fd53..2da3211e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: [push, pull_request] jobs: - build: + ci: uses: open-contracting/.github/.github/workflows/ci-profile.yml@main permissions: contents: read From a6d5ae1442e27be29b25f8a785179d5cea16e983 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:06:52 +0000 Subject: [PATCH 40/47] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.20 → v0.16.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.20...v0.16.10) - [github.com/astral-sh/uv-pre-commit: 0.11.26 → 0.12.23](https://github.com/astral-sh/uv-pre-commit/compare/0.11.26...0.12.23) - [github.com/biomejs/pre-commit: v2.5.2 → v2.5.15](https://github.com/biomejs/pre-commit/compare/v2.5.2...v2.5.15) (cherry picked from commit 8357b17f6d63b4dd5860b730cef289b6d68988f4) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa2a642f6..2f2e1cbdb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,18 +3,18 @@ ci: skip: [pip-compile] repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.20 + rev: v0.16.10 hooks: - id: ruff-check - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.11.26 + rev: 0.12.23 hooks: - id: pip-compile name: pip-compile common-requirements.in args: [common-requirements.in, -o, common-requirements.txt] files: ^common-requirements\.(in|txt)$ - repo: https://github.com/biomejs/pre-commit - rev: v2.5.2 + rev: v2.5.15 hooks: - id: biome-check From e6171fbaea271dfc327b7487eea3769b519beb82 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Wed, 6 Apr 2022 22:01:17 -0400 Subject: [PATCH 41/47] chore: Remove setup.cfg (helper was removed in f057cdb8644aef3529635f87e06393d87ca8c568) (cherry picked from commit eb2ad4113d8969f7b914b99a286460a373e36609) --- setup.cfg | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 419f5c252..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[isort] -known_local_folder = helper From 0979c1e4094e41963b038fde3ad86de8574a64ba Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:29:08 -0400 Subject: [PATCH 42/47] chore: Format code that is only on 1.1-dev, for the profile template's ruff configuration Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W1t5wbKh4Tw84WeBDujJ2G --- docs/conf.py | 6 +++--- tests/test_docs.py | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 73b47c0e4..afdefe0a5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -113,9 +113,9 @@ r"^https://www.fcny.org/fcny/$", r"^http://www.eprocurementtoolkit.org/sites/default/files/2016-11/OCDS_Implemetation_Methodology_0.pdf#page=27$", # Ignore unwanted links created by linkify. - r'^http://buyandsell.gc.ca$', - r'^http://(?:identifier|release).id$', - r'^http://vnd\.', + r"^http://buyandsell.gc.ca$", + r"^http://(?:identifier|release).id$", + r"^http://vnd\.", # Ignore expected redirects. r"^https://docs.google.com/spreadsheets/d/[^/]+/pub?gid=\d+&single=true&output=csv$", ] diff --git a/tests/test_docs.py b/tests/test_docs.py index a0316a3bc..29f89b2ef 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -5,19 +5,16 @@ from . import languages -@pytest.mark.parametrize('lang', list(languages)) +@pytest.mark.parametrize("lang", list(languages)) # This seems to be an issue in Selenium and/or ChromeDriver. @pytest.mark.filterwarnings("ignore:unclosed Date: Thu, 23 Jan 2025 11:13:22 -0500 Subject: [PATCH 43/47] chore: Spellcheck (cherry picked from commit 6811bf2ca7d0eb03102ede82e81cbe8b8385776b) --- docs/guidance/build.md | 2 +- docs/guidance/map/organization_classifications.md | 2 +- docs/guidance/publish.md | 2 +- docs/locale/es/LC_MESSAGES/guidance/build.po | 4 ++-- .../guidance/map/organization_classifications.po | 4 ++-- .../locale/es/LC_MESSAGES/guidance/map/pre-qualification.po | 2 +- docs/locale/es/LC_MESSAGES/guidance/publish.po | 6 +++--- docs/locale/es/LC_MESSAGES/schema/identifiers.po | 2 +- docs/locale/es/LC_MESSAGES/schema/reference.po | 2 +- docs/locale/fr/LC_MESSAGES/guidance/build.po | 4 ++-- .../guidance/map/organization_classifications.po | 4 ++-- .../locale/fr/LC_MESSAGES/guidance/map/pre-qualification.po | 2 +- docs/locale/fr/LC_MESSAGES/guidance/publish.po | 6 +++--- docs/locale/fr/LC_MESSAGES/schema/identifiers.po | 2 +- docs/locale/fr/LC_MESSAGES/schema/reference.po | 2 +- docs/schema/identifiers.md | 2 +- docs/schema/reference.md | 2 +- 17 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/guidance/build.md b/docs/guidance/build.md index 10e78aaea..4eec6da63 100644 --- a/docs/guidance/build.md +++ b/docs/guidance/build.md @@ -115,7 +115,7 @@ Contact the [Data Support Team](../support/index) for guidance on customizing a **Resource:** To learn about how to create a spreadsheet input template for OCDS, check out our blog series on prototyping OCDS data using spreadsheets ([Part 1](https://www.open-contracting.org/2020/04/24/prototyping-ocds-data-using-spreadsheets/), [Part 2](https://www.open-contracting.org/2020/05/11/prototyping-ocds-data-using-spreadsheets-part-ii/), [Part 3](https://www.open-contracting.org/2020/05/28/prototyping-ocds-data-using-spreadsheets-part-iii/)). ```{note} -Re-using tools isn't always easy. [Tool Reuse in Open Contracting: A Primer](https://www.open-contracting.org/resources/tool-re-use-in-open-contracting-a-primer/) is a step-by-step guide to help you determine what you need, evaluate which tool is the right fit, and evaluate whether the right conditions are in place for successful reuse of a tool. +Reusing tools isn't always easy. [Tool Reuse in Open Contracting: A Primer](https://www.open-contracting.org/resources/tool-re-use-in-open-contracting-a-primer/) is a step-by-step guide to help you determine what you need, evaluate which tool is the right fit, and evaluate whether the right conditions are in place for successful reuse of a tool. ``` ## Build your extensions diff --git a/docs/guidance/map/organization_classifications.md b/docs/guidance/map/organization_classifications.md index 1726bcf4c..7477eb11d 100644 --- a/docs/guidance/map/organization_classifications.md +++ b/docs/guidance/map/organization_classifications.md @@ -29,7 +29,7 @@ In the example below, Moldova has disclosed information about the 'Companie mic ### Option 2: Organization classification extension -In the examples below, two different publishers have disclosed information about organizations involved in their contracting processes. An organization classification needs to consist of at least two parts: an identifier for the list (scheme) from which the classification is taken, and an identifier for the category from that list being applied. It is useful to also publish a text label and/or URI that users can draw on to interpret the classification. In the first example below, the publisher re-uses an existing `classification.scheme`. In the second example below, where a publisher wishes to track specific policy-related data, a local list of categories is used in preference to mapping to a generic set. +In the examples below, two different publishers have disclosed information about organizations involved in their contracting processes. An organization classification needs to consist of at least two parts: an identifier for the list (scheme) from which the classification is taken, and an identifier for the category from that list being applied. It is useful to also publish a text label and/or URI that users can draw on to interpret the classification. In the first example below, the publisher reuses an existing `classification.scheme`. In the second example below, where a publisher wishes to track specific policy-related data, a local list of categories is used in preference to mapping to a generic set. #### Classification schemes diff --git a/docs/guidance/publish.md b/docs/guidance/publish.md index 1624665d8..b6f949079 100644 --- a/docs/guidance/publish.md +++ b/docs/guidance/publish.md @@ -35,7 +35,7 @@ The publication policy ought to also contain information about future plans for Publishing data under an open license is an important part of open contracting. Without this, restrictions on reuse can prevent many of the important [use cases](design/user_needs) for open contracting information being realized. -A license statement sets out the permission that users have to access, use and reuse the data. This can take the form of a [Public Domain Dedication or Certification](https://creativecommons.org/publicdomain/) which transfers a dataset into the public domain, or re-asserts that there are no existing copyrights or database rights inherent in the dataset (which is the case for government datasets in some jurisdictions), or the application of a license which sets out the terms under which a dataset can be re-used. +A license statement sets out the permission that users have to access, use and reuse the data. This can take the form of a [Public Domain Dedication or Certification](https://creativecommons.org/publicdomain/) which transfers a dataset into the public domain, or re-asserts that there are no existing copyrights or database rights inherent in the dataset (which is the case for government datasets in some jurisdictions), or the application of a license which sets out the terms under which a dataset can be reused. We encourage the use of either a public domain dedication/certification, or an attribution only license. diff --git a/docs/locale/es/LC_MESSAGES/guidance/build.po b/docs/locale/es/LC_MESSAGES/guidance/build.po index a684a1fe4..64d20f232 100644 --- a/docs/locale/es/LC_MESSAGES/guidance/build.po +++ b/docs/locale/es/LC_MESSAGES/guidance/build.po @@ -548,11 +548,11 @@ msgstr "" #: ../../docs/guidance/build.md:118 msgid "" -"Re-using tools isn't always easy. [Tool Re-Use in Open Contracting: A " +"Reusing tools isn't always easy. [Tool Re-Use in Open Contracting: A " "Primer](https://www.open-contracting.org/resources/tool-re-use-in-open-" "contracting-a-primer/) is a step-by-step guide to help you determine what " "you need, evaluate which tool is the right fit, and evaluate whether the " -"right conditions are in place for successful re-use of a tool." +"right conditions are in place for successful reuse of a tool." msgstr "" "Reutilizar herramientas no siempre es fácil. [Reutilización de herramientas " "en contrataciones abiertas: Una introducción](https://www.open-" diff --git a/docs/locale/es/LC_MESSAGES/guidance/map/organization_classifications.po b/docs/locale/es/LC_MESSAGES/guidance/map/organization_classifications.po index 4e04a4789..3975fe8f1 100644 --- a/docs/locale/es/LC_MESSAGES/guidance/map/organization_classifications.po +++ b/docs/locale/es/LC_MESSAGES/guidance/map/organization_classifications.po @@ -192,7 +192,7 @@ msgid "" "the list (scheme) from which the classification is taken, and an identifier " "for the category from that list being applied. It is useful to also publish " "a text label and/or URI that users can draw on to interpret the " -"classification. In the first example below, the publisher re-uses an " +"classification. In the first example below, the publisher reuses an " "existing `classification.scheme`. In the second example below, where a " "publisher wishes to track specific policy-related data, a local list of " "categories is used in preference to mapping to a generic set." @@ -238,7 +238,7 @@ msgstr "" msgid "" "Where an appropriate scheme is not listed in the [itemClassificationScheme " "codelist](../../schema/codelists.md#item-classification-scheme), publishers " -"can specify their own scheme. Publishers can either re-use an alternative " +"can specify their own scheme. Publishers can either reuse an alternative " "scheme, or provide their own. Where publishers provide their own local " "schemes, they ought to prefix their `scheme` code with an [ISO-3166-1 " "alpha-3 country code](https://en.wikipedia.org/wiki/ISO_3166-1) to preserve " diff --git a/docs/locale/es/LC_MESSAGES/guidance/map/pre-qualification.po b/docs/locale/es/LC_MESSAGES/guidance/map/pre-qualification.po index 62badd467..f15ac02d7 100644 --- a/docs/locale/es/LC_MESSAGES/guidance/map/pre-qualification.po +++ b/docs/locale/es/LC_MESSAGES/guidance/map/pre-qualification.po @@ -443,7 +443,7 @@ msgid "" "suppliers to pre-qualify for two tenders for road construction in different " "neighbourhoods](https://contrataciones.gov.py/licitaciones/convocatoria/338229-servicios-" "consultoria-estudios-factibilidad-diseno-final-ingenieria-tramos-" -"caminos-1/precalificacion.html). Each tender will re-use the list of pre-" +"caminos-1/precalificacion.html). Each tender will reuse the list of pre-" "qualified suppliers established as a result of this first procedure." msgstr "" "El Ministerio de Obras Públicas y Comunicaciones emite una [invitación a " diff --git a/docs/locale/es/LC_MESSAGES/guidance/publish.po b/docs/locale/es/LC_MESSAGES/guidance/publish.po index f62b667f1..687fc04df 100644 --- a/docs/locale/es/LC_MESSAGES/guidance/publish.po +++ b/docs/locale/es/LC_MESSAGES/guidance/publish.po @@ -136,7 +136,7 @@ msgstr "Licencia tus datos" #: ../../docs/guidance/publish.md:36 msgid "" "Publishing data under an open license is an important part of open " -"contracting. Without this, restrictions on re-use can prevent many of the " +"contracting. Without this, restrictions on reuse can prevent many of the " "important [use cases](design/user_needs) for open contracting information " "being realized." msgstr "" @@ -149,12 +149,12 @@ msgstr "" #: ../../docs/guidance/publish.md:38 msgid "" "A license statement sets out the permission that users have to access, use " -"and re-use the data. This can take the form of a [Public Domain Dedication " +"and reuse the data. This can take the form of a [Public Domain Dedication " "or Certification](https://creativecommons.org/publicdomain/) which transfers" " a dataset into the public domain, or re-asserts that there are no existing " "copyrights or database rights inherent in the dataset (which is the case for" " government datasets in some jurisdictions), or the application of a license" -" which sets out the terms under which a dataset can be re-used." +" which sets out the terms under which a dataset can be reused." msgstr "" "Una declaración de licencia establece el permiso que los usuarios tienen " "para acceder, utilizar y volver a utilizar los datos. Esto puede tomar la " diff --git a/docs/locale/es/LC_MESSAGES/schema/identifiers.po b/docs/locale/es/LC_MESSAGES/schema/identifiers.po index 0470f8ee7..e5019a425 100644 --- a/docs/locale/es/LC_MESSAGES/schema/identifiers.po +++ b/docs/locale/es/LC_MESSAGES/schema/identifiers.po @@ -694,7 +694,7 @@ msgstr "" #: ../../docs/schema/identifiers.md:168 msgid "" -"The same `id` value may be re-used in another array of items within the same" +"The same `id` value may be reused in another array of items within the same" " release, and no cross-reference between these identifiers is implied." msgstr "" "El mismo valor `id` puede reutilizarse en otra lista de artículos en la " diff --git a/docs/locale/es/LC_MESSAGES/schema/reference.po b/docs/locale/es/LC_MESSAGES/schema/reference.po index 2dc459861..266110034 100644 --- a/docs/locale/es/LC_MESSAGES/schema/reference.po +++ b/docs/locale/es/LC_MESSAGES/schema/reference.po @@ -846,7 +846,7 @@ msgstr "Referencia de los bloques" #: ../../docs/schema/reference.md:273 msgid "" -"The following building blocks are commonly re-used throughout the standard." +"The following building blocks are commonly reused throughout the standard." msgstr "" "Los siguientes bloques de se reutilizan comúnmente en todo el estándar." diff --git a/docs/locale/fr/LC_MESSAGES/guidance/build.po b/docs/locale/fr/LC_MESSAGES/guidance/build.po index cbe3ea9e1..c2c16235f 100644 --- a/docs/locale/fr/LC_MESSAGES/guidance/build.po +++ b/docs/locale/fr/LC_MESSAGES/guidance/build.po @@ -366,11 +366,11 @@ msgstr "" #: ../../docs/guidance/build.md:3 msgid "" -"Re-using tools isn't always easy. [Tool Re-Use in Open Contracting: A " +"Reusing tools isn't always easy. [Tool Re-Use in Open Contracting: A " "Primer](https://www.open-contracting.org/resources/tool-re-use-in-open-" "contracting-a-primer/) is a step-by-step guide to help you determine what " "you need, evaluate which tool is the right fit, and evaluate whether the " -"right conditions are in place for successful re-use of a tool." +"right conditions are in place for successful reuse of a tool." msgstr "" #: ../../docs/guidance/build.md:5 diff --git a/docs/locale/fr/LC_MESSAGES/guidance/map/organization_classifications.po b/docs/locale/fr/LC_MESSAGES/guidance/map/organization_classifications.po index 45e298081..6daf34683 100644 --- a/docs/locale/fr/LC_MESSAGES/guidance/map/organization_classifications.po +++ b/docs/locale/fr/LC_MESSAGES/guidance/map/organization_classifications.po @@ -129,7 +129,7 @@ msgid "" "the list (scheme) from which the classification is taken, and an identifier " "for the category from that list being applied. It is useful to also publish " "a text label and/or URI that users can draw on to interpret the " -"classification. In the first example below, the publisher re-uses an " +"classification. In the first example below, the publisher reuses an " "existing `classification.scheme`. In the second example below, where a " "publisher wishes to track specific policy-related data, a local list of " "categories is used in preference to mapping to a generic set." @@ -155,7 +155,7 @@ msgstr "" msgid "" "Where an appropriate scheme is not listed in the [itemClassificationScheme " "codelist](../../schema/codelists.md#item-classification-scheme), publishers " -"can specify their own scheme. Publishers can either re-use an alternative " +"can specify their own scheme. Publishers can either reuse an alternative " "scheme, or provide their own. Where publishers provide their own local " "schemes, they ought to prefix their `scheme` code with an [ISO-3166-1 " "alpha-3 country code](https://en.wikipedia.org/wiki/ISO_3166-1) to preserve " diff --git a/docs/locale/fr/LC_MESSAGES/guidance/map/pre-qualification.po b/docs/locale/fr/LC_MESSAGES/guidance/map/pre-qualification.po index c439e6150..8b9d523a4 100644 --- a/docs/locale/fr/LC_MESSAGES/guidance/map/pre-qualification.po +++ b/docs/locale/fr/LC_MESSAGES/guidance/map/pre-qualification.po @@ -346,7 +346,7 @@ msgid "" "suppliers to pre-qualify for two tenders for road construction in different " "neighbourhoods](https://contrataciones.gov.py/licitaciones/convocatoria/338229-servicios-" "consultoria-estudios-factibilidad-diseno-final-ingenieria-tramos-" -"caminos-1/precalificacion.html). Each tender will re-use the list of pre-" +"caminos-1/precalificacion.html). Each tender will reuse the list of pre-" "qualified suppliers established as a result of this first procedure." msgstr "" diff --git a/docs/locale/fr/LC_MESSAGES/guidance/publish.po b/docs/locale/fr/LC_MESSAGES/guidance/publish.po index 01ecf959e..93c17c35a 100644 --- a/docs/locale/fr/LC_MESSAGES/guidance/publish.po +++ b/docs/locale/fr/LC_MESSAGES/guidance/publish.po @@ -113,7 +113,7 @@ msgstr "" #: ../../docs/guidance/publish.md:36 msgid "" "Publishing data under an open license is an important part of open " -"contracting. Without this, restrictions on re-use can prevent many of the " +"contracting. Without this, restrictions on reuse can prevent many of the " "important [use cases](design/user_needs) for open contracting information " "being realized." msgstr "" @@ -121,12 +121,12 @@ msgstr "" #: ../../docs/guidance/publish.md:38 msgid "" "A license statement sets out the permission that users have to access, use " -"and re-use the data. This can take the form of a [Public Domain Dedication " +"and reuse the data. This can take the form of a [Public Domain Dedication " "or Certification](https://creativecommons.org/publicdomain/) which transfers" " a dataset into the public domain, or re-asserts that there are no existing " "copyrights or database rights inherent in the dataset (which is the case for" " government datasets in some jurisdictions), or the application of a license" -" which sets out the terms under which a dataset can be re-used." +" which sets out the terms under which a dataset can be reused." msgstr "" "Une déclaration de licence indique les autorisations dont jouissent les " "usagers pour accéder aux données, les utiliser et les réutiliser. La Licence" diff --git a/docs/locale/fr/LC_MESSAGES/schema/identifiers.po b/docs/locale/fr/LC_MESSAGES/schema/identifiers.po index 22a2450a0..62e10a46c 100644 --- a/docs/locale/fr/LC_MESSAGES/schema/identifiers.po +++ b/docs/locale/fr/LC_MESSAGES/schema/identifiers.po @@ -688,7 +688,7 @@ msgstr "" #: ../../docs/schema/identifiers.md:168 msgid "" -"The same `id` value may be re-used in another array of items within the same" +"The same `id` value may be reused in another array of items within the same" " release, and no cross-reference between these identifiers is implied." msgstr "" "Le même identifiant peut être réutilisé dans une autre liste au sein d'une " diff --git a/docs/locale/fr/LC_MESSAGES/schema/reference.po b/docs/locale/fr/LC_MESSAGES/schema/reference.po index a87ce00e9..4c9cce18e 100644 --- a/docs/locale/fr/LC_MESSAGES/schema/reference.po +++ b/docs/locale/fr/LC_MESSAGES/schema/reference.po @@ -840,7 +840,7 @@ msgstr "Documentation sur les composants essentiels" #: ../../docs/schema/reference.md:273 msgid "" -"The following building blocks are commonly re-used throughout the standard." +"The following building blocks are commonly reused throughout the standard." msgstr "" "Les composants essentiels sont des composants couramment réutilisés dans le " "standard." diff --git a/docs/schema/identifiers.md b/docs/schema/identifiers.md index 2c6eceab0..0d1f5ee9d 100644 --- a/docs/schema/identifiers.md +++ b/docs/schema/identifiers.md @@ -165,7 +165,7 @@ Contracts must cross-reference a related award (using the `awardID` field), as k An item, document or milestone identifier must be unique within a given array of items, and must be used consistently across all the releases in a contracting process. -The same `id` value may be re-used in another array of items within the same release, and no cross-reference between these identifiers is implied. +The same `id` value may be reused in another array of items within the same release, and no cross-reference between these identifiers is implied. The use of an identifier means that subsequent releases can update prior identified items, documents or milestones, without needing to republish all the items, documents or milestones. diff --git a/docs/schema/reference.md b/docs/schema/reference.md index 334cc758c..64507ac01 100644 --- a/docs/schema/reference.md +++ b/docs/schema/reference.md @@ -270,7 +270,7 @@ See the [amendment implementation guidance](../guidance/map/amendments) for more ## Building block reference -The following building blocks are commonly re-used throughout the standard. +The following building blocks are commonly reused throughout the standard. ### OrganizationReference From eac29ead924cf72fc520f061b817777ff1c03455 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:37:47 -0400 Subject: [PATCH 44/47] build: Update to latest profile template Add sphinxcontrib-opendataservices to common requirements. Move standard_theme to uncompiled theme-requirements.txt, to track the branch instead of a commit. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RmJqv44wXNcNkarFBjvyzT --- common-requirements.in | 3 ++- common-requirements.txt | 43 ++++++++++++++++++++++++++++++++--------- requirements.txt | 3 +-- script/update | 2 +- theme-requirements.txt | 2 ++ 5 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 theme-requirements.txt diff --git a/common-requirements.in b/common-requirements.in index bc75f3234..2e26689f4 100644 --- a/common-requirements.in +++ b/common-requirements.in @@ -2,7 +2,8 @@ linkify-it-py myst-parser ocds-babel Sphinx -git+https://github.com/open-contracting/standard_theme.git@open_contracting#egg=standard_theme +sphinxcontrib-opendataservices +sphinxcontrib-opendataservices-jsonschema # Profile ocdsextensionregistry diff --git a/common-requirements.txt b/common-requirements.txt index cc4e2accf..1ed5edfd6 100644 --- a/common-requirements.txt +++ b/common-requirements.txt @@ -13,6 +13,7 @@ attrs==25.4.0 # via # cattrs # outcome + # referencing # requests-cache # trio babel==2.18.0 @@ -40,9 +41,11 @@ docutils==0.18 # via # myst-parser # sphinx + # sphinxcontrib-opendataservices + # sphinxcontrib-opendataservices-jsonschema elastic-transport==9.2.1 # via elasticsearch -elasticsearch==9.3.0 +elasticsearch[requests]==9.3.0 # via ocdsindex h11==0.16.0 # via @@ -63,10 +66,21 @@ jinja2==3.1.6 # via # myst-parser # sphinx +jscc==0.5.0 + # via sphinxcontrib-opendataservices-jsonschema json-merge-patch==0.2 - # via ocdsextensionregistry + # via + # jscc + # ocdsextensionregistry +jsonpointer==3.1.1 + # via + # sphinxcontrib-opendataservices + # sphinxcontrib-opendataservices-jsonschema jsonref==1.0.0.post1 - # via ocdsextensionregistry + # via + # jscc + # ocdsextensionregistry + # sphinxcontrib-opendataservices-jsonschema linkify-it-py==1.0.1 # via -r common-requirements.in lxml==6.1.0 @@ -84,7 +98,10 @@ mdurl==0.1.2 mypy-extensions==1.1.0 # via trio-typing myst-parser==0.18.1 - # via -r common-requirements.in + # via + # -r common-requirements.in + # sphinxcontrib-opendataservices + # sphinxcontrib-opendataservices-jsonschema ocds-babel==0.3.7 # via -r common-requirements.in ocdsextensionregistry==0.6.5 @@ -116,18 +133,21 @@ python-dateutil==2.9.0.post0 # via elasticsearch pyyaml==6.0.1 # via myst-parser +referencing==0.37.0 + # via sphinxcontrib-opendataservices-jsonschema requests==2.33.0 # via # elasticsearch + # jscc # ocdsextensionregistry # requests-cache # sphinx requests-cache==1.1.0 # via ocdsextensionregistry +rpds-py==2026.6.3 + # via referencing selenium==4.40.0 # via -r common-requirements.in -setuptools==84.0.0 - # via sphinx-intl six==1.16.0 # via # python-dateutil @@ -148,6 +168,7 @@ sphinx==5.3.0 # myst-parser # sphinx-autobuild # sphinx-intl + # sphinxcontrib-opendataservices sphinx-autobuild==2024.9.3 # via -r common-requirements.in sphinx-intl==2.2.0 @@ -160,12 +181,16 @@ sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx +sphinxcontrib-opendataservices==0.6.0 + # via -r common-requirements.in +sphinxcontrib-opendataservices-jsonschema==0.7.1 + # via + # -r common-requirements.in + # sphinxcontrib-opendataservices sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -standard-theme @ git+https://github.com/open-contracting/standard_theme.git@c1594b82ed4f9d75b85b3cd0dfb86b3a0d9ef965#egg=standard_theme - # via -r common-requirements.in starlette==1.3.1 # via sphinx-autobuild trio==0.32.0 @@ -191,7 +216,7 @@ uc-micro-py==1.0.1 # via linkify-it-py url-normalize==1.4.3 # via requests-cache -urllib3==2.7.0 +urllib3[socks]==2.7.0 # via # elastic-transport # requests diff --git a/requirements.txt b/requirements.txt index 33d3d98dd..ec8f625cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,8 @@ -r common-requirements.txt +-r theme-requirements.txt # Add your own requirements below. ocdskit==1.3.0 sphinx-design==0.4.1 sphinxcontrib-opencontracting==0.0.8 -sphinxcontrib-opendataservices-jsonschema==0.7.1 -sphinxcontrib-opendataservices==0.5.0 diff --git a/script/update b/script/update index ede828d36..d3590428e 100755 --- a/script/update +++ b/script/update @@ -5,7 +5,7 @@ set -eu main() { mkdir -p script include tests - for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/js.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py; do + for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/js.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py theme-requirements.txt; do curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/$f done diff --git a/theme-requirements.txt b/theme-requirements.txt new file mode 100644 index 000000000..dd74015dd --- /dev/null +++ b/theme-requirements.txt @@ -0,0 +1,2 @@ +# Not compiled, so that the theme tracks the branch instead of a commit. +git+https://github.com/open-contracting/standard_theme.git@open_contracting#egg=standard_theme From d19e3bd1d511cd748fbf552e456e4c7691ea18c1 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:59:34 -0400 Subject: [PATCH 45/47] feat: Render the banner and version switcher in the theme, not with Apache's server-side includes The theme reads a versions.json served next to the documentation root, which any host can serve, in place of the `$BANNER` and version-options includes that Apache's mod_include resolves at request time. `versions_url` defaults to it, so only the block goes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UAeUtu5XGYmmSwtyjVU7gL --- docs/_templates/layout.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 5310a83dc..ad30f4dd5 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -29,7 +29,3 @@ {% endblock %} - -{% block version_options %} - -{% endblock %} From 3067ea7773d55351cb900fb8d63580ede58867b8 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:59:34 -0400 Subject: [PATCH 46/47] feat: Build the language switcher from the languages theme option The option also emits relative links as the no-JavaScript fallback, in place of a form posted to `{root}/{version}/switcher`. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UAeUtu5XGYmmSwtyjVU7gL --- docs/_templates/layout.html | 9 --------- docs/conf.py | 1 + 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index ad30f4dd5..07bded6c7 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -20,12 +20,3 @@
  • {% trans %}Data Registry{% endtrans %}
  • {% endblock %} - -{% block language_options %} - - - - - - -{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index afdefe0a5..a3bddcfbb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -83,6 +83,7 @@ "analytics_id": "HTWZHRIZ", "display_version": True, "root_url": f"/profiles/{profile_identifier}" if profile_identifier else "", + "languages": {"en": "English", "es": "Español", "fr": "Français"}, "short_project": project.replace("Open Contracting Data Standard", "OCDS"), "copyright": copyright, "license_name": "Apache License 2.0", From 2467afa627881aeaccb779346370f198c74b5680 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:28:26 -0400 Subject: [PATCH 47/47] build: Drop the settings that the theme now provides The theme registers itself and its catalogs through the `sphinx.html_themes` entry point, and defaults the options that every documentation repository set alike, ships the favicon, and adds the privacy notice to its footer. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UAeUtu5XGYmmSwtyjVU7gL --- docs/_static/favicon-16x16.ico | Bin 1406 -> 0 bytes docs/_templates/footer.html | 5 ----- docs/conf.py | 9 +-------- script/update | 2 +- 4 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 docs/_static/favicon-16x16.ico delete mode 100644 docs/_templates/footer.html diff --git a/docs/_static/favicon-16x16.ico b/docs/_static/favicon-16x16.ico deleted file mode 100644 index 40159444df4205bf4b951f36bf7677998df5f7ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1406 zcmeH{-A|HX7{-4H6n=$Tni`2f0z!tpydqc$scA-l#jCcJ9eZN6va+)_w>oOM)ojDQ z*E*?;YT`$!o2GNknsZ`SYW1J=(7C5=ZQjp*&+}Z@!M)wr!3`>La&i#2j!rEU0Cs_} zC>5mqjmj%Yc-?NQWf^Z(71cf;9)B&KfFG%$9#4vFgOn2bQi*D$jROdEp$cE0-wkxrpm(H^yt-xOy&A(R-bWo4pj?x`Fe~ zZJc-eF!%T2i1p(bxQF9m4Ev)Pna>AMy}Xa=)dN)V$ATxMy?KiI-80e?FR%^8DI9){ zZgdb^B98j~AlBix=spaRIUc8IVhHQ_2%4!OK?3W9_)jO$P7kB`I70E%7@C<;a%aaV zo0%lz(u)2hKHA%@@5~IMhwoJ+TGRF0< zn0~Hc`t=Rd@9!JMz3Fcz@V_UJSFA~mpefw`cM^SF%O0^`be2$Et}wE^iV~G_^scC) z9LOfqrHG1F`%{l1Wt9dMr8RQ6;!x8`p@SxEfgC#KDR=k+4aYL1r1;qi%H diff --git a/docs/_templates/footer.html b/docs/_templates/footer.html deleted file mode 100644 index 91589f404..000000000 --- a/docs/_templates/footer.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "!footer.html" %} - -{% block extrafooter %} -{% trans %}Privacy notice{% endtrans %}. -{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index a3bddcfbb..ce755efa9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,6 @@ from glob import glob from pathlib import Path -import standard_theme from docutils.nodes import make_id from ocds_babel.translate import translate from ocdskit.mapping_sheet import mapping_sheet @@ -44,8 +43,6 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "standard_theme" # 'pydata_sphinx_theme' -html_theme_path = [standard_theme.get_html_theme_path()] -html_favicon = "_static/favicon-16x16.ico" html_static_path = ["_static"] html_css_files = ["custom.css"] html_js_files = ["renderjson.js", "script.js"] @@ -62,7 +59,7 @@ gettext_compact = False # `DOMAIN_PREFIX` from `config.mk`. gettext_domain_prefix = f"{profile_identifier}-" if profile_identifier else "" -locale_dirs = ["locale/", os.path.join(standard_theme.get_html_theme_path(), "locale")] +locale_dirs = ["locale/"] # We use single quotes for codes, which docutils will change to double quotes. # https://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/utils/smartquotes.py smartquotes = False @@ -80,14 +77,10 @@ "analytics_id": "HTWZHRIZ", } html_theme_options = { - "analytics_id": "HTWZHRIZ", "display_version": True, "root_url": f"/profiles/{profile_identifier}" if profile_identifier else "", "languages": {"en": "English", "es": "Español", "fr": "Français"}, "short_project": project.replace("Open Contracting Data Standard", "OCDS"), - "copyright": copyright, - "license_name": "Apache License 2.0", - "license_url": f"{repository_url}/blob/HEAD/LICENSE", "repository_url": repository_url, } html_short_title = f"{html_theme_options['short_project']} v{release}" diff --git a/script/update b/script/update index d3590428e..3be6fa8ca 100755 --- a/script/update +++ b/script/update @@ -5,7 +5,7 @@ set -eu main() { mkdir -p script include tests - for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/js.yml .github/workflows/shell.yml docs/_static/favicon-16x16.ico include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py theme-requirements.txt; do + for f in .pre-commit-config.yaml .python-version Makefile common-requirements.in common-requirements.txt pyproject.toml .github/dependabot.yml .github/workflows/js.yml .github/workflows/shell.yml include/common.mk include/prologue.mk script/diff script/update tests/conftest.py tests/test_common.py theme-requirements.txt; do curl -sS -o $f https://raw.githubusercontent.com/open-contracting/standard_profile_template/refs/heads/latest/$f done