Coverage for youversion/clients/sync_client.py: 100%

208 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-26 11:31 +0100

1"""Synchronous client for YouVersion Bible API.""" 

2 

3import asyncio 

4from typing import Any 

5 

6from ..core.base_client import BaseClient 

7from ..models.moments import CreateMoment 

8 

9 

10class SyncClient(BaseClient): 

11 """Synchronous wrapper for BaseClient.""" 

12 

13 def __init__(self, username: str | None = None, password: str | None = None): 

14 """Initialize sync client. 

15 

16 Args: 

17 username: Username for authentication 

18 password: Password for authentication 

19 """ 

20 # Call BaseClient.__init__ with explicit self for easier test patching 

21 BaseClient.__init__(self, username, password) 

22 # Preserve provided username for tests where BaseClient is mocked 

23 self._username = username 

24 self._loop = None 

25 self._loop_owner = False 

26 

27 def _get_loop(self) -> asyncio.AbstractEventLoop: 

28 """Get or create an event loop for running async operations. 

29 

30 Returns: 

31 Event loop for async operations 

32 

33 Raises: 

34 RuntimeError: If called within an async context 

35 """ 

36 try: 

37 # Try to get the current event loop 

38 asyncio.get_running_loop() 

39 # If we're already in an async context, we can't run sync ops 

40 raise RuntimeError( 

41 "Cannot use synchronous SyncClient within an async context. " 

42 "Use AsyncClient instead or run this code outside of an " 

43 "async function." 

44 ) 

45 except RuntimeError as e: 

46 if "Cannot use synchronous SyncClient" in str(e): 

47 raise 

48 # No running loop, create a new one 

49 if self._loop is None or self._loop.is_closed(): 

50 self._loop = asyncio.new_event_loop() 

51 asyncio.set_event_loop(self._loop) 

52 self._loop_owner = True 

53 return self._loop 

54 

55 def _run_async(self, coro): 

56 """Run an async coroutine in the event loop. 

57 

58 Args: 

59 coro: Async coroutine to run 

60 

61 Returns: 

62 Result of the coroutine 

63 """ 

64 loop = self._get_loop() 

65 

66 # Ensure the client is initialized 

67 async def _ensure_client_initialized(): 

68 await self._ensure_authenticated() 

69 return await coro 

70 

71 return loop.run_until_complete(_ensure_client_initialized()) 

72 

73 def __enter__(self): 

74 """Context manager entry.""" 

75 self._run_async(self.__aenter__()) 

76 return self 

77 

78 def __exit__(self, exc_type, exc_val, exc_tb): 

79 """Context manager exit.""" 

80 self._run_async(self.__aexit__(exc_type, exc_val, exc_tb)) 

81 if self._loop_owner and self._loop and not self._loop.is_closed(): 

82 self._loop.close() 

83 

84 async def __aenter__(self): 

85 """Async context manager entry.""" 

86 await self._ensure_authenticated() 

87 return self 

88 

89 async def __aexit__(self, exc_type, exc_val, exc_tb): 

90 """Async context manager exit.""" 

91 await super().close() 

92 

93 def close(self): 

94 """Manually close the client.""" 

95 

96 async def _close_async(): 

97 await BaseClient.close(self) 

98 

99 # Run close coroutine depending on loop ownership 

100 if self._loop and not self._loop.is_closed(): 

101 self._loop.run_until_complete(_close_async()) 

102 elif self._loop_owner: 

103 # Create and assign a loop we own 

104 self._loop = asyncio.new_event_loop() 

105 asyncio.set_event_loop(self._loop) 

106 self._loop.run_until_complete(_close_async()) 

107 else: 

108 # Create a temporary loop but do not close it (not owner) 

109 temp_loop = asyncio.new_event_loop() 

110 asyncio.set_event_loop(temp_loop) 

111 temp_loop.run_until_complete(_close_async()) 

112 

113 # Close the stored loop only if we own it 

114 if self._loop_owner and self._loop and not self._loop.is_closed(): 

115 self._loop.close() 

116 

117 @property 

118 def username(self) -> str | None: 

119 """Get the username, tolerant of BaseClient being mocked in tests.""" 

120 auth = getattr(self, "_authenticator", None) 

121 if auth is not None and hasattr(auth, "username"): 

122 return auth.username 

123 return getattr(self, "_username", None) 

124 

125 # Synchronous wrapper methods 

126 def moments(self, page: int = 1) -> list[Any]: 

127 """Get moments. 

128 

129 Args: 

130 page: Page number 

131 

132 Returns: 

133 List of dynamically created Moment objects conforming to 

134 MomentProtocol 

135 """ 

136 return self._run_async(super().moments(page)) 

137 

138 def highlights(self, page: int = 1) -> list[Any]: 

139 """Get highlights. 

140 

141 Args: 

142 page: Page number 

143 

144 Returns: 

145 List of Highlight objects 

146 """ 

147 return self._run_async(super().highlights(page)) 

148 

149 def verse_of_the_day(self, day: int | None = None) -> Any: 

150 """Get verse of the day. 

151 

152 Args: 

153 day: Specific day number (optional) 

154 

155 Returns: 

156 Votd object 

157 """ 

158 return self._run_async(super().verse_of_the_day(day)) 

159 

160 def notes(self, page: int = 1) -> list[Any]: 

161 """Get notes. 

162 

163 Args: 

164 page: Page number 

165 

166 Returns: 

167 List of note data 

168 """ 

169 return self._run_async(super().notes(page)) 

170 

171 def bookmarks(self, page: int = 1) -> list[Any]: 

172 """Get bookmarks. 

173 

174 Args: 

175 page: Page number 

176 

177 Returns: 

178 List of bookmark data 

179 """ 

180 return self._run_async(super().bookmarks(page)) 

181 

182 def my_images(self, page: int = 1) -> list[Any]: 

183 """Get images. 

184 

185 Args: 

186 page: Page number 

187 

188 Returns: 

189 List of image data 

190 """ 

191 return self._run_async(super().my_images(page)) 

192 

193 def plan_progress(self, page: int = 1) -> list[Any]: 

194 """Get plan progress. 

195 

196 Args: 

197 page: Page number 

198 

199 Returns: 

200 List of plan progress data 

201 """ 

202 return self._run_async(super().plan_progress(page)) 

203 

204 def plan_subscriptions(self, page: int = 1) -> list[Any]: 

205 """Get plan subscriptions. 

206 

207 Args: 

208 page: Page number 

209 

210 Returns: 

211 List of plan subscription data 

212 """ 

213 return self._run_async(super().plan_subscriptions(page)) 

214 

215 def plan_completions(self, page: int = 1) -> list[Any]: 

216 """Get plan completions. 

217 

218 Args: 

219 page: Page number 

220 

221 Returns: 

222 List of plan completion data 

223 """ 

224 return self._run_async(super().plan_completions(page)) 

225 

226 def convert_note_to_md(self) -> list[Any]: 

227 """Convert notes to markdown. 

228 

229 Returns: 

230 List of converted note data 

231 """ 

232 return self._run_async(super().convert_note_to_md()) 

233 

234 def send_friend_request(self, user_id: int) -> dict[str, Any]: 

235 """Send a friend request to a user. 

236 

237 Args: 

238 user_id: User ID to send friend request to 

239 

240 Returns: 

241 Friend request response data with incoming and outgoing lists 

242 """ 

243 return self._run_async(super().send_friend_request(user_id)) 

244 

245 # Bible API methods 

246 def get_bible_configuration(self) -> dict[str, Any]: 

247 """Get Bible configuration. 

248 

249 Returns: 

250 Bible configuration data 

251 """ 

252 return self._run_async(super().get_bible_configuration()) 

253 

254 def get_bible_versions( 

255 self, language_tag: str = "eng", version_type: str = "all" 

256 ) -> dict[str, Any]: 

