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.
- mfx_sdk-0.0.0/.editorconfig +15 -0
- mfx_sdk-0.0.0/.gitattributes +6 -0
- mfx_sdk-0.0.0/.github/CODEOWNERS +6 -0
- mfx_sdk-0.0.0/.github/workflows/ci.yml +23 -0
- mfx_sdk-0.0.0/.github/workflows/release.yml +85 -0
- mfx_sdk-0.0.0/.gitignore +37 -0
- mfx_sdk-0.0.0/PKG-INFO +39 -0
- mfx_sdk-0.0.0/README.md +26 -0
- mfx_sdk-0.0.0/pyproject.toml +83 -0
- mfx_sdk-0.0.0/src/mfx/__init__.py +6 -0
- mfx_sdk-0.0.0/src/mfx/py.typed +0 -0
- mfx_sdk-0.0.0/tests/unit/test_repo_shape.py +27 -0
- mfx_sdk-0.0.0/uv.lock +704 -0
|
@@ -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,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.
|
mfx_sdk-0.0.0/.gitignore
ADDED
|
@@ -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
|
+
|
mfx_sdk-0.0.0/README.md
ADDED
|
@@ -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/*"]
|
|
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()
|