Update python versions

* Test python3.12
* Drop python3.8
* Drop usage of ubuntu-focal
* Rework CI job for installation check to use vitualenv to install Rally
* Add ansible-lint validation job for zuul playbooks & roles
* update upper-constraints file

Change-Id: I399f9eceab03c935aeb454b20001c0a7bd4d1619
This commit is contained in:
Andriy Kurilin 2024-04-29 12:24:34 -07:00
parent 06cd8ce716
commit 087676be40
25 changed files with 238 additions and 213 deletions

View File

@ -27,7 +27,7 @@
- job:
name: rally-docker-build
parent: build-docker-image
nodeset: ubuntu-jammy
nodeset: ubuntu-noble
run: tests/ci/playbooks/docker-build-and-check.yaml
post-run: tests/ci/playbooks/fetch-html-and-json-reports.yaml
timeout: 1800
@ -37,7 +37,7 @@
- job:
name: rally-docker-build-and-push
parent: build-docker-image
nodeset: ubuntu-jammy
nodeset: ubuntu-noble
run: tests/ci/playbooks/docker-build-check-and-push.yaml
post-run: tests/ci/playbooks/fetch-html-and-json-reports.yaml
timeout: 1800

View File

@ -6,16 +6,16 @@
post-run: tests/ci/playbooks/fetch-html-and-json-reports.yaml
timeout: 1800
- job:
name: rally-install-ubuntu-focal
parent: rally-install-base
nodeset: ubuntu-focal
- job:
name: rally-install-ubuntu-jammy
parent: rally-install-base
nodeset: ubuntu-jammy
- job:
name: rally-install-ubuntu-noble
parent: rally-install-base
nodeset: ubuntu-noble
- job:
name: rally-install-centos-9s
parent: rally-install-base

View File

@ -6,7 +6,7 @@
post-run: tests/ci/playbooks/fetch-html-and-json-reports.yaml
description: |
Run test for rally project.
nodeset: ubuntu-jammy
nodeset: ubuntu-noble
- job:
name: rally-tox-docs
@ -79,6 +79,7 @@
Uses tox with the ``py310`` environment.
vars:
tox_env: py310
nodeset: ubuntu-jammy
- job:
name: rally-tox-py311
@ -89,18 +90,13 @@
Uses tox with the ``py311`` environment.
vars:
tox_env: py311
nodeset: debian-bookworm
- job:
name: rally-tox-py311-sqlalchemy14
parent: rally-tox-py311
name: rally-tox-py312
parent: rally-tox-base
vars:
tox_env: py311-sa14
- job:
name: rally-tox-py311-sqlalchemy2
parent: rally-tox-py311
vars:
tox_env: py311-sa2
tox_env: py312
- job:
name: rally-tox-samples
@ -122,3 +118,9 @@
vars:
coverage_output_src: '{{ zuul.project.src_dir }}/cover/'
zuul_executor_dest: '{{ zuul.executor.log_root }}/coverage/'
- job:
name: rally-tox-zuul-ansible-lint
parent: rally-tox-py312
vars:
tox_env: zuul-ansible-lint

View File

