Metadata-Version: 2.4
Name: repka-sdk
Version: 1.1.0
Summary: Python SDK and CLI for Repka artifact storage
Project-URL: Homepage, https://github.com/nextgis/repka
Project-URL: Repository, https://github.com/nextgis/repka
Author: NextGIS
License: MIT License
        
        Copyright (c) 2026 NextGIS
        
        Permission is hereby granted, free of charge, to any person obtaining
        a copy of this software and associated documentation files (the
        "Software"), to deal in the Software without restriction, including
        without limitation the rights to use, copy, modify, merge, publish,
        distribute, sublicense, and/or sell copies of the Software, and to
        permit persons to whom the Software is furnished to do so, subject to
        the following conditions:
        
        The above copyright notice and this permission notice shall be
        included in all copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
        EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
        MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
        NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
        LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
        OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
        WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
License-File: LICENSE
Keywords: artifact-storage,nextgis,package-manager,repka
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Archiving :: Packaging
Requires-Python: >=3.8
Requires-Dist: httpx<1.0.0,>=0.27.0
Requires-Dist: keyring<26.0.0,>=25.5.0
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
Requires-Dist: rich<15.0.0,>=10.11.0
Requires-Dist: typer<1.0.0,>=0.15.0
Provides-Extra: dev
Requires-Dist: build<2.0.0,>=1.2.0; extra == 'dev'
Requires-Dist: pyright<2.0.0,>=1.1.390; extra == 'dev'
Requires-Dist: pytest-asyncio<1.0.0,>=0.24.0; extra == 'dev'
Requires-Dist: pytest<9.0.0,>=8.3.0; extra == 'dev'
Requires-Dist: respx<1.0.0,>=0.21.1; extra == 'dev'
Requires-Dist: ruff<1.0.0,>=0.8.0; extra == 'dev'
Requires-Dist: twine<7.0.0,>=6.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# repka-sdk

Python SDK and CLI for Repka artifact storage.

## Overview

`repka-sdk` provides a typed Python client and a practical CLI for working
with Repka repositories, packages, releases, assets, and selected
administrative endpoints.

Highlights:

- Synchronous client: `RepkaClient`
- Asynchronous client: `AsyncRepkaClient`
- CLI: `repka`
- CRUD operations for repositories, packages, releases, and assets
- Sync discovery endpoints and repository-specific helpers
- Name, ID, URL, and UI URL resolution helpers
- Idempotent workflows such as `ensure_package`, `ensure_release`, and
  `replace_release`

## Installation

Install from PyPI:

```bash
pip install repka-sdk
```

The distribution name is `repka-sdk`, while the Python import name remains
`repka_sdk`.

For local development:

```bash
pip install -e .[dev]
```

Arch Linux packaging files are available in
[packaging/arch](packaging/arch).

## Configuration

### Resolution order

When the server is not specified explicitly, the active server is resolved in
this order:

1. `dotenv`
1. `env`
1. configured default server
1. built-in `nextgis` server: `https://rm.nextgis.com`

Credential overlays follow the same rule. `.env` credentials win over process
environment credentials, and both can be applied to the active server even
when they do not define their own `REPKA_SERVER_URL`.
Credentials that specify another server URL are never overlaid onto the active
server. An explicit `--server URL` works without a saved server definition.

### Supported variables

The SDK and CLI recognize these variables in `.env` and in the process
environment:

- `REPKA_SERVER_URL`
- `REPKA_USERNAME`
- `REPKA_PASSWORD`

Example `.env`:

```dotenv
REPKA_SERVER_URL=https://rm.staging.nextgis.com
REPKA_USERNAME=editor
REPKA_PASSWORD=secret
```

### Reserved server names

The CLI exposes three reserved names:

- `nextgis`: built-in public server
- `dotenv`: server and credentials from `.env`
- `env`: server and credentials from the current shell environment

When `REPKA_USERNAME` and `REPKA_PASSWORD` are present without
`REPKA_SERVER_URL`, they are applied to the currently active server resolved
by the priority above rather than creating a standalone `dotenv` or `env`
server.

### Storage model

- System server definitions are stored in `repka-sdk/servers.json`
- System credentials are stored in `keyring`
- `.env` credentials stay in the dotenv file
- `env` credentials are exported back to the shell as `export` or `unset`
  commands

## Python API

### Sync client

CLI configuration resolution is separate from the Python constructors:
`RepkaClient()` and `AsyncRepkaClient()` use the built-in public server unless
you pass `base_url`. Pass an authentication provider explicitly in Python.

