Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Aktualna kompatybilność: **KSeF API `v2.1.2`** ([api-changelog.md](https://git
- Uwierzytelnianie: token KSeF i podpis XAdES, w tym `XadesKeyPair` dla PKCS#12 lub PEM.
- Workflows wysyłki: sesje online i batch z ZIP, partiami i pre-signed URL.
- Eksport/pobieranie: obsługa paczek i narzędzi do odszyfrowania/rozpakowania.
- Latarnia: publiczne endpointy dostępności KSeF (`client.lighthouse`, `ksef lighthouse ...`).
- Narzędzia pomocnicze: AES/ZIP/Base64Url, linki weryfikacyjne, QR.
- CLI `ksef`: szybka ścieżka od konfiguracji do pierwszych operacji: `init -> auth -> invoice/send/upo`.

Expand Down Expand Up @@ -98,6 +99,8 @@ Najważniejsze grupy komend:
- auth: `auth login-token`, `auth login-xades`, `auth status`, `auth refresh`, `auth logout`
- operacje: `invoice ...`, `send ...`, `upo ...`, `export ...`
- diagnostyka: `health check`
- latarnia: `lighthouse status`, `lighthouse messages`
- komendy Latarni sa publiczne (dzialaja bez logowania; bez profilu domyslnie uzywana jest latarnia test)

Pełna specyfikacja CLI: [`docs/cli/README.md`](docs/cli/README.md)

Expand Down Expand Up @@ -187,6 +190,13 @@ session_reference_number = BatchSessionWorkflow(client.sessions, client.http_cli
)
```

### Odczyt statusu Latarni

```python
status = client.lighthouse.get_status()
messages = client.lighthouse.get_messages()
```

## 📚 Dokumentacja

Dokumentacja SDK znajduje się w `docs/`:
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Biblioteka udostępnia dwa poziomy użycia:
- [`client.auth`](api/auth.md)
- [`client.sessions`](api/sessions.md)
- [`client.invoices`](api/invoices.md)
- [`client.lighthouse`](api/lighthouse.md)
- [`client.permissions`](api/permissions.md)
- [`client.certificates`](api/certificates.md)
- [`client.tokens`](api/tokens.md)
Expand Down
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Dla `AsyncKsefClient` zestaw metod jest taki sam – różni się tylko sposób
- [`client.auth`](auth.md)
- [`client.sessions`](sessions.md)
- [`client.invoices`](invoices.md)
- [`client.lighthouse`](lighthouse.md)
- [`client.permissions`](permissions.md)
- [`client.certificates`](certificates.md)
- [`client.tokens`](tokens.md)
Expand Down
1 change: 1 addition & 0 deletions docs/api/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `client.auth`
- `client.sessions`
- `client.invoices`
- `client.lighthouse`
- `client.permissions`
- `client.certificates`
- `client.tokens`
Expand Down
33 changes: 33 additions & 0 deletions docs/api/lighthouse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Latarnia (`client.lighthouse`)

`client.lighthouse` udostępnia publiczne endpointy Latarni KSeF (bez autoryzacji).

## `get_status()`

Endpoint: `GET /status` (API Latarni)

Zwraca `LighthouseStatusResponse`:
- `status`: `AVAILABLE`, `MAINTENANCE`, `FAILURE`, `TOTAL_FAILURE`
- `messages`: komunikaty powiązane z aktualnym statusem (lub brak dla pełnej dostępności)

## `get_messages()`

Endpoint: `GET /messages` (API Latarni)

Zwraca `list[LighthouseMessage]` z opublikowanymi komunikatami.

## Środowiska

Domyślne mapowanie dla `KsefClientOptions.base_url`:
- `TEST` -> `https://api-latarnia-test.ksef.mf.gov.pl`
- `DEMO` -> `https://api-latarnia-test.ksef.mf.gov.pl`
- `PROD` -> `https://api-latarnia.ksef.mf.gov.pl`

Nadpisanie mapowania:
- `KsefClientOptions(base_lighthouse_url="https://...")`

## CLI (`ksef lighthouse ...`)

- Komendy Latarni działają bez tokenu i bez aktywnego profilu.
- Jeśli nie ustawisz profilu i nie podasz `--base-url`/`KSEF_LIGHTHOUSE_BASE_URL`, CLI używa domyślnie **latarni test**:
- `https://api-latarnia-test.ksef.mf.gov.pl`
31 changes: 31 additions & 0 deletions docs/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ CLI ma skracac droge od instalacji do pierwszej realnej operacji KSeF:
- `auth logout`
- diagnostyka:
- `health check`
- latarnia:
- `lighthouse status`
- `lighthouse messages`
- faktury i UPO:
- `invoice list`, `invoice download`
- `send online`, `send batch`, `send status`
Expand Down Expand Up @@ -80,6 +83,9 @@ ksef
logout
health
check
lighthouse
status
messages
invoice
list
download
Expand Down Expand Up @@ -118,6 +124,7 @@ Zachowanie profilu:

Brak aktywnego profilu:
- komendy `auth`, `health`, `invoice`, `send`, `upo`, `export` wymagaja aktywnego profilu,
- komendy `lighthouse ...` dzialaja bez profilu (publiczne API Latarni),
- jesli profil nie jest ustawiony, CLI zwraca czytelny blad z podpowiedzia:
- `ksef init --set-active`
- `ksef profile use --name <name>`
Expand Down Expand Up @@ -292,6 +299,30 @@ Options:
--base-url TEXT
```

## `ksef lighthouse status`

```text
Usage: ksef lighthouse status [OPTIONS]

Options:
--base-url TEXT
```

Uwagi:
- komenda korzysta z publicznego API Latarni (bez tokenu dostepowego),
- domyslnie base URL Latarni mapowany jest z profilu KSeF (`TEST`/`DEMO` -> latarnia TEST, `PROD` -> latarnia PROD),
- gdy brak profilu i brak override (`--base-url`/`KSEF_LIGHTHOUSE_BASE_URL`), uzywana jest domyslnie **latarnia test**: `https://api-latarnia-test.ksef.mf.gov.pl`,
- `--base-url` lub `KSEF_LIGHTHOUSE_BASE_URL` pozwala nadpisac endpoint Latarni.

## `ksef lighthouse messages`

```text
Usage: ksef lighthouse messages [OPTIONS]

Options:
--base-url TEXT
```

## invoice / send / upo / export

## `ksef invoice list`
Expand Down
21 changes: 21 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ options = KsefClientOptions(
allowed_presigned_hosts=None,
allow_private_network_presigned_urls=False,
base_qr_url=None,
base_lighthouse_url=None,
)
```

Expand All @@ -44,6 +45,26 @@ Używane przez `VerificationLinkService` do budowania linków pod QR.
- Jeśli `base_qr_url` nie jest ustawione, biblioteka dobiera je na podstawie `base_url` (TEST/DEMO/PROD).
- Dla niestandardowego `base_url` wymagane jest ustawienie `base_qr_url` jawnie.

### `base_lighthouse_url`

Używane przez `client.lighthouse` (`/status`, `/messages` API Latarni).

- Jeśli `base_lighthouse_url` nie jest ustawione, biblioteka mapuje je z `base_url`:
- KSeF `TEST` -> Latarnia `TEST`
- KSeF `DEMO` -> Latarnia `TEST`
- KSeF `PROD` -> Latarnia `PROD`
- Dla niestandardowego `base_url` można ustawić `base_lighthouse_url` jawnie.

Gotowe stałe:

```python
from ksef_client import KsefLighthouseEnvironment

KsefLighthouseEnvironment.TEST.value
KsefLighthouseEnvironment.PROD.value
KsefLighthouseEnvironment.PRD.value # alias
```

### `timeout_seconds`

Timeout dla pojedynczego żądania HTTP (httpx). W przypadku wysyłki partów i pobierania paczek eksportu może być wymagane zwiększenie wartości (duże paczki, wolniejsze łącza).
Expand Down
13 changes: 12 additions & 1 deletion src/ksef_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""KSeF Python SDK."""

from .client import AsyncKsefClient, KsefClient
from .config import KsefClientOptions, KsefEnvironment
from .config import KsefClientOptions, KsefEnvironment, KsefLighthouseEnvironment
from .exceptions import KsefApiError, KsefHttpError, KsefRateLimitError
from .models import (
AuthenticationChallengeResponse,
Expand All @@ -11,13 +11,19 @@
InvoiceExportStatusResponse,
InvoicePackage,
InvoicePackagePart,
LighthouseKsefStatus,
LighthouseMessage,
LighthouseMessageCategory,
LighthouseMessageType,
LighthouseStatusResponse,
StatusInfo,
TokenInfo,
)

__all__ = [
"KsefClientOptions",
"KsefEnvironment",
"KsefLighthouseEnvironment",
"KsefClient",
"AsyncKsefClient",
"KsefApiError",
Expand All @@ -27,6 +33,11 @@
"AuthenticationInitResponse",
"AuthenticationTokensResponse",
"AuthenticationTokenRefreshResponse",
"LighthouseKsefStatus",
"LighthouseMessageCategory",
"LighthouseMessageType",
"LighthouseMessage",
"LighthouseStatusResponse",
"TokenInfo",
"StatusInfo",
"InvoicePackage",
Expand Down
2 changes: 2 additions & 0 deletions src/ksef_client/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
health_cmd,
init_cmd,
invoice_cmd,
lighthouse_cmd,
profile_cmd,
send_cmd,
upo_cmd,
Expand Down Expand Up @@ -79,6 +80,7 @@ def main(
app.add_typer(profile_cmd.app, name="profile")
app.add_typer(auth_cmd.app, name="auth")
app.add_typer(health_cmd.app, name="health")
app.add_typer(lighthouse_cmd.app, name="lighthouse")
app.add_typer(invoice_cmd.app, name="invoice")
app.add_typer(send_cmd.app, name="send")
app.add_typer(upo_cmd.app, name="upo")
Expand Down
22 changes: 21 additions & 1 deletion src/ksef_client/cli/auth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import Any

from ksef_client.config import KsefEnvironment
from ksef_client.config import KsefClientOptions, KsefEnvironment, KsefLighthouseEnvironment
from ksef_client.services.workflows import AuthCoordinator
from ksef_client.services.xades import XadesKeyPair

Expand Down Expand Up @@ -51,6 +51,26 @@ def resolve_base_url(base_url: str | None, *, profile: str | None = None) -> str
return KsefEnvironment.DEMO.value


def resolve_lighthouse_base_url(
base_lighthouse_url: str | None,
*,
profile: str | None = None,
) -> str:
if base_lighthouse_url and base_lighthouse_url.strip():
return base_lighthouse_url.strip()

if profile:
profile_base_url, _, _ = _profile_context(profile)
if profile_base_url and profile_base_url.strip():
try:
options = KsefClientOptions(base_url=profile_base_url.strip())
return options.resolve_lighthouse_base_url()
except ValueError:
pass

return KsefLighthouseEnvironment.TEST.value


def login_with_token(
*,
profile: str,
Expand Down
111 changes: 111 additions & 0 deletions src/ksef_client/cli/commands/lighthouse_cmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from __future__ import annotations

import os

import typer

from ksef_client.exceptions import KsefApiError, KsefHttpError, KsefRateLimitError

from ..auth.manager import resolve_base_url, resolve_lighthouse_base_url
from ..context import profile_label, require_context
from ..errors import CliError
from ..exit_codes import ExitCode
from ..output import get_renderer
from ..sdk.adapters import get_lighthouse_messages, get_lighthouse_status

app = typer.Typer(help="Read public KSeF Lighthouse status and messages.")


def _render_error(ctx: typer.Context, command: str, exc: Exception) -> None:
cli_ctx = require_context(ctx)
renderer = get_renderer(cli_ctx)

if isinstance(exc, CliError):
renderer.error(
command=command,
profile=profile_label(cli_ctx),
code=exc.code.name,
message=exc.message,
hint=exc.hint,
)
raise typer.Exit(int(exc.code))

if isinstance(exc, KsefRateLimitError):
hint = f"Retry-After: {exc.retry_after}" if exc.retry_after else "Wait and retry."
renderer.error(
command=command,
profile=profile_label(cli_ctx),
code="RATE_LIMIT",
message=str(exc),
hint=hint,
)
raise typer.Exit(int(ExitCode.RETRY_EXHAUSTED))

if isinstance(exc, (KsefApiError, KsefHttpError)):
renderer.error(
command=command,
profile=profile_label(cli_ctx),
code="API_ERROR",
message=str(exc),
hint="Check Lighthouse response and request parameters.",
)
raise typer.Exit(int(ExitCode.API_ERROR))

renderer.error(
command=command,
profile=profile_label(cli_ctx),
code="UNEXPECTED",
message=str(exc),
hint="Run with -v and inspect logs.",
)
raise typer.Exit(int(ExitCode.CONFIG_ERROR))


@app.command("status")
def lighthouse_status(
ctx: typer.Context,
base_url: str | None = typer.Option(
None, "--base-url", help="Override Lighthouse base URL for this command."
),
) -> None:
cli_ctx = require_context(ctx)
renderer = get_renderer(cli_ctx)
profile = profile_label(cli_ctx)
selected_profile = cli_ctx.profile
try:
result = get_lighthouse_status(
profile=selected_profile or "",
base_url=resolve_base_url(os.getenv("KSEF_BASE_URL"), profile=selected_profile),
lighthouse_base_url=resolve_lighthouse_base_url(
base_url or os.getenv("KSEF_LIGHTHOUSE_BASE_URL"),
profile=selected_profile,
),
)
except Exception as exc:
_render_error(ctx, "lighthouse.status", exc)
renderer.success(command="lighthouse.status", profile=profile, data=result)


@app.command("messages")
def lighthouse_messages(
ctx: typer.Context,
base_url: str | None = typer.Option(
None, "--base-url", help="Override Lighthouse base URL for this command."
),
) -> None:
cli_ctx = require_context(ctx)
renderer = get_renderer(cli_ctx)
profile = profile_label(cli_ctx)
selected_profile = cli_ctx.profile
try:
result = get_lighthouse_messages(
profile=selected_profile or "",
base_url=resolve_base_url(os.getenv("KSEF_BASE_URL"), profile=selected_profile),
lighthouse_base_url=resolve_lighthouse_base_url(
base_url or os.getenv("KSEF_LIGHTHOUSE_BASE_URL"),
profile=selected_profile,
),
)
except Exception as exc:
_render_error(ctx, "lighthouse.messages", exc)
renderer.success(command="lighthouse.messages", profile=profile, data=result)
Loading
Loading