@ -4,38 +4,26 @@
- docs-on-readthedocs
vars:
rtd_webhook_id: "52691"
check:
check: &queue
jobs:
- rally-tox-cover
- rally-tox-docs
- rally-tox-pep8
- rally-tox-py38
- rally-tox-py39
- rally-tox-py310
- rally-tox-py311-sqlalchemy14
- rally-tox-py311-sqlalchemy2
- rally-tox-py311
- rally-tox-py312
- rally-tox-zuul-ansible-lint:
files:
- tests/ci/playbooks/*
- rally-tox-samples
- rally-tox-functional
- rally-tox-self
- rally-install-ubuntu-focal
- rally-install-ubuntu-jammy
- rally-install-ubuntu-noble
- rally-install-centos-9s
- rally-docker-build
gate:
jobs:
- rally-tox-cover
- rally-tox-docs
- rally-tox-pep8
- rally-tox-py38
- rally-tox-py39
- rally-tox-py310
- rally-tox-py311-sqlalchemy14
- rally-tox-py311-sqlalchemy2
- rally-tox-functional
- rally-tox-self
- rally-install-ubuntu-focal
- rally-install-ubuntu-jammy
- rally-install-centos-9s
gate: *queue
post:
jobs:
- rally-docker-build-and-push:

View File

@ -17,6 +17,20 @@ Changelog
.. Release notes for existing releases are MUTABLE! If there is something that
was missed or can be improved, feel free to change it!
unreleased
----------
Added
~~~~~
* CI jobs for checking compatibility with python 3.12
Removed
~~~~~~~
* Support for Python3.8 is dropped
* Support for SQLAlchemy <2
[4.1.0] - 2024-04-29
--------------------

View File

@ -4,9 +4,9 @@
# Rally core dependencies
alembic!=1.2.0,!=1.6.3 # MIT
Jinja2 # BSD-3-Clause
Jinja2 # BSD
jsonschema # MIT
markupsafe # BSD-3-Clause
markupsafe
oslo.config!=4.3.0,!=4.4.0 # Apache Software License
# do not forget to remove `testresources` from test-requirements. it is a
# dependency of oslo.db for tests
@ -14,10 +14,10 @@ oslo.db # Apache Software License
oslo.log!=3.44.2,!=4.1.2,!=4.2.0,!=5.0.1,!=5.0.2,!=5.1.0 # Apache Software License
paramiko!=2.9.0,!=2.9.1 # LGPL
pbr!=2.1.0 # Apache Software License
PrettyTable!=3.4.0 # BSD (3 clause)
PrettyTable!=3.4.0 # BSD
pyOpenSSL # Apache License, Version 2.0
PyYAML # MIT
python-subunit # Apache-2.0 or BSD
requests!=2.20.0,!=2.24.0 # Apache License, Version 2.0
SQLAlchemy>=1.4.0 # MIT
requests!=2.20.0,!=2.24.0 # Apache-2.0
SQLAlchemy>=2 # MIT
virtualenv!=16.3.0 # MIT

View File

@ -7,7 +7,7 @@ author = OpenStack
author_email = openstack-discuss@lists.openstack.org
home_page = https://rally.readthedocs.io/
license = Apache License, Version 2.0
requires_python = >=3.8
requires_python = >=3.9
classifier =
Environment :: OpenStack
Intended Audience :: Developers
@ -17,10 +17,10 @@ classifier =
Programming Language :: Python
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
[files]
packages =

View File

@ -11,7 +11,7 @@ pytest-cov # MIT
# py.test plugin for generating HTML reports
pytest-html # MIT
# py.test xdist plugin for distributed testing and loop-on-failing modes
pytest-xdist # MIT
pytest-xdist
ddt # MIT

View File

@ -0,0 +1,31 @@
# NOTE(andreykurilin): ansible-lint expects playbooks to be in a separate
# directory called 'playbooks'. There is no real reason for such structure
# and it also requires specific ansible config option to be specified so
# playbooks can find roles.
# https://docs.ansible.com/ansible/latest/user_guide/sample_setup.html#sample-directory-layout
# Despite the fact, ansible-lint has 'kinds' configuration option for
# specifying 'custom' structure, it doesn't support relative paths, so the
# name of repository is hardcoded here. Not the best solution, but should
# work as soon as nobody renames it locally :)
kinds:
- playbook: "**/rally/tests/ci/playbooks/*.{yml,yaml}"
mock_roles:
# zuul specific roles
- copy-build-sshkey
- multi-node-hosts-file
- multi-node-known-hosts
- bindep
- fetch-tox-output
mock_modules:
- synchronize
- zuul_return
skip_list:
- no-changed-when # Commands should not change things if nothing needs doing
- unnamed-task # All tasks should be named
- fqcn[action-core] # Use FQCN for builtin module actions (shell)
- name[template] # Jinja templates should only be at the end of 'name'
- command-instead-of-shell # Use shell only when shell functionality is required.
- no-jinja-when # No Jinja2 in when.

View File

@ -1,4 +1,4 @@
- hosts: all
name: Build docker image for Rally and check it (calling the similar workload as `tox -e self`)
- name: Build docker image for Rally and check it (calling the similar workload as `tox -e self`)
hosts: all
roles:
- docker-build-image

View File

@ -1,5 +1,5 @@
- hosts: all
name: Build and push docker image for Rally
- name: Build and push docker image for Rally
hosts: all
roles:
- docker-build-image
- docker-push-image

View File

@ -1,4 +1,5 @@
- hosts: all
- name: "Post playbook that generates artifacts"
hosts: all
vars:
results_dir: "{{ zuul.project.src_dir }}/.test_results/"
html_report: "{{ tox_env | default('self') }}_report.html"
@ -6,10 +7,10 @@
tasks:
- shell: "ls {{ results_dir }}"
register: results_dir_stat
ignore_errors: True
ignore_errors: true
- name: Save results
become: yes
become: true
when: results_dir_stat.rc == 0
synchronize:
src: "{{ results_dir }}"
@ -18,10 +19,10 @@
copy_links: true
verify_host: true
rsync_opts:
- --include=/**
- --include=*/
- --exclude=*
- --prune-empty-dirs
- --include=/**
- --include=*/
- --exclude=*
- --prune-empty-dirs
- name: Return artifact to Zuul
when: html_report in results_dir_stat.stdout
@ -39,4 +40,4 @@
zuul:
artifacts:
- name: "JSON report"
url: "{{ json_report }}"
url: "{{ json_report }}"