```python
from repka_sdk import BasicAuthProvider
from repka_sdk import ReleaseInput
from repka_sdk import ReleaseOptions
from repka_sdk import RepkaClient


provider = BasicAuthProvider("editor", "secret")

with RepkaClient(auth_provider=provider) as client:
    uploaded = client.assets.upload("dist/plugin.zip")
    release = client.releases.ensure(
        42,  # Package ID; name lookup requires a repository.
        ReleaseInput(
            name="Release 3.2.1",
            tags=["experimental", "latest"],
            options=ReleaseOptions({"dist": "stable"}),
            assets=[uploaded],
        ),
        version_tag="3.2.1",
        enrich_assets=True,
    )
    print(release.id)
```

Additional endpoints remain available for automation-heavy workflows:

```python
from repka_sdk import BasicAuthProvider
from repka_sdk import RepkaClient


provider = BasicAuthProvider("editor", "secret")

with RepkaClient(auth_provider=provider) as client:
    print(client.server.stats())
    print(client.server.options())
    print(client.assets.rights(42))
    print(client.sync.list_remote_repositories("borsch"))
    print(client.admin.list_users())
```

### Async client

```python
import asyncio

from repka_sdk import AsyncRepkaClient
from repka_sdk import BasicAuthProvider


async def main() -> None:
    provider = BasicAuthProvider("editor", "secret")
    async with AsyncRepkaClient(auth_provider=provider) as client:
        user = await client.auth.whoami()
        print(user.login)


asyncio.run(main())
```

## CLI

### Global options

- `--server`
- `--json`
- `--timeout`
- `--insecure`
- `--verbose`
- `--log-file PATH`
- `--log-level LEVEL` (requires `--log-file`, default: `INFO`)
- `--dry-run`

Place global options **before** the command, for example `repka --json repo list`.
Use `-h` or `--help` on any command. `--dry-run` never uploads files or changes
stored credentials. Repository creation defaults to `--no-cleanup`; retention
values default to `1` because the current server rejects zero values.

### Authentication and server management

```bash
repka server add staging https://rm.staging.nextgis.com --default
repka server set-url dotenv https://rm.staging.nextgis.com
eval "$(repka server set-url env https://rm.staging.nextgis.com)"

repka auth login staging --username editor --password-stdin
repka auth login dotenv --username editor --password-stdin
eval "$(repka auth login env --username editor --password-stdin)"

repka auth list
repka auth status
repka auth params
repka --json auth params
repka auth params --show-password
repka server list
repka server status
```

Notes:

- `repka auth login dotenv` and `repka auth login env` do not prompt for the
  server URL.
- The reserved source URL is taken from `server set-url` when configured;
  otherwise the command authenticates against the active server resolved by the
  priority order.
- `repka auth logout` asks for confirmation when the server is not passed
  explicitly.
- `repka auth logout env` and `repka auth logout dotenv` remain explicit and do
  not ask for confirmation.
- `eval "$(...)"` is required for `env` commands because a child process
  cannot modify the parent shell environment directly.

When `--verbose` is enabled, the CLI writes HTTP request and response logs to
stderr.
`--log-file PATH` enables persistent diagnostic logging; `--log-level DEBUG`
includes HTTP timings. Unexpected failures report a private temporary log with
stack locations and exception types, without credentials or response payloads.

`auth params` prints one field per line, including `url`, server `source`,
`auth_source`, and `auth_type` (`basic` or `anonymous`). Passwords are omitted
unless `--show-password` is passed, including in JSON mode. This command shows
local configuration; use `auth status` to check it against the server.

### Search behavior

```bash
repka find package plugin
repka find package identifyplus --exact
repka find package --repo 4 identifyplus --limit 10

repka find repo qgis --type qgis

repka find release 2
repka find release --option arch=x64 --sort=name
repka find release --type installer --sort=-assets_count
repka find release --option arch=x64 --field all
```

Search rules:

- `repka find` uses substring matching by default.
- Use `--exact` for exact name matching.
- Use `--type` to limit repository, package, or release search to a repository
  type: `any`, `ubuntu`, `docker`, `borsch`, `installer`, `qgis`, `maven`.
- Use `--limit` to cap the number of printed rows.
- Release search shows `assets_count` by default.
- Use `--field all` to print every available table field.
- Sorting supports local fields such as `assets_count`; server-side sort values
  are normalized automatically to `+field` or `-field`.

### Common CLI examples

#### QtIFW installer repositories

Publish a repository produced by Qt Installer Framework's `repogen` into a
Repka repository of type `installer`:

