goodmap 1.4.0__py3-none-any.whl → 1.6.0__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/api_models.py +3 -1
- goodmap/core_api.py +50 -20
- goodmap/db.py +29 -0
- goodmap/feature_flags.py +7 -0
- goodmap/goodmap.py +9 -17
- {goodmap-1.4.0.dist-info → goodmap-1.6.0.dist-info}/METADATA +3 -3
- {goodmap-1.4.0.dist-info → goodmap-1.6.0.dist-info}/RECORD +9 -9
- goodmap/data_validator.py +0 -119
- {goodmap-1.4.0.dist-info → goodmap-1.6.0.dist-info}/LICENSE.md +0 -0
- {goodmap-1.4.0.dist-info → goodmap-1.6.0.dist-info}/WHEEL +0 -0
goodmap/api_models.py
CHANGED
|
@@ -14,7 +14,9 @@ class LocationReportRequest(BaseModel):
|
|
|
14
14
|
"""Request model for reporting a location issue."""
|
|
15
15
|
|
|
16
16
|
id: str = Field(..., description="Location UUID to report")
|
|
17
|
-
description: str = Field(
|
|
17
|
+
description: str = Field(
|
|
18
|
+
..., min_length=1, max_length=500, description="Description of the problem"
|
|
19
|
+
)
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class LocationReportResponse(BaseModel):
|
goodmap/core_api.py
CHANGED
|
@@ -7,6 +7,7 @@ import numpy
|
|
|
7
7
|
import pysupercluster
|
|
8
8
|
from flask import Blueprint, jsonify, make_response, request
|
|
9
9
|
from flask_babel import gettext
|
|
10
|
+
from platzky import FeatureFlagSet
|
|
10
11
|
from platzky.attachment import AttachmentProtocol
|
|
11
12
|
from platzky.config import AttachmentConfig, LanguagesMapping
|
|
12
13
|
from spectree import Response, SpecTree
|
|
@@ -24,6 +25,7 @@ from goodmap.clustering import (
|
|
|
24
25
|
match_clusters_uuids,
|
|
25
26
|
)
|
|
26
27
|
from goodmap.exceptions import LocationValidationError
|
|
28
|
+
from goodmap.feature_flags import CategoriesHelp
|
|
27
29
|
from goodmap.formatter import prepare_pin
|
|
28
30
|
from goodmap.json_security import (
|
|
29
31
|
MAX_JSON_DEPTH_LOCATION,
|
|
@@ -38,14 +40,29 @@ MAX_ZOOM = 16
|
|
|
38
40
|
CLUSTER_RADIUS = 200
|
|
39
41
|
CLUSTER_EXTENT = 512
|
|
40
42
|
|
|
43
|
+
# Report description validation constants
|
|
44
|
+
MAX_DESCRIPTION_LENGTH = 500
|
|
45
|
+
|
|
41
46
|
# Error message constants
|
|
42
47
|
ERROR_INVALID_REQUEST_DATA = "Invalid request data"
|
|
43
48
|
ERROR_INVALID_LOCATION_DATA = "Invalid location data"
|
|
44
49
|
ERROR_LOCATION_NOT_FOUND = "Location not found"
|
|
50
|
+
ERROR_INVALID_DESCRIPTION = "Invalid report description"
|
|
45
51
|
|
|
46
52
|
logger = logging.getLogger(__name__)
|
|
47
53
|
|
|
48
54
|
|
|
55
|
+
@deprecation.deprecated(
|
|
56
|
+
deprecated_in="1.5.0",
|
|
57
|
+
removed_in="2.0.0",
|
|
58
|
+
details="Configure 'reported_issue_types' in the database instead. "
|
|
59
|
+
"The hardcoded fallback will be removed in a future release.",
|
|
60
|
+
)
|
|
61
|
+
def get_default_issue_options():
|
|
62
|
+
"""Return hardcoded fallback issue options for backward compatibility."""
|
|
63
|
+
return ["notHere", "overload", "broken", "other"]
|
|
64
|
+
|
|
65
|
+
|
|
49
66
|
def make_tuple_translation(keys_to_translate):
|
|
50
67
|
return [(x, gettext(x)) for x in keys_to_translate]
|
|
51
68
|
|
|
@@ -83,7 +100,7 @@ def core_pages(
|
|
|
83
100
|
location_model,
|
|
84
101
|
photo_attachment_class: type[AttachmentProtocol],
|
|
85
102
|
photo_attachment_config: AttachmentConfig,
|
|
86
|
-
feature_flags
|
|
103
|
+
feature_flags: FeatureFlagSet,
|
|
87
104
|
) -> Blueprint:
|
|
88
105
|
core_api_blueprint = Blueprint("api", __name__, url_prefix="/api")
|
|
89
106
|
|
|
@@ -247,10 +264,23 @@ def core_pages(
|
|
|
247
264
|
"""
|
|
248
265
|
try:
|
|
249
266
|
location_report = request.get_json()
|
|
267
|
+
description = location_report["description"]
|
|
268
|
+
|
|
269
|
+
# Validate description against configured issue options
|
|
270
|
+
issue_options = database.get_issue_options()
|
|
271
|
+
if not issue_options:
|
|
272
|
+
issue_options = get_default_issue_options()
|
|
273
|
+
|
|
274
|
+
if description not in issue_options:
|
|
275
|
+
if "other" not in issue_options:
|
|
276
|
+
return make_response(jsonify({"message": ERROR_INVALID_DESCRIPTION}), 400)
|
|
277
|
+
if len(description) > MAX_DESCRIPTION_LENGTH:
|
|
278
|
+
return make_response(jsonify({"message": ERROR_INVALID_DESCRIPTION}), 400)
|
|
279
|
+
|
|
250
280
|
report = {
|
|
251
281
|
"uuid": str(uuid.uuid4()),
|
|
252
282
|
"location_id": location_report["id"],
|
|
253
|
-
"description":
|
|
283
|
+
"description": description,
|
|
254
284
|
"status": "pending",
|
|
255
285
|
"priority": "medium",
|
|
256
286
|
}
|
|
@@ -383,15 +413,15 @@ def core_pages(
|
|
|
383
413
|
raw_categories = database.get_categories()
|
|
384
414
|
categories = make_tuple_translation(raw_categories)
|
|
385
415
|
|
|
386
|
-
if not feature_flags
|
|
416
|
+
if CategoriesHelp not in feature_flags:
|
|
387
417
|
return jsonify(categories)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
418
|
+
|
|
419
|
+
category_data = database.get_category_data()
|
|
420
|
+
categories_help = category_data.get("categories_help")
|
|
421
|
+
proper_categories_help = []
|
|
422
|
+
if categories_help is not None:
|
|
423
|
+
for option in categories_help:
|
|
424
|
+
proper_categories_help.append({option: gettext(f"categories_help_{option}")})
|
|
395
425
|
|
|
396
426
|
return jsonify({"categories": categories, "categories_help": proper_categories_help})
|
|
397
427
|
|
|
@@ -415,7 +445,7 @@ def core_pages(
|
|
|
415
445
|
"options": make_tuple_translation(options),
|
|
416
446
|
}
|
|
417
447
|
|
|
418
|
-
if feature_flags
|
|
448
|
+
if CategoriesHelp in feature_flags:
|
|
419
449
|
option_help_list = categories_options_help.get(key, [])
|
|
420
450
|
proper_options_help = []
|
|
421
451
|
for option in option_help_list:
|
|
@@ -428,7 +458,7 @@ def core_pages(
|
|
|
428
458
|
|
|
429
459
|
response = {"categories": result}
|
|
430
460
|
|
|
431
|
-
if feature_flags
|
|
461
|
+
if CategoriesHelp in feature_flags:
|
|
432
462
|
categories_help = categories_data.get("categories_help", [])
|
|
433
463
|
proper_categories_help = []
|
|
434
464
|
for option in categories_help:
|
|
@@ -466,15 +496,15 @@ def core_pages(
|
|
|
466
496
|
proper_categories_options_help.append(
|
|
467
497
|
{option: gettext(f"categories_options_help_{option}")}
|
|
468
498
|
)
|
|
469
|
-
if not feature_flags
|
|
499
|
+
if CategoriesHelp not in feature_flags:
|
|
470
500
|
return jsonify(local_data)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
501
|
+
|
|
502
|
+
return jsonify(
|
|
503
|
+
{
|
|
504
|
+
"categories_options": local_data,
|
|
505
|
+
"categories_options_help": proper_categories_options_help,
|
|
506
|
+
}
|
|
507
|
+
)
|
|
478
508
|
|
|
479
509
|
# Register Spectree with blueprint after all routes are defined
|
|
480
510
|
spec.register(core_api_blueprint)
|
goodmap/db.py
CHANGED
|
@@ -335,6 +335,34 @@ def get_location_obligatory_fields(db):
|
|
|
335
335
|
return globals()[f"{db.module_name}_get_location_obligatory_fields"](db)
|
|
336
336
|
|
|
337
337
|
|
|
338
|
+
# ------------------------------------------------
|
|
339
|
+
# get_issue_options
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def json_db_get_issue_options(self):
|
|
343
|
+
return self.data.get("reported_issue_types", [])
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def json_file_db_get_issue_options(self):
|
|
347
|
+
with open(self.data_file_path, "r") as file:
|
|
348
|
+
return json.load(file)["map"].get("reported_issue_types", [])
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def google_json_db_get_issue_options(self):
|
|
352
|
+
return self.data.get("map", {}).get("reported_issue_types", [])
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def mongodb_db_get_issue_options(self):
|
|
356
|
+
config_doc = self.db.config.find_one({"_id": "map_config"})
|
|
357
|
+
if config_doc and "reported_issue_types" in config_doc:
|
|
358
|
+
return config_doc["reported_issue_types"]
|
|
359
|
+
return []
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def get_issue_options(db):
|
|
363
|
+
return globals()[f"{db.module_name}_get_issue_options"]
|
|
364
|
+
|
|
365
|
+
|
|
338
366
|
# ------------------------------------------------
|
|
339
367
|
# get_data
|
|
340
368
|
|
|
@@ -1440,6 +1468,7 @@ def delete_report(db, report_id):
|
|
|
1440
1468
|
|
|
1441
1469
|
|
|
1442
1470
|
def extend_db_with_goodmap_queries(db, location_model):
|
|
1471
|
+
db.extend("get_issue_options", get_issue_options(db))
|
|
1443
1472
|
db.extend("get_data", get_data(db))
|
|
1444
1473
|
db.extend("get_visible_data", get_visible_data(db))
|
|
1445
1474
|
db.extend("get_meta_data", get_meta_data(db))
|
goodmap/feature_flags.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from platzky import FeatureFlag
|
|
2
|
+
|
|
3
|
+
CategoriesHelp = FeatureFlag(alias="CATEGORIES_HELP", description="Show category help text")
|
|
4
|
+
UseLazyLoading = FeatureFlag(
|
|
5
|
+
alias="USE_LAZY_LOADING", description="Enable lazy loading of location fields"
|
|
6
|
+
)
|
|
7
|
+
EnableAdminPanel = FeatureFlag(alias="ENABLE_ADMIN_PANEL", description="Enable admin panel")
|
goodmap/goodmap.py
CHANGED
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
6
|
from flask import Blueprint, redirect, render_template, session
|
|
7
|
+
from flask_babel import gettext
|
|
7
8
|
from flask_wtf.csrf import CSRFProtect, generate_csrf
|
|
8
9
|
from platzky import platzky
|
|
9
10
|
from platzky.attachment import create_attachment_class
|
|
@@ -18,6 +19,7 @@ from goodmap.db import (
|
|
|
18
19
|
extend_db_with_goodmap_queries,
|
|
19
20
|
get_location_obligatory_fields,
|
|
20
21
|
)
|
|
22
|
+
from goodmap.feature_flags import EnableAdminPanel, UseLazyLoading
|
|
21
23
|
|
|
22
24
|
logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
@@ -35,20 +37,6 @@ def create_app(config_path: str) -> platzky.Engine:
|
|
|
35
37
|
return create_app_from_config(config)
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
# TODO Checking if there is a feature flag secition should be part of configs logic not client app
|
|
39
|
-
def is_feature_enabled(config: GoodmapConfig, feature: str) -> bool:
|
|
40
|
-
"""Check if a feature flag is enabled in the configuration.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
config: Goodmap configuration object
|
|
44
|
-
feature: Name of the feature flag to check
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
bool: True if feature is enabled, False otherwise
|
|
48
|
-
"""
|
|
49
|
-
return config.feature_flags.get(feature, False) if config.feature_flags else False
|
|
50
|
-
|
|
51
|
-
|
|
52
40
|
def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
53
41
|
"""Create and configure Goodmap application from config object.
|
|
54
42
|
|
|
@@ -73,7 +61,7 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
73
61
|
if "MAX_CONTENT_LENGTH" not in app.config:
|
|
74
62
|
app.config["MAX_CONTENT_LENGTH"] = 100 * 1024 # 100KB
|
|
75
63
|
|
|
76
|
-
if
|
|
64
|
+
if app.is_enabled(UseLazyLoading):
|
|
77
65
|
location_obligatory_fields = get_location_obligatory_fields(app.db)
|
|
78
66
|
# Extend db with goodmap queries first so we can use the bound method
|
|
79
67
|
location_model = create_location_model(location_obligatory_fields, {})
|
|
@@ -149,12 +137,16 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
149
137
|
name: spec for name, spec in properties.items() if name not in ("uuid", "position")
|
|
150
138
|
}
|
|
151
139
|
|
|
140
|
+
issue_options_raw = app.db.get_issue_options() # type: ignore[attr-defined]
|
|
141
|
+
reported_issue_types = [{"value": t, "label": gettext(t)} for t in issue_options_raw]
|
|
142
|
+
|
|
152
143
|
location_schema = { # TODO remove backward compatibility - deprecation
|
|
153
144
|
"obligatory_fields": app.extensions["goodmap"][
|
|
154
145
|
"location_obligatory_fields"
|
|
155
146
|
], # Backward compatibility
|
|
156
147
|
"categories": categories, # Backward compatibility
|
|
157
148
|
"fields": form_fields,
|
|
149
|
+
"reported_issue_types": reported_issue_types,
|
|
158
150
|
}
|
|
159
151
|
|
|
160
152
|
return render_template(
|
|
@@ -175,7 +167,7 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
175
167
|
Returns:
|
|
176
168
|
Rendered goodmap-admin.html template or redirect to login
|
|
177
169
|
"""
|
|
178
|
-
if not
|
|
170
|
+
if not app.is_enabled(EnableAdminPanel):
|
|
179
171
|
return redirect("/")
|
|
180
172
|
|
|
181
173
|
user = session.get("user", None)
|
|
@@ -194,7 +186,7 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
194
186
|
|
|
195
187
|
app.register_blueprint(goodmap)
|
|
196
188
|
|
|
197
|
-
if
|
|
189
|
+
if app.is_enabled(EnableAdminPanel):
|
|
198
190
|
admin_bp = admin_pages(app.db, location_model)
|
|
199
191
|
app.register_blueprint(admin_bp)
|
|
200
192
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: goodmap
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: Map engine to serve all the people :)
|
|
5
5
|
Author: Krzysztof Kolodzinski
|
|
6
6
|
Author-email: krzysztof.kolodzinski@problematy.pl
|
|
@@ -20,11 +20,11 @@ Requires-Dist: aiohttp (>=3.8.4,<4.0.0)
|
|
|
20
20
|
Requires-Dist: deprecation (>=2.1.0,<3.0.0)
|
|
21
21
|
Requires-Dist: google-cloud-storage (>=2.7.0,<3.0.0)
|
|
22
22
|
Requires-Dist: gql (>=3.4.0,<4.0.0)
|
|
23
|
-
Requires-Dist: gunicorn (>=20.1,<
|
|
23
|
+
Requires-Dist: gunicorn (>=20.1,<25.0)
|
|
24
24
|
Requires-Dist: humanize (>=4.6.0,<5.0.0)
|
|
25
25
|
Requires-Dist: myst-parser (>=4.0.0,<5.0.0) ; extra == "docs"
|
|
26
26
|
Requires-Dist: numpy (>=2.2.0,<3.0.0)
|
|
27
|
-
Requires-Dist: platzky (>=1.
|
|
27
|
+
Requires-Dist: platzky (>=1.4.1,<2.0.0)
|
|
28
28
|
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
29
29
|
Requires-Dist: pysupercluster-problematy (>=0.7.8,<0.8.0)
|
|
30
30
|
Requires-Dist: scipy (>=1.15.1,<2.0.0)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
goodmap/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
2
|
goodmap/admin_api.py,sha256=5tvHeqknG8WmhBYmIHlQHTOUA-zaT8FKaAyyLdvX2EE,10290
|
|
3
|
-
goodmap/api_models.py,sha256=
|
|
3
|
+
goodmap/api_models.py,sha256=drE0i6AWHat7siwTpnvpSrWVSC5uW0pTKQzo68xwb34,3468
|
|
4
4
|
goodmap/clustering.py,sha256=ULB-fPNOUDblgpBK4vzuo0o2yqIcvG84F3R6Za2X_l4,2905
|
|
5
5
|
goodmap/config.py,sha256=CsmC1zuvVab90VW50dtARHbFJpy2vfsIfbque8Zgc-U,1313
|
|
6
6
|
goodmap/core.py,sha256=AgdGLfeJvL7TlTX893NR2YdCS8EuXx93Gx6ndvWws7s,2673
|
|
7
|
-
goodmap/core_api.py,sha256=
|
|
7
|
+
goodmap/core_api.py,sha256=ao80Tynclv6IV51hXMPA0W3RQUwJPwxZFXcs-H287KE,21105
|
|
8
8
|
goodmap/data_models/location.py,sha256=_I27R06ovEL9ctv_SZ3yoLL-RwmyE3VDsVOG4a89q50,6798
|
|
9
|
-
goodmap/
|
|
10
|
-
goodmap/db.py,sha256=TcqYGbK5yk6S735Si1AzjNqcbB1nsd9pFGOy5qN9Vec,46589
|
|
9
|
+
goodmap/db.py,sha256=ZXkonYpiWwtCGsOk9XAXlaNk_db4ZGrJjgPspBdm9gc,47408
|
|
11
10
|
goodmap/exceptions.py,sha256=jkFAUoc5LHk8iPjxHxbcRp8W6qFCSEA25A8XaSwxwyo,2906
|
|
11
|
+
goodmap/feature_flags.py,sha256=-hiqTX4OlhfY_4M1Kvy-_z1Fx6YTaFi3SVGYa0Pamcw,334
|
|
12
12
|
goodmap/formatter.py,sha256=4rqcg9A9Y9opAi7eb8kMDdUC03M3uzZgCxx29cvvIag,1403
|
|
13
|
-
goodmap/goodmap.py,sha256=
|
|
13
|
+
goodmap/goodmap.py,sha256=Hi8np9oKkPzeRFYVNPiYvhW1chsfJgIoGwUuhZ_3Q-g,7811
|
|
14
14
|
goodmap/json_security.py,sha256=EHAxNlb16AVwphgf4F7yObtMZpbR9M538dwn_STRcMo,3275
|
|
15
15
|
goodmap/templates/goodmap-admin.html,sha256=LSiOZ9-n29CnlfVNwdgmXwT7Xe7t5gvGh1xSrFGqOIY,35669
|
|
16
16
|
goodmap/templates/map.html,sha256=Uk7FFrZwvHZvG0DDaQrGW5ZrIMD21XrJzMub76uIlAg,4348
|
|
17
|
-
goodmap-1.
|
|
18
|
-
goodmap-1.
|
|
19
|
-
goodmap-1.
|
|
20
|
-
goodmap-1.
|
|
17
|
+
goodmap-1.6.0.dist-info/LICENSE.md,sha256=nkCQOR7uheLRvHRfXmwx9LhBnMcPeBU9d4ebLojDiQU,1067
|
|
18
|
+
goodmap-1.6.0.dist-info/METADATA,sha256=ljeI70gs0U8RV8dm-VDX8Vt4MHy5_KxfB7tlik3l-0E,5798
|
|
19
|
+
goodmap-1.6.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
20
|
+
goodmap-1.6.0.dist-info/RECORD,,
|
goodmap/data_validator.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from enum import Enum
|
|
3
|
-
from sys import argv, stderr
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ViolationType(Enum):
|
|
7
|
-
INVALID_JSON_FORMAT = 0
|
|
8
|
-
MISSING_OBLIGATORY_FIELD = 1
|
|
9
|
-
INVALID_VALUE_IN_CATEGORY = 2
|
|
10
|
-
NULL_VALUE = 3
|
|
11
|
-
|
|
12
|
-
def get_error_message(self):
|
|
13
|
-
error_message_dict = {
|
|
14
|
-
0: "invalid json format",
|
|
15
|
-
1: "missing obligatory field",
|
|
16
|
-
2: "invalid value in category",
|
|
17
|
-
3: "attribute has null value",
|
|
18
|
-
}
|
|
19
|
-
return error_message_dict[self.value]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class DataViolation:
|
|
23
|
-
def __init__(self, violation_type: ViolationType):
|
|
24
|
-
self.violation_type = violation_type
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class FormatViolation(DataViolation):
|
|
28
|
-
def __init__(self, decoding_error):
|
|
29
|
-
super().__init__(ViolationType.INVALID_JSON_FORMAT)
|
|
30
|
-
self.decoding_error = decoding_error
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class FieldViolation(DataViolation):
|
|
34
|
-
def __init__(self, violation_type: ViolationType, datapoint, violating_field):
|
|
35
|
-
super().__init__(violation_type)
|
|
36
|
-
self.datapoint = datapoint
|
|
37
|
-
self.violating_field = violating_field
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def get_missing_obligatory_fields_violations(p, obligatory_fields):
|
|
41
|
-
violations = []
|
|
42
|
-
for field in obligatory_fields:
|
|
43
|
-
if field not in p.keys():
|
|
44
|
-
violations.append(FieldViolation(ViolationType.MISSING_OBLIGATORY_FIELD, p, field))
|
|
45
|
-
return violations
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def get_invalid_value_in_category_violations(p, categories):
|
|
49
|
-
violations = []
|
|
50
|
-
for category in categories & p.keys():
|
|
51
|
-
category_value_in_point = p[category]
|
|
52
|
-
valid_values_set = categories[category]
|
|
53
|
-
if isinstance(category_value_in_point, list):
|
|
54
|
-
for attribute_value in category_value_in_point:
|
|
55
|
-
if attribute_value not in valid_values_set:
|
|
56
|
-
violations.append(
|
|
57
|
-
FieldViolation(ViolationType.INVALID_VALUE_IN_CATEGORY, p, category)
|
|
58
|
-
)
|
|
59
|
-
else:
|
|
60
|
-
if category_value_in_point not in valid_values_set:
|
|
61
|
-
violations.append(
|
|
62
|
-
FieldViolation(ViolationType.INVALID_VALUE_IN_CATEGORY, p, category)
|
|
63
|
-
)
|
|
64
|
-
return violations
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def get_null_values_violations(p):
|
|
68
|
-
violations = []
|
|
69
|
-
for attribute, value in p.items():
|
|
70
|
-
if value is None:
|
|
71
|
-
violations.append(FieldViolation(ViolationType.NULL_VALUE, p, attribute))
|
|
72
|
-
return violations
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def report_data_violations_from_json(json_database):
|
|
76
|
-
map_data = json_database["map"]
|
|
77
|
-
datapoints = map_data["data"]
|
|
78
|
-
categories = map_data["categories"]
|
|
79
|
-
obligatory_fields = map_data["location_obligatory_fields"]
|
|
80
|
-
|
|
81
|
-
data_violations = []
|
|
82
|
-
|
|
83
|
-
for p in datapoints:
|
|
84
|
-
data_violations += get_missing_obligatory_fields_violations(p, obligatory_fields)
|
|
85
|
-
data_violations += get_invalid_value_in_category_violations(p, categories)
|
|
86
|
-
data_violations += get_null_values_violations(p)
|
|
87
|
-
|
|
88
|
-
return data_violations
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def report_data_violations_from_json_file(path_to_json_file):
|
|
92
|
-
with open(path_to_json_file) as json_file:
|
|
93
|
-
try:
|
|
94
|
-
return report_data_violations_from_json(json.load(json_file))
|
|
95
|
-
except json.JSONDecodeError as e:
|
|
96
|
-
return [FormatViolation(e)]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def print_reported_violations(data_violations): # pragma: no cover
|
|
100
|
-
for violation in data_violations:
|
|
101
|
-
violation_type = violation.violation_type
|
|
102
|
-
if violation_type == ViolationType.INVALID_JSON_FORMAT:
|
|
103
|
-
print("DATA ERROR: invalid json format", file=stderr)
|
|
104
|
-
print(violation.decoding_error, file=stderr)
|
|
105
|
-
else:
|
|
106
|
-
violating_field = violation.violating_field
|
|
107
|
-
violation_type_error = violation_type.get_error_message()
|
|
108
|
-
print(
|
|
109
|
-
f"DATA ERROR: {violation_type_error} {violating_field} in datapoint:", file=stderr
|
|
110
|
-
)
|
|
111
|
-
print(violation.datapoint, file=stderr)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if __name__ == "__main__": # pragma: no cover
|
|
115
|
-
data_violations = report_data_violations_from_json_file(argv[1])
|
|
116
|
-
if data_violations == []:
|
|
117
|
-
print("All data is valid", file=stderr)
|
|
118
|
-
else:
|
|
119
|
-
print_reported_violations(data_violations)
|
|
File without changes
|
|
File without changes
|