View File

@ -1,21 +1,6 @@
- hosts: all
name: Prepare host to install Rally
- name: Prepare host to install Rally
hosts: all
tasks:
- name: Uninstall python3-pyyaml (CentOS 8 & 9)
become: true
package:
state: absent
name: python3-pyyaml
when:
- ansible_distribution == "CentOS"
- name: Install python3.8-dev (Ubuntu 20.04)
become: true
package:
state: present
name: python3.8-dev
when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "20.04"
- name: Install python3.10-dev (Ubuntu 22.04)
become: true
package:
@ -25,46 +10,31 @@
- ansible_distribution == "Ubuntu"
- ansible_distribution_version == "22.04"
- name: Install python3.8-devel (Centos 8)
- name: Install python3.12-dev (Ubuntu 24.04)
become: true
package:
state: present
name: python38-devel
name:
- python3.12-dev
- python3.12-full
when:
- ansible_distribution == "CentOS"
- ansible_distribution_version | int < 9
- ansible_distribution == "Ubuntu"
- ansible_distribution_version == "24.04"
- name: Change default python3 to be python3.8 (Centos 8)
become: true
shell: |
set -x
set -e
echo "alias python3='python3.8'" >> ~/.bashrc"
echo "alias python3='python3.8'" >> ~zuul/.bashrc"
whereis python3.8
ls /usr/bin/python*
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 100
update-alternatives --set python3 /usr/bin/python3.8
when:
- ansible_distribution == "CentOS"
- ansible_distribution_version | int < 9
- name: Install pip3 if needed
- name: Install pip3 and venv
become: true
shell:
executable: /bin/bash
chdir: '{{ zuul.project.src_dir }}'
# noqa risky-shell-pipe if python3 fails, nothing is working
cmd: |
set -e
python_version=`python3 --version | awk '{print $2}'`
echo $python_version
curl https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py
python3 /tmp/get-pip.py
python3 /tmp/get-pip.py {% if ansible_distribution_version == "24.04" %}--break-system-packages{% endif %}
- name: Install bindep
become: true
shell: pip3 install --upgrade bindep
pip3 install virtualenv {% if ansible_distribution_version == "24.04" %}--break-system-packages{% endif %}
- name: Prepare rally plugins stored at home dir
shell: |

View File

