Coverage for youversion/core/data_processor.py: 100%
199 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 11:31 +0100
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 11:31 +0100
1"""Data processor for YouVersion API responses."""
3from datetime import datetime
4from typing import Any
6from ..models import Moment, Reference, Votd
7from ..utils import create_instance_from_response
8from .interfaces import IDataProcessor
11class DataProcessor(IDataProcessor):
12 """Processes raw API responses into structured objects."""
14 def __init__(self):
15 """Initialize data processor."""
17 def _create_references(self, references: list[dict[str, Any]]) -> list[Reference]:
18 """Create Reference objects from raw data using dynamic creation.
20 Args:
21 references: List of reference dictionaries
23 Returns:
24 List of dynamically created Reference objects
25 """
26 normalized: list[Reference] = []
27 for ref in references:
28 usfm_value = ref.get("usfm")
29 # Normalize usfm to a string when a single-item list is provided
30 if isinstance(usfm_value, list) and len(usfm_value) == 1:
31 usfm_value = usfm_value[0]
32 ref_data = {
33 "version_id": ref["version_id"],
34 "human": ref["human"],
35 "usfm": usfm_value,
36 }
37 reference = create_instance_from_response("Reference", ref_data)
38 normalized.append(reference)
39 return normalized
41 def _process_common_fields(self, obj: dict[str, Any]) -> dict[str, Any]:
42 """Process common fields in moment objects using dynamic creation.
44 Args:
45 obj: Raw object data
47 Returns:
48 Processed object data
49 """
50 # Process comments
51 comments = obj.get("comments", {})
52 if comments:
53 obj["comments"] = create_instance_from_response("Comment", comments)
55 # Process likes
56 likes = obj.get("likes", {})
57 if likes:
58 obj["likes"] = create_instance_from_response("Like", likes)
60 # Process user
61 user = obj.get("user", {})
62 if user:
63 obj["user"] = create_instance_from_response("User", user)
65 return obj
67 def process_moments(self, raw_data: list[dict[str, Any]]) -> list[Moment]:
68 """Process raw moments data using dynamic Pydantic models.
70 Args:
71 raw_data: Raw moments data from API
73 Returns:
74 List of dynamically created Moment objects conforming to
75 MomentProtocol
76 """
77 moments = []
79 for item in raw_data:
80 # Create a dynamic dataclass instance from the moment item
81 # Use kind_id to create a unique class name
82 kind_id = item.get("kind_id", "moment")
83 # Sanitize kind_id for class name
84 kind_base = kind_id.split(".")[0]
85 class_name = f"{kind_base.replace('.', '').replace('_', '').title()}"
86 moment = create_instance_from_response(class_name, item)
87 moments.append(moment)
89 return moments
91 def process_highlights(self, raw_data: list[dict[str, Any]]) -> list[Any]:
92 """Process raw highlights data using dynamic Pydantic models.
94 Args:
95 raw_data: Raw highlights data from API
97 Returns:
98 List of dynamically created Highlight objects
99 """
100 highlights = []
102 for item in raw_data:
103 # Create a dynamic dataclass instance from the highlight item
104 class_name = "Highlight"
105 highlight = create_instance_from_response(class_name, item)
106 highlights.append(highlight)
108 return highlights
110 def process_verse_of_the_day(
111 self, raw_data: dict[str, Any], day: int | None = None
112 ) -> Votd:
113 """Process verse of the day data using dynamic creation.
115 Args:
116 raw_data: Raw verse of the day data
117 day: Specific day number (optional)
119 Returns:
120 Dynamically created Votd object
121 """
122 requested_day: int | None = day
123 if day is None:
124 day = datetime.now().timetuple().tm_yday
126 votd_data = raw_data.get("votd", [])
127 for ref in votd_data:
128 if ref["day"] == day:
129 return create_instance_from_response("Votd", ref)
131 # Always fallback to first when available
132 if votd_data:
133 return create_instance_from_response("Votd", votd_data[0])
134 # No data at all
135 day_value = requested_day if requested_day is not None else day
136 raise ValueError(f"No verse of the day found for day {day_value}")
138 def process_notes(self, raw_data: list[dict[str, Any]]) -> list[Any]:
139 """Process raw notes data using dynamic Pydantic models.
141 Args:
142 raw_data: Raw notes data from API
144 Returns:
145 List of dynamically created Note objects
146 """
147 notes = []
148 for item in raw_data:
149 note = create_instance_from_response("Note", item)
150 notes.append(note)
151 return notes
153 def process_bookmarks(self, raw_data: list[dict[str, Any]]) -> list[Any]:
154 """Process raw bookmarks data using dynamic Pydantic models.
156 Args:
157 raw_data: Raw bookmarks data from API
159 Returns:
160 List of dynamically created Bookmark objects
161 """
162 bookmarks = []
163 for item in raw_data:
164 bookmark = create_instance_from_response("Bookmark", item)
165 bookmarks.append(bookmark)
166 return bookmarks
168 def process_images(self, raw_data: list[dict[str, Any]]) -> list[Any]:
169 """Process raw images data using dynamic Pydantic models.
171 Args:
172 raw_data: Raw images data from API
174 Returns:
175 List of dynamically created Image objects
176 """
177 images = []
178 for item in raw_data:
179 image = create_instance_from_response("Image", item)
180 images.append(image)
181 return images
183 def process_plan_progress(self, raw_data: list[dict[str, Any]]) -> list[Any]:
184 """Process raw plan progress data using dynamic Pydantic models.
186 Args:
187 raw_data: Raw plan progress data from API
189 Returns:
190 List of dynamically created PlanProgress objects
191 """
192 plan_progress = []
193 for item in raw_data:
194 progress = create_instance_from_response("PlanProgress", item)
195 plan_progress.append(progress)
196 return plan_progress
198 def process_plan_subscriptions(self, raw_data: list[dict[str, Any]]) -> list[Any]:
199 """Process raw plan subscriptions data using dynamic Pydantic models.
201 Args:
202 raw_data: Raw plan subscriptions data from API
204 Returns:
205 List of dynamically created PlanSubscription objects
206 """
207 plan_subscriptions = []
208 for item in raw_data:
209 subscription = create_instance_from_response("PlanSubscription", item)
210 plan_subscriptions.append(subscription)
211 return plan_subscriptions
213 def process_badges(self, raw_data: list[dict[str, Any]]) -> list[Any]:
214 """Process raw badges data using dynamic Pydantic models.
216 Args:
217 raw_data: Raw badges data from API
219 Returns:
220 List of dynamically created Badge objects
221 """
222 badges = []
223 for item in raw_data:
224 badge = create_instance_from_response("Badge", item)
225 badges.append(badge)
226 return badges
228 def process_plan_completions(self, raw_data: list[dict[str, Any]]) -> list[Any]:
229 """Process raw plan completions data using dynamic Pydantic models.
231 Args:
232 raw_data: Raw plan completions data from API
234 Returns:
235 List of dynamically created PlanCompletion objects
236 """
237 plan_completions = []
238 for item in raw_data:
239 completion = create_instance_from_response("PlanCompletion", item)
240 plan_completions.append(completion)
241 return plan_completions
243 def process_search_bible(self, raw_data: dict[str, Any]) -> dict[str, Any]:
244 """Process raw Bible search results using dynamic Pydantic models.
246 Args:
247 raw_data: Raw Bible search results data from API
249 Returns:
250 Processed search results with dynamically created objects
251 """
252 return create_instance_from_response("BibleSearch", raw_data)
254 def process_bible_chapter(self, raw_data: dict[str, Any]) -> Any:
255 """Process raw Bible chapter data using dynamic Pydantic models.
257 Args:
258 raw_data: Raw Bible chapter data from API
260 Returns:
261 Dynamically created BibleChapter object
262 """
263 return create_instance_from_response("BibleChapter", raw_data)
265 def process_bible_version(self, raw_data: dict[str, Any]) -> Any:
266 """Process raw Bible version data using dynamic Pydantic models.
268 Args:
269 raw_data: Raw Bible version data from API
271 Returns:
272 Dynamically created Version object
273 """
274 return create_instance_from_response("Version", raw_data)
276 def process_bible_versions(self, raw_data: dict[str, Any]) -> dict[str, Any]:
277 """Process raw Bible versions data using dynamic Pydantic models.
279 Args:
280 raw_data: Raw Bible versions data from API
282 Returns:
283 Processed versions data with dynamically created objects
284 """
285 processed_data = raw_data.copy()
287 # Process versions list if it exists
288 if "versions" in processed_data:
289 versions = []
290 for item in processed_data["versions"]:
291 version = create_instance_from_response("Version", item)
292 versions.append(version)
293 processed_data["versions"] = versions
295 # Create the main BibleVersions model
296 return create_instance_from_response("BibleVersions", processed_data)
298 def process_audio_chapter(
299 self, raw_data: dict[str, Any] | list[dict[str, Any]]
300 ) -> Any:
301 """Process raw audio chapter data using dynamic Pydantic models.
303 Args:
304 raw_data: Raw audio chapter data from API (dict or list of dicts)
306 Returns:
307 AudioChapter object or list of AudioChapter objects
308 """
309 # Handle list input
310 if isinstance(raw_data, list):
311 chapters = []
312 for item in raw_data:
313 chapter = create_instance_from_response("AudioChapter", item)
314 chapters.append(chapter)
315 return chapters
316 # Handle dict input
317 return create_instance_from_response("AudioChapter", raw_data)
319 def process_audio_version(self, raw_data: dict[str, Any]) -> Any:
320 """Process raw audio version data using dynamic Pydantic models.
322 Args:
323 raw_data: Raw audio version data from API
325 Returns:
326 Dynamically created AudioVersion object
327 """
328 return create_instance_from_response("AudioVersion", raw_data)
330 def process_send_friend_request(self, raw_data: dict[str, Any]) -> dict[str, Any]:
331 """Process raw friend request data using dynamic Pydantic models.
333 Args:
334 raw_data: Raw friend request data from API
336 Returns:
337 Processed friend request data with dynamically created objects
338 """
339 return create_instance_from_response("FriendshipRequest", raw_data)
341 def process_bible_configuration(self, raw_data: dict[str, Any]) -> dict[str, Any]:
342 """Process raw Bible configuration data using dynamic Pydantic models.
344 Args:
345 raw_data: Raw Bible configuration data from API
347 Returns:
348 Processed configuration data with dynamically created objects
349 """
350 return create_instance_from_response("BibleConfiguration", raw_data)
352 def process_recommended_languages(self, raw_data: dict[str, Any]) -> dict[str, Any]:
353 """Process recommended languages data using dynamic Pydantic models.
355 Args:
356 raw_data: Raw recommended languages data from API
358 Returns:
359 Processed languages data with dynamically created objects
360 """
361 return create_instance_from_response("RecommendedLanguages", raw_data)
363 def process_search_plans(self, raw_data: dict[str, Any]) -> dict[str, Any]:
364 """Process raw plan search results using dynamic Pydantic models.
366 Args:
367 raw_data: Raw plan search results data from API
369 Returns:
370 Processed search results with dynamically created objects
371 """
372 return create_instance_from_response("PlanSearch", raw_data)
374 def process_search_users(self, raw_data: dict[str, Any]) -> dict[str, Any]:
375 """Process raw user search results using dynamic Pydantic models.
377 Args:
378 raw_data: Raw user search results data from API
380 Returns:
381 Processed search results with dynamically created objects
382 """
383 return create_instance_from_response("UserSearch", raw_data)
385 def process_videos(self, raw_data: dict[str, Any]) -> dict[str, Any]:
386 """Process raw videos data using dynamic Pydantic models.
388 Args:
389 raw_data: Raw videos data from API
391 Returns:
392 Processed videos data with dynamically created objects
393 """
394 return create_instance_from_response("Videos", raw_data)
396 def process_video_details(self, raw_data: dict[str, Any]) -> dict[str, Any]:
397 """Process raw video details data using dynamic Pydantic models.
399 Args:
400 raw_data: Raw video details data from API
402 Returns:
403 Processed video details with dynamically created objects
404 """
405 return create_instance_from_response("VideoDetails", raw_data)
407 def process_image_upload_url(self, raw_data: dict[str, Any]) -> dict[str, Any]:
408 """Process raw image upload URL data using dynamic Pydantic models.
410 Args:
411 raw_data: Raw image upload URL data from API
413 Returns:
414 Processed upload URL data with dynamically created objects
415 """
416 return create_instance_from_response("ImageUploadUrl", raw_data)
418 def process_search_events(self, raw_data: dict[str, Any]) -> dict[str, Any]:
419 """Process raw event search results using dynamic Pydantic models.
421 Args:
422 raw_data: Raw event search results data from API
424 Returns:
425 Processed search results with dynamically created objects
426 """
427 return create_instance_from_response("EventSearch", raw_data)
429 def process_event_details(self, raw_data: dict[str, Any]) -> dict[str, Any]:
430 """Process raw event details data using dynamic Pydantic models.
432 Args:
433 raw_data: Raw event details data from API
435 Returns:
436 Processed event details with dynamically created objects
437 """
438 return create_instance_from_response("EventDetails", raw_data)
440 def process_saved_events(self, raw_data: dict[str, Any]) -> dict[str, Any]:
441 """Process raw saved events data using dynamic Pydantic models.
443 Args:
444 raw_data: Raw saved events data from API
446 Returns:
447 Processed saved events data with dynamically created objects
448 """
449 return create_instance_from_response("SavedEvents", raw_data)
451 def process_moments_list(self, raw_data: dict[str, Any]) -> dict[str, Any]:
452 """Process raw moments list data using dynamic Pydantic models.
454 Args:
455 raw_data: Raw moments list data from API
457 Returns:
458 Processed moments list with dynamically created objects
459 """
460 return create_instance_from_response("MomentsList", raw_data)
462 def process_moment_details(self, raw_data: dict[str, Any]) -> dict[str, Any]:
463 """Process raw moment details data using dynamic Pydantic models.
465 Args:
466 raw_data: Raw moment details data from API
468 Returns:
469 Processed moment details with dynamically created objects
470 """
471 return create_instance_from_response("MomentDetails", raw_data)
473 def process_moment_colors(self, raw_data: dict[str, Any]) -> dict[str, Any]:
474 """Process raw moment colors data using dynamic Pydantic models.
476 Args:
477 raw_data: Raw moment colors data from API
479 Returns:
480 Processed colors data with dynamically created objects
481 """
482 if isinstance(raw_data, list):
483 raw_data = {"colors": raw_data}
484 return create_instance_from_response("MomentColors", raw_data)
486 def process_moment_labels(self, raw_data: dict[str, Any]) -> dict[str, Any]:
487 """Process raw moment labels data using dynamic Pydantic models.
489 Args:
490 raw_data: Raw moment labels data from API
492 Returns:
493 Processed labels data with dynamically created objects
494 """
495 if isinstance(raw_data, list):
496 raw_data = {"labels": raw_data}
497 return create_instance_from_response("MomentLabels", raw_data)
499 def process_verse_colors(self, raw_data: dict[str, Any]) -> dict[str, Any]:
500 """Process raw verse colors data using dynamic Pydantic models.
502 Args:
503 raw_data: Raw verse colors data from API
505 Returns:
506 Processed verse colors data with dynamically created objects
507 """
508 return create_instance_from_response("VerseColors", raw_data)
510 def process_moments_configuration(self, raw_data: dict[str, Any]) -> dict[str, Any]:
511 """Process moments configuration data using dynamic Pydantic models.
513 Args:
514 raw_data: Raw moments configuration data from API
516 Returns:
517 Processed configuration data with dynamically created objects
518 """
519 return create_instance_from_response("MomentsConfiguration", raw_data)
521 def process_themes(self, raw_data: dict[str, Any]) -> dict[str, Any]:
522 """Process raw themes data using dynamic Pydantic models.
524 Args:
525 raw_data: Raw themes data from API
527 Returns:
528 Processed themes data with dynamically created objects
529 """
530 return create_instance_from_response("Themes", raw_data)
532 def process_theme_description(self, raw_data: dict[str, Any]) -> dict[str, Any]:
533 """Process raw theme description data using dynamic Pydantic models.
535 Args:
536 raw_data: Raw theme description data from API
538 Returns:
539 Processed theme description with dynamically created objects
540 """
541 return create_instance_from_response("ThemeDescription", raw_data)
543 def process_event_configuration(self, raw_data: dict[str, Any]) -> dict[str, Any]:
544 """Process raw event configuration data using dynamic Pydantic models.
546 Args:
547 raw_data: Raw event configuration data from API
549 Returns:
550 Processed configuration data with dynamically created objects
551 """
552 return create_instance_from_response("EventConfiguration", raw_data)
554 def process_all_saved_event_ids(self, raw_data: dict[str, Any]) -> dict[str, Any]:
555 """Process raw all saved event IDs data using dynamic Pydantic models.
557 Args:
558 raw_data: Raw all saved event IDs data from API
560 Returns:
561 Processed event IDs data with dynamically created objects
562 """
563 return create_instance_from_response("AllSavedEventIds", raw_data)
565 def process_save_event(self, raw_data: dict[str, Any]) -> dict[str, Any]:
566 """Process raw save event response using dynamic Pydantic models.
568 Args:
569 raw_data: Raw save event response data from API
571 Returns:
572 Processed save event response with dynamically created objects
573 """
574 return create_instance_from_response("SaveEvent", raw_data)
576 def process_delete_saved_event(self, raw_data: dict[str, Any]) -> dict[str, Any]:
577 """Process raw delete saved event response using dynamic Pydantic models.
579 Args:
580 raw_data: Raw delete saved event response data from API
582 Returns:
583 Processed delete response with dynamically created objects
584 """
585 return create_instance_from_response("DeleteSavedEvent", raw_data)
587 def process_create_moment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
588 """Process raw create moment response using dynamic Pydantic models.
590 Args:
591 raw_data: Raw create moment response data from API
593 Returns:
594 Processed create moment response with dynamically created objects
595 """
596 return create_instance_from_response("CreateMomentResponse", raw_data)
598 def process_update_moment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
599 """Process raw update moment response using dynamic Pydantic models.
601 Args:
602 raw_data: Raw update moment response data from API
604 Returns:
605 Processed update moment response with dynamically created objects
606 """
607 return create_instance_from_response("UpdateMomentResponse", raw_data)
609 def process_delete_moment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
610 """Process raw delete moment response using dynamic Pydantic models.
612 Args:
613 raw_data: Raw delete moment response data from API
615 Returns:
616 Processed delete moment response with dynamically created objects
617 """
618 return create_instance_from_response("DeleteMomentResponse", raw_data)
620 def process_hide_verse_colors(self, raw_data: dict[str, Any]) -> dict[str, Any]:
621 """Process raw hide verse colors response using dynamic Pydantic models.
623 Args:
624 raw_data: Raw hide verse colors response data from API
626 Returns:
627 Processed hide verse colors response with dynamically created objects
628 """
629 return create_instance_from_response("HideVerseColors", raw_data)
631 def process_create_comment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
632 """Process raw create comment response using dynamic Pydantic models.
634 Args:
635 raw_data: Raw create comment response data from API
637 Returns:
638 Processed create comment response with dynamically created objects
639 """
640 return create_instance_from_response("CreateComment", raw_data)
642 def process_delete_comment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
643 """Process raw delete comment response using dynamic Pydantic models.
645 Args:
646 raw_data: Raw delete comment response data from API
648 Returns:
649 Processed delete comment response with dynamically created objects
650 """
651 return create_instance_from_response("DeleteComment", raw_data)
653 def process_like_moment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
654 """Process raw like moment response using dynamic Pydantic models.
656 Args:
657 raw_data: Raw like moment response data from API
659 Returns:
660 Processed like moment response with dynamically created objects
661 """
662 return create_instance_from_response("LikeMoment", raw_data)
664 def process_unlike_moment(self, raw_data: dict[str, Any]) -> dict[str, Any]:
665 """Process raw unlike moment response using dynamic Pydantic models.
667 Args:
668 raw_data: Raw unlike moment response data from API
670 Returns:
671 Processed unlike moment response with dynamically created objects
672 """
673 return create_instance_from_response("UnlikeMoment", raw_data)
675 def process_register_device(self, raw_data: dict[str, Any]) -> dict[str, Any]:
676 """Process raw register device response using dynamic Pydantic models.
678 Args:
679 raw_data: Raw register device response data from API
681 Returns:
682 Processed register device response with dynamically created objects
683 """
684 return create_instance_from_response("RegisterDevice", raw_data)
686 def process_unregister_device(self, raw_data: dict[str, Any]) -> dict[str, Any]:
687 """Process raw unregister device response using dynamic Pydantic models.
689 Args:
690 raw_data: Raw unregister device response data from API
692 Returns:
693 Processed unregister device response with dynamically created objects
694 """
695 return create_instance_from_response("UnregisterDevice", raw_data)
697 def process_add_theme(self, raw_data: dict[str, Any]) -> dict[str, Any]:
698 """Process raw add theme response using dynamic Pydantic models.
700 Args:
701 raw_data: Raw add theme response data from API
703 Returns:
704 Processed add theme response with dynamically created objects
705 """
706 return create_instance_from_response("AddTheme", raw_data)
708 def process_remove_theme(self, raw_data: dict[str, Any]) -> dict[str, Any]:
709 """Process raw remove theme response using dynamic Pydantic models.
711 Args:
712 raw_data: Raw remove theme response data from API
714 Returns:
715 Processed remove theme response with dynamically created objects
716 """
717 return create_instance_from_response("RemoveTheme", raw_data)
719 def process_set_theme(self, raw_data: dict[str, Any]) -> dict[str, Any]:
720 """Process raw set theme response using dynamic Pydantic models.
722 Args:
723 raw_data: Raw set theme response data from API
725 Returns:
726 Processed set theme response with dynamically created objects
727 """
728 return create_instance_from_response("SetTheme", raw_data)