Metadata-Version: 2.5
Name: jfrog-xray
Version: 0.1.0
Summary: A modern, typed Python client for the JFrog Xray REST API (read side).
Project-URL: Homepage, https://github.com/helic0ptr/jfrog-xray
Project-URL: Repository, https://github.com/helic0ptr/jfrog-xray
License: MIT
License-File: LICENSE
Keywords: artifactory,cve,jfrog,sbom,sca,security,xray
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.14
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Description-Content-Type: text/markdown

# jfrog-xray

A modern, typed Python client for the **JFrog Xray REST API** — read side.

It focuses on the questions teams actually ask of Xray on Artifactory: *what
are the CVEs for this artifact?*, *what violations do we have?*, and the
summary / scan-status / license lookups around them. Responses are parsed into
[pydantic](https://docs.pydantic.dev) models, list endpoints auto-paginate,
transient failures are retried, and HTTP errors surface as typed exceptions.

> Import name is `jfrog_xray`. Sync client today; the core is structured so an
> async client can be added without a rewrite.

## Install

```bash
uv add jfrog-xray        # or: pip install jfrog-xray
```

## Quickstart

```python
from jfrog_xray import XrayClient

# base_url is the JFrog Platform root; token is a Bearer access token.
# Both fall back to env vars: XRAY_URL / XRAY_BASE_URL and XRAY_TOKEN.
with XrayClient(base_url="https://acme.jfrog.io", token="...") as x:
    x.system.ping()  # {"status": "pong"}
```

## Headline: CVEs for an artifact

Get the vulnerabilities for a single artifact as a typed list (via Summary v2):

```python
for cve in x.artifacts.vulnerabilities(repo="docker-local",
                                       path="nginx/1.25/manifest.json"):
    print(cve.cve, cve.severity, cve.component, cve.fixed_versions)

# ...or identify the artifact by checksum:
x.artifacts.vulnerabilities(sha256="9f6c...")
```

Or download a full report / SBOM (ZIP) for it:

```python
x.artifacts.export(
    component_name="nginx",
    package_type="docker",
    vulnerabilities=True,
    sbom="cyclonedx",      # or "spdx"
    out="nginx-report.zip",
)
```

## Other read APIs

```python
# Violations — the returned page auto-paginates when iterated.
for v in x.violations.list(watch_name="prod", min_severity="High"):
    print(v.issue_id, v.severity, v.violation_details_url)

# Summaries
summary = x.summaries.artifact(paths=["docker-local/nginx/1.25/manifest.json"])
build = x.summaries.build(build_name="my-app", build_number="42")

# CVE / component lookups
x.components.search_by_cves(["CVE-2023-0001"])
x.components.search_cves_by_components(["gav://com.example:app:1.0.0"])
for r in x.components.impacted_resources(vulnerability="CVE-2023-0001"):
    print(r.repository, r.path)

# Scan status & licenses
x.scans.artifact_status(repo="docker-local", path="nginx/1.25/manifest.json")
x.licenses.list()
```

## Configuration

```python
XrayClient(
    base_url=...,          # or XRAY_URL / XRAY_BASE_URL
    token=...,             # or XRAY_TOKEN; alternatively auth=<httpx.Auth>
    timeout=30.0,          # float seconds or httpx.Timeout
    max_retries=2,         # connection/timeout/5xx/429, honoring Retry-After
    http_client=...,       # inject a preconfigured httpx.Client
)

# Per-call overrides (shares the same underlying HTTP client):
x.with_options(timeout=5.0, max_retries=0).system.ping()
```

## Errors

All errors derive from `XrayError`. HTTP failures raise an `APIStatusError`
subclass carrying `.status_code`, `.response`, and `.body`:

`BadRequestError` (400), `AuthenticationError` (401), `PermissionDeniedError`
(403), `NotFoundError` (404), `ConflictError` (409), `UnprocessableEntityError`
(422), `RateLimitError` (429, with `.retry_after`), `InternalServerError` (5xx).
Network problems raise `APIConnectionError` / `APITimeoutError`.

## Development

```bash
uv sync
uv run ruff check src tests
uv run mypy src
uv run pytest                 # unit tests (respx-mocked; no network)
```

### Integration tests

Live tests in `tests/test_integration.py` run read-only against a real JFrog
Platform. They **skip** unless `XRAY_URL` (or `XRAY_BASE_URL`) and `XRAY_TOKEN`
are set; individual tests skip when their resource env var is absent.

```bash
export XRAY_URL="https://acme.jfrog.io"
export XRAY_TOKEN="..."
# optional, to exercise resource-specific tests:
export XRAY_TEST_ARTIFACT_PATH="docker-local/nginx/1.25/manifest.json"
export XRAY_TEST_CVE="CVE-2021-44228"
# ...see the module docstring for the full list

uv run pytest -m integration
```

## Scope

**In v1 (read side):** system, summaries, violations, CVE/component lookups,
scan status, licenses, and the `artifacts` convenience resource.

**Deferred:** async client, the async Reports API (create → poll → paginated
content → delete), governance reads (watches / policies / ignore rules), and
all write-side actions.

## License

MIT