257 """Get Bible versions for a language. 

258 

259 Args: 

260 language_tag: Language tag (e.g., 'eng', 'spa') 

261 version_type: Type of versions ('all', 'text', 'audio') 

262 

263 Returns: 

264 Bible versions data 

265 """ 

266 return self._run_async(super().get_bible_versions(language_tag, version_type)) 

267 

268 def get_bible_version(self, version_id: int) -> dict[str, Any]: 

269 """Get specific Bible version details. 

270 

271 Args: 

272 version_id: Version ID 

273 

274 Returns: 

275 Bible version data 

276 """ 

277 return self._run_async(super().get_bible_version(version_id)) 

278 

279 def get_bible_chapter( 

280 self, 

281 reference: str, 

282 version_id: int = 1, 

283 ) -> dict[str, Any]: 

284 """Get Bible chapter content. 

285 

286 Args: 

287 reference: USFM reference (e.g., 'GEN.1') 

288 version_id: Version ID 

289 

290 Returns: 

291 Chapter content data 

292 """ 

293 return self._run_async(super().get_bible_chapter(reference, version_id)) 

294 

295 def get_recommended_languages(self, country: str = "US") -> dict[str, Any]: 

296 """Get recommended languages for a country. 

297 

298 Args: 

299 country: Country code (e.g., 'US', 'CA') 

300 

301 Returns: 

302 Recommended languages data 

303 """ 

304 return self._run_async(super().get_recommended_languages(country)) 

305 

306 # Audio Bible API methods 

307 def get_audio_chapter( 

308 self, 

309 reference: str, 

310 version_id: int = 1, 

311 ) -> dict[str, Any]: 

312 """Get audio chapter information. 

313 

314 Args: 

315 reference: USFM reference (e.g., 'GEN.1') 

316 version_id: Audio version ID 

317 

318 Returns: 

319 Audio chapter data 

320 """ 

321 return self._run_async(super().get_audio_chapter(reference, version_id)) 

322 

323 def get_audio_version(self, audio_id: int) -> dict[str, Any]: 

324 """Get audio version details. 

325 

326 Args: 

327 audio_id: Audio version ID 

328 

329 Returns: 

330 Audio version data 

331 """ 

332 return self._run_async(super().get_audio_version(audio_id)) 

333 

334 # Search API methods 

335 def search_bible( 

336 self, 

337 query: str, 

338 version_id: int | None = None, 

339 book: str | None = None, 

340 page: int = 1, 

341 ) -> dict[str, Any]: 

342 """Search Bible text. 

343 

344 Args: 

345 query: Search query 

346 version_id: Version ID (optional) 

347 book: Book filter (optional) 

348 page: Page number 

349 

350 Returns: 

351 Search results data 

352 """ 

353 return self._run_async(super().search_bible(query, version_id, book, page)) 

354 

355 def search_plans( 

356 self, query: str, language_tag: str = "en", page: int = 1 

357 ) -> dict[str, Any]: 

358 """Search reading plans. 

359 

360 Args: 

361 query: Search query 

362 language_tag: Language tag 

363 page: Page number 

364 

365 Returns: 

366 Plan search results data 

367 """ 

368 return self._run_async(super().search_plans(query, language_tag, page)) 

369 

370 def search_users( 

371 self, query: str, language_tag: str = "eng", page: int = 1 

372 ) -> dict[str, Any]: 

373 """Search users. 

374 

375 Args: 

376 query: Search query 

377 language_tag: Language tag 

378 page: Page number 

379 

380 Returns: 

381 User search results data 

382 """ 

383 return self._run_async(super().search_users(query, language_tag, page)) 

384 

385 # Videos API methods 

386 def get_videos(self, language_tag: str = "eng") -> dict[str, Any]: 

387 """Get videos list. 

388 

389 Args: 

390 language_tag: Language tag 

391 

392 Returns: 

393 Videos data 

394 """ 

395 return self._run_async(super().get_videos(language_tag)) 

396 

