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
76 changes: 71 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,66 @@ on:
branches: [master, main]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Ruff lint
run: ruff check src/ tests/ examples/

- name: Ruff format check
run: ruff format --check src/ tests/ examples/

typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Mypy
run: mypy src/

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Bandit security scan
run: bandit -r src/ -c pyproject.toml

- name: Dependency audit
run: pip-audit

test:
runs-on: ubuntu-latest
strategy:
Expand All @@ -25,15 +85,21 @@ jobs:
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Lint
run: ruff check src/ tests/
- name: Run unit tests with coverage
run: |
pytest tests/test_types.py tests/test_daemon_client.py \
-v --cov=mina_sdk --cov-report=term-missing --cov-report=xml

- name: Run unit tests
run: pytest tests/test_types.py tests/test_daemon_client.py -v
- name: Upload coverage
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml

build:
runs-on: ubuntu-latest
needs: test
needs: [lint, typecheck, security, test]
steps:
- uses: actions/checkout@v4

Expand Down
65 changes: 64 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ archive = ["asyncpg>=0.29"]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"pytest-cov>=6.0",
"respx>=0.20",
"ruff>=0.8",
"mypy>=1.13",
"bandit>=1.8",
"pip-audit>=2.7",
]

[project.urls]
Expand All @@ -50,13 +54,72 @@ packages = ["src/mina_sdk"]
[tool.ruff]
target-version = "py38"
line-length = 100
src = ["src", "tests"]

[tool.ruff.lint]
select = ["E", "F", "I", "W"]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"W", # pycodestyle warnings
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"A", # flake8-builtins
"S", # flake8-bandit (security)
"T20", # flake8-print
"SIM", # flake8-simplify
"RUF", # ruff-specific rules
]
ignore = [
"S101", # allow assert in tests
"T201", # allow print in examples
"A001", # ConnectionError shadows builtin (fixed in quality PR)
"A004", # ConnectionError import shadows builtin (fixed in quality PR)
"N818", # CurrencyUnderflow naming (fixed in quality PR)
"S105", # false positive on "TOKEN" in query variable names
"S311", # random.randint not crypto-safe (Currency.random is not for crypto)
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101", "S106"]
"examples/*" = ["T201"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.mypy]
python_version = "3.10"
warn_return_any = false
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
ignore_missing_imports = true

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
"integration: requires a running Mina daemon (set MINA_GRAPHQL_URI)",
]

[tool.coverage.run]
source = ["mina_sdk"]
branch = true

[tool.coverage.report]
show_missing = true
fail_under = 70
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"if __name__",
]

[tool.bandit]
exclude_dirs = ["tests"]
skips = [
"B105", # false positive: "TOKEN" in GraphQL query variable names
"B311", # random.randint is fine for Currency.random (not crypto)
]
18 changes: 7 additions & 11 deletions src/mina_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,19 @@
)

__all__ = [
# Client
"MinaDaemonClient",
# Exceptions
"GraphQLError",
"DaemonConnectionError",
"CurrencyUnderflow",
# Currency
"Currency",
"CurrencyFormat",
# Data types
"AccountBalance",
"AccountData",
"BlockInfo",
"Currency",
"CurrencyFormat",
"CurrencyUnderflow",
"DaemonConnectionError",
"DaemonStatus",
"GraphQLError",
"MinaDaemonClient",
"PeerInfo",
"SendPaymentResult",
"SendDelegationResult",
"SendPaymentResult",
]

__version__ = "0.1.0"
10 changes: 3 additions & 7 deletions src/mina_sdk/daemon/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class DaemonConnectionError(Exception):


# Keep the old name as an alias for backwards compatibility
ConnectionError = DaemonConnectionError # noqa: A001
ConnectionError = DaemonConnectionError


class MinaDaemonClient:
Expand Down Expand Up @@ -209,9 +209,7 @@ def get_network_id(self) -> str:
data = self._request(queries.NETWORK_ID, query_name="get_network_id")
return data["networkID"]

def get_account(
self, public_key: str, token_id: str | None = None
) -> AccountData:
def get_account(self, public_key: str, token_id: str | None = None) -> AccountData:
"""Get account data for a public key.

Args:
Expand All @@ -228,9 +226,7 @@ def get_account(
)
else:
variables = {"publicKey": public_key}
data = self._request(
queries.GET_ACCOUNT, variables=variables, query_name="get_account"
)
data = self._request(queries.GET_ACCOUNT, variables=variables, query_name="get_account")
acc = data.get("account")
if acc is None:
raise ValueError(f"account not found: {public_key}")
Expand Down
4 changes: 1 addition & 3 deletions src/mina_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,7 @@ def __sub__(self, other: Currency) -> Currency:
if isinstance(other, Currency):
result = self._nanomina - other._nanomina
if result < 0:
raise CurrencyUnderflow(
f"subtraction would result in negative: {self} - {other}"
)
raise CurrencyUnderflow(f"subtraction would result in negative: {self} - {other}")
return Currency.from_nanomina(result)
return NotImplemented

Expand Down
Loading
Loading