jfrog-xray 0.1.0__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 (42) hide show
  1. jfrog_xray-0.1.0/.gitignore +20 -0
  2. jfrog_xray-0.1.0/.idea/.gitignore +3 -0
  3. jfrog_xray-0.1.0/.idea/workspace.xml +4 -0
  4. jfrog_xray-0.1.0/LICENSE +21 -0
  5. jfrog_xray-0.1.0/PKG-INFO +162 -0
  6. jfrog_xray-0.1.0/README.md +140 -0
  7. jfrog_xray-0.1.0/pyproject.toml +71 -0
  8. jfrog_xray-0.1.0/src/jfrog_xray/__init__.py +72 -0
  9. jfrog_xray-0.1.0/src/jfrog_xray/_auth.py +48 -0
  10. jfrog_xray-0.1.0/src/jfrog_xray/_base_client.py +178 -0
  11. jfrog_xray-0.1.0/src/jfrog_xray/_client.py +139 -0
  12. jfrog_xray-0.1.0/src/jfrog_xray/_config.py +42 -0
  13. jfrog_xray-0.1.0/src/jfrog_xray/_exceptions.py +178 -0
  14. jfrog_xray-0.1.0/src/jfrog_xray/_pagination.py +117 -0
  15. jfrog_xray-0.1.0/src/jfrog_xray/_types.py +32 -0
  16. jfrog_xray-0.1.0/src/jfrog_xray/_version.py +5 -0
  17. jfrog_xray-0.1.0/src/jfrog_xray/models/__init__.py +52 -0
  18. jfrog_xray-0.1.0/src/jfrog_xray/models/common.py +86 -0
  19. jfrog_xray-0.1.0/src/jfrog_xray/models/components.py +36 -0
  20. jfrog_xray-0.1.0/src/jfrog_xray/models/scans.py +26 -0
  21. jfrog_xray-0.1.0/src/jfrog_xray/models/summaries.py +77 -0
  22. jfrog_xray-0.1.0/src/jfrog_xray/models/violations.py +34 -0
  23. jfrog_xray-0.1.0/src/jfrog_xray/models/vulnerabilities.py +33 -0
  24. jfrog_xray-0.1.0/src/jfrog_xray/py.typed +0 -0
  25. jfrog_xray-0.1.0/src/jfrog_xray/resources/__init__.py +21 -0
  26. jfrog_xray-0.1.0/src/jfrog_xray/resources/_base.py +12 -0
  27. jfrog_xray-0.1.0/src/jfrog_xray/resources/artifacts.py +123 -0
  28. jfrog_xray-0.1.0/src/jfrog_xray/resources/components.py +86 -0
  29. jfrog_xray-0.1.0/src/jfrog_xray/resources/licenses.py +19 -0
  30. jfrog_xray-0.1.0/src/jfrog_xray/resources/scans.py +52 -0
  31. jfrog_xray-0.1.0/src/jfrog_xray/resources/summaries.py +50 -0
  32. jfrog_xray-0.1.0/src/jfrog_xray/resources/system.py +19 -0
  33. jfrog_xray-0.1.0/src/jfrog_xray/resources/violations.py +93 -0
  34. jfrog_xray-0.1.0/tests/__init__.py +0 -0
  35. jfrog_xray-0.1.0/tests/conftest.py +22 -0
  36. jfrog_xray-0.1.0/tests/test_artifacts.py +117 -0
  37. jfrog_xray-0.1.0/tests/test_client.py +67 -0
  38. jfrog_xray-0.1.0/tests/test_errors.py +64 -0
  39. jfrog_xray-0.1.0/tests/test_integration.py +154 -0
  40. jfrog_xray-0.1.0/tests/test_pagination.py +73 -0
  41. jfrog_xray-0.1.0/tests/test_retries.py +58 -0
  42. jfrog_xray-0.1.0/uv.lock +486 -0
