Skip to content

Commit dd408e0

Browse files
authored
Merge pull request #25 from MinaProtocol/dkijania/ci-quality-tooling
2 parents b878c06 + b713e37 commit dd408e0

6 files changed

Lines changed: 255 additions & 117 deletions

File tree

.github/workflows/ci.yml

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,66 @@ on:
77
branches: [master, main]
88

99
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install -e ".[dev]"
24+
25+
- name: Ruff lint
26+
run: ruff check src/ tests/ examples/
27+
28+
- name: Ruff format check
29+
run: ruff format --check src/ tests/ examples/
30+
31+
typecheck:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- name: Set up Python
37+
uses: actions/setup-python@v5
38+
with:
39+
python-version: "3.12"
40+
41+
- name: Install dependencies
42+
run: |
43+
python -m pip install --upgrade pip
44+
pip install -e ".[dev]"
45+
46+
- name: Mypy
47+
run: mypy src/
48+
49+
security:
50+
runs-on: ubuntu-latest
51+
steps:
52+
- uses: actions/checkout@v4
53+
54+
- name: Set up Python
55+
uses: actions/setup-python@v5
56+
with:
57+
python-version: "3.12"
58+
59+
- name: Install dependencies
60+
run: |
61+
python -m pip install --upgrade pip
62+
pip install -e ".[dev]"
63+
64+
- name: Bandit security scan
65+
run: bandit -r src/ -c pyproject.toml
66+
67+
- name: Dependency audit
68+
run: pip-audit
69+
1070
test:
1171
runs-on: ubuntu-latest
1272
strategy:
@@ -25,15 +85,21 @@ jobs:
2585
python -m pip install --upgrade pip
2686
pip install -e ".[dev]"
2787
28-
- name: Lint
29-
run: ruff check src/ tests/
88+
- name: Run unit tests with coverage
89+
run: |
90+
pytest tests/test_types.py tests/test_daemon_client.py \
91+
-v --cov=mina_sdk --cov-report=term-missing --cov-report=xml
3092
31-
- name: Run unit tests
32-
run: pytest tests/test_types.py tests/test_daemon_client.py -v
93+
- name: Upload coverage
94+
if: matrix.python-version == '3.12'
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: coverage-report
98+
path: coverage.xml
3399

34100
build:
35101
runs-on: ubuntu-latest
36-
needs: test
102+
needs: [lint, typecheck, security, test]
37103
steps:
38104
- uses: actions/checkout@v4
39105

pyproject.toml

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ archive = ["asyncpg>=0.29"]
3535
dev = [
3636
"pytest>=8.0",
3737
"pytest-asyncio>=0.24",
38+
"pytest-cov>=6.0",
3839
"respx>=0.20",
3940
"ruff>=0.8",
41+
"mypy>=1.13",
42+
"bandit>=1.8",
43+
"pip-audit>=2.7",
4044
]
4145

4246
[project.urls]
@@ -50,13 +54,72 @@ packages = ["src/mina_sdk"]
5054
[tool.ruff]
5155
target-version = "py38"
5256
line-length = 100
57+
src = ["src", "tests"]
5358

5459
[tool.ruff.lint]
55-
select = ["E", "F", "I", "W"]
60+
select = [
61+
"E", # pycodestyle errors
62+
"F", # pyflakes
63+
"I", # isort
64+
"W", # pycodestyle warnings
65+
"N", # pep8-naming
66+
"UP", # pyupgrade
67+
"B", # flake8-bugbear
68+
"A", # flake8-builtins
69+
"S", # flake8-bandit (security)
70+
"T20", # flake8-print
71+
"SIM", # flake8-simplify
72+
"RUF", # ruff-specific rules
73+
]
74+
ignore = [
75+
"S101", # allow assert in tests
76+
"T201", # allow print in examples
77+
"A001", # ConnectionError shadows builtin (fixed in quality PR)
78+
"A004", # ConnectionError import shadows builtin (fixed in quality PR)
79+
"N818", # CurrencyUnderflow naming (fixed in quality PR)
80+
"S105", # false positive on "TOKEN" in query variable names
81+
"S311", # random.randint not crypto-safe (Currency.random is not for crypto)
82+
]
83+
84+
[tool.ruff.lint.per-file-ignores]
85+
"tests/*" = ["S101", "S106"]
86+
"examples/*" = ["T201"]
87+
88+
[tool.ruff.format]
89+
quote-style = "double"
90+
indent-style = "space"
91+
92+
[tool.mypy]
93+
python_version = "3.10"
94+
warn_return_any = false
95+
warn_unused_configs = true
96+
disallow_untyped_defs = true
97+
check_untyped_defs = true
98+
ignore_missing_imports = true
5699

