Skip to content

Commit 3b4bd62

Browse files
authoredDec 19, 2024··
chore: output supported versions of integrations (#11372)
- Creates a `Generate Supported Integration Versions` workflow that outputs the supported versions of integrations to a `supported_versions_output.json` and `supported_versions_table.csv`. PR here: #11767 and workflow here: https://github.com/DataDog/dd-trace-py/actions/runs/12383562860/job/34566489841 - in `scripts/freshvenvs.py`, separates the workflows for outputting the outdated integrations (which is run in the `Generate Package Versions` workflow), and for creating the supported version table. - This workflow will be tied to a release, but can also be triggered manually (via `workflow_dispatch`) Future: - There will be a mechanism for converting the `csv` file to the `rst` format used by the ddtrace docs, and for generating the public datadoghq docs (in markdown) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent e8aab65 commit 3b4bd62

File tree

4 files changed

+264
-14
lines changed

4 files changed

+264
-14
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Generate Supported Integration Versions
2+
3+
on:
4+
workflow_dispatch: # can be triggered manually
5+
6+
jobs:
7+
generate-supported-versions:
8+
name: Generate supported integration versions
9+
runs-on: ubuntu-22.04
10+
permissions:
11+
actions: read
12+
contents: write
13+
pull-requests: write
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
persist-credentials: false
19+
20+
- name: Setup Python 3.7
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.7"
24+
25+
- name: Setup Python 3.8
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: "3.8"
29+
30+
- name: Setup Python 3.9
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: "3.9"
34+
35+
- name: Setup Python 3.10
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: "3.10"
39+
40+
- name: Setup Python 3.11
41+
uses: actions/setup-python@v5
42+
with:
43+
python-version: "3.11"
44+
45+
- name: Setup Python 3.12
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: "3.12"
49+
50+
- name: Setup Python 3.13
51+
uses: actions/setup-python@v5
52+
with:
53+
python-version: "3.13"
54+
55+
- name: Set up QEMU
56+
uses: docker/setup-qemu-action@v2
57+
58+
- name: Set up Docker Buildx
59+
uses: docker/setup-buildx-action@v3
60+
61+
- name: Install system dependencies
62+
run: |
63+
sudo apt-get update
64+
sudo apt-get install -y libmariadb-dev
65+
66+
- name: Install Python dependencies
67+
run: |
68+
python -m pip install --upgrade pip
69+
pip install packaging
70+
pip install requests
71+
pip install riot==0.20.1
72+
pip install wrapt==1.16.0
73+
74+
- name: Install ddtrace
75+
run: |
76+
pip install -e .
77+
78+
- run: python scripts/freshvenvs.py generate
79+
80+
- name: Generate table
81+
run: python scripts/generate_table.py
82+
83+
- run: git diff
84+
85+
- name: Create Pull Request
86+
id: pr
87+
uses: peter-evans/create-pull-request@v6
88+
with:
89+
token: ${{ secrets.GITHUB_TOKEN }}
90+
branch: "update-supported-versions"
91+
commit-message: "Update supported versions table"
92+
delete-branch: true
93+
base: main
94+
title: "chore: update supported versions"
95+
labels: changelog/no-changelog
96+
body: |
97+
Generates / updates the supported versions table for integrations.
98+
This should be tied to releases, or triggered manually.
99+
Workflow runs: [Generate Supported Integration Versions](https://github.com/DataDog/dd-trace-py/actions/workflows/generate-supported-versions.yml)
100+
101+
## Checklist
102+
- [x] PR author has checked that all the criteria below are met
103+
- The PR description includes an overview of the change
104+
- The PR description articulates the motivation for the change
105+
- The change includes tests OR the PR description describes a testing strategy
106+
- The PR description notes risks associated with the change, if any
107+
- Newly-added code is easy to change
108+
- The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
109+
- The change includes or references documentation updates if necessary
110+
- Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))
111+
112+
## Reviewer Checklist
113+
- [ ] Reviewer has checked that all the criteria below are met
114+
- Title is accurate
115+
- All changes are related to the pull request's stated goal
116+
- Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes
117+
- Testing strategy adequately addresses listed risks
118+
- Newly-added code is easy to change
119+
- Release note makes sense to a user of the library
120+
- If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment
121+
- Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)

‎scripts/freshvenvs.py

+116-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from http.client import HTTPSConnection
55
from io import StringIO
66
import json
7+
from operator import itemgetter
78
import os
89
import pathlib
910
import sys
@@ -21,7 +22,9 @@
2122
CONTRIB_ROOT = pathlib.Path("ddtrace/contrib")
2223
LATEST = ""
2324

25+
excluded = {"coverage"}
2426
suite_to_package = {
27+
"kafka": "confluent-kafka",
2528
"consul": "python-consul",
2629
"snowflake": "snowflake-connector-python",
2730
"flask_cache": "flask-caching",
@@ -30,11 +33,35 @@
3033
"asyncio": "pytest-asyncio",
3134
"sqlite3": "pysqlite3-binary",
3235
"grpc": "grpcio",
36+
"google_generativeai": "google-generativeai",
3337
"psycopg2": "psycopg2-binary",
3438
"cassandra": "cassandra-driver",
3539
"rediscluster": "redis-py-cluster",
40+
"dogpile_cache": "dogpile-cache",
41+
"vertica": "vertica_python",
3642
}
3743

