mfx-sdk 0.0.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.
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{json,yml,yaml,ts,tsx,js,jsx,md}]
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,6 @@
1
+ * text=auto eol=lf
2
+ *.png binary
3
+ *.jpg binary
4
+ *.ico binary
5
+ *.docx binary
6
+ *.zip binary
@@ -0,0 +1,6 @@
1
+ # CODEOWNERS — mfx-sdk-python
2
+ # The SDK is the tenant-facing contract (MFX-CLAUDE.md §9.6).
3
+ * @kudmani
4
+ /src/mfx/errors.py @kudmani
5
+ /src/mfx/execution.py @kudmani
6
+ /tests/conformance/ @kudmani
@@ -0,0 +1,23 @@
1
+ # CI for mfx-sdk-python — ADR-18: PRs target develop; develop and main are protected.
2
+ # Reusable workflow is pinned BY TAG, never @develop.
3
+ name: ci
4
+
5
+ on:
6
+ push:
7
+ branches: [develop, main]
8
+ pull_request:
9
+ branches: [develop, main]
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ ci:
20
+ uses: mfx-platform/mfx-infra/.github/workflows/_python-ci.yml@v1
21
+ with:
22
+ package-path: src/mfx
23
+ run-import-linter: false
@@ -0,0 +1,85 @@
1
+ # Publish mfx-sdk to PyPI — no API token, ever (AB#5626, DEC-97).
2
+ #
3
+ # Authentication is PyPI's trusted publishing: GitHub mints a short-lived OIDC token for this
4
+ # workflow, and PyPI accepts it because the project names this repository, this workflow file and
5
+ # this environment as its publisher. Nothing to store, nothing to rotate, nothing to leak — which
6
+ # is the same reason the platform signs with Ed25519 rather than a shared secret (DEC-84).
7
+ #
8
+ # Two ways in:
9
+ # * "Reserve the name" — one manual run with the default 0.0.0, which claims `mfx-sdk` on PyPI.
10
+ # PyPI reserves nothing without an upload, so the placeholder IS the reservation.
11
+ # * A tag `v*` — the real releases, from the version in pyproject.toml.
12
+ #
13
+ # A version can never be re-uploaded to PyPI, so 0.0.0 is spent on the placeholder and the first
14
+ # real release is 0.1.0.
15
+ name: release
16
+
17
+ on:
18
+ workflow_dispatch:
19
+ inputs:
20
+ version:
21
+ description: Version to publish (0.0.0 reserves the name)
22
+ type: string
23
+ default: "0.0.0"
24
+ push:
25
+ tags: ["v*"]
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ publish:
32
+ runs-on: ubuntu-latest
33
+ # The environment PyPI's pending publisher names. Keep the two in step.
34
+ environment: pypi
35
+ permissions:
36
+ # Both, because a job's permissions REPLACE the workflow's rather than adding to them:
37
+ # id-token alone leaves the token unable to read a private repository, and checkout then
38
+ # fails with "Repository not found", which reads like the repo is gone.
39
+ contents: read
40
+ # The OIDC token trusted publishing exchanges for an upload. This is the whole credential.
41
+ id-token: write
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.12"
48
+
49
+ - name: Set the version for a placeholder run
50
+ if: github.event_name == 'workflow_dispatch'
51
+ shell: bash
52
+ run: |
53
+ set -euo pipefail
54
+ version='${{ inputs.version }}'
55
+ [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([ab.rc0-9]*)?$ ]] || {
56
+ echo "::error::'$version' is not a version"; exit 1; }
57
+ # The only line that may change: the project's own version, never a dependency's.
58
+ python - "$version" <<'PY'
59
+ import re, sys
60
+ from pathlib import Path
61
+
62
+ version = sys.argv[1]
63
+ path = Path("pyproject.toml")
64
+ before = path.read_text(encoding="utf-8")
65
+ after, count = re.subn(r'(?m)^version = "[^"]+"$', f'version = "{version}"', before, count=1)
66
+ if count != 1:
67
+ raise SystemExit("pyproject.toml has no project version line to set")
68
+ path.write_text(after, encoding="utf-8")
69
+ print(f"building {version}")
70
+ PY
71
+
72
+ - name: Build the sdist and wheel
73
+ run: |
74
+ python -m pip install --upgrade build
75
+ python -m build
76
+
77
+ - name: What is about to be published
78
+ run: |
79
+ python -m pip install --upgrade twine
80
+ twine check dist/*
81
+ ls -l dist
82
+
83
+ - name: Publish to PyPI
84
+ uses: pypa/gh-action-pypi-publish@release/v1
85
+ # No `password:` — the OIDC token above is the credential.
@@ -0,0 +1,37 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .coverage
9
+ htmlcov/
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+
14
+ # Node
15
+ node_modules/
16
+ .next/
17
+ out/
18
+ *.tsbuildinfo
19
+
20
+ # Secrets — nothing credential-shaped is ever committed (guardrail #2)
21
+ .env
22
+ .env.*
23
+ *.pem
24
+ *.pfx
25
+ *.key
26
+ *.publishsettings
27
+ azureauth.json
28
+
29
+ # Local
30
+ .DS_Store
31
+ Thumbs.db
32
+ .idea/
33
+ .vscode/
34
+ !.vscode/extensions.json
35
+ .claude/
36
+ reports/
37
+ tc-manifest.json
mfx_sdk-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.5
2
+ Name: mfx-sdk
3
+ Version: 0.0.0
4
+ Summary: Typed client for the MFX API — the only way a tenant talks to MFX (MFX-CLAUDE §9.6)
5
+ License: Proprietary
6
+ Keywords: market-data,mfx,mt5,trading
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: anyio>=4.6
9
+ Requires-Dist: httpx>=0.28
10
+ Requires-Dist: pydantic>=2.9
11
+ Requires-Dist: structlog>=24.4
12
+ Description-Content-Type: text/markdown
13
+
14
+ # mfx-sdk-python
15
+
16
+ The typed Python client for the MFX v1 API. **This is the contract** — FabricX and every other
17
+ tenant consume MFX only through this library.
18
+
19
+ > **MFX is hard-isolated from FabricX (ADR-02).** No code here imports or references anything
20
+ > named `fabricx*`. FabricX is Tenant 0 and consumes the public API like any other tenant.
21
+ > Governance: [`mfx-docs/MFX-CLAUDE.md`](https://github.com/mfx-platform/mfx-docs/blob/develop/MFX-CLAUDE.md)
22
+
23
+ ## Design rules (MFX-CLAUDE.md §9.6 — binding)
24
+
25
+ 1. Total coverage of §9.1 — if a tenant needs `httpx` to call MFX, the SDK is incomplete.
26
+ 2. **Execution methods never raise.** Transport faults, timeouts and broker rejections are all *results*.
27
+ 3. Granular MT5 reasons, not buckets — `{mt5_retcode, retcode_text, reason, comment_raw}`.
28
+ 4. Structured JSON logging; credentials unloggable by type.
29
+ 5. `idempotency_key` is mandatory on every write — no silent defaults.
30
+ 6. `broker_latency_ms` and `agent_latency_ms` reported separately, always.
31
+ 7. Pydantic v2, `py.typed`, mypy --strict, generated from `openapi.yaml`.
32
+ 8. Async-first; thin sync facade for scripts only.
33
+ 9. SDK semver independent of API version.
34
+ 10. The conformance suite is the spec.
35
+
36
+ ## Completeness gate
37
+
38
+ `MFX-MetaAPI-Consumption-Inventory.md` §2 — every row needs a typed method before G1.
39
+
@@ -0,0 +1,26 @@
1
+ # mfx-sdk-python
2
+
3
+ The typed Python client for the MFX v1 API. **This is the contract** — FabricX and every other
4
+ tenant consume MFX only through this library.
5
+
6
+ > **MFX is hard-isolated from FabricX (ADR-02).** No code here imports or references anything
7
+ > named `fabricx*`. FabricX is Tenant 0 and consumes the public API like any other tenant.
8
+ > Governance: [`mfx-docs/MFX-CLAUDE.md`](https://github.com/mfx-platform/mfx-docs/blob/develop/MFX-CLAUDE.md)
9
+
10
+ ## Design rules (MFX-CLAUDE.md §9.6 — binding)
11
+
12
+ 1. Total coverage of §9.1 — if a tenant needs `httpx` to call MFX, the SDK is incomplete.
13
+ 2. **Execution methods never raise.** Transport faults, timeouts and broker rejections are all *results*.
14
+ 3. Granular MT5 reasons, not buckets — `{mt5_retcode, retcode_text, reason, comment_raw}`.
15
+ 4. Structured JSON logging; credentials unloggable by type.
16
+ 5. `idempotency_key` is mandatory on every write — no silent defaults.
17
+ 6. `broker_latency_ms` and `agent_latency_ms` reported separately, always.
18
+ 7. Pydantic v2, `py.typed`, mypy --strict, generated from `openapi.yaml`.
19
+ 8. Async-first; thin sync facade for scripts only.
20
+ 9. SDK semver independent of API version.
21
+ 10. The conformance suite is the spec.
22
+
23
+ ## Completeness gate
24
+
25
+ `MFX-MetaAPI-Consumption-Inventory.md` §2 — every row needs a typed method before G1.
26
+
@@ -0,0 +1,83 @@
1
+ [project]
2
+ name = "mfx-sdk"
3
+ version = "0.0.0"
4
+ description = "Typed client for the MFX API — the only way a tenant talks to MFX (MFX-CLAUDE §9.6)"
5
+ requires-python = ">=3.12"
6
+ readme = "README.md"
7
+ license = { text = "Proprietary" }
8
+ keywords = ["mfx", "mt5", "trading", "market-data"]
9
+
10
+ dependencies = [
11
+ "httpx>=0.28",
12
+ "pydantic>=2.9",
13
+ "anyio>=4.6",
14
+ "structlog>=24.4",
15
+ ]
16
+
17
+ [dependency-groups]
18
+ dev = [
19
+ "pytest>=8.3",
20
+ "pytest-asyncio>=0.24",
21
+ "pytest-cov>=6.0",
22
+ "mypy>=1.13",
23
+ "ruff>=0.8",
24
+ "respx>=0.22",
25
+ ]
26
+
27
+ [build-system]
28
+ requires = ["hatchling"]
29
+ build-backend = "hatchling.build"
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/mfx"]
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Quality gates — MFX-CLAUDE.md §15 and §9.6 rule 7 (typed end to end)
36
+ # ---------------------------------------------------------------------------
37
+
38
+ [tool.ruff]
39
+ line-length = 100
40
+ target-version = "py312"
41
+ src = ["src", "tests"]
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "I", "N", "UP", "B", "A", "C4", "T20", "SIM", "ARG", "ASYNC", "S"]
45
+ ignore = ["S101"]
46
+
47
+ [tool.ruff.lint.per-file-ignores]
48
+ "tests/**" = ["ARG", "S105", "S106"]
49
+ # Generated from openapi.yaml — never hand-edited, so never hand-linted (§9.6 rule 7).
50
+ "src/mfx/_generated/**" = ["ALL"]
51
+
52
+ [tool.mypy]
53
+ python_version = "3.12"
54
+ strict = true
55
+ warn_unreachable = true
56
+ disallow_any_explicit = false
57
+ plugins = ["pydantic.mypy"]
58
+
59
+ [[tool.mypy.overrides]]
60
+ module = "tests.*"
61
+ disallow_untyped_defs = false
62
+
63
+ [[tool.mypy.overrides]]
64
+ module = "mfx._generated.*"
65
+ ignore_errors = true
66
+
67
+ [tool.pytest.ini_options]
68
+ asyncio_mode = "auto"
69
+ testpaths = ["tests"]
70
+ addopts = "--cov=src/mfx --cov-report=term-missing --cov-fail-under=100"
71
+ markers = [
72
+ "tc(case_id): Azure Test Plans case id, e.g. tc('TC-S1-W4-004') — ADR-25 §2.5",
73
+ "integration: needs external services",
74
+ "sit: runs against a deployed MFX environment",
75
+ "conformance: the contract suite every MFX deployment must pass (§9.6 rule 10)",
76
+ "severity(level): severity for an auto-raised bug when this case fails (default 3)",
77
+ ]
78
+
79
+ [tool.coverage.report]
80
+ exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError", "\\.\\.\\."]
81
+
82
+ [tool.coverage.run]
83
+ omit = ["src/mfx/_generated/*"]
@@ -0,0 +1,6 @@
1
+ """MFX client library — the only way a tenant talks to MFX (MFX-CLAUDE §9.6).
2
+
3
+ Execution methods never raise across the boundary; read methods raise the typed hierarchy.
4
+ """
5
+
6
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,27 @@
1
+ """Repository hygiene for the SDK.
2
+
3
+ Given a fresh checkout / When the package is imported / Then it is typed, versioned and locked,
4
+ which is what ``uv sync --locked`` and ``mypy --strict`` need before W4 fills in the client.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ import mfx
12
+
13
+ REPO_ROOT = Path(__file__).resolve().parents[2]
14
+ PACKAGE = REPO_ROOT / "src" / "mfx"
15
+
16
+
17
+ def test_package_exposes_a_version() -> None:
18
+ assert mfx.__version__
19
+
20
+
21
+ def test_package_ships_py_typed() -> None:
22
+ """§9.6 rule 7 — typed end to end, so consumers get the types from the wheel."""
23
+ assert (PACKAGE / "py.typed").is_file()
24
+
25
+
26
+ def test_lockfile_is_committed() -> None:
27
+ assert (REPO_ROOT / "uv.lock").is_file()