AVAX S3 — Python Developer Guide¶
Praktický průvodce pro přístup k S3 úložišti z Python aplikací distribuovaných přes platformu AVAX.
Rychlý start¶
import boto3
import requests
# 1. Získej dočasné S3 credentials přes AVAX API
token = get_storage_token(access_token="...", scope="private")
# 2. Připoj se na S3
s3 = boto3.client(
"s3",
endpoint_url=token["endpoint"],
aws_access_key_id=token["access_key"],
aws_secret_access_key=token["secret_key"],
aws_session_token=token["session_token"],
)
# 3. Nahraj soubor
s3.upload_file("local_file.db", token["bucket"], f"{token['prefix']}data/evidence.db")
# 4. Stáhni soubor
s3.download_file(token["bucket"], f"{token['prefix']}data/evidence.db", "local_copy.db")
Autentizace — získání S3 tokenu¶
Aplikace nikdy nepracuje s permanentními S3 klíči. Vždy žádá backend o krátkodobý token (TTL 1 hodina).
# auth.py
import requests
from dataclasses import dataclass
from datetime import datetime
API_BASE = "https://api.avaxis.cz" # dev: "http://192.168.1.55:8000"
@dataclass
class StorageToken:
endpoint: str
bucket: str
prefix: str # základní prefix pro tento scope
access_key: str
secret_key: str
session_token: str
expires_at: datetime
can_write: bool
def get_storage_token(access_token: str, scope: str, **kwargs) -> StorageToken:
"""
scope:
'private' → soukromý adresář uživatele
'shared' → sdílený adresář (vyžaduje dir_id=<uuid>)
'app' → data aplikace (vyžaduje app_slug=<str>)
'app_backup' → zálohy (read-only, vyžaduje app_slug=<str>)
"""
params = {"scope": scope, **kwargs}
resp = requests.get(
f"{API_BASE}/storage/token",
headers={"Authorization": f"Bearer {access_token}"},
params=params,
timeout=10,
)
resp.raise_for_status()
data = resp.json()
return StorageToken(
endpoint=data["endpoint"],
bucket=data["bucket"],
prefix=data["prefix"],
access_key=data["access_key"],
secret_key=data["secret_key"],
session_token=data["session_token"],
expires_at=datetime.fromisoformat(data["expires_at"]),
can_write=data.get("can_write", True),
)
S3Client — obalová třída¶
Doporučujeme používat tuto třídu která řeší: - automatické obnovení tokenu před expirací - správné prefixování cest - kontext managera
# s3client.py
import boto3
import requests
from datetime import datetime, timedelta, timezone
from pathlib import Path
class AvaxS3Client:
"""
Klient pro přístup k AVAX S3 úložišti.
Automaticky obnovuje dočasné credentials 5 minut před expirací.
Použití:
with AvaxS3Client(access_token, scope="private") as s3:
s3.upload("local.db", "data/evidence.db")
s3.download("data/evidence.db", "local.db")
files = s3.list("data/")
"""
RENEW_BEFORE = timedelta(minutes=5)
def __init__(self, access_token: str, scope: str, **scope_kwargs):
self._access_token = access_token
self._scope = scope
self._scope_kwargs = scope_kwargs
self._token = None
self._client = None
# ── Context manager ──────────────────────────────────────────────────────
def __enter__(self):
self._refresh()
return self
def __exit__(self, *_):
self._client = None
# ── Veřejné metody ───────────────────────────────────────────────────────
def upload(self, local_path: str | Path, remote_path: str,
extra_args: dict | None = None) -> None:
"""Nahraje soubor. remote_path je relativní k prefixu tokenu."""
self._ensure_fresh()
key = self._key(remote_path)
self._client.upload_file(str(local_path), self._token.bucket, key,
ExtraArgs=extra_args or {})
def upload_bytes(self, data: bytes, remote_path: str) -> None:
"""Nahraje bytes přímo do S3 bez dočasného souboru."""
self._ensure_fresh()
self._client.put_object(Bucket=self._token.bucket,
Key=self._key(remote_path), Body=data)
def download(self, remote_path: str, local_path: str | Path) -> None:
"""Stáhne soubor z S3."""
self._ensure_fresh()
Path(local_path).parent.mkdir(parents=True, exist_ok=True)
self._client.download_file(self._token.bucket, self._key(remote_path),
str(local_path))
def download_bytes(self, remote_path: str) -> bytes:
"""Vrátí obsah souboru jako bytes."""
self._ensure_fresh()
obj = self._client.get_object(Bucket=self._token.bucket,
Key=self._key(remote_path))
return obj["Body"].read()
def list(self, prefix: str = "") -> list[dict]:
"""
Vypíše objekty pod daným prefixem.
Vrátí: [{ key, size, last_modified }]
"""
self._ensure_fresh()
full_prefix = self._key(prefix)
paginator = self._client.get_paginator("list_objects_v2")
result = []
for page in paginator.paginate(Bucket=self._token.bucket, Prefix=full_prefix):
for obj in page.get("Contents", []):
result.append({
"key": obj["Key"].removeprefix(self._token.prefix),
"size": obj["Size"],
"last_modified": obj["LastModified"],
})
return result
def exists(self, remote_path: str) -> bool:
"""Ověří zda soubor existuje."""
self._ensure_fresh()
try:
self._client.head_object(Bucket=self._token.bucket,
Key=self._key(remote_path))
return True
except self._client.exceptions.ClientError:
return False
def presigned_url(self, remote_path: str, ttl: int = 900) -> str:
"""Vygeneruje presigned GET URL platný `ttl` sekund (max 900)."""
self._ensure_fresh()
return self._client.generate_presigned_url(
"get_object",
Params={"Bucket": self._token.bucket, "Key": self._key(remote_path)},
ExpiresIn=ttl,
)
# ── Interní ──────────────────────────────────────────────────────────────
def _key(self, path: str) -> str:
"""Sestaví plný S3 klíč: prefix + path (bez dvojitých lomítek)."""
return self._token.prefix.rstrip("/") + "/" + path.lstrip("/")
def _ensure_fresh(self):
expires = self._token.expires_at.replace(tzinfo=timezone.utc) \
if self._token.expires_at.tzinfo is None \
else self._token.expires_at
if datetime.now(timezone.utc) >= expires - self.RENEW_BEFORE:
self._refresh()
def _refresh(self):
self._token = get_storage_token(
self._access_token, self._scope, **self._scope_kwargs
)
self._client = boto3.client(
"s3",
endpoint_url=self._token.endpoint,
aws_access_key_id=self._token.access_key,
aws_secret_access_key=self._token.secret_key,
aws_session_token=self._token.session_token,
)
Příklady použití¶
Soukromý adresář uživatele¶
with AvaxS3Client(access_token, scope="private") as s3:
# Uložení lokální SQLite databáze
s3.upload("data/evidence.db", "evidence.db")
# Načtení zpět
s3.download("evidence.db", "data/evidence_restored.db")
# Výpis souborů
for f in s3.list(""):
print(f["key"], f["size"])
Sdílený adresář (admin musí udělit přístup)¶
# Nejdřív zjisti dostupné sdílené adresáře
dirs = requests.get(
f"{API_BASE}/storage/directories",
headers={"Authorization": f"Bearer {access_token}"},
).json()
# [{ "id": "...", "name": "Účetní dokumenty", "can_write": True }, ...]
dir_id = dirs[0]["id"]
with AvaxS3Client(access_token, scope="shared", dir_id=dir_id) as s3:
# Nahrání sdíleného dokumentu
s3.upload("faktury/2025-01.pdf", "faktury/2025-01.pdf")
# Čtení dokumentu jiného uživatele ve stejném adresáři
data = s3.download_bytes("faktury/2025-02.pdf")
Data aplikace (AVAX SDK)¶
# Při použití AVAX SDK je token automatický:
from avaxis_sdk import AvaxApp
app = AvaxApp(slug="moje-ucetnictvi")
app.connect()
# SDK poskytuje AvaxS3Client přímo
with app.storage.app_data() as s3:
s3.upload(app.paths.data / "db.sqlite", "db.sqlite")
# Nebo ručně:
with AvaxS3Client(app.access_token, scope="app", app_slug="moje-ucetnictvi") as s3:
s3.upload("db.sqlite", "db.sqlite")
Upload bytes (bez dočasného souboru)¶
import json
with AvaxS3Client(access_token, scope="private") as s3:
# Uložení konfigurace jako JSON
config = {"theme": "dark", "lang": "cs", "version": "2.1"}
s3.upload_bytes(json.dumps(config).encode(), "settings/config.json")
# Načtení
raw = s3.download_bytes("settings/config.json")
config = json.loads(raw)
Presigned URL pro stažení v prohlížeči¶
with AvaxS3Client(access_token, scope="shared", dir_id=dir_id) as s3:
url = s3.presigned_url("reports/report-2025-Q1.pdf", ttl=600)
# url je platné 10 minut — pošli ho uživateli pro stažení
print(url)
Správa sdílených adresářů (admin)¶
import requests
headers = {"Authorization": f"Bearer {admin_token}", "Content-Type": "application/json"}
# Vytvoření adresáře
resp = requests.post(f"{API_BASE}/storage/directories", headers=headers, json={
"name": "Účetní dokumenty 2025",
"description": "Faktury, výpisy, přiznání",
})
dir_id = resp.json()["id"]
# Udělení přístupu konkrétnímu uživateli (read+write)
requests.post(f"{API_BASE}/storage/directories/{dir_id}/access", headers=headers, json={
"user_id": "uuid-uzivatele",
"can_read": True,
"can_write": True,
})
# Udělení přístupu celé roli (read-only)
requests.post(f"{API_BASE}/storage/directories/{dir_id}/access", headers=headers, json={
"role_id": "uuid-role-ucetni",
"can_read": True,
"can_write": False,
})
# Udělení přístupu všem ve firmě (read-only)
requests.post(f"{API_BASE}/storage/directories/{dir_id}/access", headers=headers, json={
"all_company": True,
"can_read": True,
"can_write": False,
})
# Odebrání přístupu
requests.delete(f"{API_BASE}/storage/directories/{dir_id}/access/{access_id}", headers=headers)
# Výpis ACL záznamu adresáře
acl = requests.get(f"{API_BASE}/storage/directories/{dir_id}/access", headers=headers).json()
Scope přehled¶
| Scope | Prefix v bucketu | TTL | Write |
|---|---|---|---|
private |
users/{user_id}/ |
1 h | ano |
shared + dir_id |
shared/custom/{dir_id}/ |
1 h | dle ACL |
app + app_slug |
apps/{slug}/data/ |
15 min | ano |
app_backup + app_slug |
apps/{slug}/backups/ |
15 min | ne |
Dev server¶
API_BASE = "http://192.168.1.55:8000"
# Testovací přihlášení
resp = requests.post(f"{API_BASE}/auth/login", json={
"email": "admin@test.cz",
"password": "Heslo123!",
})
access_token = resp.json()["access_token"]
# Test soukromého S3
with AvaxS3Client(access_token, scope="private") as s3:
s3.upload_bytes(b"hello world", "test.txt")
print(s3.download_bytes("test.txt")) # b"hello world"
print(s3.list(""))
Chybové stavy¶
import boto3.exceptions
import botocore.exceptions
try:
with AvaxS3Client(access_token, scope="shared", dir_id=dir_id) as s3:
s3.upload("file.pdf", "docs/file.pdf")
except requests.HTTPError as e:
if e.response.status_code == 403:
print("Nemáte přístup k tomuto adresáři")
elif e.response.status_code == 401:
print("Token vypršel — přihlaste se znovu")
except botocore.exceptions.ClientError as e:
code = e.response["Error"]["Code"]
if code == "NoSuchKey":
print("Soubor neexistuje")
elif code == "AccessDenied":
print("S3 přístup odepřen — token mohl vypršet")
else:
raise
Kompletní requirements.txt¶
AVAX Platform | api.avaxis.cz | Aktualizováno: 2026-04-24