44+
45+
# mapping the name of the module to the name of the package (on pypi and as defined in lockfiles)
46+
mapping_module_to_package = {
47+
"confluent_kafka": "confluent-kafka",
48+
"snowflake": "snowflake-connector-python",
49+
"cassandra": "cassandra-driver",
50+
"rediscluster": "redis-py-cluster",
51+
"vertica_python": "vertica-python",
52+
"flask_cache": "flask-cache",
53+
"flask_caching": "flask-caching",
54+
"consul": "python-consul",
55+
"grpc": "grpcio",
56+
"graphql": "graphql-core",
57+
"mysql": "pymysql",
58+
}
59+
60+
61+
supported_versions = [] # list of dicts
62+
pinned_packages = set()
63+
64+
3865
class Capturing(list):
3966
def __enter__(self):
4067
self._stdout = sys.stdout
@@ -77,27 +104,35 @@ def _get_riot_envs_including_any(modules: typing.Set[str]) -> typing.Set[str]:
77104
with open(f".riot/requirements/{item}", "r") as lockfile:
78105
lockfile_content = lockfile.read()
79106
for module in modules:
80-
if module in lockfile_content:
107+
if module in lockfile_content or (
108+
module in suite_to_package and suite_to_package[module] in lockfile_content
109+
):
81110
envs |= {item.split(".")[0]}
82111
break
83112
return envs
84113

85114

86115
def _get_updatable_packages_implementing(modules: typing.Set[str]) -> typing.Set[str]:
87-
"""Return all packages that can be updated and have contribs implemented for them"""
116+
"""Return all packages have contribs implemented for them"""
88117
all_venvs = riotfile.venv.venvs
89118

90119
for v in all_venvs:
91120
package = v.name
92121
if package not in modules:
93122
continue
94123
if not _venv_sets_latest_for_package(v, package):
95-
modules.remove(package)
124+
pinned_packages.add(package)
96125

97126
packages = {m for m in modules if "." not in m}
98127
return packages
99128

100129