@ -1,8 +1,13 @@
- hosts: all
name: a run script for rally-install-* jobs
- name: Run script for rally-install-* jobs
hosts: all
tasks:
- name: Create virtualenv and install bindep
shell: |
virtualenv {{ zuul.project.src_dir }}/.venv
{{ zuul.project.src_dir }}/.venv/bin/pip3 install bindep
- name: Get list of packages to install from bindep
command: "bindep -b -f {{ zuul.project.src_dir }}/bindep.txt"
command: "{{ zuul.project.src_dir }}/.venv/bin/bindep -b -f {{ zuul.project.src_dir }}/bindep.txt"
register: bindep_output
failed_when: bindep_output.rc != 0 and bindep_output.rc != 1
@ -10,32 +15,35 @@
package:
name: "{{ bindep_output.stdout_lines }}"
state: present
become: yes
become: true
when: bindep_output.stdout_lines
# Required for https://bugs.launchpad.net/devstack/+bug/1968798
- name: Configure project src_dir as git safe
become: yes
become: true
# noqa command-instead-of-module there is no builtin git-config module
command: git config --system --add safe.directory {{ ansible_user_dir }}/{{ zuul.project.src_dir }}
- name: Install Rally system wide
become: true
shell:
executable: /bin/sh
chdir: '{{ zuul.project.src_dir }}'
cmd: "sudo pip3 install --constraint ./upper-constraints.txt ./"
cmd: '.venv/bin/pip3 install --constraint ./upper-constraints.txt ./'
- name: Create direcotry for html and json reports
- name: Create directory for html and json reports
shell:
executable: /bin/bash
chdir: '{{ zuul.project.src_dir }}'
cmd: mkdir .test_results
- name: Execute the similar script as `tox -e self`
become: true
shell:
executable: /bin/bash
chdir: '{{ zuul.project.src_dir }}'
cmd: >
python3 ./tests/ci/rally_self_job.py
--task ./rally-jobs/self-rally.yaml
--plugins-path ./rally-jobs/plugins
--results-dir ./.test_results
cmd: |
. .venv/bin/activate
python3 ./tests/ci/rally_self_job.py \
--task ./rally-jobs/self-rally.yaml \
--plugins-path ./rally-jobs/plugins \
--results-dir ./.test_results

View File

@ -3,4 +3,4 @@ docker_image_version: "{{ zuul.tag }}"
docker_image_tag: "{{ docker_repository }}:{{ docker_image_version }}"
rally_project_path: "{{ zuul.projects['opendev.org/openstack/rally'].src_dir }}"
rally_plugin_name: "Dummy.dummy"
rally_package_name: ""
rally_package_name: ""

View File

@ -25,8 +25,8 @@
fail:
msg: "{{ rally_package_name }} is not discoverable."
when:
- '"{{ rally_package_name }}" != ""'
- '"{{ rally_package_name }}" not in rally_version_info.stdout'
- '"{{ rally_package_name }}" != ""'
- '"{{ rally_package_name }}" not in rally_version_info.stdout'
- name: "Check availability of {{ rally_plugin_name }} plugin"
shell: docker run {{ docker_image_tag }} plugin show {{ rally_plugin_name }}

View File

@ -1,4 +0,0 @@
default_pip_url: "https://bootstrap.pypa.io/get-pip.py"
versioned_pip_url:
python3.6: "https://bootstrap.pypa.io/pip/3.6/get-pip.py"

View File

@ -53,7 +53,7 @@ def main():
)
parser.add_argument(
"--default-python3-version", metavar="<python-interpreter>",
type=str, required=False, default="python3.10",
type=str, required=False, default="python3.12",
help="Default python3 interpreter to use for 'python3' case."
)
args = parser.parse_args()

View File

@ -12,23 +12,22 @@
when: python_exec_from_tox is defined and python_exec_from_tox.stdout.strip()
- name: Install the proper python version and pip
become: True
become: true
become_user: root
shell: |
set -e
apt-get update
apt-get install {{ python_exec }}-dev --yes
curl {{ versioned_pip_url.get(python_exec, default_pip_url) }} -o get-pip.py
{{ python_exec }} get-pip.py --force-reinstall
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
{{ python_exec }} get-pip.py --force-reinstall --break-system-packages
when: python_exec is defined
- name: Install python tox
become: True
become: true
become_user: root
shell: "{{ python_exec }} -m pip install tox"
shell: "{{ python_exec }} -m pip install tox --break-system-packages"
- name: Install system deps
include_role:

View File

@ -1,6 +1,6 @@
- hosts: all
- name: "Pre playbook"
hosts: all
tasks:
- include_role:
name: "rally-tox"
vars:
action: "install"
tasks_from: "install"

View File

@ -1,6 +1,6 @@
- hosts: all
- name: "Run playbook"
hosts: all
tasks:
- include_role:
name: "rally-tox"
vars:
action: "run"
tasks_from: "run"

View File

