From e940761cf4f0d2505ce95f92ce12fb568d2de67a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 26 Sep 2026 08:17:42 +0000 Subject: [PATCH 1/5] Updated from generate-constraints Pin versions for packages that are not ready for updating: - pyroute2: Nova/os-vif issues - sqlalchemy: broken compatibility with sqlalchemy-utils - pyparsing: due to regression in 3.3.3[1] [1] https://github.com/pyparsing/pyparsing/issues/672 Co-Authored-by: Dr. Jens Harbott Co-Authored-by: Takashi Kajinami Change-Id: I98b8c4c0c778dcd2cb20f2e830fb80d99351a228 Signed-off-by: OpenStack Proposal Bot Generated-By: openstack/project-config:playbooks/proposal/propose_update.sh --- upper-constraints.txt | 238 +++++++++++++++++++++++------------------- 1 file changed, 131 insertions(+), 107 deletions(-) diff --git a/upper-constraints.txt b/upper-constraints.txt index 757449101..b44ddc0be 100644 --- a/upper-constraints.txt +++ b/upper-constraints.txt @@ -8,15 +8,15 @@ voluptuous===0.16.0 chardet===6.0.0.post1 enum-compat===0.0.3 -netmiko===4.7.0 +netmiko===4.8.0 sshtunnel===0.4.0 PasteDeploy===3.1.0 Routes===2.5.1 rtslib-fb===2.2.4 oslo.limit===2.12.0 restructuredtext_lint===2.0.2 -yarl===1.24.5 -tzdata===2026.3 +yarl===1.25.1 +tzdata===2026.4 smmap===5.0.3 confget===5.1.2 XStatic-Angular-Bootstrap===2.5.0.1 @@ -24,34 +24,34 @@ WebOb===1.8.11 sphinxcontrib-actdiag===3.0.0 pecan===1.8.0 os-api-ref===3.2.0 -python-ldap===3.4.7 +python-ldap===3.4.8 oslo.concurrency===7.6.1 -websocket-client===1.9.0 +websocket-client===1.9.2 osprofiler===4.4.0 os-resource-classes===1.1.0 mypy_extensions===1.1.0 tabulate===0.10.0 python-ironic-inspector-client===5.4.0 -lxml===6.1.1 +lxml===6.1.3 vintage===0.4.1 rst2txt===1.1.0 setproctitle===1.3.7 pytest===9.1.1 a2wsgi===1.10.10 -python-slugify===8.0.4 +python-slugify===9.1.1 cursive===0.3.1 oslo.service===4.8.0 django-appconf===1.2.0 -ntc_templates===9.2.0 +ntc_templates===9.3.0 sphinxcontrib-nwdiag===2.0.0 rbd-iscsi-client===0.1.8 alabaster===1.0.0 -multidict===6.7.1 +multidict===6.9.1 pbr===7.0.3 munch===4.0.0 waiting===1.5.0 attrs===26.1.0 -jwcrypto===1.5.8 +jwcrypto===1.6.1 Pint===0.25.3 oslo.i18n===6.9.0 jsonpath-rw-ext===1.2.2 @@ -59,82 +59,90 @@ python-mistralclient===6.3.0 oslo.context===6.5.0 rcssmin===1.2.2 pycadf===4.1.0 -grpcio===1.83.0 +grpcio===1.84.0 sniffio===1.3.1 fixtures===4.3.2 neutron-lib===5.0.1 XStatic-FileSaver===1.3.2.1 jaraco.functools===4.6.0 +annotated-doc===0.0.5 +jaraco.text===4.3.0 oslo.metrics===0.17.0 storage-interfaces===1.0.5 -pydantic===2.13.4 +pydantic===2.13.5 pystache===0.6.8 XStatic-Font-Awesome===6.2.1.2 aiohttp===3.14.3 waitress===3.0.2 os-refresh-config===14.0.1 -pysnmp===7.1.28 -Mako===1.4.1 +pysnmp===7.1.29 +Mako===1.4.3 +vultr===1.0.1 sphinxcontrib-htmlhelp===2.1.0 XStatic-jQuery===3.7.1.1 sphinx-copybutton===0.5.2 beartype===0.22.9 +jaraco.collections===5.2.1 ddt===1.7.2 pyserial===3.5 -moto===5.2.2 +moto===5.2.3 infi.dtypes.wwn===0.2.0 -awscrt===0.36.2 +awscrt===0.37.0 pcre2===0.7.1 python-freezerclient===6.5.0 -py-pure-client===1.93.0 +py-pure-client===1.94.2 krest===1.3.8 -psycopg2===2.9.12 +psycopg2===2.9.13 networkx===3.6.1 cheroot===11.1.2 XStatic-Angular===1.8.2.3 zuul-sphinx===0.8.1 ply===3.11 -google-api-core===2.34.0 +google-api-core===2.39.0 requests-toolbelt===1.0.0 -simplejson===4.1.1 +simplejson===4.1.2 python-swiftclient===4.11.0 -pyOpenSSL===24.2.1 +shellingham===1.5.4 +pyOpenSSL===26.4.0 typing-inspection===0.4.4 monasca-common===3.8.0 hyperframe===6.1.0 -zeroconf===0.150.0 +zeroconf===0.151.3 scipy===1.17.1 -opentelemetry-exporter-otlp===1.44.0 +opentelemetry-exporter-otlp===1.45.0 +python-gnupg===0.5.6 rsd-lib===1.2.0 XStatic-Jasmine===2.4.1.3 -googleapis-common-protos===1.75.1 +googleapis-common-protos===1.75.4 python-glanceclient===4.13.0 prometheus_client===0.26.0 jaraco.classes===3.4.0 +typer-slim===0.24.0 debtcollector===3.1.0 -responses===0.26.2 +responses===0.26.3 prompt_toolkit===3.0.53 croniter===6.2.4 horizon===27.0.0 octavia-lib===3.12.0 python-watcherclient===4.11.0 MarkupSafe===3.0.3 +cloudpickle===3.1.2 doc8===2.0.0 -pymongo===4.17.0 +pymongo===4.18.2 python-cloudkittyclient===6.2.0 -soupsieve===2.9.2 +soupsieve===2.10 sqlparse===0.6.0 oslotest===6.1.1 jsonpointer===3.1.1 defusedxml===0.7.1 -opentelemetry-sdk===1.44.0 +opentelemetry-sdk===1.45.0 netaddr===1.3.0 pyghmi===1.6.19 sphinxcontrib-blockdiag===3.0.0 aiosqlite===0.22.1 priority===2.0.0 gnocchiclient===7.2.0 -wcwidth===0.8.2 +wcwidth===0.9.1 sphinxcontrib.datatemplates===0.11.0 jsonpath-rw===1.4.0 prettytable===3.18.0 @@ -143,14 +151,14 @@ pathspec===1.1.1 taskflow===6.4.0 arrow===1.4.0 semantic-version===2.10.0 -ConfigArgParse===1.7.5 +ConfigArgParse===1.7.7 async-timeout===5.0.1 virtualbmc===3.3.0 -SQLAlchemy===2.0.52 +SQLAlchemy===2.0.54 pyroute2===0.8.1 -google-auth===2.56.3 +google-auth===2.58.1 kazoo===2.11.0 -pyspnego===0.12.1 +pyspnego===0.12.2 trio-websocket===0.12.2 XStatic-roboto-fontface===0.8.0.1 pyudev===0.24.4 @@ -158,41 +166,45 @@ eventlet===0.41.2 openstack-doc-tools===4.0.3 oslo.messaging===18.3.0 jira===3.10.5 -PyJWT===2.13.0 +PyJWT===2.15.0 typing_extensions===4.16.0 zVMCloudConnector===1.6.3 -paramiko===4.0.0 +paramiko===5.0.0 ifaddr===0.2.0 reno===4.1.0 ncclient===0.7.1 -imagesize===2.0.0 +imagesize===2.0.1 pydot===4.0.1 -urllib3===2.7.0 +urllib3===2.8.0 graphviz===0.21 -PyKMIP===0.10.0 +portend===3.2.1 +PyKMIP===0.11.0 python-observabilityclient===1.4.0 whereto===0.6.0 networking-generic-switch===10.1.0 -pywbem===1.9.0 +pywbem===1.9.1 python-subunit===1.4.6 +tornado===6.5.10 pycparser===3.0 mock===5.2.0 PyYAML===6.0.3 beautifulsoup4===4.15.0 -ovs===3.7.1 -cryptography===43.0.3 +ovs===4.0.0 +cryptography===50.0.1 httpcore===1.0.9 URLObject===3.0.0 -psycopg2-binary===2.9.12 +psycopg2-binary===2.9.13 glance_store===5.7.0 openstack-release-test===8.5.0 +pycryptodomex===3.23.0 requests-mock===1.12.1 os-apply-config===14.0.1 -gunicorn===26.0.0 +xxhash===4.0.1 +gunicorn===26.2.0 storpool===7.3.0 textfsm===2.1.0 python-3parclient===4.4 -libvirt-python===12.6.0 +libvirt-python===12.7.0 python-zunclient===5.5.0 tzlocal===5.4.4 sysv_ipc===1.2.0 @@ -207,7 +219,7 @@ os-client-config===2.3.0 XStatic-Angular-Gettext===2.4.1.1 Deprecated===1.3.1 h11===0.16.0 -Pygments===2.20.0 +Pygments===2.21.0 XStatic-Hogan===2.0.0.5 api_object_schema===2.0.0 XStatic-objectpath===1.2.1.1 @@ -219,10 +231,11 @@ Jinja2===3.1.6 XStatic-Bootstrap-SCSS===3.4.1.1 pyzabbix===1.3.1 ptyprocess===0.7.0 -amqp===5.3.1 +amqp===5.4.0 ruamel.yaml===0.19.1 websockify===0.13.0 -gssapi===1.11.1 +CherryPy===18.10.0 +gssapi===1.12.0 XStatic-JQuery.quicksearch===2.0.3.3 pyasn1_modules===0.4.2 mpmath===1.3.0 @@ -233,8 +246,9 @@ sphinx-feature-classification===2.1.0 XStatic-JQuery-Migrate===3.3.2.2 pytest-html===4.2.0 appdirs===1.4.4 -google-auth-httplib2===0.4.1 +google-auth-httplib2===0.4.2 daiquiri===3.4.0 +apache-libcloud===3.9.1 influxdb===5.3.2 funcparserlib===2.0.0a0 passlib===1.7.4 @@ -243,39 +257,42 @@ os-brick===7.1.1 valkey===6.1.1 scp===0.16.1 lark===1.3.1 +opentelemetry-exporter-prometheus===0.66b0 python-zaqarclient===4.7.0 ldappool===3.0.0 hpack===4.2.0 -joblib===1.5.3 +joblib===1.6.0 roman-numerals===4.1.0 -google-api-python-client===2.198.0 +google-api-python-client===2.200.0 castellan===5.8.0 oslo.versionedobjects===3.12.0 enmerkar===0.7.1 +typer===0.27.2 webcolors===25.10.0 aodhclient===3.12.0 autobahn===26.7.1 +truststore===0.10.4 SQLAlchemy-Utils===0.42.1 retryz===0.1.9 pluggy===1.6.0 -coverage===7.15.4 +coverage===7.16.1 pyee===13.0.1 freezegun===1.5.5 mdurl===0.1.2 toml===0.10.2 -pycdlib===1.20.0 +pycdlib===1.21.0 pyperclip===1.11.0 cassandra-driver===3.30.1 XStatic-Angular-Schema-Form===0.8.13.1 -opentelemetry-exporter-otlp-proto-http===1.44.0 -gabbi===4.2.0 +opentelemetry-exporter-otlp-proto-http===1.45.0 +gabbi===4.2.3 nwdiag===3.0.0 XStatic-bootswatch===3.3.7.1 annotated-types===0.8.0 pytest-xdist===3.8.0 XStatic-JS-Yaml===3.13.1.2 XStatic-term.js===0.0.7.1 -oslo.log===8.3.1 +oslo.log===8.3.2 nodeenv===1.10.0 gossip===2.5.0 suds-community===1.2.0 @@ -283,13 +300,13 @@ os_vif===5.2.1 qrcode===8.2 oslo.middleware===8.2.0 XStatic-mdi===1.6.50.3 -pydantic_core===2.46.4 +pydantic_core===2.46.5 uritemplate===4.2.0 docutils===0.21.2 -threadpoolctl===3.6.0 +threadpoolctl===3.7.0 os-ken===4.2.2 -ujson===5.13.0 -selenium===4.47.0 +ujson===6.0.0 +selenium===4.49.0 pytest-subtests===0.15.0 mistral-lib===3.6.0 dogtag-pki===11.2.1 @@ -298,11 +315,11 @@ dfs_sdk===1.2.27 sphinxcontrib-seqdiag===3.0.0 os-win===5.9.0 capacity===1.3.14 -playwright===1.62.0 +playwright===1.63.0 markdown-it-py===4.2.0 retrying===1.4.2 -python-discovery===1.5.2 -platformdirs===4.11.3 +python-discovery===1.6.1 +platformdirs===4.11.15 pydotplus===2.0.2 boto3===1.35.99 jeepney===0.9.0 @@ -311,7 +328,7 @@ pillow===12.3.0 infoblox-client===0.6.2 oslo.serialization===5.11.0 warlock===2.1.0 -exabgp===5.0.10 +exabgp===5.0.13 aiomysql===0.3.2 sphinxcontrib-httpdomain===2.0.0 metalsmith===2.5.0 @@ -322,7 +339,7 @@ oslo.vmware===4.10.1 autopage===0.6.0 gitdb===4.0.12 python-monascaclient===2.8.0 -opentelemetry-api===1.44.0 +opentelemetry-api===1.45.0 frozenlist===1.8.0 automaton===3.5.0 os-service-types===1.9.0 @@ -330,17 +347,17 @@ keyring===25.7.0 elementpath===4.8.0 wsgi_intercept===1.13.1 jsonschema-specifications===2025.9.1 -testscenarios===0.6.2 +testscenarios===0.7.0 sphinxcontrib-pecanwsme===0.11.0 sadisplay===0.4.9 enum34===1.1.10 infinisdk===289.1.4 rich-argparse===1.8.0 -packaging===26.3 -opentelemetry-exporter-otlp-proto-grpc===1.44.0 +packaging===24.0 +opentelemetry-exporter-otlp-proto-grpc===1.45.0 psutil===7.2.2 txaio===26.6.1 -elasticsearch===9.5.0 +elasticsearch===9.5.1 asgiref===3.12.1 XStatic-JQuery.TableSorter===2.14.5.3 pifpaf===3.4.0 @@ -361,19 +378,19 @@ SecretStorage===3.5.0 XStatic-Rickshaw===1.5.1.3 iso8601===2.1.0 tooz===9.1.0 -idna===3.18 +idna===3.20 Hypercorn===0.18.0 yamlloader===1.6.0 -protobuf===7.35.1 +protobuf===7.36.2 sushy===5.13.0 python-neutronclient===14.0.0 pika===1.4.4 oslo.cache===4.3.0 WebTest===3.0.7 os-collect-config===14.0.1 -edgegrid-python===2.0.7 +edgegrid-python===2.0.8 python-octaviaclient===3.15.0 -pysaml2===7.5.4 +pysaml2===7.5.5 requests-oauthlib===2.0.0 oslo.reports===3.9.0 bitmath===2.1.1 @@ -385,8 +402,9 @@ PyNaCl===1.6.2 osc-lib===4.7.0 more-itertools===11.1.0 seqdiag===3.0.0 +zc.lockfile===4.0 numpy===2.4.6 -msgpack===1.2.1 +msgpack===1.2.2 Sphinx===9.0.4 oslo.config===10.7.0 openstackdocstheme===3.6.0 @@ -395,27 +413,29 @@ rpds-py===2026.6.3 zake===0.2.2 flux===1.4.0 flexparser===0.4 -krb5===0.9.0 -PyMySQL===1.2.0 +krb5===0.10.0 +PyMySQL===1.2.3 uhashring===2.5 kubernetes===36.0.3 httplib2===0.32.0 +opentelemetry-exporter-otlp-common===0.66b0 betamax===0.9.0 construct===2.10.70 pytest-metadata===3.1.1 pyparsing===3.3.2 geomet===1.1.0 -opentelemetry-exporter-otlp-proto-common===1.44.0 +opentelemetry-exporter-otlp-proto-common===1.45.0 distlib===0.4.3 -ast_serialize===0.8.0 +ast_serialize===0.11.2 dogpile.cache===1.5.0 python-barbicanclient===7.6.0 -salt===3008.0 -opentelemetry-semantic-conventions===0.65b0 +salt===3008.2 +opentelemetry-semantic-conventions===0.66b0 blinker===1.9.0 WSME===0.13.0 oslo.upgradecheck===2.8.0 sherlock===0.4.1 +tempora===5.12.0 stevedore===5.9.1 botocore===1.35.99 xmltodict===1.0.4 @@ -424,12 +444,12 @@ oslo.rootwrap===7.10.0 Django===5.2.17 pexpect===4.9.0 elastic-transport===9.4.2 -cmd2===4.2.0 -python-json-logger===4.1.0 +cmd2===4.2.4 +python-json-logger===4.2.0 redis===8.1.0 jmespath===1.1.0 cbor2===6.1.4 -click===8.4.2 +click===8.5.0 XStatic-smart-table===1.4.13.3 kuryr-lib===3.5.0 jsonpatch===1.33 @@ -442,35 +462,39 @@ systemd-python===235 python-memcached===1.62 openstacksdk===4.20.0 infi.dtypes.nqn===0.1.0 +looseversion===1.3.0 six===1.17.0 h2===4.4.1 -dulwich===1.2.12 +dulwich===1.2.15 sentinels===1.1.1 kombu===5.6.2 distro===1.9.0 zstd===1.5.7.2 yaql===3.2.0 -durationpy===0.10 +durationpy===0.11 requestsexceptions===1.4.0 testresources===2.1.2 falcon===4.3.1 tomlkit===0.15.1 +opentelemetry-exporter-http-transport===0.66b0 etcd3gw===2.7.0 +timelib===0.3.0 +rpm_vercmp===0.1.2 Flask-RESTful===0.3.10 -GitPython===3.1.59 +GitPython===3.1.62 python-ironicclient===6.3.0 babel===2.18.0 XStatic===1.0.3 XStatic-Angular-FileUpload===12.2.13.2 python-openstackclient===10.3.0 -pyzmq===27.1.0 +pyzmq===27.2.0 oslo.db===18.1.1 simplegeneric===0.8.1 yappi===1.7.6 mbstrdecoder===1.1.5 wsproto===1.3.2 pymemcache===4.0.0 -wrapt===2.3.0 +wrapt===2.4.1 PySocks===1.7.1 oslo.privsep===3.12.0 sphinxcontrib-apidoc===0.6.0 @@ -499,28 +523,28 @@ hpe-storage-flowkit-py===0.9.5 cachez===0.1.2 XStatic-Bootstrap-Datepicker===1.4.0.1 netifaces===0.11.0 -propcache===0.5.2 -cachetools===7.1.7 +propcache===0.5.4 +cachetools===7.2.0 flexcache===0.3 sphinxcontrib-qthelp===2.0.0 keystoneauth1===5.17.0 statsd===4.0.1 -proto-plus===1.28.3 +proto-plus===1.28.4 python-keystoneclient===6.0.0 -diskimage-builder===3.42.0 +diskimage-builder===3.43.0 py-consul===1.7.1 heat-translator===3.5.0 python-magnumclient===5.0.0 docker===7.2.0 repoze-lru===0.8 storops===1.2.12 -anyio===4.14.2 +anyio===4.15.1 aiosignal===1.4.0 XStatic-Angular-lrdragndrop===1.0.2.7 ovsdbapp===2.19.0 aniso8601===10.0.1 rjsmin===1.2.5 -icalendar===7.2.2 +icalendar===7.3.0 configparser===7.2.0 decorator===5.3.1 DateTimeRange===2.3.2 @@ -530,36 +554,36 @@ futurist===3.5.0 jsonschema===4.26.0 sphinxcontrib-devhelp===2.0.0 python-blazarclient===4.6.0 -alembic===1.19.1 +alembic===1.20.0 execnet===2.1.2 sphinxcontrib-programoutput===0.20 storpool.spopenstack===3.2.0 dnspython===2.8.0 oauthlib===3.3.1 zipp===4.1.0 -greenlet===3.5.5 +greenlet===3.5.6 XStatic-Angular-Vis===4.16.0.1 iniconfig===2.3.0 referencing===0.37.0 -confluent-kafka===2.15.0 +confluent-kafka===2.15.1 backports.tarfile===1.2.0 -narwhals===2.24.0 -xvfbwrapper===0.2.29 +narwhals===2.26.0 +xvfbwrapper===0.2.34 influxdb-client===1.50.0 -tosca-parser===2.14.0 +tosca-parser===2.15.0 python-consul2===0.1.5 -charset-normalizer===3.5.0 +charset-normalizer===3.5.1 Flask===3.1.3 httpx===0.28.1 sqlalchemy-filters===0.13.0 sphinxcontrib-runcmd===0.2.0 confspirator===0.3.0 fasteners===0.20 -importlib_metadata===9.0.0 +importlib_metadata===9.0.1 sortedcontainers===2.4.0 microversion_parse===2.1.0 -python-linstor===1.29.0 -filelock===3.32.3 +python-linstor===1.29.1 +filelock===4.0.3 python-tackerclient===2.6.0 python-heatclient===5.3.0 oslo.utils===10.2.0 @@ -568,14 +592,14 @@ itsdangerous===2.2.0 XStatic-jquery-ui===1.13.0.2 monasca-statsd===2.7.0 python-dateutil===2.9.0.post0 -virtualenv===21.7.4 +virtualenv===21.12.1 colorama===0.4.6 confetti===2.5.3 ironic-lib===7.0.0 aiohappyeyeballs===2.7.1 -pytz===2026.3.post1 -opentelemetry-proto===1.44.0 +pytz===2026.4 +opentelemetry-proto===1.45.0 XStatic-D3===3.5.17.1 actdiag===3.0.0 sphinxcontrib-applehelp===2.0.0 -scikit-learn===1.9.0 +scikit-learn===1.9.1 From 3d18c773440f6e000a33d6de67f05f95fe8e95e5 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 28 Sep 2026 23:45:42 +0900 Subject: [PATCH 2/5] Downgrade salt salt 3008.2 pins the packaging library to 24.0, which is not compatible with the latest tox. The latest tox requires the packaging.pylock module which was added in packaging 26.0. [1] e940761cf4f0d2505ce95f92ce12fb568d2de67a Change-Id: Ib1eae8b3e3752bba4bc6e75ecb9c6c25b38be58b Signed-off-by: Takashi Kajinami --- upper-constraints.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/upper-constraints.txt b/upper-constraints.txt index b44ddc0be..24ba51218 100644 --- a/upper-constraints.txt +++ b/upper-constraints.txt @@ -353,7 +353,7 @@ sadisplay===0.4.9 enum34===1.1.10 infinisdk===289.1.4 rich-argparse===1.8.0 -packaging===24.0 +packaging===26.3 opentelemetry-exporter-otlp-proto-grpc===1.45.0 psutil===7.2.2 txaio===26.6.1 @@ -429,7 +429,7 @@ distlib===0.4.3 ast_serialize===0.11.2 dogpile.cache===1.5.0 python-barbicanclient===7.6.0 -salt===3008.2 +salt===3008.0 opentelemetry-semantic-conventions===0.66b0 blinker===1.9.0 WSME===0.13.0 From b3ccdfb30f19877d17ab000d2778fddb7470cb7b Mon Sep 17 00:00:00 2001 From: Michal Nasiadka Date: Wed, 8 Jul 2026 12:57:59 +0200 Subject: [PATCH 3/5] Do not fail on optional dependencies Some of the optional dependencies are quite specific to projects and we may not want to maintain these fully in global requirements. Change-Id: I27af77991b850249d9fef3aa60c3645b83ad6cc4 Signed-off-by: Michal Nasiadka --- openstack_requirements/check.py | 46 ++++++--- openstack_requirements/project.py | 8 +- openstack_requirements/tests/test_check.py | 108 ++++++++++++++++++--- 3 files changed, 132 insertions(+), 30 deletions(-) diff --git a/openstack_requirements/check.py b/openstack_requirements/check.py index c6a5d6637..502ff3fb9 100644 --- a/openstack_requirements/check.py +++ b/openstack_requirements/check.py @@ -36,6 +36,7 @@ class RequirementsList: def __init__(self, name: str, project: Project) -> None: self.name = name self.reqs_by_file: dict[str, dict[str, set[str]]] = {} + self.optional_reqs_by_file: dict[str, dict[str, set[str]]] = {} self.project = project self.failed = False @@ -112,7 +113,7 @@ def process(self, strict: bool = True) -> None: print(f"Processing {fname} (extras)") for name, content in extras.items(): print(f" Processing {name!r} extra") - self.reqs_by_file[f'{fname} ({name!r} extra)'] = ( + self.optional_reqs_by_file[f'{fname} ({name!r} extra)'] = ( self.extract_reqs(content, strict) ) @@ -120,9 +121,9 @@ def process(self, strict: bool = True) -> None: print(f"Processing {fname} (dependency-groups)") for name, content in groups.items(): print(f" Processing {name!r} dependency group") - self.reqs_by_file[f'{fname} ({name!r} dependency group)'] = ( - self.extract_reqs(content, strict) - ) + self.optional_reqs_by_file[ + f'{fname} ({name!r} dependency group)' + ] = self.extract_reqs(content, strict) def _get_exclusions(req): @@ -257,6 +258,8 @@ def _validate_one( denylist, global_reqs, backports, + *, + is_optional, ): """Returns True if there is a failure.""" @@ -267,6 +270,9 @@ def _validate_one( return False if name not in global_reqs: + if is_optional: + return False + print(f"ERROR: Requirement '{reqs}' not in openstack/requirements") return True @@ -319,18 +325,26 @@ def validate( failed = False # iterate through the changing entries and see if they match the global # equivalents we want enforced - for fname, freqs in head_reqs.reqs_by_file.items(): - print(f"Validating {fname}") - for name, reqs in freqs.items(): - failed = ( - _validate_one( - name, - reqs, - denylist, - global_reqs, - backports, + # note that extras and dependency groups are project-specific and may not + # be present in global-requirements, so we only note their absence rather + # than failing if so + for reqs_by_file, is_optional in ( + (head_reqs.reqs_by_file, False), + (head_reqs.optional_reqs_by_file, True), + ): + for fname, freqs in reqs_by_file.items(): + print(f"Validating {fname}") + for name, reqs in freqs.items(): + failed = ( + _validate_one( + name, + reqs, + denylist, + global_reqs, + backports, + is_optional=is_optional, + ) + or failed ) - or failed - ) return failed diff --git a/openstack_requirements/project.py b/openstack_requirements/project.py index 6924a9c08..3676c9e0d 100644 --- a/openstack_requirements/project.py +++ b/openstack_requirements/project.py @@ -164,10 +164,14 @@ def verify_pyproject_toml(root: str) -> bool: return False if 'build-system' not in data: - print("pyproject.toml is missing 'build-system' table", file=sys.stderr) + print( + "pyproject.toml is missing 'build-system' table", file=sys.stderr + ) return False - if (build_backend := data['build-system'].get('build-backend')) != 'pbr.build': + if ( + build_backend := data['build-system'].get('build-backend') + ) != 'pbr.build': print( f"pyproject.toml has invalid 'build-system.build-backend'. " f"Expected 'pbr.build'; got {build_backend!r}", diff --git a/openstack_requirements/tests/test_check.py b/openstack_requirements/tests/test_check.py index 09b28a581..881228323 100644 --- a/openstack_requirements/tests/test_check.py +++ b/openstack_requirements/tests/test_check.py @@ -44,11 +44,15 @@ def test_extras__setup_cfg(self): req_list = check.RequirementsList('test-project', project_data) req_list.process(strict=False) - self.assertIn("setup.cfg ('test' extra)", req_list.reqs_by_file) - self.assertIn("setup.cfg ('dev' extra)", req_list.reqs_by_file) + self.assertIn( + "setup.cfg ('test' extra)", req_list.optional_reqs_by_file + ) + self.assertIn( + "setup.cfg ('dev' extra)", req_list.optional_reqs_by_file + ) - test_reqs = req_list.reqs_by_file["setup.cfg ('test' extra)"] - dev_reqs = req_list.reqs_by_file["setup.cfg ('dev' extra)"] + test_reqs = req_list.optional_reqs_by_file["setup.cfg ('test' extra)"] + dev_reqs = req_list.optional_reqs_by_file["setup.cfg ('dev' extra)"] self.assertEqual(2, len(test_reqs)) self.assertIn('pytest', test_reqs) @@ -88,30 +92,33 @@ def test_extras__pyproject_toml(self): self.assertIn('pyproject.toml (dependencies)', req_list.reqs_by_file) self.assertIn( - "pyproject.toml ('security' extra)", req_list.reqs_by_file + "pyproject.toml ('security' extra)", + req_list.optional_reqs_by_file, ) self.assertIn( - "pyproject.toml ('test' dependency group)", req_list.reqs_by_file + "pyproject.toml ('test' dependency group)", + req_list.optional_reqs_by_file, ) self.assertIn( - "pyproject.toml ('typing' dependency group)", req_list.reqs_by_file + "pyproject.toml ('typing' dependency group)", + req_list.optional_reqs_by_file, ) self.assertIn( "pyproject.toml ('typing-test' dependency group)", - req_list.reqs_by_file, + req_list.optional_reqs_by_file, ) main_reqs = req_list.reqs_by_file['pyproject.toml (dependencies)'] - security_reqs = req_list.reqs_by_file[ + security_reqs = req_list.optional_reqs_by_file[ "pyproject.toml ('security' extra)" ] - test_reqs = req_list.reqs_by_file[ + test_reqs = req_list.optional_reqs_by_file[ "pyproject.toml ('test' dependency group)" ] - typing_reqs = req_list.reqs_by_file[ + typing_reqs = req_list.optional_reqs_by_file[ "pyproject.toml ('typing' dependency group)" ] - typing_test_reqs = req_list.reqs_by_file[ + typing_test_reqs = req_list.optional_reqs_by_file[ "pyproject.toml ('typing-test' dependency group)" ] @@ -347,6 +354,7 @@ def test_unchanged(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -361,6 +369,7 @@ def test_denylisted(self): denylist=requirement.parse('name'), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -376,6 +385,7 @@ def test_denylisted_mismatch(self): denylist=requirement.parse('name'), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -390,6 +400,7 @@ def test_not_in_global_list(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -404,6 +415,7 @@ def test_new_item_matches_global_list(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -419,6 +431,7 @@ def test_new_item_lower_min(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -436,6 +449,7 @@ def test_new_item_extra_exclusion(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -451,6 +465,7 @@ def test_new_item_missing_exclusion(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -476,6 +491,7 @@ def test_new_item_matches_global_list_with_extra(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -500,6 +516,7 @@ def test_new_item_missing_extra_line(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, ) ) @@ -525,6 +542,73 @@ def test_new_item_mismatches_global_list_with_extra(self): denylist=requirement.parse(''), backports=self.backports, global_reqs=global_reqs, + is_optional=False, + ) + ) + + def test_optional_not_in_global_list(self): + # If an optional package is not in the global list, that is not an + # error. + reqs = [r for r, line in requirement.parse('name>=1.2,!=1.4')['name']] + global_reqs = check.get_global_reqs('') + self.assertFalse( + check._validate_one( + 'name', + reqs=reqs, + denylist=requirement.parse(''), + backports=self.backports, + global_reqs=global_reqs, + is_optional=True, + ) + ) + + def test_optional_matches_global_list(self): + # If an optional package is in the global list and matches, + # everything is OK. + reqs = [r for r, line in requirement.parse('name>=1.2,!=1.4')['name']] + global_reqs = check.get_global_reqs('name>=1.2,!=1.4') + self.assertFalse( + check._validate_one( + 'name', + reqs=reqs, + denylist=requirement.parse(''), + backports=self.backports, + global_reqs=global_reqs, + is_optional=True, + ) + ) + + def test_optional_mismatches_global_list(self): + # If an optional package is in the global list and does not match, + # that is an error. + reqs = [ + r for r, line in requirement.parse('name>=1.2,!=1.4,!=1.5')['name'] + ] + global_reqs = check.get_global_reqs('name>=1.2,!=1.4') + self.assertTrue( + check._validate_one( + 'name', + reqs=reqs, + denylist=requirement.parse(''), + backports=self.backports, + global_reqs=global_reqs, + is_optional=True, + ) + ) + + def test_optional_denylisted(self): + # If the optional package is denylisted, everything is OK even if absent + # from global-requirements. + reqs = [r for r, line in requirement.parse('name>=1.2,!=1.4')['name']] + global_reqs = check.get_global_reqs('') + self.assertFalse( + check._validate_one( + 'name', + reqs=reqs, + denylist=requirement.parse('name'), + backports=self.backports, + global_reqs=global_reqs, + is_optional=True, ) ) From 2e5d2be48264f0a27a059eaab06b0b04e6181afc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Oct 2026 15:09:24 +0100 Subject: [PATCH 4/5] Remove dead code These are unused since I65fd1ae3a4c00a4fd6186f4cd915ae4095c2916f. Change-Id: Ied6621a2e7c1702aa7f0061a6de925359735bfcf Signed-off-by: Stephen Finucane --- openstack_requirements/check.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/openstack_requirements/check.py b/openstack_requirements/check.py index c6a5d6637..65ff17508 100644 --- a/openstack_requirements/check.py +++ b/openstack_requirements/check.py @@ -18,12 +18,9 @@ import re import sys -from packaging import markers - from openstack_requirements.project import Project from openstack_requirements import requirement -MIN_PY_VERSION = '3.5' PY3_GLOBAL_SPECIFIER_RE = re.compile( r'python_version(==|>=|>)[\'"]3\.\d+[\'"]' ) @@ -234,23 +231,6 @@ def get_global_reqs(content): return global_reqs -def _get_python3_reqs(reqs): - """Filters out the reqs that are less than our minimum version.""" - results = [] - for req in reqs: - if not req.markers: - results.append(req) - else: - req_markers = markers.Marker(req.markers) - if req_markers.evaluate( - { - 'python_version': MIN_PY_VERSION, - } - ): - results.append(req) - return results - - def _validate_one( name, reqs, From b233f03163602d4ca4623597ff3993ac7c7387c2 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Oct 2026 15:11:52 +0100 Subject: [PATCH 5/5] Remove unused property Nothing is using this in- or out-of-tree. Change-Id: I49ab4d38f3357e4af6bcfaa272f89fb6f4219df4 Signed-off-by: Stephen Finucane --- openstack_requirements/check.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openstack_requirements/check.py b/openstack_requirements/check.py index 65ff17508..d9a262753 100644 --- a/openstack_requirements/check.py +++ b/openstack_requirements/check.py @@ -36,11 +36,6 @@ def __init__(self, name: str, project: Project) -> None: self.project = project self.failed = False - @property - def reqs(self) -> dict[str, set[str]]: - """Flattens the list of per-file reqs.""" - return {k: v for d in self.reqs_by_file.values() for k, v in d.items()} - def extract_reqs( self, content: list[str], strict: bool ) -> dict[str, set[str]]: