(body: &[Statement])
| 358 | // ─── schemas ─── |
| 359 | |
| 360 | fn extract_schemas(body: &[Statement]) -> Vec<Model> { |
| 361 | let mut schemas = Vec::new(); |
| 362 | |
| 363 | for statement in body { |
| 364 | let Statement::ClassDef(ClassDef { |
| 365 | name, |
| 366 | body, |
| 367 | arguments, |
| 368 | .. |
| 369 | }) = statement |
| 370 | else { |
| 371 | continue; |
| 372 | }; |
| 373 | |
| 374 | // Heuristic: a model has at least one base class (Base/BaseModel/models.Model/...). |
| 375 | let bases: Vec<String> = arguments |
| 376 | .as_deref() |
| 377 | .map(|args| args.args.iter().filter_map(dotted).collect()) |
| 378 | .unwrap_or_default(); |
| 379 | |
| 380 | if bases.is_empty() { |
| 381 | continue; |
| 382 | } |
| 383 | |
| 384 | let mut fields = Vec::new(); |
| 385 | let mut relations = Vec::new(); |
| 386 | let mut orm: Option<&'static str> = None; |
| 387 | |
| 388 | for member in body { |
| 389 | match member { |
| 390 | // Annotated: `id: Mapped[int] = mapped_column(primary_key=True)`, |
| 391 | // `name: str` (pydantic), `email: str = Field(unique=True)` (SQLModel). |
| 392 | Statement::AnnAssign(AnnotationAssignment { |
| 393 | target, |
| 394 | annotation, |
| 395 | value, |
| 396 | .. |
| 397 | }) => { |
| 398 | let Expression::Name(NameExpression { id, .. }) = &**target else { |
| 399 | continue; |
| 400 | }; |
| 401 | |
| 402 | let field_name = id.to_string(); |
| 403 | |
| 404 | if field_name.starts_with("__") { |
| 405 | continue; |
| 406 | } |
| 407 | |
| 408 | let (mut r#type, nullable) = annotation_type(annotation); |
| 409 | let mut flags = Vec::new(); |
| 410 | |
| 411 | if let Some(call) = value.as_deref().and_then(analyze_call) { |
| 412 | orm = orm.or(call.orm); |
| 413 | |
| 414 | if let Some(target) = call.relation { |
| 415 | relations.push(target); |
| 416 | continue; |
| 417 | } |
nothing calls this directly
no test coverage detected