src-py-lib 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. src_py_lib-0.1.3/.github/workflows/ci.yml +16 -0
  2. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/.github/workflows/release.yml +76 -36
  3. src_py_lib-0.1.3/.github/workflows/validate.yml +135 -0
  4. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/AGENTS.md +11 -6
  5. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/PKG-INFO +8 -1
  6. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/README.md +7 -0
  7. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/pyproject.toml +2 -2
  8. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/__init__.py +32 -1
  9. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/graphql.py +20 -3
  10. src_py_lib-0.1.3/src/src_py_lib/clients/sourcegraph.py +472 -0
  11. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/http.py +69 -3
  12. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/logging.py +121 -12
  13. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/tests/test_logging_http_clients.py +127 -4
  14. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/uv.lock +1 -1
  15. src_py_lib-0.1.1/.github/workflows/ci.yml +0 -93
  16. src_py_lib-0.1.1/src/src_py_lib/clients/sourcegraph.py +0 -127
  17. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/.gitignore +0 -0
  18. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/.markdownlint-cli2.yaml +0 -0
  19. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/.python-version +0 -0
  20. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/LICENSE +0 -0
  21. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/SECURITY.md +0 -0
  22. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/renovate.json +0 -0
  23. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/__init__.py +0 -0
  24. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/github.py +0 -0
  25. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/google_sheets.py +0 -0
  26. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/linear.py +0 -0
  27. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/one_password.py +0 -0
  28. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/clients/slack.py +0 -0
  29. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/py.typed +0 -0
  30. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/__init__.py +0 -0
  31. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/config.py +0 -0
  32. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/json_cache.py +0 -0
  33. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/json_types.py +0 -0
  34. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/src/src_py_lib/utils/tsv.py +0 -0
  35. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/tests/test_import.py +0 -0
  36. {src_py_lib-0.1.1 → src_py_lib-0.1.3}/tests/test_tsv.py +0 -0
@@ -0,0 +1,16 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ concurrency:
10
+ group: ci-${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ validate:
15
+ name: Validate
16
+ uses: ./.github/workflows/validate.yml
@@ -23,6 +23,13 @@ defaults:
23
23
  shell: bash
24
24
 
25
25
  jobs:
26
+ validate:
27
+ name: Validate
28
+ uses: ./.github/workflows/validate.yml
29
+ with:
30
+ ref: ${{ github.event.inputs.tag || github.ref }}
31
+ build-package: false
32
+
26
33
  wheel:
27
34
  name: Build wheel
28
35
  runs-on: ubuntu-24.04
@@ -45,10 +52,16 @@ jobs:
45
52
  python-version: ${{ env.PYTHON_VERSION }}
46
53
  cache: pip
47
54
 
55
+ - name: Cache uv
56
+ uses: actions/cache@v5
57
+ with:
58
+ path: ~/.cache/uv
59
+ key: uv-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('uv.lock') }}
60
+ restore-keys: |
61
+ uv-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-
62
+
48
63
  - name: Install build tools
49
- run: |
50
- python -m pip install --upgrade pip
51
- python -m pip install "uv==${UV_VERSION}"
64
+ run: python -m pip install "uv==${UV_VERSION}"
52
65
 
53
66
  - name: Validate release inputs
54
67
  id: release
@@ -62,6 +75,14 @@ jobs:
62
75
  echo "::error title=Missing tag::Tag '${release_tag}' was not fetched. Create and push it before running this workflow."
63
76
  exit 1
64
77
  fi
78
+ tag_revision="$(git rev-list -n 1 "${release_tag}")"
79
+ git fetch --no-tags origin main
80
+ main_revision="$(git rev-parse origin/main)"
81
+ if ! git merge-base --is-ancestor "${tag_revision}" "${main_revision}"; then
82
+ echo "::error title=Tag is not on main::Tag '${release_tag}' points at ${tag_revision}, which is not reachable from origin/main."
83
+ echo "::error::Merge the release PR first, then tag the main commit."
84
+ exit 1
85
+ fi
65
86
 
