goodmap 1.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
goodmap/exceptions.py ADDED
@@ -0,0 +1,100 @@
1
+ """Custom exceptions for Goodmap application."""
2
+
3
+ import logging
4
+ import uuid as uuid_lib
5
+
6
+ from pydantic import ValidationError as PydanticValidationError
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def _sanitize_uuid(uuid: str | None) -> str:
12
+ """Validate and sanitize UUID to prevent injection attacks."""
13
+ if uuid is None:
14
+ return "<unknown>"
15
+ try:
16
+ # Validate UUID format
17
+ uuid_lib.UUID(uuid)
18
+ return uuid
19
+ except (ValueError, AttributeError, TypeError):
20
+ logger.warning("Invalid UUID format detected", extra={"raw_uuid": repr(uuid)})
21
+ return "<invalid-uuid>"
22
+
23
+
24
+ class GoodmapError(Exception):
25
+ """Base exception for all Goodmap errors."""
26
+
27
+ pass
28
+
29
+
30
+ class ValidationError(GoodmapError):
31
+ """Base validation error."""
32
+
33
+ pass
34
+
35
+
36
+ class LocationValidationError(ValidationError):
37
+ """Validation error for location data with enhanced context."""
38
+
39
+ def __init__(self, validation_error: PydanticValidationError, uuid: str | None = None):
40
+ self.uuid = _sanitize_uuid(uuid)
41
+ self.original_error = validation_error
42
+ self.validation_errors = validation_error.errors()
43
+ super().__init__(str(validation_error))
44
+
45
+ def __str__(self):
46
+ # Don't expose error details in string representation
47
+ if self.uuid and self.uuid not in ("<unknown>", "<invalid-uuid>"):
48
+ return f"Validation failed for location '{self.uuid}'"
49
+ return "Validation failed"
50
+
51
+
52
+ class NotFoundError(GoodmapError):
53
+ """Resource not found error."""
54
+
55
+ def __init__(self, uuid: str, resource_type: str = "Resource"):
56
+ self.uuid = _sanitize_uuid(uuid)
57
+ super().__init__(f"{resource_type} with uuid '{self.uuid}' not found")
58
+
59
+
60
+ class LocationNotFoundError(NotFoundError):
61
+ """Location with specified UUID not found."""
62
+
63
+ def __init__(self, uuid: str):
64
+ super().__init__(uuid, "Location")
65
+
66
+
67
+ class AlreadyExistsError(GoodmapError):
68
+ """Resource already exists error."""
69
+
70
+ def __init__(self, uuid: str, resource_type: str = "Resource"):
71
+ self.uuid = _sanitize_uuid(uuid)
72
+ super().__init__(f"{resource_type} with uuid '{self.uuid}' already exists")
73
+
74
+
75
+ class LocationAlreadyExistsError(AlreadyExistsError):
76
+ """Location with specified UUID already exists."""
77
+
78
+ def __init__(self, uuid: str):
79
+ super().__init__(uuid, "Location")
80
+
81
+
82
+ class SuggestionNotFoundError(NotFoundError):
83
+ """Suggestion with specified UUID not found."""
84
+
85
+ def __init__(self, uuid: str):
86
+ super().__init__(uuid, "Suggestion")
87
+
88
+
89
+ class SuggestionAlreadyExistsError(AlreadyExistsError):
90
+ """Suggestion with specified UUID already exists."""
91
+
92
+ def __init__(self, uuid: str):
93
+ super().__init__(uuid, "Suggestion")
94
+
95
+
96
+ class ReportNotFoundError(NotFoundError):
97
+ """Report with specified UUID not found."""
98
+
99
+ def __init__(self, uuid: str):
100
+ super().__init__(uuid, "Report")
goodmap/formatter.py ADDED
@@ -0,0 +1,27 @@
1
+ from flask_babel import gettext, lazy_gettext
2
+
3
+
4
+ def safe_gettext(text):
5
+ if isinstance(text, list):
6
+ return list(map(gettext, text))
7
+ elif isinstance(text, dict):
8
+ return text
9
+ else:
10
+ return gettext(text)
11
+
12
+
13
+ def prepare_pin(place, visible_fields, meta_data):
14
+ pin_data = {
15
+ "title": place["name"],
16
+ "subtitle": lazy_gettext(place["type_of_place"]), # TODO this should not be obligatory
17
+ "position": place["position"],
18
+ "metadata": {
19
+ gettext(field): safe_gettext(place[field]) for field in meta_data if field in place
20
+ },
21
+ "data": [
22
+ [gettext(field), safe_gettext(place[field])]
23
+ for field in visible_fields
24
+ if field in place
25
+ ],
26
+ }
27
+ return pin_data
goodmap/goodmap.py ADDED
@@ -0,0 +1,89 @@
1
+ import os
2
+
3
+ from flask import Blueprint, redirect, render_template, session
4
+ from flask_wtf.csrf import CSRFProtect, generate_csrf
5
+ from platzky import platzky
6
+ from platzky.config import languages_dict
7
+ from platzky.models import CmsModule
8
+
9
+ from goodmap.config import GoodmapConfig
10
+ from goodmap.core_api import core_pages
11
+ from goodmap.data_models.location import create_location_model
12
+ from goodmap.db import extend_db_with_goodmap_queries, get_location_obligatory_fields
13
+
14
+
15
+ def create_app(config_path: str) -> platzky.Engine:
16
+ config = GoodmapConfig.parse_yaml(config_path)
17
+ return create_app_from_config(config)
18
+
19
+
20
+ # TODO Checking if there is a feature flag secition should be part of configs logic not client app
21
+ def is_feature_enabled(config: GoodmapConfig, feature: str) -> bool:
22
+ return config.feature_flags.get(feature, False) if config.feature_flags else False
23
+
24
+
25
+ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
26
+ directory = os.path.dirname(os.path.realpath(__file__))
27
+
28
+ locale_dir = os.path.join(directory, "locale")
29
+ config.translation_directories.append(locale_dir)
30
+ app = platzky.create_app_from_config(config)
31
+
32
+ if is_feature_enabled(config, "USE_LAZY_LOADING"):
33
+ location_obligatory_fields = get_location_obligatory_fields(app.db)
34
+ else:
35
+ location_obligatory_fields = []
36
+
37
+ location_model = create_location_model(location_obligatory_fields)
38
+
39
+ app.db = extend_db_with_goodmap_queries(app.db, location_model)
40
+
41
+ CSRFProtect(app)
42
+
43
+ cp = core_pages(
44
+ app.db,
45
+ languages_dict(config.languages),
46
+ app.notify,
47
+ generate_csrf,
48
+ location_model,
49
+ feature_flags=config.feature_flags,
50
+ )
51
+ app.register_blueprint(cp)
52
+ goodmap = Blueprint("goodmap", __name__, url_prefix="/", template_folder="templates")
53
+
54
+ @goodmap.route("/")
55
+ def index():
56
+ return render_template(
57
+ "map.html",
58
+ feature_flags=config.feature_flags,
59
+ goodmap_frontend_lib_url=config.goodmap_frontend_lib_url,
60
+ )
61
+
62
+ @goodmap.route("/goodmap-admin")
63
+ def admin():
64
+ user = session.get("user", None)
65
+ if not user:
66
+ return redirect("/admin")
67
+
68
+ # TODO: This should be replaced with a proper user authentication check,
69
+ # cms_modules should be passed from the app
70
+ return render_template(
71
+ "goodmap-admin.html",
72
+ feature_flags=config.feature_flags,
73
+ goodmap_frontend_lib_url=config.goodmap_frontend_lib_url,
74
+ user=user,
75
+ cms_modules=app.cms_modules,
76
+ )
77
+
78
+ app.register_blueprint(goodmap)
79
+ goodmap_cms_modules = CmsModule.model_validate(
80
+ {
81
+ "name": "Map admin panel",
82
+ "description": "Admin panel for managing map data",
83
+ "slug": "goodmap-admin",
84
+ "template": "goodmap-admin.html",
85
+ }
86
+ )
87
+ app.add_cms_module(goodmap_cms_modules)
88
+
89
+ return app