Metadata-Version: 2.4
Name: vexxhost-costexplorerclient
Version: 0.3.1
Summary: Python client and OpenStack CLI plugin for Cost Explorer
Author: VEXXHOST
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/vexxhost/python-costexplorerclient
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: keystoneauth1>=5.3
Requires-Dist: osc-lib>=3.0
Requires-Dist: python-openstackclient>=7.0
Requires-Dist: typing_extensions>=4.7
Requires-Dist: vexxhost-clientcore<0.2,>=0.1.1
Provides-Extra: cli
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: requests-mock>=1.12; extra == "test"
Requires-Dist: python-openstackclient>=7.0; extra == "test"
Dynamic: license-file

# Python Cost Explorer Client

Python client and OpenStackClient plugin for the Keystone-authenticated
[Cost Explorer API](https://github.com/vexxhost/cost-explorer).
Reports and forecasts are estimates computed on demand; creating one does not
store a resource or create an invoice.

## Install

```sh
pip install vexxhost-costexplorerclient
openstack cost report create --help
```

This installs the `openstack` CLI as well. If you installed OpenStackClient with
pipx, add the commands to it instead:

```sh
pipx inject python-openstackclient vexxhost-costexplorerclient
```

## Authentication and endpoint

Use your existing `clouds.yaml`, `--os-cloud`, or `OS_*` Keystone credentials.
The plugin reuses OpenStackClient's session, token, TLS settings and scope.
It does not require its own username/password and never grants additional access.

The deployed service is registered in Keystone as service type `cost-explorer`,
with a public endpoint in region `ca-east-1`. The CLI discovers it automatically:

```sh
openstack --os-cloud mycloud --os-region-name ca-east-1 --os-interface public \
  cost report create \
  --start 2026-09-01 --end 2026-10-01 --granularity monthly
```

You can also set `OS_REGION_NAME=ca-east-1` and `OS_INTERFACE=public`, or set
`region_name: ca-east-1` and `interface: public` in the chosen `clouds.yaml`
entry. This region currently contains the Cost Explorer endpoint; other cloud
services may use their existing region names. Prefer command-specific
`--os-region-name ca-east-1` when using one cloud profile for multiple services.

Inspect the authenticated catalog with:

```sh
openstack --os-cloud mycloud catalog show cost-explorer
```

No Cost Explorer endpoint override is needed. If you configured one previously,
unset `OS_COST_EXPLORER_ENDPOINT` and remove `cost_explorer_endpoint` from the
cloud profile to use catalog discovery. Obtain a fresh token if an existing
cached catalog predates the registration.

For development or a deployment without a catalog entry, endpoint overrides
remain supported through `--os-cost-explorer-endpoint URL`,
`OS_COST_EXPLORER_ENDPOINT`, or `cost_explorer_endpoint: URL` in `clouds.yaml`.
Endpoints may be the root URL or end in `/v1`. API version 1 is supported
(`--os-cost-explorer-api-version 1`).

TLS certificate verification is enabled by the normal OpenStack session. Use
`--os-cacert` for a private CA. Requests have a 60-second client timeout, and API
redirects are not followed. The Python client accepts a configurable timeout.

## Commands

### Cost reports

```sh
# Monthly total for the authenticated project, through the current time.
openstack cost report create \
  --start 2026-09-01 --end 2026-10-01 --granularity monthly

# Daily compute/volume/image breakdown, including usage quantities.
openstack cost report create \
  --start 2026-09-01 --end 2026-09-17 --detail

# Operator query for a particular project; filters are repeatable.
openstack cost report create \
  --start 2026-09-01 --end 2026-10-01 \
  --project PROJECT_UUID --region ca-ymq-1 --service compute \
  --group-by project_id --group-by usage_type --detail

# Totals only, formatted as JSON with exact decimal strings.
openstack cost report create \
  --start 2026-09-01 --end 2026-10-01 --no-group -f json
```

Dates are interpreted as UTC midnight; timezone-aware RFC3339 timestamps are
also accepted. Start is inclusive and end exclusive. Naive timestamps are
rejected. Other options: `--as-of`, `--usage-type`, `--metric cost`,
`--metric usage_quantity`, `--group-by`, and `--granularity hourly|daily|monthly`.
Usage quantity requires grouping by `usage_type`; use `--metric cost` when you
want other groupings. `--no-group` defaults to cost only.

`--project` takes IDs, not names. It filters data and does not change the
Keystone authentication scope. `--region` filters report data; `--os-region-name`
selects the catalog endpoint. The deployment reports region `ca-ymq-1` even
though its hostname contains `ca-east-1`.

### Forecasts

```sh
# Choose dates starting today or later, within the API's 90-day horizon.
openstack cost forecast create \
  --start 2026-09-18 --end 2026-09-25 --lookback-days 7

openstack cost forecast create \
  --start 2026-09-18 --end 2026-09-25 \
  --project PROJECT_UUID -f json
```

Forecast granularity is `daily` or `monthly`. Lookback is 1–90 days (default 7).
The same project, region, service and usage-type filters are available.

### Output and errors

Default report tables contain one row per period/total metric. `--detail`
shows group/metric rows. Forecast tables contain one row per projected period.
Standard `-f json|yaml|csv|table|value` and `-c COLUMN` formatting is supported.

`-f json` and `-f yaml` serialize the same rows shown by the table formatter.
Use `--detail` to include report group/metric rows and `-c COLUMN` to select
output columns. Monetary and usage amounts remain decimal strings; the client
does not round or convert them to floats. The Python client returns the complete
API response, including metadata such as `as_of` and pricing version.

Errors exit nonzero and include the API's problem detail, schema errors,
request ID and Retry-After value when available. Requests are not automatically
retried by this client. Authorization remains server-enforced: ordinary project
identities cannot read other projects.

## Python usage

```python
from keystoneauth1 import loading, session
from costexplorerclient import Client

# Reuse your application's configured Keystone session/auth plugin.
loader = loading.get_plugin_loader("v3applicationcredential")
auth = loader.load_from_options(
    auth_url="https://auth.vexxhost.net/v3",
    application_credential_id="YOUR_ID",
    application_credential_secret="YOUR_SECRET",
)
client = Client(
    "1",
    session=session.Session(auth=auth),
    endpoint_override="https://cost-explorer.public.ca-east-1.vexxhost.net",
    timeout=60,
)
report = client.cost_reports.create(**{
    "time_period": {"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
    "granularity": "monthly",
})
```

The versioned factory `Client("1", session=...)` creates a
`costexplorerclient.v1.client.Client`. Resource managers expose
`client.cost_reports.create(**request)` and
`client.cost_forecasts.create(**request)`, returning the complete response dict.
The initial `create_report(body)` and `create_forecast(body)` convenience methods
and session-first constructor remain supported.

The transport is `vexxhost-clientcore`'s `keystoneauth1.adapter.Adapter`
subclass, used with service type `cost-explorer`; the budget client uses the same
one. In the CLI it is built by `openstack.config`, so `clouds.yaml` settings such
as `cost_explorer_endpoint_override`, `cost_explorer_interface` and
`cost_explorer_status_code_retries` apply as they do to built-in services. Exceptions derive from `ClientException` (also exported as
`CostExplorerError`), which is shared by every client built on
`vexxhost-clientcore` and also the matching `keystoneauth1.exceptions` HTTP
error, and expose `status_code`, `code`, `problem`, `request_id`
and `retry_after`. Specific failures can be caught as `Unauthorized`,
`Forbidden`, `TooManyRequests`, `ServiceUnavailable`, etc. The library exception
hierarchy does not depend on CLI exceptions.

See [OpenStack client conventions](docs/client-conventions.md) for the upstream
comparison and the intentionally smaller scope of this client.

## Development

```sh
python -m venv .venv
. .venv/bin/activate
pip install -e '.[test]' build
pytest
python -m build
```

Tests exercise real CLI plugin discovery, HTTP authentication and request
construction using a local mock server. CI never requires cloud credentials.

### Generated models

`costexplorerclient/v1/models.py` holds `TypedDict` models of the requests and
responses, generated from the schema cost-explorer serves, and the CLI's choices
(granularities, services, metrics, groupings) are read from them, so they follow
the API instead of being copied from it. `SPEC_VERSION` names the cost-explorer
build the models came from. The generator lives in `vexxhost-clientcore`, so every
client produces models the same way. Regenerate with:

```sh
make generate SPEC_URL=https://cost-explorer.public.global.vexxhost.net/openapi-3.0.yaml
```

The `regenerate` workflow does this daily against the repository variable
`SPEC_URL` and opens a pull request when the served schema changed.

The deployment currently has incomplete pricing for some internal flavors
(such as `m1.amphora`), and broad all-project queries may exceed server limits.
Use project filters. Server-side 503 errors are reported, not converted to
partial/zero totals. Successful responses are estimates, not billing certification.
