anafpy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anafpy-0.1.0/.gitattributes +2 -0
- anafpy-0.1.0/.github/workflows/ci.yml +33 -0
- anafpy-0.1.0/.github/workflows/release.yml +67 -0
- anafpy-0.1.0/.gitignore +42 -0
- anafpy-0.1.0/.pre-commit-config.yaml +13 -0
- anafpy-0.1.0/.python-version +1 -0
- anafpy-0.1.0/CLAUDE.md +390 -0
- anafpy-0.1.0/DESIGN.md +513 -0
- anafpy-0.1.0/INSTALL.md +297 -0
- anafpy-0.1.0/LICENSE +173 -0
- anafpy-0.1.0/NOTICE +17 -0
- anafpy-0.1.0/PKG-INFO +417 -0
- anafpy-0.1.0/README.md +384 -0
- anafpy-0.1.0/docs/anaf-reference/README.md +25 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/Oauth_procedura_inregistrare_aplicatii_portal_ANAF.pdf +0 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/README.md +83 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/eTransport-validation_v.2.0.2_12082024.sch +588 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/descarcare.html +54 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/listamesaje.html +522 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/staremesaj.html +54 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/upload.html +570 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/validare.html +226 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/validaresemnatura.html +137 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura-swagger/xmltopdf.html +356 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/efactura_prezentare_api.pdf +0 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/etransport-swagger/info_transportatori.html +223 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/etransport-swagger/lista.html +341 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/etransport-swagger/stare.html +232 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/etransport-swagger/upload_param.html +439 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/etransport_29072024.pdf +0 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/limiteApeluriAPI.txt +25 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/docV1.txt +63 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/doc_WS_Async_V8.txt +210 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/doc_WS_Bilant_V1.txt +196 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/doc_WS_V9.txt +156 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/documentatie_SWRARG_v2.txt +83 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/index_cult_v2.html +124 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/servicii_asincron.html +46 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/servicii_sincron.html +157 -0
- anafpy-0.1.0/docs/anaf-reference/_sources/servicii-web/servicii_web.html +316 -0
- anafpy-0.1.0/docs/anaf-reference/efactura/api.md +336 -0
- anafpy-0.1.0/docs/anaf-reference/etransport/api.md +223 -0
- anafpy-0.1.0/docs/anaf-reference/oauth/authentication.md +257 -0
- anafpy-0.1.0/docs/anaf-reference/public/api.md +224 -0
- anafpy-0.1.0/pyproject.toml +113 -0
- anafpy-0.1.0/schemas/README.md +23 -0
- anafpy-0.1.0/schemas/etransport/schema_ETR_v2_20230126.xsd +945 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/CCTS_CCT_SchemaModule-2.1.xsd +731 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-CommonAggregateComponents-2.1.xsd +39799 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-CommonBasicComponents-2.1.xsd +5389 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-CommonExtensionComponents-2.1.xsd +223 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-CommonSignatureComponents-2.1.xsd +101 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-CoreComponentParameters-2.1.xsd +63 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-ExtensionContentDataType-2.1.xsd +89 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-QualifiedDataTypes-2.1.xsd +69 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-SignatureAggregateComponents-2.1.xsd +138 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-SignatureBasicComponents-2.1.xsd +78 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-UnqualifiedDataTypes-2.1.xsd +553 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-XAdESv132-2.1.xsd +476 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-XAdESv141-2.1.xsd +25 -0
- anafpy-0.1.0/schemas/ubl-2.1/common/UBL-xmldsig-core-schema-2.1.xsd +330 -0
- anafpy-0.1.0/schemas/ubl-2.1/maindoc/UBL-CreditNote-2.1.xsd +951 -0
- anafpy-0.1.0/schemas/ubl-2.1/maindoc/UBL-Invoice-2.1.xsd +1002 -0
- anafpy-0.1.0/scripts/generate_etransport.py +203 -0
- anafpy-0.1.0/scripts/generate_ubl.py +103 -0
- anafpy-0.1.0/skills/etransport-declare/SKILL.md +275 -0
- anafpy-0.1.0/src/anafpy/__init__.py +25 -0
- anafpy-0.1.0/src/anafpy/_transport/__init__.py +7 -0
- anafpy-0.1.0/src/anafpy/_transport/base.py +145 -0
- anafpy-0.1.0/src/anafpy/auth/__init__.py +25 -0
- anafpy-0.1.0/src/anafpy/auth/callback.py +215 -0
- anafpy-0.1.0/src/anafpy/auth/models.py +94 -0
- anafpy-0.1.0/src/anafpy/auth/oauth.py +119 -0
- anafpy-0.1.0/src/anafpy/auth/provider.py +132 -0
- anafpy-0.1.0/src/anafpy/auth/store.py +230 -0
- anafpy-0.1.0/src/anafpy/cli/__init__.py +1 -0
- anafpy-0.1.0/src/anafpy/cli/main.py +299 -0
- anafpy-0.1.0/src/anafpy/efactura/__init__.py +48 -0
- anafpy-0.1.0/src/anafpy/efactura/client.py +509 -0
- anafpy-0.1.0/src/anafpy/efactura/models.py +425 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/__init__.py +1 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/__init__.py +5852 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ccts_cct_schema_module_2_1.py +1127 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_common_aggregate_components_2_1.py +55291 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_common_basic_components_2_1.py +12592 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_common_extension_components_2_1.py +297 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_common_signature_components_2_1.py +60 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_extension_content_data_type_2_1.py +30 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_signature_aggregate_components_2_1.py +86 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_signature_basic_components_2_1.py +24 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_unqualified_data_types_2_1.py +577 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_xad_esv132_2_1.py +1613 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_xad_esv141_2_1.py +58 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/common/ubl_xmldsig_core_schema_2_1.py +881 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/maindoc/__init__.py +15 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/maindoc/ubl_credit_note_2_1.py +1151 -0
- anafpy-0.1.0/src/anafpy/efactura/ubl/maindoc/ubl_invoice_2_1.py +1229 -0
- anafpy-0.1.0/src/anafpy/etransport/__init__.py +61 -0
- anafpy-0.1.0/src/anafpy/etransport/client.py +376 -0
- anafpy-0.1.0/src/anafpy/etransport/models.py +1048 -0
- anafpy-0.1.0/src/anafpy/etransport/schema/__init__.py +49 -0
- anafpy-0.1.0/src/anafpy/etransport/schema/schema_etr_v2_20230126.py +1143 -0
- anafpy-0.1.0/src/anafpy/exceptions.py +67 -0
- anafpy-0.1.0/src/anafpy/mcp/__init__.py +13 -0
- anafpy-0.1.0/src/anafpy/mcp/__main__.py +8 -0
- anafpy-0.1.0/src/anafpy/mcp/config.py +140 -0
- anafpy-0.1.0/src/anafpy/mcp/context.py +144 -0
- anafpy-0.1.0/src/anafpy/mcp/documents.py +68 -0
- anafpy-0.1.0/src/anafpy/mcp/models.py +83 -0
- anafpy-0.1.0/src/anafpy/mcp/nomenclatures.py +119 -0
- anafpy-0.1.0/src/anafpy/mcp/server/__init__.py +39 -0
- anafpy-0.1.0/src/anafpy/mcp/server/_shared.py +15 -0
- anafpy-0.1.0/src/anafpy/mcp/server/app.py +107 -0
- anafpy-0.1.0/src/anafpy/mcp/server/efactura.py +221 -0
- anafpy-0.1.0/src/anafpy/mcp/server/etransport.py +375 -0
- anafpy-0.1.0/src/anafpy/mcp/server/prompts.py +54 -0
- anafpy-0.1.0/src/anafpy/mcp/server/public.py +91 -0
- anafpy-0.1.0/src/anafpy/mcp/server/resources.py +45 -0
- anafpy-0.1.0/src/anafpy/mcp/skills.py +71 -0
- anafpy-0.1.0/src/anafpy/mcp/tokens.py +124 -0
- anafpy-0.1.0/src/anafpy/mcp/unitcodes.py +127 -0
- anafpy-0.1.0/src/anafpy/public/__init__.py +57 -0
- anafpy-0.1.0/src/anafpy/public/client.py +445 -0
- anafpy-0.1.0/src/anafpy/public/models.py +368 -0
- anafpy-0.1.0/src/anafpy/py.typed +0 -0
- anafpy-0.1.0/tests/_wire.py +287 -0
- anafpy-0.1.0/tests/conftest.py +67 -0
- anafpy-0.1.0/tests/test_auth.py +521 -0
- anafpy-0.1.0/tests/test_cli.py +167 -0
- anafpy-0.1.0/tests/test_efactura_client.py +690 -0
- anafpy-0.1.0/tests/test_efactura_read.py +70 -0
- anafpy-0.1.0/tests/test_efactura_roundtrip_live.py +328 -0
- anafpy-0.1.0/tests/test_etransport_client.py +682 -0
- anafpy-0.1.0/tests/test_etransport_read.py +293 -0
- anafpy-0.1.0/tests/test_etransport_roundtrip_live.py +178 -0
- anafpy-0.1.0/tests/test_etransport_schema.py +135 -0
- anafpy-0.1.0/tests/test_mcp_server.py +942 -0
- anafpy-0.1.0/tests/test_mcp_tokens.py +83 -0
- anafpy-0.1.0/tests/test_oauth_live.py +107 -0
- anafpy-0.1.0/tests/test_public_client.py +564 -0
- anafpy-0.1.0/tests/test_public_live.py +69 -0
- anafpy-0.1.0/tests/test_ubl_roundtrip.py +63 -0
- anafpy-0.1.0/tests/test_version.py +15 -0
- anafpy-0.1.0/uv.lock +1654 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Credential-free gates on every push/PR: the respx-mocked test suite, ruff
|
|
2
|
+
# lint + format, and mypy --strict, across the supported Python versions.
|
|
3
|
+
# The live-marked tests stay opt-in (ANAFPY_LIVE=1) and never run here.
|
|
4
|
+
name: CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: astral-sh/setup-uv@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
enable-cache: true
|
|
24
|
+
- name: Sync dependencies
|
|
25
|
+
run: uv sync --all-extras
|
|
26
|
+
- name: Tests
|
|
27
|
+
run: uv run pytest -q
|
|
28
|
+
- name: Ruff lint
|
|
29
|
+
run: uv run ruff check .
|
|
30
|
+
- name: Ruff format
|
|
31
|
+
run: uv run ruff format --check .
|
|
32
|
+
- name: Mypy (strict)
|
|
33
|
+
run: uv run mypy
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Publish to PyPI on a version tag (v*) via trusted publishing (OIDC) — no
|
|
2
|
+
# API token is stored in the repo. PyPI side: the project's trusted publisher
|
|
3
|
+
# must name this repo, this filename (release.yml), and the `pypi` environment.
|
|
4
|
+
# The full CI gates re-run here so a tag can never ship an artifact that a
|
|
5
|
+
# broken branch produced.
|
|
6
|
+
name: Release
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
gates:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
enable-cache: true
|
|
25
|
+
- name: Sync dependencies
|
|
26
|
+
run: uv sync --all-extras
|
|
27
|
+
- name: Tests
|
|
28
|
+
run: uv run pytest -q
|
|
29
|
+
- name: Ruff lint
|
|
30
|
+
run: uv run ruff check . && uv run ruff format --check .
|
|
31
|
+
- name: Mypy (strict)
|
|
32
|
+
run: uv run mypy
|
|
33
|
+
|
|
34
|
+
build:
|
|
35
|
+
needs: gates
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: astral-sh/setup-uv@v6
|
|
40
|
+
- name: Tag matches the package version
|
|
41
|
+
run: |
|
|
42
|
+
version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
|
|
43
|
+
if [ "v${version}" != "${GITHUB_REF_NAME}" ]; then
|
|
44
|
+
echo "pyproject version ${version} does not match tag ${GITHUB_REF_NAME}" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
- name: Build sdist + wheel
|
|
48
|
+
run: uv build
|
|
49
|
+
- uses: actions/upload-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
|
|
54
|
+
publish:
|
|
55
|
+
needs: build
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
environment:
|
|
58
|
+
name: pypi
|
|
59
|
+
url: https://pypi.org/p/anafpy
|
|
60
|
+
permissions:
|
|
61
|
+
id-token: write # OIDC for PyPI trusted publishing
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/download-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: dist
|
|
66
|
+
path: dist/
|
|
67
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
anafpy-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.so
|
|
9
|
+
|
|
10
|
+
# Environments / tooling
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
.uv/
|
|
14
|
+
uv.lock.bak
|
|
15
|
+
|
|
16
|
+
# Caches
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
htmlcov/
|
|
22
|
+
|
|
23
|
+
# OS / editor
|
|
24
|
+
.DS_Store
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
.claude/
|
|
28
|
+
|
|
29
|
+
# Local secrets / tokens (never commit ANAF credentials or token stores)
|
|
30
|
+
*.token.json
|
|
31
|
+
.anafpy/
|
|
32
|
+
secrets.toml
|
|
33
|
+
.env
|
|
34
|
+
|
|
35
|
+
# TLS certs / keys (mkcert localhost certs for `anafpy auth login --tls-cert/--tls-key`)
|
|
36
|
+
*.pem
|
|
37
|
+
*.key
|
|
38
|
+
*.crt
|
|
39
|
+
*.cert
|
|
40
|
+
*.p12
|
|
41
|
+
*.pfx
|
|
42
|
+
rootCA*.pem
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.5.7
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
9
|
+
rev: v1.11.1
|
|
10
|
+
hooks:
|
|
11
|
+
- id: mypy
|
|
12
|
+
additional_dependencies: [pydantic, httpx, tenacity]
|
|
13
|
+
exclude: '(^|/)(ubl|schema)/'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
anafpy-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for working in this repository. See [DESIGN.md](DESIGN.md) for the full
|
|
4
|
+
design rationale and [docs/anaf-reference/](docs/anaf-reference/) for a compiled local
|
|
5
|
+
reference of ANAF's APIs.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
`anafpy` — typed Python clients for Romania's **ANAF** tax-authority web services,
|
|
10
|
+
**e-Factura** (electronic invoicing), **e-Transport** (goods transport), and the
|
|
11
|
+
**public no-auth services** (`anafpy.public`: registry lookups + financial
|
|
12
|
+
statements). It is a
|
|
13
|
+
**thin, stateless transport client, not invoicing software**. **e-Factura outbound is
|
|
14
|
+
XML pass-through** (no invoice composition): callers bring complete invoice XML their
|
|
15
|
+
own system produced, and anafpy validates, files, tracks, and downloads; received UBL
|
|
16
|
+
is wrapped in a friendly **flat read view** (`FlatInvoice`, UBL→flat only) backing
|
|
17
|
+
the e-Factura inbox. **e-Transport is fully
|
|
18
|
+
translated** (decided 2026-07-03): there is usually no upstream software producing
|
|
19
|
+
declaration XML and ANAF's XSD is small and fully enumerated, so the e-Transport flat
|
|
20
|
+
models are **bidirectional** — the same models author a filing
|
|
21
|
+
(`build_etransport`/`render_etransport`, `upload_document`) and view a parsed one
|
|
22
|
+
(`read_flat_transport`), covering all four operations (declaration/correction,
|
|
23
|
+
deletion, confirmation, vehicle change). Phase 1 is the typed async clients; phase 2 is
|
|
24
|
+
the **MCP server** (`anafpy.mcp`, extra `anafpy[mcp]`) exposing the operations as
|
|
25
|
+
Claude Cowork skills. The client methods map 1:1 onto MCP tools — discrete operations,
|
|
26
|
+
serializable typed inputs/outputs, good docstrings. Distribution is **free and
|
|
27
|
+
as-is**: the library is for anyone to use; the MCP server is **best-effort**, and
|
|
28
|
+
configuring it — including provisioning the OAuth application on ANAF's portal —
|
|
29
|
+
is the user's responsibility (DESIGN.md §11).
|
|
30
|
+
|
|
31
|
+
Python **3.12+** (`requires-python`; the repo `.python-version` dev pin stays 3.13).
|
|
32
|
+
Built on **httpx** and **Pydantic v2**.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv sync --all-extras # set up env with all dev dependency groups
|
|
38
|
+
uv run pytest -q # tests (respx-mocked, credential-free)
|
|
39
|
+
uv run pytest tests/test_auth.py # one file
|
|
40
|
+
uv run ruff check . && uv run ruff format --check .
|
|
41
|
+
uv run mypy # strict
|
|
42
|
+
ANAFPY_LIVE=1 uv run pytest -m live # opt-in live smoke: public services + authenticated TEST (needs .env + auth login)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run the MCP server (host-side, where the `anafpy auth login` token store lives):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ANAFPY_CLIENT_ID=... ANAFPY_CLIENT_SECRET=... ANAFPY_CIF=... \
|
|
49
|
+
uv run python -m anafpy.mcp # stdio; or the `anafpy-mcp` console script
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Config is env-only — `anafpy.mcp.config.ServerConfig` is a `pydantic-settings`
|
|
53
|
+
`BaseSettings` (use `ServerConfig.from_env()` for a friendly `AnafConfigError`):
|
|
54
|
+
`ANAFPY_CLIENT_ID`,
|
|
55
|
+
`ANAFPY_CLIENT_SECRET` (optional — without them the server still starts and serves
|
|
56
|
+
the public `anaf_*` lookups; the authenticated tools raise a how-to-enable
|
|
57
|
+
`AnafConfigError`), `ANAFPY_TOKEN_STORE` (default `~/.anafpy/tokens.json`),
|
|
58
|
+
`ANAFPY_TOKEN_STORE_BACKEND` (`keyring`/`file`, default `keyring` — tokens live in
|
|
59
|
+
the OS credential store via `KeyringTokenStore` (`keyring` is a core dependency);
|
|
60
|
+
`file` is the opt-out for Docker/headless hosts without a credential store; the
|
|
61
|
+
CLI honours the same variable and `--store-backend`),
|
|
62
|
+
`ANAFPY_ENV` (`test`/`prod`, default `prod`), `ANAFPY_CIF` (default fiscal code), `ANAFPY_DOCS_DIR`
|
|
63
|
+
(reference resources, defaults to the repo `docs/anaf-reference/`),
|
|
64
|
+
`ANAFPY_SKILLS_DIR` (workflow skills re-served as MCP prompts, defaults to the repo
|
|
65
|
+
`skills/`).
|
|
66
|
+
|
|
67
|
+
Codegen (only when re-vendoring XSDs — see below):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run python scripts/generate_ubl.py
|
|
71
|
+
uv run python scripts/generate_etransport.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
All three gates (pytest / ruff / mypy --strict) are currently green and must stay green.
|
|
75
|
+
|
|
76
|
+
## Layout
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
src/anafpy/
|
|
80
|
+
exceptions.py # AnafError hierarchy (see "Error model")
|
|
81
|
+
_transport/base.py # Environment, Service, service_base_url + shared error raising
|
|
82
|
+
auth/ # OAuth2 layer: models, store, oauth, provider, callback
|
|
83
|
+
cli/main.py # `anafpy auth login|status|logout`
|
|
84
|
+
efactura/
|
|
85
|
+
ubl/ # GENERATED UBL 2.1 models (xsdata-pydantic) — do not hand-edit
|
|
86
|
+
client.py # EFacturaClient (async)
|
|
87
|
+
models.py # value types (UploadResult, MessageStatus, ...) + FlatInvoice read view + UBL→flat reader
|
|
88
|
+
__init__.py # re-exports Invoice, CreditNote from ubl.maindoc
|
|
89
|
+
etransport/
|
|
90
|
+
schema/ # GENERATED e-Transport XSD models — do not hand-edit
|
|
91
|
+
client.py # ETransportClient (async) — incl. upload_document (flat -> XML -> upload)
|
|
92
|
+
models.py # value types + BIDIRECTIONAL flat models (4 ops) + read/build/render
|
|
93
|
+
public/
|
|
94
|
+
client.py # PublicClient (async, no auth) — webservicesp.anaf.ro lookups
|
|
95
|
+
# + the stateless e-Factura document services (validare/transformare)
|
|
96
|
+
models.py # lookup value types (TaxpayerRecord, RegistryLookup[...], ...)
|
|
97
|
+
# + TransformStandard, RemoteValidationResult
|
|
98
|
+
mcp/ # MCP server (extra: anafpy[mcp]) — phase 2
|
|
99
|
+
config.py # ServerConfig.from_env (creds, store path, env, default CIF)
|
|
100
|
+
context.py # AppContext: TokenProvider + lazy clients + token ledger; auth_status
|
|
101
|
+
models.py # UBL XML pass-through inputs + prepared-submission gate
|
|
102
|
+
documents.py # resolve XML input -> bytes; parse bytes -> client flat models
|
|
103
|
+
nomenclatures.py # e-Transport code lists (from the XSD enums) for the model
|
|
104
|
+
skills.py # skills/*/SKILL.md loader (frontmatter + body) for MCP prompts
|
|
105
|
+
tokens.py # HMAC confirmation tokens for two-step gated mutations
|
|
106
|
+
server/ # FastMCP server package: app.py (`create_server`, `main`,
|
|
107
|
+
# auth_status + instructions), tool modules efactura.py /
|
|
108
|
+
# etransport.py / public.py, resources.py (ANAF reference),
|
|
109
|
+
# prompts.py (skills), _shared.py (tool annotations)
|
|
110
|
+
__main__.py # `python -m anafpy.mcp` (stdio)
|
|
111
|
+
skills/ # workflow skills, served by the MCP server as same-name
|
|
112
|
+
# prompts (etransport-declare: source data -> FlatTransport
|
|
113
|
+
# -> prepare -> approval -> submit -> status)
|
|
114
|
+
schemas/ # vendored XSDs (git-tracked, NOT shipped in the wheel)
|
|
115
|
+
scripts/ # codegen scripts
|
|
116
|
+
docs/anaf-reference/ # compiled ANAF API reference (oauth/efactura/etransport/public)
|
|
117
|
+
tests/ # respx-mocked unit tests (+ opt-in live: test_public_live.py, test_oauth_live.py read-only; test_{efactura,etransport}_roundtrip_live.py file to TEST)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Architecture & conventions
|
|
121
|
+
|
|
122
|
+
- **Both OAuth services share one host** `api.anaf.ro`, differing only by path prefix
|
|
123
|
+
(`FCTEL/rest` vs `ETRANSPORT/ws/v1`) and `test`/`prod` segment. All of that lives in
|
|
124
|
+
[_transport/base.py](src/anafpy/_transport/base.py); clients take an `environment`.
|
|
125
|
+
- **`PublicClient` is the odd one out**: the unauthenticated registries/bilanț live on
|
|
126
|
+
`PUBLIC_HOST` (`webservicesp.anaf.ro`) — no `TokenProvider`, no `environment`
|
|
127
|
+
(production only). Unlike the OAuth clients' no-auto-backoff stance, it **paces its
|
|
128
|
+
own requests** (`min_request_interval`, default 1 req/s) because ANAF states that
|
|
129
|
+
limit as a usage *rule*, not via 429s. Registry membership is read from the
|
|
130
|
+
`registered` booleans, never from presence in `found` (RegAgric/RegCult return
|
|
131
|
+
unknown CUIs in `found` with empty fields). The e-Factura register's HTTP 404 with a
|
|
132
|
+
`found`/`notFound` body is a business "not found" (returned), not raised.
|
|
133
|
+
`PublicClient` also carries the **stateless e-Factura document services**
|
|
134
|
+
`validate_invoice` (`validare`) and `render_invoice_pdf` (`transformare`) — they
|
|
135
|
+
live on the same host, need no auth, and exist only under the `prod` segment
|
|
136
|
+
(moved from `EFacturaClient` 2026-07-04 so validation works with no OAuth
|
|
137
|
+
credentials configured); only the MF signature check (`validate_signature`, on
|
|
138
|
+
`api.anaf.ro`) stays on `EFacturaClient`.
|
|
139
|
+
- **Auth is a separate layer.** Clients receive a `TokenProvider` and drive httpx via
|
|
140
|
+
the `AnafAuth` (`httpx.Auth`) class, which handles transparent token refresh. The
|
|
141
|
+
qualified-certificate step happens only in the interactive `anafpy auth login` browser
|
|
142
|
+
flow; code-exchange and refresh are headless. Don't add cert/mTLS handling to clients.
|
|
143
|
+
ANAF's portal only registers `https://` callback URLs (an `http://` one 400s —
|
|
144
|
+
verified 2026-07-02); the login captures the code via `--paste` (no listener, the
|
|
145
|
+
baseline), a TLS listener (`--tls-cert/--tls-key`), or plain HTTP behind an external
|
|
146
|
+
TLS terminator — the listener binds *before* the browser opens (a fast redirect must
|
|
147
|
+
not outrun it) and the CLI falls back to paste if it can't start or times out.
|
|
148
|
+
Every login binds a random OAuth `state` (login-CSRF protection, 2026-07-04):
|
|
149
|
+
the listener 400s (and keeps waiting on) redirects that don't echo it, and the
|
|
150
|
+
paste parser rejects a mismatching URL — a pasted bare code is exempt.
|
|
151
|
+
`anafpy auth logout` is **purely local** — it clears the token store and makes
|
|
152
|
+
no network call: ANAF's documented `/revoke` is not reachable headlessly
|
|
153
|
+
(live-probed 2026-07-05: 302 to the F5 APM login wall, identical to a
|
|
154
|
+
nonexistent path — see the oauth reference §3), so tokens end only by expiry
|
|
155
|
+
or the portal's Renunțare Oauth. Don't (re)add a revoke call unless ANAF
|
|
156
|
+
actually routes the endpoint.
|
|
157
|
+
Token persistence is the `TokenStore` protocol (`load`/`save`/`clear`):
|
|
158
|
+
`KeyringTokenStore` (OS credential store — the **default** backend since
|
|
159
|
+
2026-07-05, `keyring` is a core dependency; splits the set across vault
|
|
160
|
+
entries on Windows, whose 2560-byte blob cap is smaller than one ANAF JWT) or
|
|
161
|
+
`FileTokenStore` (JSON file, the opt-out for Docker/headless hosts); selected
|
|
162
|
+
by `ANAFPY_TOKEN_STORE_BACKEND` / `--store-backend`. The test suite installs
|
|
163
|
+
an in-memory fake keyring **autouse** so no test can touch the real OS store.
|
|
164
|
+
- **Clients are async**, own their `httpx.AsyncClient` (unless one is injected), and are
|
|
165
|
+
async context managers (`async with EFacturaClient(...) as c:`).
|
|
166
|
+
- **Discrete methods do NO transport retry** — one call, one result-or-raise — so the
|
|
167
|
+
non-idempotent `upload` POST is never silently repeated. Consumers bring their own
|
|
168
|
+
retry. `tenacity` is used in exactly one place: the `upload_and_wait` poll loop, which
|
|
169
|
+
retries on the *business* processing state, not on transport errors.
|
|
170
|
+
- **Module style**: `from __future__ import annotations`, explicit `__all__`, module +
|
|
171
|
+
class docstrings, Google-style docstring sections. Line length 88. Keep new code in the
|
|
172
|
+
voice of the surrounding files.
|
|
173
|
+
- **English identifiers everywhere in hand-written code**: variables, parameters, and
|
|
174
|
+
model field names are descriptive English; ANAF's Romanian wire names survive only
|
|
175
|
+
as Pydantic validation aliases (`data_creare` -> `created_at`), string literals
|
|
176
|
+
(dict keys, query params, URL segments), and the generated schema models. Domain
|
|
177
|
+
acronyms with no sensible translation (`cui`, `cif`, `uit`, `caen`) and enum
|
|
178
|
+
members named after ANAF's own codes stay as-is.
|
|
179
|
+
|
|
180
|
+
## MCP server (`anafpy.mcp`)
|
|
181
|
+
|
|
182
|
+
- **Local stdio connector built on the phase-1 clients.** `create_server(config)`
|
|
183
|
+
returns a `FastMCP`; `AppContext` owns one `TokenProvider` + lazily-built clients and
|
|
184
|
+
closes them in the server lifespan. The server reads the existing token store and
|
|
185
|
+
refreshes headlessly — it never drives the cert/browser step (that stays the CLI).
|
|
186
|
+
- **Workflow skills are served as MCP prompts** (2026-07-03). Each
|
|
187
|
+
`skills/*/SKILL.md` is served as a same-name prompt (`anafpy.mcp.skills` reads
|
|
188
|
+
frontmatter + body; optional `source` argument seeds the workflow), giving
|
|
189
|
+
prompt-capable clients (Claude Desktop, `claude mcp add`) the playbooks as a
|
|
190
|
+
user-invoked entry point. The SKILL.md files are the single source of
|
|
191
|
+
truth — never duplicate their content into the server; parsing is
|
|
192
|
+
`python-frontmatter`'s (the `mcp` extra), with `skills.py` only enforcing that
|
|
193
|
+
`name`/`description` are present (missing fields fail loudly at server start).
|
|
194
|
+
- **No e-Factura filing tools** (removed 2026-07-03): outbound invoices come from
|
|
195
|
+
third-party invoicing software, which files with ANAF directly — there is no MCP
|
|
196
|
+
use case, so the e-Factura surface is **read-only** (inbox, download,
|
|
197
|
+
`efactura_validate`); `efactura_get_status` went with the filing tools — an
|
|
198
|
+
e-Factura upload id was only ever produced by them. `EFacturaClient.upload` /
|
|
199
|
+
`get_status` stay for library users. If filing
|
|
200
|
+
tools ever return, the pass-through rule still applies: the input must be the
|
|
201
|
+
complete UBL XML the caller's software exported (`UblXmlInput` in
|
|
202
|
+
`mcp/models.py`, now feeding only `efactura_validate`) — never composed, never
|
|
203
|
+
the generated UBL schema models as tool input, no flat→UBL write mapping.
|
|
204
|
+
- **Binary artifacts go to disk (or a resource), never into context.** The model
|
|
205
|
+
works from the flat `invoice` view; `efactura_download` optionally writes the
|
|
206
|
+
signed archive ZIP (`save_zip_as`) and ANAF's `transformare` PDF rendering
|
|
207
|
+
(`save_pdf_as`, best-effort — failures surface in `pdf_error`, never fail the
|
|
208
|
+
download; rendered with `validate=False` since the message already passed ANAF
|
|
209
|
+
validation at filing) to caller-given paths — the server is local stdio, so its
|
|
210
|
+
filesystem is the user's (this is what enables batch flows like "save last
|
|
211
|
+
month's invoices as `<date> - <partner>.pdf`", where the agent names the files).
|
|
212
|
+
An **existing file is never silently replaced** (added 2026-07-04): a name
|
|
213
|
+
collision is refused and reported in `pdf_error`/`zip_error`, and only an
|
|
214
|
+
explicit `overwrite=true` replaces the file — so a batch flow can't lose an
|
|
215
|
+
invoice to two identical names, while a deliberate re-export stays one flag away.
|
|
216
|
+
The PDF is also the resource template `anafmsg://{message_id}/pdf`
|
|
217
|
+
(fetch+convert on read); there is deliberately **no ZIP resource** — a base64
|
|
218
|
+
ZIP serves neither the model nor any host UI. Don't return base64 blobs from
|
|
219
|
+
tools. (decided 2026-07-03; the
|
|
220
|
+
pass-through rule is e-Factura-only). `etransport_prepare_declaration` /
|
|
221
|
+
`_deletion` / `_confirmation` / `_vehicle_change` take the client-layer flat models
|
|
222
|
+
or scalars, render the XML via `render_etransport`, and return it in
|
|
223
|
+
`PreparedSubmission.xml` alongside the preview and token; the caller passes that
|
|
224
|
+
XML back to `etransport_submit` verbatim (the token is bound to the rendered
|
|
225
|
+
bytes, so any mangling fails closed). `etransport_prepare` (`EtransportXmlInput`)
|
|
226
|
+
remains for callers with ready-made XML. `etransport_nomenclature` lists the XSD
|
|
227
|
+
code lists (names accepted anywhere an enum-coded field is) plus the code-only
|
|
228
|
+
`unit_codes` — the UN/ECE Rec 20/21 list ANAF's Schematron enforces for goods
|
|
229
|
+
lines, carried in [mcp/unitcodes.py](src/anafpy/mcp/unitcodes.py) — see
|
|
230
|
+
[mcp/nomenclatures.py](src/anafpy/mcp/nomenclatures.py).
|
|
231
|
+
- **`FlatInvoice` is a read view; the e-Transport flat models are bidirectional.**
|
|
232
|
+
All are defined at the **client layer**
|
|
233
|
+
([efactura/models.py](src/anafpy/efactura/models.py),
|
|
234
|
+
[etransport/models.py](src/anafpy/etransport/models.py)). `FlatInvoice` is produced
|
|
235
|
+
*from* UBL by `read_flat_invoice` (UBL→flat only), backs `DownloadedMessage.view`
|
|
236
|
+
(`download` tier 3) and the e-Factura inbox, is lossy by
|
|
237
|
+
design — raw bytes + full UBL stay authoritative — and carries `complete` /
|
|
238
|
+
`dropped_fields` when it can't represent something. There is no flat→UBL path; do
|
|
239
|
+
not add one. The e-Transport `FlatTransport` / `FlatDeletion` / `FlatConfirmation`
|
|
240
|
+
/ `FlatVehicleChange` (union `FlatSubmission`) are a **full translation** of the
|
|
241
|
+
XSD: `read_flat_transport` views, `build_etransport` / `render_etransport` author
|
|
242
|
+
(only the schema's unused `xs:any` hooks are not carried); enum-coded fields are
|
|
243
|
+
typed with the generated XSD enums, accept name or code, and serialize as names.
|
|
244
|
+
- **Tool display names**: every tool has an English MCP `title` following
|
|
245
|
+
`Service: operation` — services are `e-Factura`, `e-Transport`, `ANAF Info`
|
|
246
|
+
(public no-auth lookups), plus bare `ANAF` for `auth_status`. Titles are
|
|
247
|
+
UI-only (the model sees `name` + `description`); keep them single-language.
|
|
248
|
+
- **Branded service names in prose**: in strings, messages, and docs the services
|
|
249
|
+
are written exactly `e-Factura` and `e-Transport` — even at the start of a
|
|
250
|
+
sentence or title. This is the branding ANAF itself uses on its website
|
|
251
|
+
(decided 2026-07-03). Exceptions: identifiers stay English-convention
|
|
252
|
+
(`EFacturaClient`, `efactura_*`), ANAF wire facts stay verbatim (the
|
|
253
|
+
`eTransport` XML root/namespace, endpoint names, URLs), and quotes of ANAF's
|
|
254
|
+
own material in `docs/anaf-reference/` keep ANAF's spelling.
|
|
255
|
+
- **Read-first, two-step gated mutations.** Read-only tools (`*_list*`, `*_status`,
|
|
256
|
+
`*_lookup`, `etransport_nomenclature`, `auth_status`, and — over the no-auth
|
|
257
|
+
`PublicClient`, so usable even with no OAuth credentials configured —
|
|
258
|
+
`efactura_validate` and the `anaf_*` public lookups, registries + financial
|
|
259
|
+
statements) are annotated `readOnlyHint` and freely
|
|
260
|
+
callable. `efactura_download` is also freely callable but carries honest
|
|
261
|
+
annotations (`readOnlyHint=False`, idempotent, non-destructive) because it may
|
|
262
|
+
write files at caller-given paths; the two-step gate is for ANAF filings only. Filing (e-Transport only) is split `etransport_prepare*` →
|
|
263
|
+
`etransport_submit`: prepare
|
|
264
|
+
parses (or composes) the XML for a preview and returns an HMAC **confirmation token**
|
|
265
|
+
(`mcp/tokens.py`) bound to the exact XML bytes and the CIF;
|
|
266
|
+
submit requires that token (same document, same CIF) **and** `confirm=True`,
|
|
267
|
+
and each token is **single-use** (`TokenLedger`) so a non-idempotent upload is never
|
|
268
|
+
repeated on one approval. Don't collapse this into a `dry_run` bool.
|
|
269
|
+
- **Validation is ANAF's, not local.** `efactura_validate` calls the server-side
|
|
270
|
+
`validare` endpoint via `PublicClient.validate_invoice` (authoritative by
|
|
271
|
+
definition). `validare`/`transformare` are **public, no-auth, prod-only** (their
|
|
272
|
+
TEST paths 404), which is why they live on `PublicClient`
|
|
273
|
+
(`webservicesp.anaf.ro/prod`) — validation works on test configs too, needs no
|
|
274
|
+
OAuth credentials, and files
|
|
275
|
+
nothing; e-Transport has no standalone validator — ANAF validates on upload.
|
|
276
|
+
There is deliberately **no local rule engine** (a Schematron/saxonche extra existed
|
|
277
|
+
and was removed 2026-07-02); prepare never blocks on validation — the human review +
|
|
278
|
+
ANAF's verdict are the gates. Don't reintroduce local validation. Distinct from
|
|
279
|
+
that: the e-Transport flat models carry **field-level shape checks** — the XSD
|
|
280
|
+
constraints tightened by the *unconditional* rules of ANAF's e-Transport
|
|
281
|
+
Schematron (UIT check digits, gross ≥ net, `ALTELE` needs a note, ...; the list
|
|
282
|
+
is in DESIGN.md §5) — which fail at model construction as data hygiene. The
|
|
283
|
+
Schematron's operation-type *conditional* rules stay ANAF's and appear only as
|
|
284
|
+
field descriptions.
|
|
285
|
+
|
|
286
|
+
## Error model (important)
|
|
287
|
+
|
|
288
|
+
Hybrid, per design — do not collapse it:
|
|
289
|
+
|
|
290
|
+
- **Exceptions** (`AnafError` → `AnafAuthError`, `AnafTransportError`/`AnafResponseError`,
|
|
291
|
+
`AnafRateLimitError`, `AnafConfigError`) are for transport / auth / programming errors.
|
|
292
|
+
HTTP 429 raises `AnafRateLimitError` exposing `retry_after`; the client does **not**
|
|
293
|
+
auto-back-off.
|
|
294
|
+
- **Business outcomes** (e-Factura `nok`/`REJECTED`, upload rejections with BR-RO
|
|
295
|
+
findings) are returned as **typed values** (e.g. `UploadResult.accepted is False`,
|
|
296
|
+
`MessageStatus.state`), never raised.
|
|
297
|
+
- **Listing and `info`** (`list_messages` / `list_notifications` / e-Transport `info`) are
|
|
298
|
+
where a 200-with-error-note is split: ANAF overloads the note (e-Factura: `eroare`;
|
|
299
|
+
e-Transport: `Errors[].errorMessage`, `info` also a top-level `error` string) for both
|
|
300
|
+
"no results" and real errors, so a no-results note yields an **empty iterator** (`info`:
|
|
301
|
+
an empty `InfoList` with the note in `.error`) while a genuine error **raises
|
|
302
|
+
`AnafResponseError`** (`status_code=200`). The classifier is
|
|
303
|
+
`_transport.base.is_empty_result_message`.
|
|
304
|
+
|
|
305
|
+
## Generated code — do not hand-edit
|
|
306
|
+
|
|
307
|
+
`src/anafpy/efactura/ubl/` and `src/anafpy/etransport/schema/` are generated by the
|
|
308
|
+
`scripts/generate_*.py` scripts from vendored XSDs in `schemas/`. They are committed as
|
|
309
|
+
source but excluded from ruff, mypy, and pyright/Pylance (see `extend-exclude` /
|
|
310
|
+
`exclude` in [pyproject.toml](pyproject.toml)). To change them, edit the script /
|
|
311
|
+
re-vendor the XSD and regenerate; never edit the output by hand. Note: `xsdata[cli]`
|
|
312
|
+
is pinned `<25` — the `xsdata-pydantic` plugin targets the 24.x line and newer core
|
|
313
|
+
emits invalid fields. The e-Transport script post-processes the output: nomenclature
|
|
314
|
+
enum members get descriptive names derived from the XSD's own `xs:documentation`
|
|
315
|
+
annotations (`CodJudetType.CLUJ`, `CodTaraType.ROMANIA`; operation types use ANAF's
|
|
316
|
+
sigla with the full label as a trailing comment: `CodTipOperatiuneType.TTN`, ...)
|
|
317
|
+
instead of `VALUE_<code>`.
|
|
318
|
+
|
|
319
|
+
Public UBL entry points: `from anafpy.efactura import Invoice, CreditNote`.
|
|
320
|
+
|
|
321
|
+
## ANAF response formats
|
|
322
|
+
|
|
323
|
+
Response schemas come from ANAF's official per-endpoint **swagger presentations**
|
|
324
|
+
(vendored 2026-07-02 under `docs/anaf-reference/_sources/{efactura,etransport}-swagger/`
|
|
325
|
+
and folded into `docs/anaf-reference/*/api.md`) — the API PDFs cover URLs/params only.
|
|
326
|
+
First live TEST confirmations 2026-07-02: the e-Factura paginated list's no-results
|
|
327
|
+
shape (200 + `eroare` note) and the e-Transport `lista` no-results shape
|
|
328
|
+
(`Errors[].errorMessage`, `ExecutionStatus: 1`) both matched the docs exactly. A full
|
|
329
|
+
e-Transport TEST **roundtrip** 2026-07-02 (upload → `stareMesaj` `in prelucrare`→`ok`
|
|
330
|
+
→ `lista` → `info`) confirmed the upload/status/lista-with-results shapes and surfaced
|
|
331
|
+
one doc gap: `info`'s no-results case rides a **top-level singular `error` string**
|
|
332
|
+
(not `Errors[]`) — now handled by `_InfoEnvelope` / `_parse_info`. A full **e-Factura
|
|
333
|
+
TEST roundtrip** the same day (upload → `stareMesaj` `in prelucrare`→`ok` →
|
|
334
|
+
`descarcare` ZIP → paginated list with results) confirmed the e-Factura
|
|
335
|
+
upload/status/download shapes, and established that **`validare` and `transformare`
|
|
336
|
+
are prod-only** (the `test` paths answer HTTP 404) — since they are also public and
|
|
337
|
+
no-auth, they live on `PublicClient` (`validate_invoice` / `render_invoice_pdf`),
|
|
338
|
+
which always calls them on `webservicesp.anaf.ro/prod`. A **production** message-list
|
|
339
|
+
pull (2026-07-06, 522 messages) established that the list's `cif_emitent` /
|
|
340
|
+
`cif_beneficiar` are **never emitted** despite ANAF's API PDF listing them as
|
|
341
|
+
response fields — the swagger `Mesaj` schema (the response-schema authority) omits
|
|
342
|
+
them, and the CIFs ride only inside the free-text `detalii` (see the e-Factura
|
|
343
|
+
reference §3). The
|
|
344
|
+
`live`-marked `tests/test_oauth_live.py` re-confirms the authenticated TEST shapes on
|
|
345
|
+
demand (needs `.env` credentials + `anafpy auth login`). The **public services** have no swagger —
|
|
346
|
+
their reference (`docs/anaf-reference/public/api.md`) is compiled from ANAF's
|
|
347
|
+
instruction files and **was live-confirmed in production** (2026-07-02); the `live`
|
|
348
|
+
test marker re-confirms those shapes on demand. When touching parsing code, treat the
|
|
349
|
+
doc as the source of truth and prefer being explicit over silently returning empty
|
|
350
|
+
results.
|
|
351
|
+
|
|
352
|
+
## Conventions for changes
|
|
353
|
+
|
|
354
|
+
- Keep `pytest`, `ruff`, and `mypy --strict` green; add/extend respx tests for client
|
|
355
|
+
behavior changes (upload→poll→download, `nok` path, 401-refresh, 429 surfacing).
|
|
356
|
+
The respx suite is the gate; the `live`-marked smoke tests
|
|
357
|
+
([tests/test_public_live.py](tests/test_public_live.py) — public services;
|
|
358
|
+
[tests/test_oauth_live.py](tests/test_oauth_live.py) — authenticated TEST, read-only,
|
|
359
|
+
credentials from the gitignored repo-root `.env` loaded by `tests/conftest.py`) exist
|
|
360
|
+
only to re-confirm wire shapes on demand (`ANAFPY_LIVE=1`) and are skipped by
|
|
361
|
+
default — don't move behavioural assertions there, and keep them read-only. The **two
|
|
362
|
+
deliberate exceptions** are the roundtrip files —
|
|
363
|
+
[tests/test_etransport_roundtrip_live.py](tests/test_etransport_roundtrip_live.py)
|
|
364
|
+
**files** a domestic declaration composed via the flat authoring models — also
|
|
365
|
+
keeping anafpy's own rendered XML honest — (upload → `stareMesaj` → `lista` →
|
|
366
|
+
`info`) and
|
|
367
|
+
[tests/test_efactura_roundtrip_live.py](tests/test_efactura_roundtrip_live.py)
|
|
368
|
+
**files** a minimal CIUS-RO invoice (upload → `stareMesaj` → `descarcare` → list) —
|
|
369
|
+
**TEST only, never prod** — to keep the filing wire shapes honest; don't add uploads
|
|
370
|
+
to any other live file.
|
|
371
|
+
- **Keep the docs in sync with the change.** When a change alters the public surface,
|
|
372
|
+
status, layout, or conventions, update the affected docs in the same change:
|
|
373
|
+
[README.md](README.md) (what works / usage / install), [INSTALL.md](INSTALL.md)
|
|
374
|
+
(the end-user setup walkthrough — accountant audience: ANAF app registration,
|
|
375
|
+
cert login, Claude/Cowork config), this `CLAUDE.md` (layout,
|
|
376
|
+
commands, conventions), [DESIGN.md](DESIGN.md) (design decisions), and
|
|
377
|
+
`docs/anaf-reference/` (only when ANAF API facts change — keep its provenance
|
|
378
|
+
frontmatter intact). Don't let docs drift behind the code.
|
|
379
|
+
- **Repo boundary**: this repo is the whole project (typed clients + local stdio MCP
|
|
380
|
+
server + skills + docs) and is intended to be publishable. Never add hosted-service
|
|
381
|
+
code here — token custody, multi-tenancy, and an OAuth-provider surface toward
|
|
382
|
+
Claude are out of scope (DESIGN.md §11, decided 2026-07-04).
|
|
383
|
+
- Don't commit, push, or create branches/PRs unless asked.
|
|
384
|
+
- The remote is `github.com/robert-malai/anafpy`. CI is GitHub Actions:
|
|
385
|
+
`.github/workflows/ci.yml` runs the three gates on 3.12 + 3.13 for every
|
|
386
|
+
push/PR; `release.yml` re-runs them on a `v*` tag, checks the tag against
|
|
387
|
+
`pyproject.toml`'s version, builds, and publishes to PyPI via trusted
|
|
388
|
+
publishing (OIDC, environment `pypi` — no stored token). The version is
|
|
389
|
+
declared in `pyproject.toml` **and** `anafpy.__version__`;
|
|
390
|
+
`tests/test_version.py` keeps them agreeing.
|