397 def get_video_details(self, video_id: int) -> dict[str, Any]: 

398 """Get video details. 

399 

400 Args: 

401 video_id: Video ID 

402 

403 Returns: 

404 Video details data 

405 """ 

406 return self._run_async(super().get_video_details(video_id)) 

407 

408 # Badges API methods 

409 def badges(self, page: int = 1) -> list[Any]: 

410 """Get badges. 

411 

412 Args: 

413 page: Page number 

414 

415 Returns: 

416 List of badge data 

417 """ 

418 return self._run_async(super().badges(page)) 

419 

420 # Images API methods 

421 def get_images( 

422 self, reference: str, language_tag: str = "eng", page: int = 1 

423 ) -> dict[str, Any]: 

424 """Get images for a reference. 

425 

426 Args: 

427 reference: USFM reference 

428 language_tag: Language tag 

429 page: Page number 

430 

431 Returns: 

432 Images data 

433 """ 

434 return self._run_async(super().get_images(reference, language_tag, page)) 

435 

436 def get_image_upload_url(self) -> dict[str, Any]: 

437 """Get image upload URL and parameters. 

438 

439 Returns: 

440 Upload URL data 

441 """ 

442 return self._run_async(super().get_image_upload_url()) 

443 

444 # Events API methods 

445 def search_events( 

446 self, 

447 query: str, 

448 latitude: float | None = None, 

449 longitude: float | None = None, 

450 page: int = 1, 

451 ) -> dict[str, Any]: 

452 """Search events. 

453 

454 Args: 

455 query: Search query 

456 latitude: Latitude (optional) 

457 longitude: Longitude (optional) 

458 page: Page number 

459 

460 Returns: 

461 Event search results data 

462 """ 

463 return self._run_async(super().search_events(query, latitude, longitude, page)) 

464 

465 def get_event_details(self, event_id: int) -> dict[str, Any]: 

466 """Get event details. 

467 

468 Args: 

469 event_id: Event ID 

470 

471 Returns: 

472 Event details data 

473 """ 

474 return self._run_async(super().get_event_details(event_id)) 

475 

476 def get_saved_events(self, page: int = 1) -> dict[str, Any]: 

477 """Get saved events. 

478 

479 Args: 

480 page: Page number 

481 

482 Returns: 

483 Saved events data 

484 """ 

485 return self._run_async(super().get_saved_events(page)) 

486 

487 def save_event( 

488 self, event_id: int, comments: dict[str, Any] | None = None 

489 ) -> dict[str, Any]: 

490 """Save event. 

491 

492 Args: 

493 event_id: Event ID 

494 comments: Comments (optional) 

495 

496 Returns: 

497 Save result data 

498 """ 

499 return self._run_async(super().save_event(event_id, comments)) 

500 

501 def delete_saved_event(self, event_id: int) -> dict[str, Any]: 

502 """Delete saved event. 

503 

504 Args: 

505 event_id: Event ID 

506 

507 Returns: 

508 Delete result data 

509 """ 

510 return self._run_async(super().delete_saved_event(event_id)) 

511 

512 def get_all_saved_event_ids(self) -> dict[str, Any]: 

513 """Get all saved event IDs. 

514 

515 Returns: 

516 All saved event IDs data 

517 """ 

518 return self._run_async(super().get_all_saved_event_ids()) 

519 

520 def get_event_configuration(self) -> dict[str, Any]: 

521 """Get event configuration. 

522 

523 Returns: 

524 Event configuration data 

525 """ 

526 return self._run_async(super().get_event_configuration()) 

527 

528 # Moments API methods 

529 def get_moments( 

530 self, 

531 page: int = 1, 

532 user_id: int | None = None, 

533 kind: str | None = None, 

534 version_id: int | None = None, 

535 usfm: str | None = None, 

536 ) -> dict[str, Any]: 

537 """Get moments list. 

538 

539 Args: 

540 page: Page number 

541 user_id: User ID (optional) 

542 kind: Kind of moment (optional) 

543 version_id: Bible version ID (optional) 

544 usfm: USFM reference (optional) 

545 

546 Returns: 

547 Moments data 

548 """ 