66
87
  project_version=$(uv run --frozen python - <<'PY'
67
88
  import tomllib
@@ -77,22 +98,6 @@ jobs:
77
98
 
78
99
  echo "tag=${release_tag}" >> "${GITHUB_OUTPUT}"
79
100
 
80
- - name: Validate package
81
- run: |
82
- uv lock --check
83
- uv run --frozen ruff check .
84
- uv run --frozen ruff format --check .
85
- uv run --frozen pyright
86
- uv run --frozen python -m unittest discover -s tests
87
- uv run --frozen python - <<'PY'
88
- import os
89
-
90
- import src_py_lib
91
-
92
- if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
93
- raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
94
- PY
95
-
96
101
  - name: Build distributions
97
102
  id: build
98
103
  run: |
@@ -115,23 +120,29 @@ jobs:
115
120
  wheel_path="${project_wheels[0]}"
116
121
  wheel_name="$(basename "${wheel_path}")"
117
122
  source_distribution_path="${source_distributions[0]}"
118
- checksum_path="${wheel_path}.sha256"
123
+ source_distribution_name="$(basename "${source_distribution_path}")"
124
+ wheel_checksum_path="${wheel_path}.sha256"
125
+ source_distribution_checksum_path="${source_distribution_path}.sha256"
119
126
 
120
127
  (
121
128
  cd "$(dirname "${wheel_path}")"
122
- shasum -a 256 "${wheel_name}" > "$(basename "${checksum_path}")"
129
+ shasum -a 256 "${wheel_name}" > "$(basename "${wheel_checksum_path}")"
130
+ shasum -a 256 "${source_distribution_name}" > "$(basename "${source_distribution_checksum_path}")"
123
131
  )
124
132
 
125
- echo "wheel_path=${wheel_path}" >> "${GITHUB_OUTPUT}"
126
- echo "wheel_name=${wheel_name}" >> "${GITHUB_OUTPUT}"
127
- echo "source_distribution_path=${source_distribution_path}" >> "${GITHUB_OUTPUT}"
128
- echo "checksum_path=${checksum_path}" >> "${GITHUB_OUTPUT}"
133
+ {
134
+ echo "wheel_path=${wheel_path}"
135
+ echo "wheel_name=${wheel_name}"
136
+ echo "source_distribution_path=${source_distribution_path}"
137
+ echo "source_distribution_name=${source_distribution_name}"
138
+ echo "wheel_checksum_path=${wheel_checksum_path}"
139
+ echo "source_distribution_checksum_path=${source_distribution_checksum_path}"
140
+ } >> "${GITHUB_OUTPUT}"
129
141
 
130
142
  - name: Smoke test installed wheel
131
143
  run: |
132
144
  python -m venv build/release/install-venv
133
145
  . build/release/install-venv/bin/activate
134
- python -m pip install --upgrade pip
135
146
  python -m pip install "${{ steps.build.outputs.wheel_path }}"
136
147
  python - <<'PY'
137
148
  import os
@@ -147,6 +158,7 @@ jobs:
147
158
  run: |
148
159
  release_tag="${{ steps.release.outputs.tag }}"
149
160
  wheel_name="${{ steps.build.outputs.wheel_name }}"
161
+ source_distribution_name="${{ steps.build.outputs.source_distribution_name }}"
150
162
  notes_path="build/release/release-notes.md"
151
163
  cat > "${notes_path}" <<EOF
152
164
  ## Install
@@ -163,13 +175,19 @@ jobs:
163
175
  pip install "https://github.com/sourcegraph/src-py-lib/releases/download/${release_tag}/${wheel_name}"
164
176
  \`\`\`
165
177
 
178
+ Source distribution:
179
+
180
+ \`\`\`sh
181
+ curl -LO "https://github.com/sourcegraph/src-py-lib/releases/download/${release_tag}/${source_distribution_name}"
182
+ \`\`\`
183
+
166
184
  Or install this tag with uv:
167
185
 
168
186
  \`\`\`sh
169
187
  uv add "git+https://github.com/sourcegraph/src-py-lib.git@${release_tag}"
170
188
  \`\`\`
171
189
 
172
- Verify downloaded wheel assets with the matching \`.sha256\` file.
190
+ Verify downloaded assets with the matching \`.sha256\` files.
173
191
  EOF
174
192
  echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
175
193
 
@@ -179,7 +197,9 @@ jobs:
179
197
  name: src-py-lib-release
180
198
  path: |
181
199
  ${{ steps.build.outputs.wheel_path }}
182
- ${{ steps.build.outputs.checksum_path }}
200
+ ${{ steps.build.outputs.source_distribution_path }}
201
+ ${{ steps.build.outputs.wheel_checksum_path }}
202
+ ${{ steps.build.outputs.source_distribution_checksum_path }}
183
203
  ${{ steps.notes.outputs.path }}
184
204
 
185
205
  - name: Upload PyPI artifact
@@ -190,22 +210,41 @@ jobs:
190
210
  ${{ steps.build.outputs.wheel_path }}
191
211
  ${{ steps.build.outputs.source_distribution_path }}
192
212
 
213
+ github-release:
214
+ name: Publish GitHub release assets
215
+ needs: [validate, wheel]
216
+ runs-on: ubuntu-24.04
217
+
218
+ steps:
219
+ - name: Download release assets
220
+ uses: actions/download-artifact@v7
221
+ with:
222
+ name: src-py-lib-release
223
+ path: release-assets
224
+
193
225
  - name: Publish GitHub release assets
194
226
  env:
195
227
  GH_TOKEN: ${{ github.token }}
196
228
  run: |
197
- release_tag="${{ steps.release.outputs.tag }}"
198
- wheel_path="${{ steps.build.outputs.wheel_path }}"
199
- checksum_path="${{ steps.build.outputs.checksum_path }}"
200
- notes_path="${{ steps.notes.outputs.path }}"
229
+ release_tag="${{ github.event.inputs.tag || github.ref_name }}"
230
+ notes_path="$(find release-assets -name release-notes.md -print -quit)"
231
+ mapfile -t release_assets < <(find release-assets -type f ! -name release-notes.md | sort)
232
+
233
+ if [[ -z "${notes_path}" ]]; then
234
+ echo "::error title=Missing release notes::release-notes.md was not found in release artifact."
235
+ exit 1
236
+ fi
237
+ if [[ "${#release_assets[@]}" -eq 0 ]]; then
238
+ echo "::error title=Missing release assets::No release assets were downloaded."
239
+ exit 1
240
+ fi
201
241
 
202
242
  if gh release view "${release_tag}" >/dev/null 2>&1; then
203
243
  gh release edit "${release_tag}" --title "${release_tag}" --notes-file "${notes_path}"
204
- gh release upload "${release_tag}" "${wheel_path}" "${checksum_path}" --clobber
244
+ gh release upload "${release_tag}" "${release_assets[@]}" --clobber
205
245
  else
206
246
  gh release create "${release_tag}" \
207
- "${wheel_path}" \
208
- "${checksum_path}" \
247
+ "${release_assets[@]}" \
209
248
  --title "${release_tag}" \
210
249
  --notes-file "${notes_path}" \
211
250
  --verify-tag
@@ -213,7 +252,7 @@ jobs:
213
252
 
214
253
  pypi:
215
254
  name: Publish PyPI package
216
- needs: wheel
255
+ needs: [validate, wheel]
217
256
  runs-on: ubuntu-24.04
218
257
  permissions:
219
258
  contents: read
@@ -233,3 +272,4 @@ jobs:
233
272
  uses: pypa/gh-action-pypi-publish@release/v1
234
273
  with:
235
274
  packages-dir: dist
275
+ skip-existing: true
@@ -0,0 +1,135 @@
1
+ name: Validate
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ ref:
7
+ description: "Git ref to validate. Defaults to the caller's ref."
8
+ required: false
9
+ type: string
10
+ build-package:
11
+ description: "Build and smoke-test package artifacts. Release builds do this separately."
12
+ required: false
13
+ type: boolean
14
+ default: true
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ defaults:
20
+ run:
21
+ shell: bash
22
+
23
+ jobs:
24
+ package:
25
+ name: Validate package
26
+ runs-on: ubuntu-24.04
27
+ env:
28
+ ACTIONLINT_VERSION: "1.7.12"
29
+ IMPORT_NAME: src_py_lib
30
+ MARKDOWNLINT_CLI2_VERSION: "0.22.1"
31
+ PYTHON_VERSION: "3.11"
32
+ UV_VERSION: "0.11.7"
33
+
34
+ steps:
35
+ - name: Check out code
36
+ uses: actions/checkout@v6
37
+ with:
38
+ persist-credentials: false
39
+ ref: ${{ inputs.ref || github.ref }}
40
+
41
+ - name: Cache actionlint
42
+ id: cache-actionlint
43
+ uses: actions/cache@v5
44
+ with:
45
+ path: ~/.local/bin/actionlint
46
+ key: actionlint-${{ runner.os }}-${{ runner.arch }}-${{ env.ACTIONLINT_VERSION }}
47
+
48
+ - name: Install actionlint
49
+ if: steps.cache-actionlint.outputs.cache-hit != 'true'
50
+ run: |
51
+ mkdir -p "${HOME}/.local/bin"
52
+ asset="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
53
+ checksums="actionlint_${ACTIONLINT_VERSION}_checksums.txt"
54
+ base_url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}"
55
+
56
+ curl -fsSLO "${base_url}/${asset}"
57
+ curl -fsSLO "${base_url}/${checksums}"
58
+ grep " ${asset}$" "${checksums}" | sha256sum --check
59
+ tar -xzf "${asset}" -C "${HOME}/.local/bin" actionlint
60
+ chmod 0755 "${HOME}/.local/bin/actionlint"
61
+
62
+ - name: Lint GitHub Actions
63
+ run: |
64
+ "${HOME}/.local/bin/actionlint"
65
+
66
+ - name: Cache npm
67
+ uses: actions/cache@v5
68
+ with:
69
+ path: ~/.npm
70
+ key: npm-${{ runner.os }}-markdownlint-cli2-${{ env.MARKDOWNLINT_CLI2_VERSION }}
71
+
72
+ - name: Lint Markdown
73
+ run: npx --yes "markdownlint-cli2@${MARKDOWNLINT_CLI2_VERSION}"
74
+
75
+ - name: Set up Python
76
+ uses: actions/setup-python@v6
77
+ with:
78
+ python-version: ${{ env.PYTHON_VERSION }}
79
+ cache: pip
80
+
81
+ - name: Cache uv
82
+ uses: actions/cache@v5
83
+ with:
84
+ path: ~/.cache/uv
85
+ key: uv-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('uv.lock') }}
86
+ restore-keys: |
87
+ uv-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-
88
+
89
+ - name: Install uv
90
+ run: python -m pip install "uv==${UV_VERSION}"
91
+
92
+ - name: Validate lockfile
93
+ run: uv lock --check
94
+
95
+ - name: Lint Python
96
+ run: uv run --frozen ruff check .
97
+
98
+ - name: Check Python formatting
99
+ run: uv run --frozen ruff format --check .
100
+
101
+ - name: Type check
102
+ run: uv run --frozen pyright
103
+
104
+ - name: Run tests
105
+ run: uv run --frozen python -m unittest discover -s tests
106
+
107
+ - name: Smoke test source checkout import
108
+ run: |
109
+ uv run --frozen python - <<'PY'
110
+ import os
111
+
112
+ import src_py_lib
113
+
114
+ if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
115
+ raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
116
+ PY
117
+
118
+ - name: Build wheel
119
+ if: inputs.build-package
120
+ run: uv build --wheel --out-dir dist --no-create-gitignore
121
+
122
+ - name: Smoke test installed wheel
123
+ if: inputs.build-package
124
+ run: |
125
+ python -m venv build/ci-venv
126
+ . build/ci-venv/bin/activate
127
+ python -m pip install dist/*.whl
128
+ python - <<'PY'
129
+ import os
130
+
131
+ import src_py_lib
132
+
133
+ if src_py_lib.__name__ != os.environ["IMPORT_NAME"]:
134
+ raise SystemExit(f"unexpected import name: {src_py_lib.__name__}")
135
+ PY
@@ -14,7 +14,8 @@
14
14
  ## Standard commands
15
15
 
16
16
  ```sh
17
- npx --yes markdownlint-cli2
17
+ actionlint
18
+ npx --yes markdownlint-cli2@0.22.1
18
19
  uv sync
19
20
  uv run ruff format .
20
21
  uv run ruff check .
@@ -56,11 +57,14 @@ uv run python -m unittest discover -s tests
56
57
  verifies that it matches `project.version` before building GitHub release
57
58
  assets and publishing to PyPI.
58
59
  - Prepare releases on a branch from current `main`. Set `VERSION`, then run:
60
+ - As part of every release bump, find old release-version literals in
61
+ `AGENTS.md`, `README.md`, and release snippets, and replace them with the
62
+ new version where they are meant to stay current.
59
63
 
60
64
  ```sh
61
65
  set -euo pipefail
62
66
 
63
- VERSION=0.1.1
67
+ VERSION=0.1.2
64
68
  BRANCH="release-v${VERSION}"
65
69
 
66
70
  git fetch origin --tags --prune
@@ -96,7 +100,8 @@ uv lock
96
100
  set -euo pipefail
97
101
 
98
102
  uv lock --check
99
- npx --yes markdownlint-cli2
103
+ actionlint
104
+ npx --yes markdownlint-cli2@0.22.1
100
105
  uv run ruff check .
101
106
  uv run ruff format --check .
102
107
  uv run pyright
@@ -111,7 +116,7 @@ rm -rf /tmp/src-py-lib-release-check
111
116
  ```sh
112
117
  set -euo pipefail
113
118
 
114
- VERSION=0.1.1
119
+ VERSION=0.1.2
115
120
  BRANCH="release-v${VERSION}"
116
121
  GH_REPO="sourcegraph/src-py-lib"
117
122
 
@@ -135,7 +140,7 @@ gh pr merge "${BRANCH}" --repo "${GH_REPO}" --squash --delete-branch
135
140
  ```sh
136
141
  set -euo pipefail
137
142
 
138
- VERSION=0.1.1
143
+ VERSION=0.1.2
139
144
 
140
145
  git fetch origin --tags --prune
141
146
  git switch main
@@ -149,7 +154,7 @@ git push origin "v${VERSION}"
149
154
  ```sh
150
155
  set -euo pipefail
151
156
 
152
- VERSION=0.1.1
157
+ VERSION=0.1.2
153
158
  GH_REPO="sourcegraph/src-py-lib"
154
159
 
155
160
  RUN_ID="$(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: src-py-lib
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Reusable libraries for Sourcegraph projects
5
5
  Project-URL: Homepage, https://github.com/sourcegraph/src-py-lib
6
6
  Project-URL: Issues, https://github.com/sourcegraph/src-py-lib/issues
@@ -31,6 +31,13 @@ This repo was created for Sourcegraph Implementation Engineering deployments,
31
31
  and is not intended, designed, built, or supported for use in any other scenario.
32
32
  Feel free to open issues or PRs, but responses are best effort.
33
33
 
34
+ ## Semantic Versioning
35
+
36
+ - Release versions are `major.minor.patch`
37
+ - Because this project is still major version 0:
38
+ - Minor version updates are breaking changes
39
+ - Patch version updates are not breaking changes
40
+
34
41
  ## Install
35
42
 
36
43
  From PyPI:
@@ -8,6 +8,13 @@ This repo was created for Sourcegraph Implementation Engineering deployments,
8
8
  and is not intended, designed, built, or supported for use in any other scenario.
9
9
  Feel free to open issues or PRs, but responses are best effort.
10
10
 
11
+ ## Semantic Versioning
12
+
13
+ - Release versions are `major.minor.patch`
14
+ - Because this project is still major version 0:
15
+ - Minor version updates are breaking changes
16
+ - Patch version updates are not breaking changes
17
+
11
18
  ## Install
12
19
 
13
20
  From PyPI:
@@ -10,7 +10,7 @@ dev = [
10
10
 
11
11
  [project]
12
12
  name = "src-py-lib"
13
- version = "0.1.1"
13
+ version = "0.1.3"
14
14
  description = "Reusable libraries for Sourcegraph projects"
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.11"
@@ -45,7 +45,7 @@ Issues = "https://github.com/sourcegraph/src-py-lib/issues"
45
45
  packages = ["src/src_py_lib"]
46
46
 
47
47
  [tool.pyright]
48
- include = ["src/src_py_lib"]
48
+ include = ["src/src_py_lib", "tests"]
49
49
  typeCheckingMode = "strict"
50
50
  extraPaths = ["src"]
51
51
  pythonVersion = "3.11"
@@ -37,6 +37,14 @@ from src_py_lib.clients.slack import (
37
37
  from src_py_lib.clients.sourcegraph import (
38
38
  SourcegraphClient,
39
39
  SourcegraphClientConfig,
40
+ SourcegraphJaegerTraceError,
41
+ SourcegraphJaegerTraceSummary,
42
+ SourcegraphTrace,
43
+ decode_external_service_id,
44
+ decode_repository_id,
45
+ decode_sourcegraph_node_id,
46
+ encode_repository_id,
47
+ encode_sourcegraph_node_id,
40
48
  normalize_sourcegraph_endpoint,
41
49
  sourcegraph_client_from_config,
42
50
  )
@@ -49,7 +57,7 @@ from src_py_lib.utils.config import (
49
57
  from src_py_lib.utils.config import (
50
58
  config_parse_args as parse_args,
51
59
  )
52
- from src_py_lib.utils.http import HTTPClient, HTTPClientError
60
+ from src_py_lib.utils.http import HTTPClient, HTTPClientError, HTTPResponse
53
61
  from src_py_lib.utils.json_cache import load_json_cache, load_json_subset, save_json_cache
54
62
  from src_py_lib.utils.json_types import (
55
63
  JSONDict,
@@ -63,8 +71,10 @@ from src_py_lib.utils.json_types import (
63
71
  from src_py_lib.utils.logging import (
64
72
  LoggingConfig,
65
73
  LoggingSettings,
74
+ TraceContext,
66
75
  configure_logging,
67
76
  critical,
77
+ current_trace_context,
68
78
  debug,
69
79
  error,
70
80
  event,
@@ -73,10 +83,15 @@ from src_py_lib.utils.logging import (
73
83
  log_context,
74
84
  logging_context,
75
85
  logging_settings_from_config,
86
+ new_trace_context,
76
87
  resolve_log_level_name,
88
+ sampled_traceparent,
77
89
  stage,
78
90
  startup_event,
79
91
  submit_with_log_context,
92
+ trace_context,
93
+ trace_context_from_traceparent,
94
+ traceparent_header,
80
95
  warning,
81
96
  )
82
97
  from src_py_lib.utils.tsv import write_tsv
@@ -116,6 +131,7 @@ __all__ = [
116
131
  "GoogleSheetsError",
117
132
  "HTTPClient",
118
133
  "HTTPClientError",
134
+ "HTTPResponse",
119
135
  "JSONDict",
120
136
  "LinearClient",
121
137
  "LinearClientConfig",
@@ -128,12 +144,22 @@ __all__ = [
128
144
  "SlackPacer",
129
145
  "SourcegraphClient",
130
146
  "SourcegraphClientConfig",
147
+ "SourcegraphJaegerTraceError",
148
+ "SourcegraphJaegerTraceSummary",
149
+ "SourcegraphTrace",
150
+ "TraceContext",
131
151
  "aliased_batched_query",
132
152
  "config_field",
133
153
  "config_snapshot",
134
154
  "configure_logging",
135
155
  "critical",
156
+ "current_trace_context",
136
157
  "debug",
158
+ "decode_external_service_id",
159
+ "decode_repository_id",
160
+ "decode_sourcegraph_node_id",
161
+ "encode_repository_id",
162
+ "encode_sourcegraph_node_id",
137
163
  "error",
138
164
  "event",
139
165
  "gh_cli_token",
@@ -153,18 +179,23 @@ __all__ = [
153
179
  "logging_settings_from_config",
154
180
  "log",
155
181
  "log_context",
182
+ "new_trace_context",
156
183
  "normalize_sourcegraph_endpoint",
157
184
  "parse_args",
158
185
  "pr_ref_from_url",
159
186
  "quota_project_from_adc",
160
187
  "resolve_log_level_name",
161
188
  "save_json_cache",
189
+ "sampled_traceparent",
162
190
  "slack_client_from_config",
163
191
  "sourcegraph_client_from_config",
164
192
  "stage",
165
193
  "startup_event",
166
194
  "stream_connection_nodes",
167
195
  "submit_with_log_context",
196
+ "trace_context",
197
+ "trace_context_from_traceparent",
198
+ "traceparent_header",
168
199
  "warning",
169
200
  "write_tsv",
170
201
  ]
@@ -9,11 +9,13 @@ from dataclasses import dataclass, field
9
9
  from pathlib import Path
10
10
  from typing import cast
11
11
 
12
- from src_py_lib.utils.http import HTTPClient, HTTPClientError
12
+ from src_py_lib.utils.http import HTTPClient, HTTPClientError, HTTPResponse
13
13
  from src_py_lib.utils.json_types import JSONDict, JSONValue, json_dict, json_list, json_str
14
14
  from src_py_lib.utils.logging import event
15
15
 
16
16
  _OPERATION_NAME_RE = re.compile(r"\b(?:query|mutation|subscription)\s+(\w+)")
17
+ HeaderProvider = Mapping[str, str] | Callable[[], Mapping[str, str]]
18
+ GraphQLResponseHook = Callable[[HTTPResponse, Mapping[str, str]], None]
17
19
 
18
20
  GRAPHQL_INTROSPECTION_QUERY = """
19
21
  query IntrospectionQuery {
@@ -130,10 +132,11 @@ class GraphQLClient:
130
132
  """POST JSON GraphQL operations and return the `data` object."""
131
133
 
132
134
  url: str
133
- headers: dict[str, str]
135
+ headers: HeaderProvider
134
136
  label: str
135
137
  http: HTTPClient = field(default_factory=HTTPClient)
136
138
  tolerate_partial_errors: bool = False
139
+ response_hook: GraphQLResponseHook | None = None
137
140
 
138
141
  def execute(
139
142
  self,
@@ -251,7 +254,16 @@ class GraphQLClient:
251
254
  query_bytes=len(query.encode("utf-8")),
252
255
  ) as fields:
253
256
  try:
254
- payload = self.http.json("POST", self.url, headers=self.headers, json_body=body)
257
+ request_headers = self._headers()
258
+ if self.response_hook is None:
259
+ payload = self.http.json(
260
+ "POST", self.url, headers=request_headers, json_body=body
261
+ )
262
+ else:
263
+ payload, response = self.http.json_response(
264
+ "POST", self.url, headers=request_headers, json_body=body
265
+ )
266
+ self.response_hook(response, request_headers)
255
267
  except HTTPClientError as exception:
256
268
  raise GraphQLError(
257
269
  f"{self.label} GraphQL request failed: {exception}",
@@ -269,6 +281,11 @@ class GraphQLClient:
269
281
  )
270
282
  return data
271
283
 
284
+ def _headers(self) -> dict[str, str]:
285
+ if callable(self.headers):
286
+ return dict(self.headers())
287
+ return dict(self.headers)
288
+
272
289
 
273
290
  def operation_name(query: str) -> str:
274
291
  """Extract the operation name from a GraphQL document."""