```bash
repka release create --repo installers --package stable --name 2.0.0 \
  --qtifw ./repogen-output --repository-name repository-linux
```

For a directory, the default archive/root name is `repository`; the example
uploads `repository-linux.zip` containing `repository-linux/Updates.xml` and
the component files. A supplied ZIP is validated and uploaded unchanged: its
only top-level directory must match its filename stem. `--repository-name`
cannot rename an existing ZIP. A single `--file` ZIP/directory in an installer
repository also selects this workflow. Other installer artifacts use normal
file uploads.

Validation checks XML structure, component names/versions, referenced data
archives, declared component or unified metadata archives, and required SHA1
sidecar files. ZIP integrity, paths, and server-supported compression are
checked before upload. This validates the repository layout, not the contents
of inner 7z archives or an actual installer run. `--dry-run` validates without
packing or uploading.

Normal output is the URL to pass to QtIFW, for example:

```text
https://rm.example.com/api/repo/42/installer/stable/repository-linux
```

JSON output contains `url`, `url_query_string`, and `release`. With
`--no-latest`, pass the separately printed `UrlQueryString=release_tag=...`
argument to the installer too. Do not append this query to the repository URL:
QtIFW appends `/Updates.xml` and archive paths itself. The default URL follows
the package's `latest` release; keep the same archive/root name across updates.

#### Release publication defaults

`release create`, `ensure`, `update`, and `replace` mark the release `latest`
by default. Use `--no-latest` to omit/remove that tag. QGIS publications always
omit `latest`: the plugin index selects versions itself. When no version tags
are supplied, publication uses the release name as its version tag. Repository
type is resolved before uploading, including for package IDs without parent
metadata; `--repo` can narrow that lookup. Dry runs perform these reads but no
writes. Low-level SDK methods continue to use the explicitly supplied tags.

```bash
repka whoami
repka repo list --field id --field type --field name
repka repo list --field all
repka --json repo list

repka package ensure --repo my-repo --name my-package --description "SDK managed"

repka release ensure \
  --package 42 \
  --name "Release 1.2.3" \
  --version-tag 1.2.3 \
  --latest \
  --option dist=stable \
  --enrich-assets \
  --file dist/package.zip

repka release get --package 42 "Release 1.2.3"
repka asset list 77 -F id,name,size,downloads
repka --json asset upload dist/package.zip
repka asset download --all 77 ./downloads/
repka --json asset rights 42
repka browse release 77 --print-url
repka --json browse --print-url
```

### Output behavior

- Calling `repka`, command groups such as `repka auth`, and commands that need
  required arguments such as `repka find` prints help instead of a raw usage
  error.
- In table output, `server list` is grouped by source in this order:
  `builtin`, `system`, `env`, `dotenv`.
- The `default` and `active` columns use `✓`.
- In JSON mode, scalar values are wrapped into an object:

```json
{"value": "https://rm.staging.nextgis.com"}
```

Search propagates authentication, network, and server failures with a nonzero
exit code rather than reporting an empty successful result. Empty API lists
serialized as `null` are still accepted. Explicit `--sort` order is preserved
in both tables and JSON. Search/upload/download status goes to stderr, with a
spinner on terminals and stable lines when redirected.

## Examples

See [examples/_common.py](examples/_common.py),
[examples/async_whoami.py](examples/async_whoami.py), and
[examples/async_list_entities.py](examples/async_list_entities.py).

## CLI migration notes

- List commands use `--field all` instead of the old `--all` output flag.
- The short release tag option is `-t` (previously `-g`).
- Global `--json`, `--server`, and logging options precede the command.
- Release writes accept `--repo` for package names and `--package` for release
  names; numeric IDs do not require name lookup.
- `package update ID` works without a repository argument.

## Notes

- Backend uses `packet` internally; the SDK exposes `package`.
- Backend stores release options as `key:value|...`; the SDK normalizes them
  to `ReleaseOptions`.
- Metadata-only release updates preserve existing asset IDs when possible.
- Clearing all assets from an existing release is intentionally rejected until
  the server can safely handle `files: []`.
- Clearing a nonempty release description, all tags, or all options is rejected
  because the current server silently ignores those empty values.
- `latest` uniqueness is enforced client-side within a package.
- Supply the parent `Package` to release writes to avoid parent discovery on
  servers that omit `packet_id`. Client-side reconciliation is not atomic across
  concurrent publishers; the server must enforce uniqueness for that guarantee.
- Downloads sanitize server-provided filenames and replace local files only
  after the response is received successfully.