549 return self._run_async( 

550 super().get_moments(page, user_id, kind, version_id, usfm) 

551 ) 

552 

553 def get_moment_details(self, moment_id: int) -> dict[str, Any]: 

554 """Get moment details. 

555 

556 Args: 

557 moment_id: Moment ID 

558 

559 Returns: 

560 Moment details data 

561 """ 

562 return self._run_async(super().get_moment_details(moment_id)) 

563 

564 def create_moment(self, data: CreateMoment | dict[str, Any]) -> dict[str, Any]: 

565 """Create a new moment. 

566 

567 Args: 

568 data: Moment data as CreateMoment model or dict 

569 

570 Returns: 

571 Created moment data 

572 """ 

573 return self._run_async(super().create_moment(data)) 

574 

575 def update_moment(self, data: dict[str, Any]) -> dict[str, Any]: 

576 """Update an existing moment. 

577 

578 Args: 

579 data: Moment data 

580 

581 Returns: 

582 Updated moment data 

583 """ 

584 return self._run_async(super().update_moment(data)) 

585 

586 def delete_moment(self, moment_id: int) -> dict[str, Any]: 

587 """Delete a moment. 

588 

589 Args: 

590 moment_id: Moment ID 

591 

592 Returns: 

593 Delete result data 

594 """ 

595 return self._run_async(super().delete_moment(moment_id)) 

596 

597 def get_moment_colors(self) -> dict[str, Any]: 

598 """Get available highlight colors. 

599 

600 Returns: 

601 Colors data 

602 """ 

603 return self._run_async(super().get_moment_colors()) 

604 

605 def get_moment_labels(self) -> dict[str, Any]: 

606 """Get moment labels. 

607 

608 Returns: 

609 Labels data 

610 """ 

611 return self._run_async(super().get_moment_labels()) 

612 

613 def get_verse_colors(self, usfm: str, version_id: int) -> dict[str, Any]: 

614 """Get verse highlight colors. 

615 

616 Args: 

617 usfm: USFM reference 

618 version_id: Bible version ID 

619 

620 Returns: 

621 Verse colors data 

622 """ 

623 return self._run_async(super().get_verse_colors(usfm, version_id)) 

624 

625 def hide_verse_colors(self, data: dict[str, Any]) -> dict[str, Any]: 

626 """Hide verse highlight colors. 

627 

628 Args: 

629 data: Hide colors data 

630 

631 Returns: 

632 Hide result data 

633 """ 

634 return self._run_async(super().hide_verse_colors(data)) 

635 

636 def get_moments_configuration(self) -> dict[str, Any]: 

637 """Get moments configuration. 

638 

639 Returns: 

640 Moments configuration data 

641 """ 

642 return self._run_async(super().get_moments_configuration()) 

643 

644 # Comments API methods 

645 def create_comment(self, moment_id: int, comment: str) -> dict[str, Any]: 

646 """Create a comment on a moment. 

647 

648 Args: 

649 moment_id: Moment ID 

650 comment: Comment text 

651 

652 Returns: 

653 Created comment data 

654 """ 

655 return self._run_async(super().create_comment(moment_id, comment)) 

656 

657 def delete_comment(self, comment_id: int) -> dict[str, Any]: 

658 """Delete a comment. 

659 

660 Args: 

661 comment_id: Comment ID 

662 

663 Returns: 

664 Delete result data 

665 """ 

666 return self._run_async(super().delete_comment(comment_id)) 

667 

668 # Likes API methods 

669 def like_moment(self, moment_id: int) -> dict[str, Any]: 

670 """Like a moment. 

671 

672 Args: 

673 moment_id: Moment ID 

674 

675 Returns: 

676 Like result data 

677 """ 

678 return self._run_async(super().like_moment(moment_id)) 

679 

680 def unlike_moment(self, moment_id: int) -> dict[str, Any]: 