@ -14,6 +14,7 @@
# under the License.
import copy
import math
import ddt
@ -42,7 +43,17 @@ class GraphZipperTestCase(test.TestCase):
zipped_size=None, expected=None):
merger = utils.GraphZipper(len(data_stream), zipped_size)
[merger.add_point(value) for value in data_stream]
self.assertEqual(expected, merger.get_zipped_graph())
actual_graph = merger.get_zipped_graph()
self.assertEqual(len(expected), len(actual_graph))
for i, (e_point, e_value) in enumerate(expected):
a_point, a_value = actual_graph[i]
self.assertEqual(e_point, a_point,
msg=f"Point order of element #{i} is different")
self.assertTrue(
math.isclose(e_value, a_value, rel_tol=1e-15),
msg=f"Point value at #{i} is not equal: {e_value} != {a_value}"
)
def test_add_point_raises(self):
merger = utils.GraphZipper(10, 8)

View File

@ -114,16 +114,16 @@ class FuncMockArgsDecoratorsChecker(ast.NodeVisitor):
"""
val = None
if isinstance(node, ast.Constant):
val = node.s
val = node.value
elif isinstance(node, ast.BinOp):
if pairwise_isinstance(
(node.op, ast.Mod), (node.left, ast.Constant),
(node.right, ast.Name)):
val = node.left.s % self.globals_[node.right.id]
val = node.left.value % self.globals_[node.right.id]
elif pairwise_isinstance(
(node.op, ast.Add), (node.left, ast.Name),
(node.right, ast.Constant)):
val = self.globals_[node.left.id] + node.right.s
val = self.globals_[node.left.id] + node.right.value
elif isinstance(node, ast.Name):
val = self.globals_[node.id]

56
tox.ini
View File

@ -1,17 +1,17 @@
[tox]
minversion = 4.0.0
ignore_basepython_conflict = true
envlist = py38,py39,pep8,samples
envlist = py39,py312,pep8,samples
[testenv]
extras = {env:RALLY_EXTRAS:}
setenv = VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LC_CTYPE="en.US.UTF-8"
LANGUAGE=en_US:en
LC_ALL=C
PYTHONHASHSEED=0
TOX_ENV_NAME={envname}
sa14: SQLALCHEMY_SILENCE_UBER_WARNING=1
allowlist_externals = find
rm
make
@ -19,33 +19,33 @@ allowlist_externals = find
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
-c{toxinidir}/upper-constraints.txt
sa14: SQLAlchemy<1.5
sa2: SQLAlchemy>=2
usedevelop = True
commands =
find . -type f -name "*.pyc" -delete
python {toxinidir}/tests/ci/pytest_launcher.py tests/unit --posargs={posargs}
pre_commands = find . -type f -name "*.pyc" -delete
commands = python {toxinidir}/tests/ci/pytest_launcher.py tests/unit --posargs={posargs}
distribute = false
basepython = python3
passenv =
PYTEST_REPORT
http_proxy
HTTP_PROXY
https_proxy
HTTPS_PROXY
no_proxy
NO_PROXY
HOME
PYTEST_REPORT
http_proxy
HTTP_PROXY
https_proxy
HTTPS_PROXY
no_proxy
NO_PROXY
HOME
[testenv:zuul-ansible-lint]
setenv = VIRTUAL_ENV={envdir}
distribute = false
deps = ansible-lint
commands = ansible-lint --strict --config-file tests/ci/playbooks/.ansible-lint tests/ci/playbooks
[testenv:pep8]
commands = flake8
distribute = false
[testenv:samples]
commands =
find . -type f -name "*.pyc" -delete
python {toxinidir}/tests/ci/pytest_launcher.py tests/samples --posargs={posargs}
commands = python {toxinidir}/tests/ci/pytest_launcher.py tests/samples --posargs={posargs}
[testenv:venv]
commands = {posargs}
@ -54,16 +54,13 @@ commands = {posargs}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
stestr
commands =
find . -type f -name "*.pyc" -delete
python {toxinidir}/tests/ci/pytest_launcher.py tests/functional --posargs={posargs}
commands = python {toxinidir}/tests/ci/pytest_launcher.py tests/functional --posargs={posargs}
[testenv:cover]
commands = {toxinidir}/tests/ci/cover.sh {posargs}
allowlist_externals = {toxinidir}/tests/ci/cover.sh
[testenv:docs]
# min py3.6
basepython = python3
deps =
-c{toxinidir}/upper-constraints.txt
@ -87,9 +84,6 @@ ignore = H703,H105,E731,W503
show-source = true
exclude=.venv,.git,.tox,dist,*lib/python*,*egg,tools,build,setup.py
[hacking]
import_exceptions = rally.common.i18n
[flake8:local-plugins]
extension =
N301 = checks:check_assert_methods_from_mock
@ -124,13 +118,6 @@ paths = ./tests/hacking
deps = bindep
commands = bindep
[testenv:self-py38]
basepython = python3.8
commands =
find . -type f -name "*.pyc" -delete
mkdir -p .test_results
python3 {toxinidir}/tests/ci/rally_self_job.py --task {toxinidir}/rally-jobs/self-rally.yaml --plugins-path {toxinidir}/rally-jobs/plugins
[testenv:self]
commands = \
find . -type f -name "*.pyc" -delete
@ -150,3 +137,6 @@ filterwarnings =
ignore:pkg_resources is deprecated as an API:DeprecationWarning:
# pytest-cov & pytest-xdist
ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning:
# python3.11 ?
ignore:datetime\.datetime\.utcnow\(\) is deprecated.*:DeprecationWarning:
ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated.*:DeprecationWarning:

View File

@ -1,57 +1,72 @@
alembic===1.9.4
attrs===23.2.0
bcrypt===4.1.2
certifi===2024.2.2
cffi===1.16.0
charset-normalizer===3.3.2
cryptography===42.0.5
alembic===1.13.3
attrs===24.2.0
autocommand===2.2.2
backports.tarfile===1.2.0
bcrypt===4.0.1
certifi===2024.8.30
cffi===1.17.1
charset-normalizer===3.4.0
cryptography===42.0.8
debtcollector===3.0.0
distlib===0.3.8
filelock===3.13.3
idna===3.6
distlib===0.3.9
filelock===3.16.1
greenlet===3.1.1
idna===3.10
importlib-metadata===8.0.0
importlib-resources===6.4.0
inflect===7.3.1
iso8601===2.1.0
Jinja2===3.1.3
jaraco.collections===5.1.0
jaraco.context===6.0.1
jaraco.functools===4.1.0
jaraco.text===4.0.0
jinja2===3.1.4
jsonschema===4.19.2
jsonschema-specifications===2023.12.1
Mako===1.3.2
MarkupSafe===2.1.5
msgpack===1.0.8
jsonschema-specifications===2024.10.1
Mako===1.3.5
MarkupSafe===3.0.2
more-itertools===10.5.0
msgpack===1.1.0
netaddr===0.10.1
netifaces===0.11.0
oslo.config===9.4.0
oslo.context===5.5.0
oslo.db===15.0.0
oslo.i18n===6.3.0
oslo.log===5.5.1
oslo.serialization===5.4.0
oslo.utils===7.1.0
packaging===24.0
paramiko===3.4.0
pbr===6.0.0
pip===24.0
platformdirs===4.2.0
prettytable===3.10.0
oslo.config===9.7.0
oslo.context===5.7.0
oslo.db===16.0.0
oslo.i18n===6.5.0
oslo.log===6.1.2
oslo.serialization===5.6.0
oslo.utils===7.3.0
packaging===24.1
paramiko===3.5.0
pbr===6.1.0
pip===24.2
platformdirs===4.3.6
prettytable===3.11.0
psutil===6.1.0
pycparser===2.22
PyNaCl===1.5.0
pyOpenSSL===24.1.0
pyparsing===3.1.2
pyOpenSSL===24.2.1
pyparsing===3.2.0
python-dateutil===2.9.0.post0
python-subunit===1.4.4
PyYAML===6.0.1
referencing===0.34.0
requests===2.31.0
PyYAML===6.0.2
referencing===0.35.1
requests===2.32.3
rfc3986===2.0.0
rpds-py===0.18.0
setuptools===69.2.0
rpds-py===0.20.0
setuptools===75.1.0
six===1.16.0
stevedore===5.2.0
SQLAlchemy===2.0.36
stevedore===5.4.0
testresources===2.0.1
testscenarios===0.5.0
testtools===2.7.1
typing-extensions===4.11.0
tzdata===2024.1
urllib3===1.26.18
virtualenv===20.25.1
testtools===2.7.2
tomli===2.0.2
typeguard===4.3.0
typing-extensions===4.12.2
tzdata===2024.2
urllib3===1.26.20
virtualenv===20.27.0
wcwidth===0.2.13
wheel===0.43.0
wheel===0.44.0
wrapt===1.16.0
zipp===3.20.2