130+
def _get_all_modules(modules: typing.Set[str]) -> typing.Set[str]:
131+
"""Return all packages have contribs implemented for them"""
132+
contrib_modules = {m for m in modules if "." not in m}
133+
return contrib_modules
134+
135+
101136
def _get_version_extremes(package_name: str) -> typing.Tuple[Optional[str], Optional[str]]:
102137
"""Return the (earliest, latest) supported versions of a given package"""
103138
with Capturing() as output:
@@ -134,16 +169,27 @@ def _get_version_extremes(package_name: str) -> typing.Tuple[Optional[str], Opti
134169

135170

136171
def _get_package_versions_from(env: str, packages: typing.Set[str]) -> typing.List[typing.Tuple[str, str]]:
137-
"""Return the list of package versions that are tested"""
172+
"""Return the list of package versions that are tested, related to the modules"""
173+
# Returns [(package, version), (package, versions)]
138174
lockfile_content = pathlib.Path(f".riot/requirements/{env}.txt").read_text().splitlines()
139175
lock_packages = []
140176
for line in lockfile_content:
141177
package, _, versions = line.partition("==")
178+
# remap the package -> module name
142179
if package in packages:
143180
lock_packages.append((package, versions))
181+
144182
return lock_packages
145183

146184

185+
def _is_module_autoinstrumented(module: str) -> bool:
186+
import importlib
187+
188+
_monkey = importlib.import_module("ddtrace._monkey")
189+
PATCH_MODULES = getattr(_monkey, "PATCH_MODULES")
190+
191+
return module in PATCH_MODULES and PATCH_MODULES[module]
192+
147193
def _versions_fully_cover_bounds(bounds: typing.Tuple[str, str], versions: typing.List[str]) -> bool:
148194
"""Return whether the tested versions cover the full range of supported versions"""
149195
if not versions:
@@ -173,12 +219,25 @@ def _venv_sets_latest_for_package(venv: riotfile.Venv, suite_name: str) -> bool:
173219
return False
174220

175221

176-
def main():
177-
all_required_modules = _get_integrated_modules()
178-
all_required_packages = _get_updatable_packages_implementing(all_required_modules)
179-
envs = _get_riot_envs_including_any(all_required_modules)
222+
def _get_all_used_versions(envs, packages) -> dict:
223+
# Returns dict(module, set(versions)) for a venv, as defined in riotfiles.
224+
all_used_versions = defaultdict(set)
225+
for env in envs:
226+
versions_used = _get_package_versions_from(env, packages) # returns list of (package, versions)
227+
for package, version in versions_used:
228+
all_used_versions[package].add(version)
229+
return all_used_versions
180230

231+
232+
def _get_version_bounds(packages) -> dict:
233+
# Return dict(module: (earliest, latest)) of the module on PyPI
181234
bounds = dict()
235+
for package in packages:
236+
earliest, latest = _get_version_extremes(package)
237+
bounds[package] = (earliest, latest)
238+
return bounds
239+
240+
def output_outdated_packages(all_required_packages, envs, bounds):
182241
for package in all_required_packages:
183242
earliest, latest = _get_version_extremes(package)
184243
bounds[package] = (earliest, latest)
@@ -194,10 +253,55 @@ def main():
194253
if not ordered:
195254
continue
196255
if not _versions_fully_cover_bounds(bounds[package], ordered):
197-
print(
198-
f"{package}: policy supports version {bounds[package][0]} through {bounds[package][1]} "
199-
f"but only these versions are used: {[str(v) for v in ordered]}"
200-
)
256+
print(f"{package}")
257+
258+
def generate_supported_versions(contrib_packages, all_used_versions, patched):
259+
for mod in mapping_module_to_package:
260+
contrib_packages.remove(mod)
261+
contrib_packages.add(mapping_module_to_package[mod])
262+
patched[mapping_module_to_package[mod]] = _is_module_autoinstrumented(mod)
263+
264+
# Generate supported versions
265+
for package in contrib_packages:
266+
ordered = sorted([Version(v) for v in all_used_versions[package]], reverse=True)
267+
if not ordered:
268+
continue
269+
json_format = {
270+
"integration": package,
271+
"minimum_tracer_supported": str(ordered[-1]),
272+
"max_tracer_supported": str(ordered[0]),
273+
}
274+
275+
if package in pinned_packages:
276+
json_format["pinned"] = "true"
277+
278+
if package not in patched:
279+
patched[package] = _is_module_autoinstrumented(package)
280+
json_format["auto-instrumented"] = patched[package]
281+
supported_versions.append(json_format)
282+
283+
supported_versions_output = sorted(supported_versions, key=itemgetter("integration"))
284+
with open("supported_versions_output.json", "w") as file:
285+
json.dump(supported_versions_output, file, indent=4)
286+
287+
def main():
288+
all_required_modules = _get_integrated_modules()
289+
all_required_packages = _get_updatable_packages_implementing(all_required_modules) # these are MODULE names
290+
contrib_modules = _get_all_modules(all_required_modules)
291+
envs = _get_riot_envs_including_any(all_required_modules)
292+
patched = {}
293+
294+
contrib_packages = contrib_modules
295+
all_used_versions = _get_all_used_versions(envs, contrib_packages)
296+
bounds = _get_version_bounds(contrib_packages)
297+
298+
if len(sys.argv) != 2:
299+
print("usage: python scripts/freshvenvs.py <output> or <generate>")
300+
return
301+
if sys.argv[1] == "output":
302+
output_outdated_packages(all_required_packages, envs, bounds)
303+
if sys.argv[1] == "generate":
304+
generate_supported_versions(contrib_packages, all_used_versions, patched)
201305

202306

203307
if __name__ == "__main__":

‎scripts/generate_table.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import csv
2+
import json
3+
4+
5+
print("Reading supported_versions_output.json")
6+
7+
with open("supported_versions_output.json", "r") as json_file:
8+
data = json.load(json_file)
9+
10+
columns = ["integration", "minimum_tracer_supported", "max_tracer_supported", "auto-instrumented"]
11+
csv_rows = []
12+
13+
for entry in data:
14+
integration_name = entry.get("integration", "")
15+
if entry.get("pinned", "").lower() == "true":
16+
integration_name += " *"
17+
entry["integration"] = integration_name
18+
csv_rows.append({col: entry.get(col, "") for col in columns})
19+
20+
with open("supported_versions_table.csv", "w", newline="") as csv_file:
21+
print("Wrote to supported_versions_table.csv")
22+
writer = csv.DictWriter(csv_file, fieldnames=columns)
23+
writer.writeheader()
24+
writer.writerows(csv_rows)

‎scripts/regenerate-riot-latest.sh

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set -e
33

44
DDTEST_CMD=scripts/ddtest
55

6-
pkgs=$(python scripts/freshvenvs.py | cut -d':' -f1)
6+
pkgs=$(python scripts/freshvenvs.py output)
77
echo $pkgs
88

99
if ! $DDTEST_CMD; then
@@ -20,7 +20,8 @@ for pkg in ${pkgs[*]}; do
2020
echo "No riot hashes found for pattern: $VENV_NAME"
2121
else
2222
echo "VENV_NAME=$VENV_NAME" >> $GITHUB_ENV
23-
for h in ${RIOT_HASHES[@]}; do
23+
for h in ${RIOT_HASHES[@]}; do
24+
echo "Removing riot lockfiles"
2425
rm ".riot/requirements/${h}.txt"
2526
done
2627
scripts/compile-and-prune-test-requirements

0 commit comments

Comments
 (0)
Please sign in to comment.