681 """Unlike a moment. 

682 

683 Args: 

684 moment_id: Moment ID 

685 

686 Returns: 

687 Unlike result data 

688 """ 

689 return self._run_async(super().unlike_moment(moment_id)) 

690 

691 # Messaging API methods 

692 def register_device( 

693 self, 

694 device_id: str, 

695 device_type: str = "android", 

696 user_id: int | None = None, 

697 old_device_id: str | None = None, 

698 tags: str | None = None, 

699 ) -> dict[str, Any]: 

700 """Register device for push notifications. 

701 

702 Args: 

703 device_id: Device ID 

704 device_type: Device type 

705 user_id: User ID (optional) 

706 old_device_id: Previous device ID (optional) 

707 tags: Device tags (optional) 

708 

709 Returns: 

710 Registration result data 

711 """ 

712 return self._run_async( 

713 super().register_device( 

714 device_id, device_type, user_id, old_device_id, tags 

715 ) 

716 ) 

717 

718 def unregister_device(self, device_id: str) -> dict[str, Any]: 

719 """Unregister device from push notifications. 

720 

721 Args: 

722 device_id: Device ID 

723 

724 Returns: 

725 Unregistration result data 

726 """ 

727 return self._run_async(super().unregister_device(device_id)) 

728 

729 # Themes API methods 

730 def get_themes(self, page: int = 1, language_tag: str = "eng") -> dict[str, Any]: 

731 """Get available themes. 

732 

733 Args: 

734 page: Page number 

735 language_tag: Language tag 

736 

737 Returns: 

738 Themes data 

739 """ 

740 return self._run_async(super().get_themes(page, language_tag)) 

741 

742 def add_theme( 

743 self, 

744 theme_id: int, 

745 available_locales: list[str], 

746 colors: dict[str, Any], 

747 cta_urls: dict[str, Any], 

748 msgid_suffix: str, 

749 version_ids: dict[str, int], 

750 ) -> dict[str, Any]: 

751 """Add a theme to user's collection. 

752 

753 Args: 

754 theme_id: Theme ID 

755 available_locales: List of available locale codes 

756 colors: Theme colors dictionary 

757 cta_urls: Call-to-action URLs dictionary 

758 msgid_suffix: Message ID suffix 

759 version_ids: Dictionary of version IDs by locale code 

760 

761 Returns: 

762 Add result data 

763 """ 

764 return self._run_async( 

765 super().add_theme( 

766 theme_id, available_locales, colors, cta_urls, msgid_suffix, version_ids 

767 ) 

768 ) 

769 

770 def remove_theme(self, theme_id: int) -> dict[str, Any]: 

771 """Remove a theme from user's collection. 

772 

773 Args: 

774 theme_id: Theme ID 

775 

776 Returns: 

777 Remove result data 

778 """ 

779 return self._run_async(super().remove_theme(theme_id)) 

780 

781 def set_theme( 

782 self, theme_id: int, previous_theme_id: int | None = None 

783 ) -> dict[str, Any]: 

784 """Set active theme. 

785 

786 Args: 

787 theme_id: Theme ID 

788 previous_theme_id: Previous theme ID (optional) 

789 

790 Returns: 

791 Set result data 

792 """ 

793 return self._run_async(super().set_theme(theme_id, previous_theme_id)) 

794 

795 def get_theme_description( 

796 self, theme_id: int, language_tag: str = "eng" 

797 ) -> dict[str, Any]: 

798 """Get theme description. 

799 

800 Args: 

801 theme_id: Theme ID 

802 language_tag: Language tag 

803 

804 Returns: 

805 Theme description data 

806 """ 

807 return self._run_async(super().get_theme_description(theme_id, language_tag)) 

808 

809 # Friends API methods 

810 def get_friends(self, page: int = 1) -> dict[str, Any]: 

811 """Get the authenticated user's friends list.""" 

812 return self._run_async(super().get_friends(page)) 

813 

