1"""
2Model mapping implementation.
3
4Implements :any:`/specs/spec_model_mapping` §1-5, covering:
5
6* General mapping rules (§1)
7* Entity-specific details (§2)
8* Content extraction rules (§3)
9* Derived metadata extraction (§4)
10* Error handling (§5)
11
12"""
13
14from __future__ import annotations
15
16from datetime import datetime, timezone
17from typing import Any, Mapping
18
19from ..config.settings import Settings
20
21from ..rendering.renderer_base import Renderer
22
23from .group import Group
24from .project import Project
25from .issue import Issue
26from .readme import Readme
27
28ISO_KEYS = {"last_activity_at"}
29
30
[docs]
31def _parse_dates(data: Mapping[str, Any]) -> dict[str, Any]: # noqa: D401
32 """Return *data* copy with ISO date strings converted to datetime."""
33 converted: dict[str, Any] = {}
34 for k, v in data.items():
35 if k in ISO_KEYS and isinstance(v, str):
36 parsed_date = Renderer.parse_iso_date(v)
37 converted[k] = parsed_date if parsed_date is not None else v
38 else:
39 converted[k] = v
40 return converted
41
42
[docs]
43def _robust_type_conversion(data: Mapping[str, Any]) -> dict[str, Any]:
44 """Convert types and handle nulls/str/int mismatches for all fields."""
45 result = dict(data)
46 # Convert known int fields
47 int_fields = ["id"]
48 for key in int_fields:
49 if key in result and result[key] is not None:
50 result[key] = Renderer.safe_int(result[key], result[key])
51 # Convert date fields
52 for key in ISO_KEYS:
53 if key in result and result[key] is not None:
54 v = result[key]
55 if isinstance(v, str):
56 parsed_date = Renderer.parse_iso_date(v)
57 if parsed_date is not None:
58 result[key] = parsed_date
59 return result
60
61
62# ---------- Public mapper helpers ------------------------------------------
63
64
[docs]
65def group_from_json(data: Mapping[str, Any]) -> Group: # noqa: D401
66 return Group.from_api_json(_robust_type_conversion(_parse_dates(data)))
67
68
[docs]
69def project_from_json(data: Mapping[str, Any]) -> Project: # noqa: D401
70 return Project.from_api_json(_robust_type_conversion(_parse_dates(data)))
71
72
[docs]
73def issue_from_json(data: Mapping[str, Any]) -> Issue: # noqa: D401
74 return Issue.from_api_json(_robust_type_conversion(_parse_dates(data)))
75
76
[docs]
77def readme_from_str(
78 project_id: int, content: str, ref: str = "main", path: str = "README.md"
79) -> Readme: # noqa: D401
80 """Create Readme from content string with extracted metadata."""
81 from ..services.readme_extraction import (
82 extract_readme_data,
83 extract_first_paragraph,
84 extract_todo_sections,
85 )
86
87 extra = extract_readme_data(content)
88 first_paragraph = extract_first_paragraph(content) or ""
89 todo_md = extract_todo_sections(content, Settings.current().todo_keywords)
90 return Readme(
91 project_id=project_id,
92 content=first_paragraph,
93 full_content=content,
94 extra=extra,
95 todo=todo_md,
96 ref=ref,
97 path=path,
98 )
99
100
101__all__ = [
102 "group_from_json",
103 "project_from_json",
104 "issue_from_json",
105 "readme_from_str",
106]