@punks/cli 1.0.1 → 1.0.3

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.
Files changed (31) hide show
  1. package/README.md +47 -4
  2. package/dist/data/AGENTS.md +0 -6
  3. package/dist/data/catalog/hooks.ts +26 -0
  4. package/dist/data/catalog/lint.ts +11 -26
  5. package/dist/data/catalog/packs.ts +5 -3
  6. package/dist/data/catalog/skills.ts +9 -1
  7. package/dist/data/catalog/tools.ts +13 -1
  8. package/dist/data/scripts/sync-subagents.mjs +163 -120
  9. package/dist/data/subagents/manifest.mjs +148 -0
  10. package/dist/index.js +2589 -1944
  11. package/dist/skills/agnostic/backend/logging-best-practices/SKILL.md +127 -0
  12. package/dist/skills/agnostic/backend/logging-best-practices/rules/context.md +157 -0
  13. package/dist/skills/agnostic/backend/logging-best-practices/rules/pitfalls.md +118 -0
  14. package/dist/skills/agnostic/backend/logging-best-practices/rules/structure.md +193 -0
  15. package/dist/skills/agnostic/backend/logging-best-practices/rules/wide-events.md +113 -0
  16. package/dist/skills/agnostic/cli/dp-cli/SKILL.md +84 -0
  17. package/dist/skills/agnostic/cli/dp-cli/references/commands.md +33 -0
  18. package/dist/skills/agnostic/cli/dp-cli/references/post-command-flow.md +47 -0
  19. package/dist/skills/agnostic/debug/debug-agent/SKILL.md +184 -0
  20. package/dist/skills/agnostic/requirements/write-backlog/REFERENCE.md +1 -1
  21. package/dist/skills/languages/python/async-python-patterns/SKILL.md +735 -0
  22. package/dist/skills/languages/python/python-code-style/SKILL.md +360 -0
  23. package/dist/skills/languages/python/python-design-patterns/SKILL.md +433 -0
  24. package/dist/skills/languages/python/python-project-structure/SKILL.md +252 -0
  25. package/dist/skills/languages/python/python-testing-patterns/SKILL.md +622 -0
  26. package/dist/skills/languages/python/python-testing-patterns/references/advanced-patterns.md +411 -0
  27. package/dist/skills/languages/typescript/quality-types/SKILL.md +93 -0
  28. package/docs/README.md +14 -4
  29. package/docs/reference/dp-requirements.md +16 -1
  30. package/docs/runbooks/dp-cli-scaffolding.md +82 -10
  31. package/package.json +5 -2