@@ -0,0 +1,20 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+
7
+ # Build
8
+ build/
9
+ dist/
10
+
11
+ # Environments
12
+ .venv/
13
+ .env
14
+
15
+ # Tooling caches
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
@@ -0,0 +1,3 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PropertiesComponent">{}</component>
4
+ </project>
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xray-api contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.5
2
+ Name: jfrog-xray
3
+ Version: 0.1.0
4
+ Summary: A modern, typed Python client for the JFrog Xray REST API (read side).
5
+ Project-URL: Homepage, https://github.com/helic0ptr/jfrog-xray
6
+ Project-URL: Repository, https://github.com/helic0ptr/jfrog-xray
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: artifactory,cve,jfrog,sbom,sca,security,xray
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Security
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.14
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: pydantic>=2.7
21
+ Description-Content-Type: text/markdown
22
+
23
+ # jfrog-xray
24
+
25
+ A modern, typed Python client for the **JFrog Xray REST API** — read side.
26
+
27
+ It focuses on the questions teams actually ask of Xray on Artifactory: *what
28
+ are the CVEs for this artifact?*, *what violations do we have?*, and the
29
+ summary / scan-status / license lookups around them. Responses are parsed into
30
+ [pydantic](https://docs.pydantic.dev) models, list endpoints auto-paginate,
31
+ transient failures are retried, and HTTP errors surface as typed exceptions.
32
+
33
+ > Import name is `jfrog_xray`. Sync client today; the core is structured so an
34
+ > async client can be added without a rewrite.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ uv add jfrog-xray # or: pip install jfrog-xray
40
+ ```
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ from jfrog_xray import XrayClient
46
+
47
+ # base_url is the JFrog Platform root; token is a Bearer access token.
48
+ # Both fall back to env vars: XRAY_URL / XRAY_BASE_URL and XRAY_TOKEN.
49
+ with XrayClient(base_url="https://acme.jfrog.io", token="...") as x:
50
+ x.system.ping() # {"status": "pong"}
51
+ ```
52
+
53
+ ## Headline: CVEs for an artifact
54
+
55
+ Get the vulnerabilities for a single artifact as a typed list (via Summary v2):
56
+
57
+ ```python
58
+ for cve in x.artifacts.vulnerabilities(repo="docker-local",
59
+ path="nginx/1.25/manifest.json"):
60
+ print(cve.cve, cve.severity, cve.component, cve.fixed_versions)
61
+
62
+ # ...or identify the artifact by checksum:
63
+ x.artifacts.vulnerabilities(sha256="9f6c...")
64
+ ```
65
+
66
+ Or download a full report / SBOM (ZIP) for it:
67
+
68
+ ```python
69
+ x.artifacts.export(
70
+ component_name="nginx",
71
+ package_type="docker",
72
+ vulnerabilities=True,
73
+ sbom="cyclonedx", # or "spdx"
74
+ out="nginx-report.zip",
75
+ )
76
+ ```
77
+
78
+ ## Other read APIs
79
+
80
+ ```python
81
+ # Violations — the returned page auto-paginates when iterated.
82
+ for v in x.violations.list(watch_name="prod", min_severity="High"):
83
+ print(v.issue_id, v.severity, v.violation_details_url)
84
+
85
+ # Summaries
86
+ summary = x.summaries.artifact(paths=["docker-local/nginx/1.25/manifest.json"])
87
+ build = x.summaries.build(build_name="my-app", build_number="42")
88
+
89
+ # CVE / component lookups
90
+ x.components.search_by_cves(["CVE-2023-0001"])
91
+ x.components.search_cves_by_components(["gav://com.example:app:1.0.0"])
92
+ for r in x.components.impacted_resources(vulnerability="CVE-2023-0001"):
93
+ print(r.repository, r.path)
94
+
95
+ # Scan status & licenses
96
+ x.scans.artifact_status(repo="docker-local", path="nginx/1.25/manifest.json")
97
+ x.licenses.list()
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ ```python
103
+ XrayClient(
104
+ base_url=..., # or XRAY_URL / XRAY_BASE_URL
105
+ token=..., # or XRAY_TOKEN; alternatively auth=<httpx.Auth>
106
+ timeout=30.0, # float seconds or httpx.Timeout
107
+ max_retries=2, # connection/timeout/5xx/429, honoring Retry-After
108
+ http_client=..., # inject a preconfigured httpx.Client
109
+ )
110
+
111
+ # Per-call overrides (shares the same underlying HTTP client):
112
+ x.with_options(timeout=5.0, max_retries=0).system.ping()
113
+ ```
114
+
115
+ ## Errors
116
+
117
+ All errors derive from `XrayError`. HTTP failures raise an `APIStatusError`
118
+ subclass carrying `.status_code`, `.response`, and `.body`:
119
+
120
+ `BadRequestError` (400), `AuthenticationError` (401), `PermissionDeniedError`
121
+ (403), `NotFoundError` (404), `ConflictError` (409), `UnprocessableEntityError`
122
+ (422), `RateLimitError` (429, with `.retry_after`), `InternalServerError` (5xx).
123
+ Network problems raise `APIConnectionError` / `APITimeoutError`.
124
+
125
+ ## Development
126
+
127
+ ```bash
128
+ uv sync
129
+ uv run ruff check src tests
130
+ uv run mypy src
131
+ uv run pytest # unit tests (respx-mocked; no network)
132
+ ```
133
+
134
+ ### Integration tests
135
+
136
+ Live tests in `tests/test_integration.py` run read-only against a real JFrog
137
+ Platform. They **skip** unless `XRAY_URL` (or `XRAY_BASE_URL`) and `XRAY_TOKEN`
138
+ are set; individual tests skip when their resource env var is absent.
139
+
140
+ ```bash
141
+ export XRAY_URL="https://acme.jfrog.io"
142
+ export XRAY_TOKEN="..."
143
+ # optional, to exercise resource-specific tests:
144
+ export XRAY_TEST_ARTIFACT_PATH="docker-local/nginx/1.25/manifest.json"
145
+ export XRAY_TEST_CVE="CVE-2021-44228"
146
+ # ...see the module docstring for the full list
147
+
148
+ uv run pytest -m integration
149
+ ```
150
+
151
+ ## Scope
152
+
153
+ **In v1 (read side):** system, summaries, violations, CVE/component lookups,
154
+ scan status, licenses, and the `artifacts` convenience resource.
155
+
156
+ **Deferred:** async client, the async Reports API (create → poll → paginated
157
+ content → delete), governance reads (watches / policies / ignore rules), and
158
+ all write-side actions.
159
+
160
+ ## License
161
+
162
+ MIT
@@ -0,0 +1,140 @@
1
+ # jfrog-xray
2
+
3
+ A modern, typed Python client for the **JFrog Xray REST API** — read side.
4
+
5
+ It focuses on the questions teams actually ask of Xray on Artifactory: *what
6
+ are the CVEs for this artifact?*, *what violations do we have?*, and the
7
+ summary / scan-status / license lookups around them. Responses are parsed into
8
+ [pydantic](https://docs.pydantic.dev) models, list endpoints auto-paginate,
9
+ transient failures are retried, and HTTP errors surface as typed exceptions.
10
+
11
+ > Import name is `jfrog_xray`. Sync client today; the core is structured so an
12
+ > async client can be added without a rewrite.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ uv add jfrog-xray # or: pip install jfrog-xray
18
+ ```
19
+
20
+ ## Quickstart
21
+
22
+ ```python
23
+ from jfrog_xray import XrayClient
24
+
25
+ # base_url is the JFrog Platform root; token is a Bearer access token.
26
+ # Both fall back to env vars: XRAY_URL / XRAY_BASE_URL and XRAY_TOKEN.
27
+ with XrayClient(base_url="https://acme.jfrog.io", token="...") as x:
28
+ x.system.ping() # {"status": "pong"}
29
+ ```
30
+
31
+ ## Headline: CVEs for an artifact
32
+
33
+ Get the vulnerabilities for a single artifact as a typed list (via Summary v2):
34
+
35
+ ```python
36
+ for cve in x.artifacts.vulnerabilities(repo="docker-local",
37
+ path="nginx/1.25/manifest.json"):
38
+ print(cve.cve, cve.severity, cve.component, cve.fixed_versions)
39
+
40
+ # ...or identify the artifact by checksum:
41
+ x.artifacts.vulnerabilities(sha256="9f6c...")
42
+ ```
43
+
44
+ Or download a full report / SBOM (ZIP) for it:
45
+
46
+ ```python
47
+ x.artifacts.export(
48
+ component_name="nginx",
49
+ package_type="docker",
50
+ vulnerabilities=True,
51
+ sbom="cyclonedx", # or "spdx"
52
+ out="nginx-report.zip",
53
+ )
54
+ ```
55
+
56
+ ## Other read APIs
57
+
58
+ ```python
59
+ # Violations — the returned page auto-paginates when iterated.
60
+ for v in x.violations.list(watch_name="prod", min_severity="High"):
61
+ print(v.issue_id, v.severity, v.violation_details_url)
62
+
63
+ # Summaries
64
+ summary = x.summaries.artifact(paths=["docker-local/nginx/1.25/manifest.json"])
65
+ build = x.summaries.build(build_name="my-app", build_number="42")
66
+
67
+ # CVE / component lookups
68
+ x.components.search_by_cves(["CVE-2023-0001"])
69
+ x.components.search_cves_by_components(["gav://com.example:app:1.0.0"])
70
+ for r in x.components.impacted_resources(vulnerability="CVE-2023-0001"):
71
+ print(r.repository, r.path)
72
+
73
+ # Scan status & licenses
74
+ x.scans.artifact_status(repo="docker-local", path="nginx/1.25/manifest.json")
75
+ x.licenses.list()
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ ```python
81
+ XrayClient(
82
+ base_url=..., # or XRAY_URL / XRAY_BASE_URL
83
+ token=..., # or XRAY_TOKEN; alternatively auth=<httpx.Auth>
84
+ timeout=30.0, # float seconds or httpx.Timeout
85
+ max_retries=2, # connection/timeout/5xx/429, honoring Retry-After
86
+ http_client=..., # inject a preconfigured httpx.Client
87
+ )
88
+
89
+ # Per-call overrides (shares the same underlying HTTP client):
90
+ x.with_options(timeout=5.0, max_retries=0).system.ping()
91
+ ```
92
+
93
+ ## Errors
94
+
95
+ All errors derive from `XrayError`. HTTP failures raise an `APIStatusError`
96
+ subclass carrying `.status_code`, `.response`, and `.body`:
97
+
98
+ `BadRequestError` (400), `AuthenticationError` (401), `PermissionDeniedError`
99
+ (403), `NotFoundError` (404), `ConflictError` (409), `UnprocessableEntityError`
100
+ (422), `RateLimitError` (429, with `.retry_after`), `InternalServerError` (5xx).
101
+ Network problems raise `APIConnectionError` / `APITimeoutError`.
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ uv sync
107
+ uv run ruff check src tests
108
+ uv run mypy src
109
+ uv run pytest # unit tests (respx-mocked; no network)
110
+ ```
111
+
112
+ ### Integration tests
113
+
114
+ Live tests in `tests/test_integration.py` run read-only against a real JFrog
115
+ Platform. They **skip** unless `XRAY_URL` (or `XRAY_BASE_URL`) and `XRAY_TOKEN`
116
+ are set; individual tests skip when their resource env var is absent.
117
+
118
+ ```bash
119
+ export XRAY_URL="https://acme.jfrog.io"
120
+ export XRAY_TOKEN="..."
121
+ # optional, to exercise resource-specific tests:
122
+ export XRAY_TEST_ARTIFACT_PATH="docker-local/nginx/1.25/manifest.json"
123
+ export XRAY_TEST_CVE="CVE-2021-44228"
124
+ # ...see the module docstring for the full list
125
+
126
+ uv run pytest -m integration
127
+ ```
128
+
129
+ ## Scope
130
+
131
+ **In v1 (read side):** system, summaries, violations, CVE/component lookups,
132
+ scan status, licenses, and the `artifacts` convenience resource.
133
+
134
+ **Deferred:** async client, the async Reports API (create → poll → paginated
135
+ content → delete), governance reads (watches / policies / ignore rules), and
136
+ all write-side actions.
137
+
138
+ ## License
139
+
140
+ MIT
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jfrog-xray"
7
+ version = "0.1.0"
8
+ description = "A modern, typed Python client for the JFrog Xray REST API (read side)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.14"
11
+ license = { text = "MIT" }
12
+ keywords = ["jfrog", "xray", "artifactory", "security", "sca", "cve", "sbom"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Topic :: Security",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.27",
25
+ "pydantic>=2.7",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/helic0ptr/jfrog-xray"
30
+ Repository = "https://github.com/helic0ptr/jfrog-xray"
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "pytest>=8.2",
35
+ "respx>=0.21",
36
+ "anyio>=4.4",
37
+ "ruff>=0.6",
38
+ "mypy>=1.11",
39
+ ]
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/jfrog_xray"]
43
+
44
+ [tool.ruff]
45
+ line-length = 100
46
+ src = ["src", "tests"]
47
+
48
+ [tool.ruff.lint]
49
+ select = ["E", "F", "I", "UP", "B", "SIM", "C4", "RUF"]
50
+ ignore = ["B008"]
51
+
52
+ [tool.ruff.lint.per-file-ignores]
53
+ "tests/**" = ["S101"]
54
+
55
+ [tool.mypy]
56
+ python_version = "3.14"
57
+ strict = true
58
+ plugins = ["pydantic.mypy"]
59
+ files = ["src"]
60
+
61
+ [tool.pydantic-mypy]
62
+ init_forbid_extra = true
63
+ init_typed = true
64
+ warn_required_dynamic_aliases = true
65
+
66
+ [tool.pytest.ini_options]
67
+ testpaths = ["tests"]
68
+ addopts = "-ra"
69
+ markers = [
70
+ "integration: tests that hit a real JFrog Platform (require XRAY_URL/XRAY_TOKEN)",
71
+ ]
@@ -0,0 +1,72 @@
1
+ """A modern, typed Python client for the JFrog Xray REST API (read side)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._auth import ApiKeyAuth, BearerAuth
6
+ from ._client import XrayClient
7
+ from ._exceptions import (
8
+ APIConnectionError,
9
+ APIStatusError,
10
+ APITimeoutError,
11
+ AuthenticationError,
12
+ BadRequestError,
13
+ ConflictError,
14
+ InternalServerError,
15
+ NotFoundError,
16
+ PermissionDeniedError,
17
+ RateLimitError,
18
+ UnprocessableEntityError,
19
+ XrayError,
20
+ )
21
+ from ._pagination import SyncPage
22
+ from ._version import __version__
23
+ from .models import (
24
+ ArtifactSummary,
25
+ BuildSummary,
26
+ Component,
27
+ ComponentDetail,
28
+ Cve,
29
+ CveSearchResult,
30
+ IgnoredViolation,
31
+ ImpactedResource,
32
+ License,
33
+ PackageType,
34
+ Severity,
35
+ Violation,
36
+ Vulnerability,
37
+ )
38
+
39
+ __all__ = [ # noqa: RUF022 -- grouped by category for readability
40
+ "__version__",
41
+ "XrayClient",
42
+ "BearerAuth",
43
+ "ApiKeyAuth",
44
+ "SyncPage",
45
+ # errors
46
+ "XrayError",
47
+ "APIConnectionError",
48
+ "APITimeoutError",
49
+ "APIStatusError",
50
+ "BadRequestError",
51
+ "AuthenticationError",
52
+ "PermissionDeniedError",
53
+ "NotFoundError",
54
+ "ConflictError",
55
+ "UnprocessableEntityError",
56
+ "RateLimitError",
57
+ "InternalServerError",
58
+ # models
59
+ "Severity",
60
+ "PackageType",
61
+ "Cve",
62
+ "Component",
63
+ "License",
64
+ "ArtifactSummary",
65
+ "BuildSummary",
66
+ "Violation",
67
+ "IgnoredViolation",
68
+ "ComponentDetail",
69
+ "CveSearchResult",
70
+ "ImpactedResource",
71
+ "Vulnerability",
72
+ ]
@@ -0,0 +1,48 @@
1
+ """Authentication flows for the Xray client.
2
+
3
+ Bearer access tokens are first-class (JFrog's recommended method), but auth is
4
+ pluggable: any :class:`httpx.Auth` can be supplied instead. Auth classes are
5
+ I/O-free so a single instance works for both the sync and (future) async client.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Generator
11
+
12
+ import httpx
13
+
14
+ __all__ = ["ApiKeyAuth", "BearerAuth", "resolve_auth"]
15
+
16
+
17
+ class BearerAuth(httpx.Auth):
18
+ """Adds ``Authorization: Bearer <token>`` to every request."""
19
+
20
+ def __init__(self, token: str) -> None:
21
+ self._token = token
22
+
23
+ def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response]:
24
+ request.headers["Authorization"] = f"Bearer {self._token}"
25
+ yield request
26
+
27
+
28
+ class ApiKeyAuth(httpx.Auth):
29
+ """Adds a legacy JFrog API-key header (``X-JFrog-Art-Api`` by default)."""
30
+
31
+ def __init__(self, api_key: str, *, header: str = "X-JFrog-Art-Api") -> None:
32
+ self._api_key = api_key
33
+ self._header = header
34
+
35
+ def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response]:
36
+ request.headers[self._header] = self._api_key
37
+ yield request
38
+
39
+
40
+ def resolve_auth(*, token: str | None, auth: httpx.Auth | None) -> httpx.Auth | None:
41
+ """Turn the ``token`` / ``auth`` constructor arguments into an ``httpx.Auth``."""
42
+ if token is not None and auth is not None:
43
+ raise ValueError("Pass either `token=` or `auth=`, not both.")
44
+ if auth is not None:
45
+ return auth
46
+ if token is not None:
47
+ return BearerAuth(token)
48
+ return None