814 def get_all_friends(self, page: int = 1) -> dict[str, Any]: 

815 """Get all friends including extended metadata.""" 

816 return self._run_async(super().get_all_friends(page)) 

817 

818 def delete_friend(self, user_id: int) -> dict[str, Any]: 

819 """Remove a friend by user ID.""" 

820 return self._run_async(super().delete_friend(user_id)) 

821 

822 def get_incoming_friend_requests(self, page: int = 1) -> dict[str, Any]: 

823 """Get incoming friend requests.""" 

824 return self._run_async(super().get_incoming_friend_requests(page)) 

825 

826 def get_friend_suggestions(self, page: int = 1) -> dict[str, Any]: 

827 """Get friend suggestions.""" 

828 return self._run_async(super().get_friend_suggestions(page)) 

829 

830 def accept_friend_request(self, user_id: int) -> dict[str, Any]: 

831 """Accept an incoming friend request.""" 

832 return self._run_async(super().accept_friend_request(user_id)) 

833 

834 def decline_friend_request(self, user_id: int) -> dict[str, Any]: 

835 """Decline an incoming friend request.""" 

836 return self._run_async(super().decline_friend_request(user_id)) 

837 

838 def dismiss_friend_suggestion(self, user_id: int) -> dict[str, Any]: 

839 """Dismiss a friend suggestion.""" 

840 return self._run_async(super().dismiss_friend_suggestion(user_id)) 

841 

842 def get_facebook_friends(self, page: int = 1) -> dict[str, Any]: 

843 """Get Facebook-linked friend suggestions.""" 

844 return self._run_async(super().get_facebook_friends(page)) 

845 

846 def sync_friendship_contacts( 

847 self, contacts: list[dict[str, Any]] 

848 ) -> dict[str, Any]: 

849 """Upload contacts for friend matching.""" 

850 return self._run_async(super().sync_friendship_contacts(contacts)) 

851 

852 # Notifications API methods 

853 def get_notifications(self, page: int = 1) -> dict[str, Any]: 

854 """Get notification feed items.""" 

855 return self._run_async(super().get_notifications(page)) 

856 

857 def get_notification_settings(self) -> dict[str, Any]: 

858 """Get notification settings.""" 

859 return self._run_async(super().get_notification_settings()) 

860 

861 def update_notification_settings(self, data: dict[str, Any]) -> dict[str, Any]: 

862 """Update notification settings.""" 

863 return self._run_async(super().update_notification_settings(data)) 

864 

865 def update_notifications(self, data: dict[str, Any]) -> dict[str, Any]: 

866 """Update notification read state.""" 

867 return self._run_async(super().update_notifications(data)) 

868 

869 def get_votd_notification_settings(self) -> dict[str, Any]: 

870 """Get VOTD notification settings.""" 

871 return self._run_async(super().get_votd_notification_settings()) 

872 

873 def update_votd_notification_settings(self, data: dict[str, Any]) -> dict[str, Any]: 

874 """Update VOTD notification settings.""" 

875 return self._run_async(super().update_votd_notification_settings(data)) 

876 

877 # Share API methods 

878 def invite_by_email(self, data: dict[str, Any]) -> dict[str, Any]: 

879 """Send a YouVersion invite by email.""" 

880 return self._run_async(super().invite_by_email(data)) 

881 

882 def invite_by_sms(self, data: dict[str, Any]) -> dict[str, Any]: 

883 """Send a YouVersion invite by SMS.""" 

884 return self._run_async(super().invite_by_sms(data)) 

885 

886 # Additional moments and search API methods 

887 # Additional moments API methods 

888 def get_client_side_moments(self, page: int = 1) -> dict[str, Any]: 

889 """Get client-side moment items.""" 

890 return self._run_async(super().get_client_side_moments(page)) 

891 

892 def get_moments_votd(self, language_tag: str | None = None) -> dict[str, Any]: 

893 """Get verse of the day from the moments API.""" 

894 return self._run_async(super().get_moments_votd(language_tag))