57100
[tool.pytest.ini_options]
58101
testpaths = ["tests"]
59102
asyncio_mode = "auto"
60103
markers = [
61104
"integration: requires a running Mina daemon (set MINA_GRAPHQL_URI)",
62105
]
106+
107+
[tool.coverage.run]
108+
source = ["mina_sdk"]
109+
branch = true
110+
111+
[tool.coverage.report]
112+
show_missing = true
113+
fail_under = 70
114+
exclude_lines = [
115+
"pragma: no cover",
116+
"if TYPE_CHECKING:",
117+
"if __name__",
118+
]
119+
120+
[tool.bandit]
121+
exclude_dirs = ["tests"]
122+
skips = [
123+
"B105", # false positive: "TOKEN" in GraphQL query variable names
124+
"B311", # random.randint is fine for Currency.random (not crypto)
125+
]

src/mina_sdk/__init__.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,19 @@
3232
)
3333

3434
__all__ = [
35-
# Client
36-
"MinaDaemonClient",
37-
# Exceptions
38-
"GraphQLError",
39-
"DaemonConnectionError",
40-
"CurrencyUnderflow",
41-
# Currency
42-
"Currency",
43-
"CurrencyFormat",
44-
# Data types
4535
"AccountBalance",
4636
"AccountData",
4737
"BlockInfo",
38+
"Currency",
39+
"CurrencyFormat",
40+
"CurrencyUnderflow",
41+
"DaemonConnectionError",
4842
"DaemonStatus",
43+
"GraphQLError",
44+
"MinaDaemonClient",
4945
"PeerInfo",
50-
"SendPaymentResult",
5146
"SendDelegationResult",
47+
"SendPaymentResult",
5248
]
5349

5450
__version__ = "0.1.0"

src/mina_sdk/daemon/client.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class DaemonConnectionError(Exception):
5050

5151

5252
# Keep the old name as an alias for backwards compatibility
53-
ConnectionError = DaemonConnectionError # noqa: A001
53+
ConnectionError = DaemonConnectionError
5454

5555

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

212-
def get_account(
213-
self, public_key: str, token_id: str | None = None
214-
) -> AccountData:
212+
def get_account(self, public_key: str, token_id: str | None = None) -> AccountData:
215213
"""Get account data for a public key.
216214
217215
Args:
@@ -228,9 +226,7 @@ def get_account(
228226
)
229227
else:
230228
variables = {"publicKey": public_key}
231-
data = self._request(
232-
queries.GET_ACCOUNT, variables=variables, query_name="get_account"
233-
)
229+
data = self._request(queries.GET_ACCOUNT, variables=variables, query_name="get_account")
234230
acc = data.get("account")
235231
if acc is None:
236232
raise ValueError(f"account not found: {public_key}")

src/mina_sdk/types.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,7 @@ def __sub__(self, other: Currency) -> Currency:
172172
if isinstance(other, Currency):
173173
result = self._nanomina - other._nanomina
174174
if result < 0:
175-
raise CurrencyUnderflow(
176-
f"subtraction would result in negative: {self} - {other}"
177-
)
175+
raise CurrencyUnderflow(f"subtraction would result in negative: {self} - {other}")
178176
return Currency.from_nanomina(result)
179177
return NotImplemented
180178

0 commit comments

Comments
 (0)