@martinffx/atelier 0.1.0
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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/agents/architect.md +38 -0
- package/agents/oracle.md +40 -0
- package/agents/recon.md +39 -0
- package/dist/atelier.js +33082 -0
- package/package.json +44 -0
- package/skills/code-conventional-commit/SKILL.md +134 -0
- package/skills/code-conventional-commit/evals/evals.json +23 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
- package/skills/code-debug/SKILL.md +271 -0
- package/skills/code-debug/references/language-tools.md +139 -0
- package/skills/code-debug/references/techniques.md +117 -0
- package/skills/code-docs/SKILL.md +171 -0
- package/skills/code-docs/references/api-docs.md +116 -0
- package/skills/code-docs/references/changelog.md +64 -0
- package/skills/code-docs/references/readme-structure.md +94 -0
- package/skills/code-git-worktrees/SKILL.md +218 -0
- package/skills/code-perf/SKILL.md +163 -0
- package/skills/code-perf/references/common-bottlenecks.md +124 -0
- package/skills/code-perf/references/optimization-patterns.md +167 -0
- package/skills/code-perf/references/profiling-tools.md +126 -0
- package/skills/code-review/SKILL.md +78 -0
- package/skills/code-review/references/output.md +200 -0
- package/skills/code-review/references/reviewers.md +719 -0
- package/skills/code-review/references/rq.md +357 -0
- package/skills/code-review/references/rs.md +30 -0
- package/skills/code-security/SKILL.md +183 -0
- package/skills/code-security/references/owasp-top-10.md +129 -0
- package/skills/code-security/references/security-tools.md +102 -0
- package/skills/code-security/references/vulnerability-patterns.md +129 -0
- package/skills/code-stacked-commit/SKILL.md +155 -0
- package/skills/code-subagents/SKILL.md +184 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
- package/skills/code-subagents/references/implementor-prompt.md +79 -0
- package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
- package/skills/oracle-architect/SKILL.md +246 -0
- package/skills/oracle-architect/references/api-design.md +345 -0
- package/skills/oracle-architect/references/data-modeling.md +318 -0
- package/skills/oracle-architect/references/ddd-patterns.md +247 -0
- package/skills/oracle-challenge/SKILL.md +126 -0
- package/skills/oracle-testing/SKILL.md +237 -0
- package/skills/oracle-testing/references/anti-patterns.md +618 -0
- package/skills/oracle-testing/references/boundaries.md +579 -0
- package/skills/oracle-testing/references/core-testing.md +691 -0
- package/skills/oracle-testing/references/edge-testing.md +794 -0
- package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
- package/skills/oracle-thinkdeep/SKILL.md +164 -0
- package/skills/python-architecture/SKILL.md +148 -0
- package/skills/python-architecture/references/data-modeling.md +558 -0
- package/skills/python-architecture/references/ddd.md +569 -0
- package/skills/python-architecture/references/functional-core.md +337 -0
- package/skills/python-build-tools/SKILL.md +153 -0
- package/skills/python-build-tools/references/basedpyright.md +321 -0
- package/skills/python-build-tools/references/ruff.md +225 -0
- package/skills/python-build-tools/references/uv.md +257 -0
- package/skills/python-fastapi/SKILL.md +184 -0
- package/skills/python-fastapi/references/api-design.md +563 -0
- package/skills/python-fastapi/references/dependencies.md +479 -0
- package/skills/python-fastapi/references/middleware.md +475 -0
- package/skills/python-fastapi/references/validation.md +497 -0
- package/skills/python-modern-python/SKILL.md +201 -0
- package/skills/python-modern-python/references/async.md +76 -0
- package/skills/python-modern-python/references/pattern-matching.md +89 -0
- package/skills/python-modern-python/references/typing.md +96 -0
- package/skills/python-monorepo/SKILL.md +146 -0
- package/skills/python-monorepo/references/docker.md +515 -0
- package/skills/python-monorepo/references/namespace-packages.md +426 -0
- package/skills/python-monorepo/references/workspace-config.md +341 -0
- package/skills/python-sqlalchemy/SKILL.md +119 -0
- package/skills/python-sqlalchemy/references/async.md +51 -0
- package/skills/python-sqlalchemy/references/models.md +67 -0
- package/skills/python-sqlalchemy/references/queries.md +57 -0
- package/skills/python-temporal/SKILL.md +128 -0
- package/skills/python-temporal/references/patterns.md +87 -0
- package/skills/python-temporal/references/testing.md +54 -0
- package/skills/python-testing/SKILL.md +161 -0
- package/skills/python-testing/references/boundaries.md +449 -0
- package/skills/python-testing/references/mocking.md +449 -0
- package/skills/python-testing/references/pytest.md +524 -0
- package/skills/spec-brainstorm/SKILL.md +325 -0
- package/skills/spec-finish/SKILL.md +159 -0
- package/skills/spec-implement/SKILL.md +296 -0
- package/skills/spec-orchestrator/SKILL.md +156 -0
- package/skills/spec-plan/SKILL.md +248 -0
- package/skills/typescript-api-design/SKILL.md +396 -0
- package/skills/typescript-api-design/references/error-responses.md +469 -0
- package/skills/typescript-build-tools/SKILL.md +447 -0
- package/skills/typescript-build-tools/references/biome.md +436 -0
- package/skills/typescript-build-tools/references/bun.md +432 -0
- package/skills/typescript-build-tools/references/turborepo.md +426 -0
- package/skills/typescript-build-tools/references/vitest.md +518 -0
- package/skills/typescript-drizzle-orm/SKILL.md +398 -0
- package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
- package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
- package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
- package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
- package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
- package/skills/typescript-effect-ts/SKILL.md +341 -0
- package/skills/typescript-effect-ts/references/core.md +412 -0
- package/skills/typescript-effect-ts/references/error-handling.md +468 -0
- package/skills/typescript-effect-ts/references/resources.md +476 -0
- package/skills/typescript-effect-ts/references/schema.md +536 -0
- package/skills/typescript-effect-ts/references/services.md +461 -0
- package/skills/typescript-fastify/SKILL.md +405 -0
- package/skills/typescript-fastify/references/plugins.md +329 -0
- package/skills/typescript-fastify/references/typeid.md +315 -0
- package/skills/typescript-functional-patterns/SKILL.md +402 -0
- package/skills/typescript-functional-patterns/references/adts.md +497 -0
- package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
- package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
- package/skills/typescript-functional-patterns/references/option-result.md +585 -0
- package/skills/typescript-testing/SKILL.md +353 -0
- package/skills/typescript-testing/references/mocking.md +675 -0
- package/skills/typescript-testing/references/msw.md +852 -0
- package/skills/typescript-testing/references/snapshot-testing.md +774 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Functional Core / Imperative Shell
|
|
2
|
+
|
|
3
|
+
The functional core pattern separates pure business logic from side effects, creating highly testable and maintainable code.
|
|
4
|
+
|
|
5
|
+
## Core Concept
|
|
6
|
+
|
|
7
|
+
**Functional Core:**
|
|
8
|
+
- Pure functions with no side effects
|
|
9
|
+
- Deterministic: same input always produces same output
|
|
10
|
+
- No IO operations (database, HTTP, files, time, random)
|
|
11
|
+
- Easy to test (no mocks needed)
|
|
12
|
+
- Business logic and domain rules
|
|
13
|
+
|
|
14
|
+
**Imperative Shell:**
|
|
15
|
+
- Thin layer handling all side effects
|
|
16
|
+
- Coordinates external dependencies
|
|
17
|
+
- Calls functional core for decisions
|
|
18
|
+
- Hard to test (but simple enough to not need testing)
|
|
19
|
+
|
|
20
|
+
## Pattern Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────┐
|
|
24
|
+
│ Imperative Shell │
|
|
25
|
+
│ (HTTP, Database, Files, Time) │
|
|
26
|
+
│ │
|
|
27
|
+
│ ┌───────────────────────────┐ │
|
|
28
|
+
│ │ Functional Core │ │
|
|
29
|
+
│ │ (Pure Business Logic) │ │
|
|
30
|
+
│ │ - Calculations │ │
|
|
31
|
+
│ │ - Decisions │ │
|
|
32
|
+
│ │ - Transformations │ │
|
|
33
|
+
│ └───────────────────────────┘ │
|
|
34
|
+
│ │
|
|
35
|
+
└─────────────────────────────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
### ❌ Bad: Mixed Concerns
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import requests
|
|
44
|
+
from datetime import datetime
|
|
45
|
+
from database import save_order
|
|
46
|
+
|
|
47
|
+
def process_order(order_id: str) -> None:
|
|
48
|
+
"""Everything mixed together - hard to test"""
|
|
49
|
+
# IO
|
|
50
|
+
response = requests.get(f"/api/orders/{order_id}")
|
|
51
|
+
order_data = response.json()
|
|
52
|
+
|
|
53
|
+
# Business logic
|
|
54
|
+
if order_data["total"] > 100:
|
|
55
|
+
discount = 0.1
|
|
56
|
+
else:
|
|
57
|
+
discount = 0
|
|
58
|
+
|
|
59
|
+
# More IO
|
|
60
|
+
order_data["discount"] = discount
|
|
61
|
+
order_data["processed_at"] = datetime.now().isoformat()
|
|
62
|
+
|
|
63
|
+
# More IO
|
|
64
|
+
save_order(order_data)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Problems:**
|
|
68
|
+
- Can't test business logic without HTTP, database, time
|
|
69
|
+
- Hard to verify discount calculation
|
|
70
|
+
- Slow tests (real IO)
|
|
71
|
+
- Unpredictable (depends on current time)
|
|
72
|
+
|
|
73
|
+
### ✅ Good: Separated Concerns
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# ===== Functional Core (pure functions) =====
|
|
77
|
+
from decimal import Decimal
|
|
78
|
+
from dataclasses import dataclass
|
|
79
|
+
from datetime import datetime
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class OrderData:
|
|
83
|
+
total: Decimal
|
|
84
|
+
discount: Decimal
|
|
85
|
+
processed_at: datetime
|
|
86
|
+
|
|
87
|
+
def calculate_discount(total: Decimal) -> Decimal:
|
|
88
|
+
"""Pure function - no side effects"""
|
|
89
|
+
if total > 100:
|
|
90
|
+
return Decimal("0.1")
|
|
91
|
+
return Decimal("0")
|
|
92
|
+
|
|
93
|
+
def apply_discount(order: OrderData, discount: Decimal, processed_at: datetime) -> OrderData:
|
|
94
|
+
"""Pure function - returns new data"""
|
|
95
|
+
return OrderData(
|
|
96
|
+
total=order.total * (1 - discount),
|
|
97
|
+
discount=discount,
|
|
98
|
+
processed_at=processed_at
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# ===== Imperative Shell (IO coordination) =====
|
|
102
|
+
import requests
|
|
103
|
+
from database import save_order
|
|
104
|
+
|
|
105
|
+
def process_order(order_id: str) -> None:
|
|
106
|
+
"""Thin shell - just coordinates IO"""
|
|
107
|
+
# IO: fetch data
|
|
108
|
+
response = requests.get(f"/api/orders/{order_id}")
|
|
109
|
+
order_data = response.json()
|
|
110
|
+
|
|
111
|
+
# Convert to domain model
|
|
112
|
+
order = OrderData(
|
|
113
|
+
total=Decimal(order_data["total"]),
|
|
114
|
+
discount=Decimal("0"),
|
|
115
|
+
processed_at=datetime.now() # IO: current time
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Pure: calculate discount
|
|
119
|
+
discount = calculate_discount(order.total)
|
|
120
|
+
|
|
121
|
+
# Pure: apply discount
|
|
122
|
+
processed_order = apply_discount(order, discount, datetime.now())
|
|
123
|
+
|
|
124
|
+
# IO: save result
|
|
125
|
+
save_order({
|
|
126
|
+
"total": float(processed_order.total),
|
|
127
|
+
"discount": float(processed_order.discount),
|
|
128
|
+
"processed_at": processed_order.processed_at.isoformat()
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Benefits:**
|
|
133
|
+
- Test `calculate_discount` with no mocks
|
|
134
|
+
- Test `apply_discount` with deterministic inputs
|
|
135
|
+
- Shell is simple enough to not need tests
|
|
136
|
+
- Fast tests (no real IO)
|
|
137
|
+
- Predictable behavior
|
|
138
|
+
|
|
139
|
+
## Testing Pure Functions
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import pytest
|
|
143
|
+
from decimal import Decimal
|
|
144
|
+
|
|
145
|
+
def test_discount_for_large_order():
|
|
146
|
+
"""No mocks needed!"""
|
|
147
|
+
result = calculate_discount(Decimal("150"))
|
|
148
|
+
assert result == Decimal("0.1")
|
|
149
|
+
|
|
150
|
+
def test_no_discount_for_small_order():
|
|
151
|
+
"""No setup, no teardown"""
|
|
152
|
+
result = calculate_discount(Decimal("50"))
|
|
153
|
+
assert result == Decimal("0")
|
|
154
|
+
|
|
155
|
+
def test_apply_discount():
|
|
156
|
+
"""Deterministic test"""
|
|
157
|
+
order = OrderData(
|
|
158
|
+
total=Decimal("100"),
|
|
159
|
+
discount=Decimal("0"),
|
|
160
|
+
processed_at=datetime(2024, 1, 1)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
result = apply_discount(
|
|
164
|
+
order,
|
|
165
|
+
Decimal("0.1"),
|
|
166
|
+
datetime(2024, 1, 2)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
assert result.total == Decimal("90")
|
|
170
|
+
assert result.discount == Decimal("0.1")
|
|
171
|
+
assert result.processed_at == datetime(2024, 1, 2)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Identifying Side Effects
|
|
175
|
+
|
|
176
|
+
Ask these questions:
|
|
177
|
+
1. **Does it perform IO?** (Database, HTTP, files, logging)
|
|
178
|
+
2. **Does it depend on current time?** (datetime.now(), time.time())
|
|
179
|
+
3. **Does it use randomness?** (random.random(), uuid.uuid4())
|
|
180
|
+
4. **Does it mutate state?** (list.append(), dict.update())
|
|
181
|
+
5. **Does it depend on global state?** (environment variables, config)
|
|
182
|
+
|
|
183
|
+
If yes to any → **Imperative Shell**
|
|
184
|
+
If no to all → **Functional Core**
|
|
185
|
+
|
|
186
|
+
## Pushing Side Effects to the Edges
|
|
187
|
+
|
|
188
|
+
### ❌ Bad: Side effects in the middle
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
def calculate_price(product_id: str) -> Decimal:
|
|
192
|
+
"""Pure function name, but has hidden IO"""
|
|
193
|
+
product = db.get_product(product_id) # Hidden side effect!
|
|
194
|
+
return product.price * Decimal("1.1")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### ✅ Good: Accept data as parameter
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
def calculate_price(product: Product) -> Decimal:
|
|
201
|
+
"""Truly pure - takes data, returns result"""
|
|
202
|
+
return product.price * Decimal("1.1")
|
|
203
|
+
|
|
204
|
+
def get_calculated_price(product_id: str) -> Decimal:
|
|
205
|
+
"""Shell function - name reflects IO"""
|
|
206
|
+
product = db.get_product(product_id) # IO is explicit
|
|
207
|
+
return calculate_price(product) # Delegates to pure function
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Handling Time as a Side Effect
|
|
211
|
+
|
|
212
|
+
### ❌ Bad: Hidden time dependency
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
def is_expired(user: User) -> bool:
|
|
216
|
+
"""Not pure - depends on current time"""
|
|
217
|
+
return user.expires_at < datetime.now()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### ✅ Good: Pass time as parameter
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
def is_expired(user: User, current_time: datetime) -> bool:
|
|
224
|
+
"""Pure - time is explicit parameter"""
|
|
225
|
+
return user.expires_at < current_time
|
|
226
|
+
|
|
227
|
+
def check_user_expiration(user: User) -> bool:
|
|
228
|
+
"""Shell - provides current time"""
|
|
229
|
+
return is_expired(user, datetime.now())
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Advanced: Decision/Effect Separation
|
|
233
|
+
|
|
234
|
+
For complex workflows, separate decisions from effects:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from dataclasses import dataclass
|
|
238
|
+
from enum import Enum
|
|
239
|
+
|
|
240
|
+
class Effect(Enum):
|
|
241
|
+
SEND_EMAIL = "send_email"
|
|
242
|
+
CHARGE_CARD = "charge_card"
|
|
243
|
+
CREATE_ORDER = "create_order"
|
|
244
|
+
|
|
245
|
+
@dataclass
|
|
246
|
+
class Decision:
|
|
247
|
+
"""Pure data describing what to do"""
|
|
248
|
+
effects: list[Effect]
|
|
249
|
+
order_data: dict
|
|
250
|
+
|
|
251
|
+
def decide_order_workflow(cart: Cart, user: User, payment: Payment) -> Decision:
|
|
252
|
+
"""Pure function - returns description of effects"""
|
|
253
|
+
effects = []
|
|
254
|
+
|
|
255
|
+
if user.requires_verification:
|
|
256
|
+
effects.append(Effect.SEND_EMAIL)
|
|
257
|
+
|
|
258
|
+
if payment.amount > 0:
|
|
259
|
+
effects.append(Effect.CHARGE_CARD)
|
|
260
|
+
|
|
261
|
+
effects.append(Effect.CREATE_ORDER)
|
|
262
|
+
|
|
263
|
+
return Decision(
|
|
264
|
+
effects=effects,
|
|
265
|
+
order_data={"cart": cart, "user": user, "payment": payment}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def execute_order_workflow(cart: Cart, user: User, payment: Payment) -> None:
|
|
269
|
+
"""Shell - executes the effects"""
|
|
270
|
+
decision = decide_order_workflow(cart, user, payment)
|
|
271
|
+
|
|
272
|
+
for effect in decision.effects:
|
|
273
|
+
if effect == Effect.SEND_EMAIL:
|
|
274
|
+
email_service.send_verification(user.email)
|
|
275
|
+
elif effect == Effect.CHARGE_CARD:
|
|
276
|
+
payment_service.charge(payment.token, payment.amount)
|
|
277
|
+
elif effect == Effect.CREATE_ORDER:
|
|
278
|
+
order_service.create(decision.order_data)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Benefits:**
|
|
282
|
+
- Test decisions without executing effects
|
|
283
|
+
- Retry-safe (decisions are pure)
|
|
284
|
+
- Effects can be logged, audited, replayed
|
|
285
|
+
|
|
286
|
+
## Guidelines
|
|
287
|
+
|
|
288
|
+
1. **Start with pure functions**: Write business logic first without IO
|
|
289
|
+
2. **Name functions honestly**: `get_user()` implies IO, `calculate_discount()` implies pure
|
|
290
|
+
3. **Minimize shell thickness**: Keep imperative shell as thin as possible
|
|
291
|
+
4. **Test the core**: Focus tests on functional core (business logic)
|
|
292
|
+
5. **Accept data, don't fetch it**: Pure functions take parameters, don't call repositories
|
|
293
|
+
6. **Return new data**: Don't mutate, return new values
|
|
294
|
+
7. **Make time explicit**: Pass datetime as parameter instead of calling `datetime.now()`
|
|
295
|
+
|
|
296
|
+
## When to Break the Rules
|
|
297
|
+
|
|
298
|
+
**Logging**: Acceptable side effect in functional core if:
|
|
299
|
+
- Non-invasive (doesn't change behavior)
|
|
300
|
+
- Can be disabled/stubbed in tests
|
|
301
|
+
- Doesn't affect return value
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
import logging
|
|
305
|
+
|
|
306
|
+
logger = logging.getLogger(__name__)
|
|
307
|
+
|
|
308
|
+
def calculate_discount(total: Decimal) -> Decimal:
|
|
309
|
+
"""Logging is acceptable side effect"""
|
|
310
|
+
if total > 100:
|
|
311
|
+
logger.debug(f"Applying discount to order total: {total}")
|
|
312
|
+
return Decimal("0.1")
|
|
313
|
+
return Decimal("0")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Caching**: Acceptable mutation if:
|
|
317
|
+
- Implementation detail (doesn't affect observable behavior)
|
|
318
|
+
- Transparent to callers
|
|
319
|
+
- Doesn't break determinism
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from functools import lru_cache
|
|
323
|
+
|
|
324
|
+
@lru_cache(maxsize=128)
|
|
325
|
+
def fibonacci(n: int) -> int:
|
|
326
|
+
"""Pure function with internal caching"""
|
|
327
|
+
if n < 2:
|
|
328
|
+
return n
|
|
329
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Summary
|
|
333
|
+
|
|
334
|
+
- **Functional Core**: Pure functions, business logic, easy to test
|
|
335
|
+
- **Imperative Shell**: IO coordination, thin layer, simple
|
|
336
|
+
- **Benefits**: Testability, reliability, maintainability
|
|
337
|
+
- **Key insight**: Most bugs are in business logic, not IO - make that testable
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-build-tools
|
|
3
|
+
description: Python project tooling with uv, mise, ruff, basedpyright, and pytest. Use when setting up pyproject.toml, running builds, typechecking, configuring tests, linting, formatting, or managing Python environments.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Python Build Tools
|
|
8
|
+
|
|
9
|
+
Modern Python development tooling using uv, mise, ruff, basedpyright, and pytest.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Minimal pyproject.toml
|
|
14
|
+
|
|
15
|
+
```toml
|
|
16
|
+
[project]
|
|
17
|
+
name = "my-project"
|
|
18
|
+
version = "0.1.0"
|
|
19
|
+
requires-python = ">=3.12"
|
|
20
|
+
dependencies = ["fastapi", "pydantic"]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = ["pytest>=8.0.0", "ruff>=0.8.0", "basedpyright>=1.0.0"]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["hatchling"]
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
target-version = "py312"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["E", "F", "I", "N", "UP", "RUF"]
|
|
34
|
+
|
|
35
|
+
[tool.basedpyright]
|
|
36
|
+
typeCheckingMode = "strict"
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Setup Project
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv init my-project && cd my-project
|
|
46
|
+
uv sync
|
|
47
|
+
uv add fastapi pydantic
|
|
48
|
+
uv add --dev pytest ruff basedpyright
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Tool Overview
|
|
52
|
+
|
|
53
|
+
| Tool | Purpose | Replaces |
|
|
54
|
+
|------|---------|----------|
|
|
55
|
+
| **uv** | Package management | pip, virtualenv |
|
|
56
|
+
| **mise** | Version & tasks | pyenv, asdf |
|
|
57
|
+
| **ruff** | Lint & format | black, isort, flake8 |
|
|
58
|
+
| **basedpyright** | Type checking | mypy |
|
|
59
|
+
| **pytest** | Testing | unittest |
|
|
60
|
+
|
|
61
|
+
## Common Commands
|
|
62
|
+
|
|
63
|
+
### Lint and Format
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv run ruff check --fix .
|
|
67
|
+
uv run ruff format .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Type Check
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv run basedpyright
|
|
74
|
+
uv run basedpyright src/main.py
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Test
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run pytest
|
|
81
|
+
uv run pytest --cov=src --cov-report=html
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Manage Dependencies
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv add fastapi
|
|
88
|
+
uv add --dev pytest
|
|
89
|
+
uv lock --upgrade
|
|
90
|
+
uv tree
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Mise Configuration
|
|
94
|
+
|
|
95
|
+
Create `.mise.toml` for consistent development:
|
|
96
|
+
|
|
97
|
+
```toml
|
|
98
|
+
[tools]
|
|
99
|
+
python = "3.12"
|
|
100
|
+
|
|
101
|
+
[tasks.lint]
|
|
102
|
+
run = "uv run ruff check --fix ."
|
|
103
|
+
|
|
104
|
+
[tasks.format]
|
|
105
|
+
run = "uv run ruff format ."
|
|
106
|
+
|
|
107
|
+
[tasks.typecheck]
|
|
108
|
+
run = "uv run basedpyright"
|
|
109
|
+
|
|
110
|
+
[tasks.test]
|
|
111
|
+
run = "uv run pytest"
|
|
112
|
+
|
|
113
|
+
[tasks.check]
|
|
114
|
+
depends = ["lint", "format", "typecheck", "test"]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
mise install
|
|
121
|
+
mise run check
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Type Hints Example
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from decimal import Decimal
|
|
128
|
+
from typing import Optional
|
|
129
|
+
|
|
130
|
+
def calculate_discount(
|
|
131
|
+
total: Decimal,
|
|
132
|
+
rate: Optional[Decimal] = None
|
|
133
|
+
) -> Decimal:
|
|
134
|
+
if rate is None:
|
|
135
|
+
rate = Decimal("0.1")
|
|
136
|
+
return total * rate
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Best Practices
|
|
140
|
+
|
|
141
|
+
1. Use uv for all package management (faster, reliable)
|
|
142
|
+
2. Pin Python version with mise
|
|
143
|
+
3. Configure tools in pyproject.toml
|
|
144
|
+
4. Enable strict type checking
|
|
145
|
+
5. Run checks before commit
|
|
146
|
+
|
|
147
|
+
## References
|
|
148
|
+
|
|
149
|
+
For detailed configuration and advanced patterns:
|
|
150
|
+
|
|
151
|
+
- **[references/uv.md](references/uv.md)** - Workspaces, scripts, dependency management
|
|
152
|
+
- **[references/ruff.md](references/ruff.md)** - Rules, per-file ignores, pre-commit integration
|
|
153
|
+
- **[references/basedpyright.md](references/basedpyright.md)** - Type patterns, generics, protocols
|