Files
simple-nanoshare/cli/nanoshare_client/app.py
T
daniel156161 73fdeacbfe
Build and Push Docker Container / build-and-push (push) Successful in 2m25s
feat!: adopt Python 3.14 and 3.15 language features
- Require Python 3.15 for the server and Python 3.14 for the CLI.
- Match the server requirement to quart_common's Python 3.15 features.
- Replace future annotations with native deferred evaluation.
- Defer CLI command dependencies and QR imports on Python 3.15.
- Enable argparse suggestions for mistyped CLI commands.
- Remove the obsolete tomllib import fallback.
- Update my_helpers and quart_common submodule references.
- Lock test dependencies and exclude them from Docker installations.
- Add regression coverage for lazy imports and deferred annotations.
- Document runtime requirements and standalone CLI validation.
- Validate 276 tests on Python 3.15 and 18 CLI tests on Python 3.14.

BREAKING CHANGE: The server now requires Python 3.15+ and the standalone CLI requires Python 3.14+.
2026-10-26 14:47:29 +01:00

64 lines
2.1 KiB
Python

import os
import sys
from pathlib import Path
import tomllib
from .config import DEFAULT_CONFIG
from .http_client import NanoShareClient
def parse_registry_env(value: str) -> dict[str, str]:
registry: dict[str, str] = {}
for pair in value.split(','):
pair = pair.strip()
if not pair:
continue
name, _, url = pair.partition('=')
if name.strip() and url.strip():
registry[name.strip()] = url.strip()
return registry
def load_config(path: str | os.PathLike | None) -> dict:
if not path:
return {}
config_path = Path(path).expanduser()
if not config_path.is_file():
return {}
with config_path.open('rb') as handle:
return tomllib.load(handle)
def resolve(args) -> tuple[dict[str, str], str, dict[str, str | None]]:
config_path = args.config or os.getenv('NANOSHARE_CONFIG') or DEFAULT_CONFIG
config = load_config(config_path)
registry: dict[str, str] = dict(config.get('nodes', {}))
env_registry = os.getenv('NANOSHARE_REGISTRY')
if env_registry:
registry.update(parse_registry_env(env_registry))
for item in (args.url or []):
name, _, url = item.partition('=')
if not url:
raise SystemExit(f'--url expects NODE=URL, got: {item!r}')
registry[name.strip()] = url.strip()
source = args.source or os.getenv('NANOSHARE_SOURCE') or config.get('source') or 'nanoshare-cli'
auth = {
'token': args.token or os.getenv('NANOSHARE_TOKEN') or config.get('token'),
'refresh_token': getattr(args, 'refresh_token', None) or os.getenv('NANOSHARE_REFRESH_TOKEN') or config.get('refresh_token'),
'token_url': getattr(args, 'token_url', None) or os.getenv('NANOSHARE_TOKEN_URL') or config.get('token_url'),
}
return registry, source, auth
def make_client(args) -> NanoShareClient:
registry, source, auth = resolve(args)
if not registry:
raise SystemExit('no NanoShare node configured (run `nanoshare login BASE_URL`, pass --url, or set NANOSHARE_REGISTRY).')
return NanoShareClient(
source=source,
registry=registry,
token=auth.get('token'),
refresh_token=auth.get('refresh_token'),
token_url=auth.get('token_url'),
timeout=getattr(args, 'timeout', 30.0),
)