@@ -0,0 +1,433 @@
1
+ ---
2
+ name: python-design-patterns
3
+ description: Python design patterns including KISS, Separation of Concerns, Single Responsibility, and composition over inheritance. Use this skill when designing a new service or component from scratch and choosing how to layer responsibilities, when refactoring a God class or monolithic function that has grown too large, when deciding whether to add a new abstraction or live with duplication, when evaluating a pull request for structural issues like tight coupling or leaking internal types, when choosing between inheritance and composition for a new class hierarchy, or when a codebase is becoming hard to test because of entangled I/O and business logic.
4
+ ---
5
+
6
+ # Python Design Patterns
7
+
8
+ Write maintainable Python code using fundamental design principles. These patterns help you build systems that are easy to understand, test, and modify.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Designing new components or services
13
+ - Refactoring complex or tangled code
14
+ - Deciding whether to create an abstraction
15
+ - Choosing between inheritance and composition
16
+ - Evaluating code complexity and coupling
17
+ - Planning modular architectures
18
+
19
+ ## Core Concepts
20
+
21
+ ### 1. KISS (Keep It Simple)
22
+
23
+ Choose the simplest solution that works. Complexity must be justified by concrete requirements.
24
+
25
+ ### 2. Single Responsibility (SRP)
26
+
27
+ Each unit should have one reason to change. Separate concerns into focused components.
28
+
29
+ ### 3. Composition Over Inheritance
30
+
31
+ Build behavior by combining objects, not extending classes.
32
+
33
+ ### 4. Rule of Three
34
+
35
+ Wait until you have three instances before abstracting. Duplication is often better than premature abstraction.
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ # Simple beats clever
41
+ # Instead of a factory/registry pattern:
42
+ FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}
43
+
44
+ def get_formatter(name: str) -> Formatter:
45
+ return FORMATTERS[name]()
46
+ ```
47
+
48
+ ## Fundamental Patterns
49
+
50
+ ### Pattern 1: KISS - Keep It Simple
51
+
52
+ Before adding complexity, ask: does a simpler solution work?
53
+
54
+ ```python
55
+ # Over-engineered: Factory with registration
56
+ class OutputFormatterFactory:
57
+ _formatters: dict[str, type[Formatter]] = {}
58
+
59
+ @classmethod
60
+ def register(cls, name: str):
61
+ def decorator(formatter_cls):
62
+ cls._formatters[name] = formatter_cls
63
+ return formatter_cls
64
+ return decorator
65
+
66
+ @classmethod
67
+ def create(cls, name: str) -> Formatter:
68
+ return cls._formatters[name]()
69
+
70
+ @OutputFormatterFactory.register("json")
71
+ class JsonFormatter(Formatter):
72
+ ...
73
+
74
+ # Simple: Just use a dictionary
75
+ FORMATTERS = {
76
+ "json": JsonFormatter,
77
+ "csv": CsvFormatter,
78
+ "xml": XmlFormatter,
79
+ }
80
+
81
+ def get_formatter(name: str) -> Formatter:
82
+ """Get formatter by name."""
83
+ if name not in FORMATTERS:
84
+ raise ValueError(f"Unknown format: {name}")
85
+ return FORMATTERS[name]()
86
+ ```
87
+
88
+ The factory pattern adds code without adding value here. Save patterns for when they solve real problems.
89
+
90
+ ### Pattern 2: Single Responsibility Principle
91
+
92
+ Each class or function should have one reason to change.
93
+
94
+ ```python
95
+ # BAD: Handler does everything
96
+ class UserHandler:
97
+ async def create_user(self, request: Request) -> Response:
98
+ # HTTP parsing
99
+ data = await request.json()
100
+
101
+ # Validation
102
+ if not data.get("email"):
103
+ return Response({"error": "email required"}, status=400)
104
+
105
+ # Database access
106
+ user = await db.execute(
107
+ "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
108
+ data["email"], data["name"]
109
+ )
110
+
111
+ # Response formatting
112
+ return Response({"id": user.id, "email": user.email}, status=201)
113
+
114
+ # GOOD: Separated concerns
115
+ class UserService:
116
+ """Business logic only."""
117
+
118
+ def __init__(self, repo: UserRepository) -> None:
119
+ self._repo = repo
120
+
121
+ async def create_user(self, data: CreateUserInput) -> User:
122
+ # Only business rules here
123
+ user = User(email=data.email, name=data.name)
124
+ return await self._repo.save(user)
125
+
126
+ class UserHandler:
127
+ """HTTP concerns only."""
128
+
129
+ def __init__(self, service: UserService) -> None:
130
+ self._service = service
131
+
132
+ async def create_user(self, request: Request) -> Response:
133
+ data = CreateUserInput(**(await request.json()))
134
+ user = await self._service.create_user(data)
135
+ return Response(user.to_dict(), status=201)
136
+ ```
137
+
138
+ Now HTTP changes don't affect business logic, and vice versa.
139
+
140
+ ### Pattern 3: Separation of Concerns
141
+
142
+ Organize code into distinct layers with clear responsibilities.
143
+
144
+ ```
145
+ ┌─────────────────────────────────────────────────────┐
146
+ │ API Layer (handlers) │
147
+ │ - Parse requests │
148
+ │ - Call services │
149
+ │ - Format responses │
150
+ └─────────────────────────────────────────────────────┘
151
+
152
+
153
+ ┌─────────────────────────────────────────────────────┐
154
+ │ Service Layer (business logic) │
155
+ │ - Domain rules and validation │
156
+ │ - Orchestrate operations │
157
+ │ - Pure functions where possible │
158
+ └─────────────────────────────────────────────────────┘
159
+
160
+
161
+ ┌─────────────────────────────────────────────────────┐
162
+ │ Repository Layer (data access) │
163
+ │ - SQL queries │
164
+ │ - External API calls │
165
+ │ - Cache operations │
166
+ └─────────────────────────────────────────────────────┘
167
+ ```
168
+
169
+ Each layer depends only on layers below it:
170
+
171
+ ```python
172
+ # Repository: Data access
173
+ class UserRepository:
174
+ async def get_by_id(self, user_id: str) -> User | None:
175
+ row = await self._db.fetchrow(
176
+ "SELECT * FROM users WHERE id = $1", user_id
177
+ )
178
+ return User(**row) if row else None
179
+
180
+ # Service: Business logic
181
+ class UserService:
182
+ def __init__(self, repo: UserRepository) -> None:
183
+ self._repo = repo
184
+
185
+ async def get_user(self, user_id: str) -> User:
186
+ user = await self._repo.get_by_id(user_id)
187
+ if user is None:
188
+ raise UserNotFoundError(user_id)
189
+ return user
190
+
191
+ # Handler: HTTP concerns
192
+ @app.get("/users/{user_id}")
193
+ async def get_user(user_id: str) -> UserResponse:
194
+ user = await user_service.get_user(user_id)
195
+ return UserResponse.from_user(user)
196
+ ```
197
+
198
+ ### Pattern 4: Composition Over Inheritance
199
+
200
+ Build behavior by combining objects rather than inheriting.
201
+
202
+ ```python
203
+ # Inheritance: Rigid and hard to test
204
+ class EmailNotificationService(NotificationService):
205
+ def __init__(self):
206
+ super().__init__()
207
+ self._smtp = SmtpClient() # Hard to mock
208
+
209
+ def notify(self, user: User, message: str) -> None:
210
+ self._smtp.send(user.email, message)
211
+
212
+ # Composition: Flexible and testable
213
+ class NotificationService:
214
+ """Send notifications via multiple channels."""
215
+
216
+ def __init__(
217
+ self,
218
+ email_sender: EmailSender,
219
+ sms_sender: SmsSender | None = None,
220
+ push_sender: PushSender | None = None,
221
+ ) -> None:
222
+ self._email = email_sender
223
+ self._sms = sms_sender
224
+ self._push = push_sender
225
+
226
+ async def notify(
227
+ self,
228
+ user: User,
229
+ message: str,
230
+ channels: set[str] | None = None,
231
+ ) -> None:
232
+ channels = channels or {"email"}
233
+
234
+ if "email" in channels:
235
+ await self._email.send(user.email, message)
236
+
237
+ if "sms" in channels and self._sms and user.phone:
238
+ await self._sms.send(user.phone, message)
239
+
240
+ if "push" in channels and self._push and user.device_token:
241
+ await self._push.send(user.device_token, message)
242
+
243
+ # Easy to test with fakes
244
+ service = NotificationService(
245
+ email_sender=FakeEmailSender(),
246
+ sms_sender=FakeSmsSender(),
247
+ )
248
+ ```
249
+
250
+ ## Advanced Patterns
251
+
252
+ ### Pattern 5: Rule of Three
253
+
254
+ Wait until you have three instances before abstracting.
255
+
256
+ ```python
257
+ # Two similar functions? Don't abstract yet
258
+ def process_orders(orders: list[Order]) -> list[Result]:
259
+ results = []
260
+ for order in orders:
261
+ validated = validate_order(order)
262
+ result = process_validated_order(validated)
263
+ results.append(result)
264
+ return results
265
+
266
+ def process_returns(returns: list[Return]) -> list[Result]:
267
+ results = []
268
+ for ret in returns:
269
+ validated = validate_return(ret)
270
+ result = process_validated_return(validated)
271
+ results.append(result)
272
+ return results
273
+
274
+ # These look similar, but wait! Are they actually the same?
275
+ # Different validation, different processing, different errors...
276
+ # Duplication is often better than the wrong abstraction
277
+
278
+ # Only after a third case, consider if there's a real pattern
279
+ # But even then, sometimes explicit is better than abstract
280
+ ```
281
+
282
+ ### Pattern 6: Function Size Guidelines
283
+
284
+ Keep functions focused. Extract when a function:
285
+
286
+ - Exceeds 20-50 lines (varies by complexity)
287
+ - Serves multiple distinct purposes
288
+ - Has deeply nested logic (3+ levels)
289
+
290
+ ```python
291
+ # Too long, multiple concerns mixed
292
+ def process_order(order: Order) -> Result:
293
+ # 50 lines of validation...
294
+ # 30 lines of inventory check...
295
+ # 40 lines of payment processing...
296
+ # 20 lines of notification...
297
+ pass
298
+
299
+ # Better: Composed from focused functions
300
+ def process_order(order: Order) -> Result:
301
+ """Process a customer order through the complete workflow."""
302
+ validate_order(order)
303
+ reserve_inventory(order)
304
+ payment_result = charge_payment(order)
305
+ send_confirmation(order, payment_result)
306
+ return Result(success=True, order_id=order.id)
307
+ ```
308
+
309
+ ### Pattern 7: Dependency Injection
310
+
311
+ Pass dependencies through constructors for testability.
312
+
313
+ ```python
314
+ from typing import Protocol
315
+
316
+ class Logger(Protocol):
317
+ def info(self, msg: str, **kwargs) -> None: ...
318
+ def error(self, msg: str, **kwargs) -> None: ...
319
+
320
+ class Cache(Protocol):
321
+ async def get(self, key: str) -> str | None: ...
322
+ async def set(self, key: str, value: str, ttl: int) -> None: ...
323
+
324
+ class UserService:
325
+ """Service with injected dependencies."""
326
+
327
+ def __init__(
328
+ self,
329
+ repository: UserRepository,
330
+ cache: Cache,
331
+ logger: Logger,
332
+ ) -> None:
333
+ self._repo = repository
334
+ self._cache = cache
335
+ self._logger = logger
336
+
337
+ async def get_user(self, user_id: str) -> User:
338
+ # Check cache first
339
+ cached = await self._cache.get(f"user:{user_id}")
340
+ if cached:
341
+ self._logger.info("Cache hit", user_id=user_id)
342
+ return User.from_json(cached)
343
+
344
+ # Fetch from database
345
+ user = await self._repo.get_by_id(user_id)
346
+ if user:
347
+ await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300)
348
+
349
+ return user
350
+
351
+ # Production
352
+ service = UserService(
353
+ repository=PostgresUserRepository(db),
354
+ cache=RedisCache(redis),
355
+ logger=StructlogLogger(),
356
+ )
357
+
358
+ # Testing
359
+ service = UserService(
360
+ repository=InMemoryUserRepository(),
361
+ cache=FakeCache(),
362
+ logger=NullLogger(),
363
+ )
364
+ ```
365
+
366
+ ### Pattern 8: Avoiding Common Anti-Patterns
367
+
368
+ **Don't expose internal types:**
369
+
370
+ ```python
371
+ # BAD: Leaking ORM model to API
372
+ @app.get("/users/{id}")
373
+ def get_user(id: str) -> UserModel: # SQLAlchemy model
374
+ return db.query(UserModel).get(id)
375
+
376
+ # GOOD: Use response schemas
377
+ @app.get("/users/{id}")
378
+ def get_user(id: str) -> UserResponse:
379
+ user = db.query(UserModel).get(id)
380
+ return UserResponse.from_orm(user)
381
+ ```
382
+
383
+ **Don't mix I/O with business logic:**
384
+
385
+ ```python
386
+ # BAD: SQL embedded in business logic
387
+ def calculate_discount(user_id: str) -> float:
388
+ user = db.query("SELECT * FROM users WHERE id = ?", user_id)
389
+ orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
390
+ # Business logic mixed with data access
391
+
392
+ # GOOD: Repository pattern
393
+ def calculate_discount(user: User, order_history: list[Order]) -> float:
394
+ # Pure business logic, easily testable
395
+ if len(order_history) > 10:
396
+ return 0.15
397
+ return 0.0
398
+ ```
399
+
400
+ ## Best Practices Summary
401
+
402
+ 1. **Keep it simple** - Choose the simplest solution that works
403
+ 2. **Single responsibility** - Each unit has one reason to change
404
+ 3. **Separate concerns** - Distinct layers with clear purposes
405
+ 4. **Compose, don't inherit** - Combine objects for flexibility
406
+ 5. **Rule of three** - Wait before abstracting
407
+ 6. **Keep functions small** - 20-50 lines (varies by complexity), one purpose
408
+ 7. **Inject dependencies** - Constructor injection for testability
409
+ 8. **Delete before abstracting** - Remove dead code, then consider patterns
410
+ 9. **Test each layer** - Isolated tests for each concern
411
+ 10. **Explicit over clever** - Readable code beats elegant code
412
+
413
+ ## Troubleshooting
414
+
415
+ **A class is growing and seems to have multiple responsibilities, but splitting it feels wrong.**
416
+ Apply the "reason to change" test: list every change that could require editing this class. If the list has items from different domains (e.g., HTTP parsing AND business rules AND formatting), split it. If all changes stem from the same domain concern, the class may be appropriately sized.
417
+
418
+ **Injecting all dependencies through the constructor is producing constructors with 7+ parameters.**
419
+ This is a sign of too many responsibilities in one class, not a problem with dependency injection. Split the class into smaller units first, then each constructor naturally becomes smaller.
420
+
421
+ **Composition is producing deeply nested wrapper objects that are hard to trace.**
422
+ Keep the composition shallow (2-3 levels). If wrapping is the only mechanism, consider whether a Protocol-based approach or simple function composition would be cleaner than a chain of decorator objects.
423
+
424
+ **The rule of three says not to abstract yet, but the duplication is causing bugs when one copy is updated but not the other.**
425
+ Duplication that diverges in dangerous ways should be abstracted sooner. The rule of three is a heuristic, not a law. If the copies are already diverging incorrectly, extract immediately and add a test that exercises the shared behavior.
426
+
427
+ **A service layer is importing from the API layer, breaking the dependency direction.**
428
+ This is a layering violation. The service layer must not import from handlers. Introduce a shared types/models layer that both can import from, keeping the dependency arrow pointing downward (API → Service → Repository).
429
+
430
+ ## Related Skills
431
+
432
+ - [python-testing-patterns](../python-testing-patterns/SKILL.md) — Test each layer in isolation using the dependency injection structure established here
433
+ - [python-project-setup](../python-project-setup/SKILL.md) — Set up project structure and tooling that enforces layer boundaries from the start
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: python-project-structure
3
+ description: Python project organization, module architecture, and public API design. Use when setting up new projects, organizing modules, defining public interfaces with __all__, or planning directory layouts.
4
+ ---
5
+
6
+ # Python Project Structure & Module Architecture
7
+
8
+ Design well-organized Python projects with clear module boundaries, explicit public interfaces, and maintainable directory structures. Good organization makes code discoverable and changes predictable.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Starting a new Python project from scratch
13
+ - Reorganizing an existing codebase for clarity
14
+ - Defining module public APIs with `__all__`
15
+ - Deciding between flat and nested directory structures
16
+ - Determining test file placement strategies
17
+ - Creating reusable library packages
18
+
19
+ ## Core Concepts
20
+
21
+ ### 1. Module Cohesion
22
+
23
+ Group related code that changes together. A module should have a single, clear purpose.
24
+
25
+ ### 2. Explicit Interfaces
26
+
27
+ Define what's public with `__all__`. Everything not listed is an internal implementation detail.
28
+
29
+ ### 3. Flat Hierarchies
30
+
31
+ Prefer shallow directory structures. Add depth only for genuine sub-domains.
32
+
33
+ ### 4. Consistent Conventions
34
+
35
+ Apply naming and organization patterns uniformly across the project.
36
+
37
+ ## Quick Start
38
+
39
+ ```
40
+ myproject/
41
+ ├── src/
42
+ │ └── myproject/
43
+ │ ├── __init__.py
44
+ │ ├── services/
45
+ │ ├── models/
46
+ │ └── api/
47
+ ├── tests/
48
+ ├── pyproject.toml
49
+ └── README.md
50
+ ```
51
+
52
+ ## Fundamental Patterns
53
+
54
+ ### Pattern 1: One Concept Per File
55
+
56
+ Each file should focus on a single concept or closely related set of functions. Consider splitting when a file:
57
+
58
+ - Handles multiple unrelated responsibilities
59
+ - Grows beyond 300-500 lines (varies by complexity)
60
+ - Contains classes that change for different reasons
61
+
62
+ ```python
63
+ # Good: Focused files
64
+ # user_service.py - User business logic
65
+ # user_repository.py - User data access
66
+ # user_models.py - User data structures
67
+
68
+ # Avoid: Kitchen sink files
69
+ # user.py - Contains service, repository, models, utilities...
70
+ ```
71
+
72
+ ### Pattern 2: Explicit Public APIs with `__all__`
73
+
74
+ Define the public interface for every module. Unlisted members are internal implementation details.
75
+
76
+ ```python
77
+ # mypackage/services/__init__.py
78
+ from .user_service import UserService
79
+ from .order_service import OrderService
80
+ from .exceptions import ServiceError, ValidationError
81
+
82
+ __all__ = [
83
+ "UserService",
84
+ "OrderService",
85
+ "ServiceError",
86
+ "ValidationError",
87
+ ]
88
+
89
+ # Internal helpers remain private by omission
90
+ # from .internal_helpers import _validate_input # Not exported
91
+ ```
92
+
93
+ ### Pattern 3: Flat Directory Structure
94
+
95
+ Prefer minimal nesting. Deep hierarchies make imports verbose and navigation difficult.
96
+
97
+ ```
98
+ # Preferred: Flat structure
99
+ project/
100
+ ├── api/
101
+ │ ├── routes.py
102
+ │ └── middleware.py
103
+ ├── services/
104
+ │ ├── user_service.py
105
+ │ └── order_service.py
106
+ ├── models/
107
+ │ ├── user.py
108
+ │ └── order.py
109
+ └── utils/
110
+ └── validation.py
111
+
112
+ # Avoid: Deep nesting
113
+ project/core/internal/services/impl/user/
114
+ ```
115
+
116
+ Add sub-packages only when there's a genuine sub-domain requiring isolation.
117
+
118
+ ### Pattern 4: Test File Organization
119
+
120
+ Choose one approach and apply it consistently throughout the project.
121
+
122
+ **Option A: Colocated Tests**
123
+
124
+ ```
125
+ src/
126
+ ├── user_service.py
127
+ ├── test_user_service.py
128
+ ├── order_service.py
129
+ └── test_order_service.py
130
+ ```
131
+
132
+ Benefits: Tests live next to the code they verify. Easy to see coverage gaps.
133
+
134
+ **Option B: Parallel Test Directory**
135
+
136
+ ```
137
+ src/
138
+ ├── services/
139
+ │ ├── user_service.py
140
+ │ └── order_service.py
141
+ tests/
142
+ ├── services/
143
+ │ ├── test_user_service.py
144
+ │ └── test_order_service.py
145
+ ```
146
+
147
+ Benefits: Clean separation between production and test code. Standard for larger projects.
148
+
149
+ ## Advanced Patterns
150
+
151
+ ### Pattern 5: Package Initialization
152
+
153
+ Use `__init__.py` to provide a clean public interface for package consumers.
154
+
155
+ ```python
156
+ # mypackage/__init__.py
157
+ """MyPackage - A library for doing useful things."""
158
+
159
+ from .core import MainClass, HelperClass
160
+ from .exceptions import PackageError, ConfigError
161
+ from .config import Settings
162
+
163
+ __all__ = [
164
+ "MainClass",
165
+ "HelperClass",
166
+ "PackageError",
167
+ "ConfigError",
168
+ "Settings",
169
+ ]
170
+
171
+ __version__ = "1.0.0"
172
+ ```
173
+
174
+ Consumers can then import directly from the package:
175
+
176
+ ```python
177
+ from mypackage import MainClass, Settings
178
+ ```
179
+
180
+ ### Pattern 6: Layered Architecture
181
+
182
+ Organize code by architectural layer for clear separation of concerns.
183
+
184
+ ```
185
+ myapp/
186
+ ├── api/ # HTTP handlers, request/response
187
+ │ ├── routes/
188
+ │ └── middleware/
189
+ ├── services/ # Business logic
190
+ ├── repositories/ # Data access
191
+ ├── models/ # Domain entities
192
+ ├── schemas/ # API schemas (Pydantic)
193
+ └── config/ # Configuration
194
+ ```
195
+
196
+ Each layer should only depend on layers below it, never above.
197
+
198
+ ### Pattern 7: Domain-Driven Structure
199
+
200
+ For complex applications, organize by business domain rather than technical layer.
201
+
202
+ ```
203
+ ecommerce/
204
+ ├── users/
205
+ │ ├── models.py
206
+ │ ├── services.py
207
+ │ ├── repository.py
208
+ │ └── api.py
209
+ ├── orders/
210
+ │ ├── models.py
211
+ │ ├── services.py
212
+ │ ├── repository.py
213
+ │ └── api.py
214
+ └── shared/
215
+ ├── database.py
216
+ └── exceptions.py
217
+ ```
218
+
219
+ ## File and Module Naming
220
+
221
+ ### Conventions
222
+
223
+ - Use `snake_case` for all file and module names: `user_repository.py`
224
+ - Avoid abbreviations that obscure meaning: `user_repository.py` not `usr_repo.py`
225
+ - Match class names to file names: `UserService` in `user_service.py`
226
+
227
+ ### Import Style
228
+
229
+ Use absolute imports for clarity and reliability:
230
+
231
+ ```python
232
+ # Preferred: Absolute imports
233
+ from myproject.services import UserService
234
+ from myproject.models import User
235
+
236
+ # Avoid: Relative imports
237
+ from ..services import UserService
238
+ from . import models
239
+ ```
240
+
241
+ Relative imports can break when modules are moved or reorganized.
242
+
243
+ ## Best Practices Summary
244
+
245
+ 1. **Keep files focused** - One concept per file, consider splitting at 300-500 lines (varies by complexity)
246
+ 2. **Define `__all__` explicitly** - Make public interfaces clear
247
+ 3. **Prefer flat structures** - Add depth only for genuine sub-domains
248
+ 4. **Use absolute imports** - More reliable and clearer
249
+ 5. **Be consistent** - Apply patterns uniformly across the project
250
+ 6. **Match names to content** - File names should describe their purpose
251
+ 7. **Separate concerns** - Keep layers distinct and dependencies flowing one direction
252
+ 8. **Document your structure** - Include a README explaining the organization