|
1 | 1 | import json |
2 | 2 | import uuid |
3 | 3 | from datetime import date, datetime |
| 4 | +from urllib.parse import urlparse |
4 | 5 | from typing import Annotated, List, Optional, Union |
5 | 6 |
|
6 | 7 | import geojson |
@@ -624,6 +625,25 @@ def calculate_pagination(cls, values): |
624 | 625 | return values |
625 | 626 |
|
626 | 627 |
|
| 628 | +def safe_url(callable_fn, *, label: str): |
| 629 | + """ |
| 630 | + Check if a URL is valid & also avoid error if S3 pre-sign fails. |
| 631 | + """ |
| 632 | + try: |
| 633 | + url = callable_fn() |
| 634 | + if not url: |
| 635 | + return None |
| 636 | + |
| 637 | + parsed = urlparse(url) |
| 638 | + if parsed.scheme not in {"http", "https"}: |
| 639 | + raise ValueError(f"Invalid URL scheme: {url}") |
| 640 | + |
| 641 | + return url |
| 642 | + except Exception as e: |
| 643 | + log.warning(f"Failed to generate {label} URL: {e}") |
| 644 | + return None |
| 645 | + |
| 646 | + |
627 | 647 | class ProjectInfo(BaseModel): |
628 | 648 | """Out model for the project endpoint.""" |
629 | 649 |
|
@@ -659,37 +679,49 @@ class ProjectInfo(BaseModel): |
659 | 679 |
|
660 | 680 | @model_validator(mode="after") |
661 | 681 | def set_image_url(cls, values): |
662 | | - """Set image_url before rendering the model.""" |
663 | 682 | project_id = values.id |
664 | | - if project_id: |
665 | | - image_dir = f"dtm-data/projects/{project_id}/map_screenshot.png" |
666 | | - values.image_url = generate_presigned_download_url( |
667 | | - settings.S3_BUCKET_NAME, image_dir, 5 |
668 | | - ) |
| 683 | + if not project_id: |
| 684 | + return values |
| 685 | + |
| 686 | + image_dir = f"dtm-data/projects/{project_id}/map_screenshot.png" |
| 687 | + |
| 688 | + values.image_url = safe_url( |
| 689 | + lambda: generate_presigned_download_url( |
| 690 | + settings.S3_BUCKET_NAME, |
| 691 | + image_dir, |
| 692 | + 5, |
| 693 | + ), |
| 694 | + label="image_url", |
| 695 | + ) |
| 696 | + |
669 | 697 | return values |
670 | 698 |
|
671 | 699 | @model_validator(mode="after") |
672 | 700 | def set_assets_url(cls, values): |
673 | | - """Set assets_url before rendering the model.""" |
674 | 701 | project_id = values.id |
675 | | - if project_id: |
676 | | - values.assets_url = ( |
677 | | - get_assets_url_for_project(project_id) |
678 | | - if values.image_processing_status == "SUCCESS" |
679 | | - else None |
680 | | - ) |
| 702 | + if not project_id or values.image_processing_status != "SUCCESS": |
| 703 | + values.assets_url = None |
| 704 | + return values |
| 705 | + |
| 706 | + values.assets_url = safe_url( |
| 707 | + lambda: get_assets_url_for_project(project_id), |
| 708 | + label="assets_url", |
| 709 | + ) |
| 710 | + |
681 | 711 | return values |
682 | 712 |
|
683 | 713 | @model_validator(mode="after") |
684 | 714 | def set_orthophoto_url(cls, values): |
685 | | - """Set orthophoto_url before rendering the model.""" |
686 | 715 | project_id = values.id |
687 | | - if project_id: |
688 | | - values.orthophoto_url = ( |
689 | | - get_orthophoto_url_for_project(project_id) |
690 | | - if values.image_processing_status == "SUCCESS" |
691 | | - else None |
692 | | - ) |
| 716 | + if not project_id or values.image_processing_status != "SUCCESS": |
| 717 | + values.orthophoto_url = None |
| 718 | + return values |
| 719 | + |
| 720 | + values.orthophoto_url = safe_url( |
| 721 | + lambda: get_orthophoto_url_for_project(project_id), |
| 722 | + label="orthophoto_url", |
| 723 | + ) |
| 724 | + |
693 | 725 | return values |
694 | 726 |
|
695 | 727 | @model_validator(mode="after") |
|
0 commit comments