autoworkflow 3.1.4 → 3.5.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.
Files changed (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,293 @@
1
+ # Prisma Skill
2
+
3
+ ## Schema Design
4
+ \`\`\`prisma
5
+ // schema.prisma
6
+ generator client {
7
+ provider = "prisma-client-js"
8
+ previewFeatures = ["fullTextSearch", "filteredRelationCount"]
9
+ }
10
+
11
+ datasource db {
12
+ provider = "postgresql"
13
+ url = env("DATABASE_URL")
14
+ }
15
+
16
+ model User {
17
+ id String @id @default(cuid())
18
+ email String @unique
19
+ name String?
20
+ role Role @default(USER)
21
+ posts Post[]
22
+ profile Profile?
23
+ accounts Account[]
24
+ createdAt DateTime @default(now())
25
+ updatedAt DateTime @updatedAt
26
+ deletedAt DateTime? // Soft delete
27
+
28
+ @@index([email])
29
+ @@index([createdAt])
30
+ @@map("users") // Table name
31
+ }
32
+
33
+ model Post {
34
+ id String @id @default(cuid())
35
+ title String
36
+ content String?
37
+ published Boolean @default(false)
38
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
39
+ authorId String
40
+ categories Category[]
41
+ createdAt DateTime @default(now())
42
+
43
+ @@index([authorId])
44
+ @@index([published, createdAt])
45
+ }
46
+
47
+ model Category {
48
+ id String @id @default(cuid())
49
+ name String @unique
50
+ posts Post[]
51
+ }
52
+
53
+ // One-to-one
54
+ model Profile {
55
+ id String @id @default(cuid())
56
+ bio String?
57
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
58
+ userId String @unique
59
+ }
60
+
61
+ enum Role {
62
+ USER
63
+ ADMIN
64
+ MODERATOR
65
+ }
66
+ \`\`\`
67
+
68
+ ## Query Patterns
69
+ \`\`\`typescript
70
+ // CRUD Operations
71
+ const user = await prisma.user.create({
72
+ data: {
73
+ email: 'user@example.com',
74
+ name: 'John',
75
+ profile: { create: { bio: 'Developer' } }, // Nested create
76
+ },
77
+ include: { profile: true },
78
+ });
79
+
80
+ // Find operations
81
+ const user = await prisma.user.findUnique({
82
+ where: { id },
83
+ select: { id: true, email: true, name: true }, // Only select needed fields
84
+ });
85
+
86
+ const user = await prisma.user.findUniqueOrThrow({ where: { id } }); // Throws if not found
87
+
88
+ // Filter with multiple conditions
89
+ const users = await prisma.user.findMany({
90
+ where: {
91
+ AND: [
92
+ { email: { contains: '@company.com' } },
93
+ { role: 'USER' },
94
+ { deletedAt: null },
95
+ ],
96
+ OR: [
97
+ { name: { startsWith: 'A' } },
98
+ { name: { startsWith: 'B' } },
99
+ ],
100
+ NOT: { role: 'ADMIN' },
101
+ },
102
+ orderBy: { createdAt: 'desc' },
103
+ skip: 0,
104
+ take: 20,
105
+ });
106
+
107
+ // Include relations with filtering
108
+ const usersWithPosts = await prisma.user.findMany({
109
+ include: {
110
+ posts: {
111
+ where: { published: true },
112
+ orderBy: { createdAt: 'desc' },
113
+ take: 5,
114
+ },
115
+ _count: { select: { posts: true } }, // Count relations
116
+ },
117
+ });
118
+
119
+ // Update operations
120
+ const updated = await prisma.user.update({
121
+ where: { id },
122
+ data: {
123
+ name: 'New Name',
124
+ posts: {
125
+ updateMany: { where: { published: false }, data: { published: true } },
126
+ },
127
+ },
128
+ });
129
+
130
+ // Upsert
131
+ const user = await prisma.user.upsert({
132
+ where: { email: 'user@example.com' },
133
+ update: { name: 'Updated Name' },
134
+ create: { email: 'user@example.com', name: 'New User' },
135
+ });
136
+
137
+ // Delete with cascade (defined in schema)
138
+ await prisma.user.delete({ where: { id } });
139
+
140
+ // Soft delete pattern
141
+ await prisma.user.update({
142
+ where: { id },
143
+ data: { deletedAt: new Date() },
144
+ });
145
+ \`\`\`
146
+
147
+ ## Transactions
148
+ \`\`\`typescript
149
+ // Sequential transactions (array)
150
+ const [user, post] = await prisma.$transaction([
151
+ prisma.user.create({ data: userData }),
152
+ prisma.post.create({ data: postData }),
153
+ ]);
154
+
155
+ // Interactive transactions (function)
156
+ const result = await prisma.$transaction(async (tx) => {
157
+ const user = await tx.user.findUnique({ where: { id } });
158
+ if (!user) throw new Error('User not found');
159
+
160
+ const updatedUser = await tx.user.update({
161
+ where: { id },
162
+ data: { balance: { decrement: 100 } },
163
+ });
164
+
165
+ await tx.transaction.create({
166
+ data: { userId: id, amount: -100, type: 'WITHDRAWAL' },
167
+ });
168
+
169
+ return updatedUser;
170
+ }, {
171
+ maxWait: 5000, // Max wait to start transaction
172
+ timeout: 10000, // Max transaction duration
173
+ isolationLevel: 'Serializable', // Optional isolation level
174
+ });
175
+ \`\`\`
176
+
177
+ ## Middleware
178
+ \`\`\`typescript
179
+ // Logging middleware
180
+ prisma.$use(async (params, next) => {
181
+ const before = Date.now();
182
+ const result = await next(params);
183
+ const after = Date.now();
184
+
185
+ console.log(\`Query \${params.model}.\${params.action} took \${after - before}ms\`);
186
+ return result;
187
+ });
188
+
189
+ // Soft delete middleware
190
+ prisma.$use(async (params, next) => {
191
+ if (params.model === 'User') {
192
+ // Intercept delete and convert to soft delete
193
+ if (params.action === 'delete') {
194
+ params.action = 'update';
195
+ params.args.data = { deletedAt: new Date() };
196
+ }
197
+
198
+ // Filter out soft-deleted in find queries
199
+ if (params.action === 'findMany' || params.action === 'findFirst') {
200
+ params.args.where = { ...params.args.where, deletedAt: null };
201
+ }
202
+ }
203
+ return next(params);
204
+ });
205
+ \`\`\`
206
+
207
+ ## Raw Queries
208
+ \`\`\`typescript
209
+ // Raw SQL queries
210
+ const users = await prisma.$queryRaw\`
211
+ SELECT * FROM users
212
+ WHERE email LIKE \${pattern}
213
+ LIMIT \${limit}
214
+ \`;
215
+
216
+ // Raw SQL with typed result
217
+ const users = await prisma.$queryRaw<User[]>\`
218
+ SELECT id, email, name FROM users WHERE role = \${role}
219
+ \`;
220
+
221
+ // Execute raw (INSERT, UPDATE, DELETE)
222
+ const count = await prisma.$executeRaw\`
223
+ UPDATE users SET updated_at = NOW() WHERE id = \${id}
224
+ \`;
225
+ \`\`\`
226
+
227
+ ## Prisma Client Extensions
228
+ \`\`\`typescript
229
+ // Extend Prisma Client (v4.16+)
230
+ const prisma = new PrismaClient().$extends({
231
+ model: {
232
+ user: {
233
+ async findByEmail(email: string) {
234
+ return prisma.user.findUnique({ where: { email } });
235
+ },
236
+ async softDelete(id: string) {
237
+ return prisma.user.update({
238
+ where: { id },
239
+ data: { deletedAt: new Date() },
240
+ });
241
+ },
242
+ },
243
+ },
244
+ result: {
245
+ user: {
246
+ fullName: {
247
+ needs: { firstName: true, lastName: true },
248
+ compute(user) {
249
+ return \`\${user.firstName} \${user.lastName}\`;
250
+ },
251
+ },
252
+ },
253
+ },
254
+ });
255
+
256
+ // Usage
257
+ const user = await prisma.user.findByEmail('test@example.com');
258
+ console.log(user?.fullName);
259
+ \`\`\`
260
+
261
+ ## Migrations
262
+ \`\`\`bash
263
+ # Create migration from schema changes
264
+ npx prisma migrate dev --name add_user_role
265
+
266
+ # Apply migrations in production
267
+ npx prisma migrate deploy
268
+
269
+ # Reset database (dangerous!)
270
+ npx prisma migrate reset
271
+
272
+ # Generate Prisma Client
273
+ npx prisma generate
274
+
275
+ # View database in browser
276
+ npx prisma studio
277
+ \`\`\`
278
+
279
+ ## ❌ DON'T
280
+ - Use \`findMany()\` without pagination on large tables
281
+ - Fetch relations you don't need (use select/include wisely)
282
+ - Ignore the N+1 problem with nested queries
283
+ - Use raw queries when Prisma Client suffices
284
+ - Skip migrations in production
285
+
286
+ ## ✅ DO
287
+ - Use \`select\` to fetch only needed fields
288
+ - Use \`take\` and \`skip\` for pagination (or cursor-based)
289
+ - Use transactions for related operations
290
+ - Use middleware for cross-cutting concerns
291
+ - Use extensions for reusable model methods
292
+ - Use \`findUniqueOrThrow\` when record must exist
293
+ - Run migrations in CI/CD pipeline
@@ -0,0 +1,304 @@
1
+ # Pydantic Skill
2
+
3
+ ## Basic Model with Validators
4
+ \`\`\`python
5
+ from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator
6
+ from typing import Annotated
7
+ from datetime import datetime
8
+
9
+ class UserBase(BaseModel):
10
+ """Base model with shared fields and configuration."""
11
+ email: EmailStr
12
+ name: Annotated[str, Field(min_length=1, max_length=100)]
13
+
14
+ model_config = {
15
+ "from_attributes": True, # ORM mode
16
+ "str_strip_whitespace": True,
17
+ "validate_assignment": True,
18
+ }
19
+
20
+ class UserCreate(UserBase):
21
+ password: str = Field(min_length=8)
22
+ password_confirm: str
23
+
24
+ @field_validator('name')
25
+ @classmethod
26
+ def name_must_not_be_empty(cls, v: str) -> str:
27
+ if not v.strip():
28
+ raise ValueError('Name cannot be empty')
29
+ return v.title()
30
+
31
+ @model_validator(mode='after')
32
+ def passwords_match(self) -> 'UserCreate':
33
+ if self.password != self.password_confirm:
34
+ raise ValueError('Passwords do not match')
35
+ return self
36
+
37
+ class UserResponse(UserBase):
38
+ id: int
39
+ created_at: datetime
40
+ \`\`\`
41
+
42
+ ## Computed Fields
43
+ \`\`\`python
44
+ from pydantic import BaseModel, computed_field
45
+ from datetime import datetime, date
46
+
47
+ class User(BaseModel):
48
+ first_name: str
49
+ last_name: str
50
+ birth_date: date
51
+
52
+ @computed_field
53
+ @property
54
+ def full_name(self) -> str:
55
+ return f"{self.first_name} {self.last_name}"
56
+
57
+ @computed_field
58
+ @property
59
+ def age(self) -> int:
60
+ today = date.today()
61
+ return today.year - self.birth_date.year - (
62
+ (today.month, today.day) < (self.birth_date.month, self.birth_date.day)
63
+ )
64
+
65
+ class Order(BaseModel):
66
+ items: list[dict]
67
+ tax_rate: float = 0.08
68
+
69
+ @computed_field
70
+ @property
71
+ def subtotal(self) -> float:
72
+ return sum(item['price'] * item['quantity'] for item in self.items)
73
+
74
+ @computed_field
75
+ @property
76
+ def total(self) -> float:
77
+ return self.subtotal * (1 + self.tax_rate)
78
+ \`\`\`
79
+
80
+ ## Custom Types and Validators
81
+ \`\`\`python
82
+ from pydantic import BaseModel, BeforeValidator, AfterValidator, PlainValidator
83
+ from typing import Annotated
84
+ import re
85
+
86
+ # Custom type with validation
87
+ def validate_phone(v: str) -> str:
88
+ digits = re.sub(r'\\D', '', v)
89
+ if len(digits) != 10:
90
+ raise ValueError('Phone must be 10 digits')
91
+ return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
92
+
93
+ PhoneNumber = Annotated[str, AfterValidator(validate_phone)]
94
+
95
+ # Coercing validator
96
+ def to_lowercase(v: str) -> str:
97
+ return v.lower().strip()
98
+
99
+ LowercaseStr = Annotated[str, BeforeValidator(to_lowercase)]
100
+
101
+ # Multiple validators
102
+ def validate_username(v: str) -> str:
103
+ if not re.match(r'^[a-z0-9_]+$', v):
104
+ raise ValueError('Username must be alphanumeric with underscores')
105
+ if len(v) < 3:
106
+ raise ValueError('Username must be at least 3 characters')
107
+ return v
108
+
109
+ Username = Annotated[str, BeforeValidator(to_lowercase), AfterValidator(validate_username)]
110
+
111
+ class Contact(BaseModel):
112
+ username: Username
113
+ email: LowercaseStr
114
+ phone: PhoneNumber
115
+ \`\`\`
116
+
117
+ ## Discriminated Unions
118
+ \`\`\`python
119
+ from pydantic import BaseModel, Field
120
+ from typing import Literal, Union
121
+ from datetime import datetime
122
+
123
+ class EmailNotification(BaseModel):
124
+ type: Literal["email"] = "email"
125
+ recipient: str
126
+ subject: str
127
+ body: str
128
+
129
+ class SMSNotification(BaseModel):
130
+ type: Literal["sms"] = "sms"
131
+ phone_number: str
132
+ message: str
133
+
134
+ class PushNotification(BaseModel):
135
+ type: Literal["push"] = "push"
136
+ device_token: str
137
+ title: str
138
+ body: str
139
+
140
+ # Discriminated union - Pydantic uses 'type' field to determine which model
141
+ Notification = Union[EmailNotification, SMSNotification, PushNotification]
142
+
143
+ class NotificationRequest(BaseModel):
144
+ notification: Annotated[Notification, Field(discriminator='type')]
145
+ scheduled_at: datetime | None = None
146
+
147
+ # Usage
148
+ req = NotificationRequest(
149
+ notification={"type": "email", "recipient": "user@example.com", "subject": "Hi", "body": "Hello!"}
150
+ )
151
+ \`\`\`
152
+
153
+ ## Settings Management
154
+ \`\`\`python
155
+ from pydantic import Field, SecretStr, PostgresDsn
156
+ from pydantic_settings import BaseSettings, SettingsConfigDict
157
+ from functools import lru_cache
158
+
159
+ class Settings(BaseSettings):
160
+ """Application settings loaded from environment variables."""
161
+
162
+ model_config = SettingsConfigDict(
163
+ env_file=".env",
164
+ env_file_encoding="utf-8",
165
+ env_prefix="APP_",
166
+ case_sensitive=False,
167
+ extra="ignore",
168
+ )
169
+
170
+ # App settings
171
+ app_name: str = "MyApp"
172
+ debug: bool = False
173
+ environment: Literal["development", "staging", "production"] = "development"
174
+
175
+ # Database
176
+ database_url: PostgresDsn
177
+ db_pool_size: int = Field(default=5, ge=1, le=100)
178
+
179
+ # Secrets
180
+ secret_key: SecretStr
181
+ api_key: SecretStr
182
+
183
+ # External services
184
+ redis_url: str = "redis://localhost:6379"
185
+
186
+ @computed_field
187
+ @property
188
+ def is_production(self) -> bool:
189
+ return self.environment == "production"
190
+
191
+ @lru_cache
192
+ def get_settings() -> Settings:
193
+ """Cached settings instance."""
194
+ return Settings()
195
+
196
+ # Usage in FastAPI
197
+ # settings = get_settings()
198
+ # app = FastAPI(title=settings.app_name, debug=settings.debug)
199
+ \`\`\`
200
+
201
+ ## Serialization Options
202
+ \`\`\`python
203
+ from pydantic import BaseModel, Field, ConfigDict
204
+ from datetime import datetime
205
+ from enum import Enum
206
+
207
+ class Status(Enum):
208
+ ACTIVE = "active"
209
+ INACTIVE = "inactive"
210
+
211
+ class User(BaseModel):
212
+ model_config = ConfigDict(
213
+ use_enum_values=True,
214
+ populate_by_name=True, # Allow alias or field name
215
+ )
216
+
217
+ id: int
218
+ email: str
219
+ internal_code: str = Field(exclude=True) # Never serialized
220
+ display_name: str = Field(alias="displayName")
221
+ status: Status
222
+ created_at: datetime
223
+
224
+ user = User(
225
+ id=1,
226
+ email="user@example.com",
227
+ internal_code="SECRET123",
228
+ displayName="John Doe",
229
+ status=Status.ACTIVE,
230
+ created_at=datetime.now()
231
+ )
232
+
233
+ # Serialization methods
234
+ user.model_dump() # Dict with all fields (except excluded)
235
+ user.model_dump(by_alias=True) # Use aliases in keys
236
+ user.model_dump(exclude={"email"}) # Exclude specific fields
237
+ user.model_dump(include={"id", "email"}) # Only include specific fields
238
+ user.model_dump(exclude_none=True) # Exclude None values
239
+ user.model_dump(mode='json') # JSON-compatible types
240
+
241
+ user.model_dump_json() # JSON string
242
+ user.model_dump_json(indent=2) # Formatted JSON
243
+
244
+ # Round-trip
245
+ User.model_validate(user.model_dump()) # From dict
246
+ User.model_validate_json(user.model_dump_json()) # From JSON string
247
+ \`\`\`
248
+
249
+ ## Generic Models
250
+ \`\`\`python
251
+ from pydantic import BaseModel
252
+ from typing import Generic, TypeVar
253
+
254
+ T = TypeVar('T')
255
+
256
+ class PaginatedResponse(BaseModel, Generic[T]):
257
+ items: list[T]
258
+ total: int
259
+ page: int
260
+ per_page: int
261
+
262
+ @computed_field
263
+ @property
264
+ def total_pages(self) -> int:
265
+ return (self.total + self.per_page - 1) // self.per_page
266
+
267
+ @computed_field
268
+ @property
269
+ def has_next(self) -> bool:
270
+ return self.page < self.total_pages
271
+
272
+ class User(BaseModel):
273
+ id: int
274
+ name: str
275
+
276
+ class Product(BaseModel):
277
+ id: int
278
+ title: str
279
+ price: float
280
+
281
+ # Usage
282
+ users_response: PaginatedResponse[User] = PaginatedResponse(
283
+ items=[User(id=1, name="John")],
284
+ total=50,
285
+ page=1,
286
+ per_page=10
287
+ )
288
+ \`\`\`
289
+
290
+ ## ✅ DO
291
+ - Use \`model_config\` instead of nested Config class (Pydantic v2)
292
+ - Use \`@field_validator\` with \`@classmethod\` decorator
293
+ - Use \`computed_field\` for derived properties
294
+ - Use \`Annotated\` types for reusable validation
295
+ - Use \`pydantic-settings\` for environment configuration
296
+ - Use discriminated unions for polymorphic data
297
+ - Use \`SecretStr\` for sensitive data (won't be logged)
298
+
299
+ ## ❌ DON'T
300
+ - Don't use \`@validator\` (Pydantic v1 syntax, deprecated)
301
+ - Don't use \`class Config:\` (use \`model_config\` dict)
302
+ - Don't store secrets as plain strings
303
+ - Don't use \`.dict()\` (use \`.model_dump()\`)
304
+ - Don't use \`.json()\` (use \`.model_dump_json()\`)