udata 10.8.2.dev36809__py2.py3-none-any.whl → 10.8.2.dev36863__py2.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.

Potentially problematic release.


This version of udata might be problematic. Click here for more details.

Files changed (43) hide show
  1. udata/core/metrics/helpers.py +10 -0
  2. udata/core/metrics/tasks.py +144 -1
  3. udata/static/chunks/{10.471164b2a9fe15614797.js → 10.8ca60413647062717b1e.js} +3 -3
  4. udata/static/chunks/{10.471164b2a9fe15614797.js.map → 10.8ca60413647062717b1e.js.map} +1 -1
  5. udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.b6f741fcc366abfad9c4.js} +3 -3
  6. udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.b6f741fcc366abfad9c4.js.map} +1 -1
  7. udata/static/chunks/{13.f29411b06be1883356a3.js → 13.2d06442dd9a05d9777b5.js} +2 -2
  8. udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.2d06442dd9a05d9777b5.js.map} +1 -1
  9. udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.e8e4caaad5cb0cc0bacc.js} +2 -2
  10. udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.e8e4caaad5cb0cc0bacc.js.map} +1 -1
  11. udata/static/chunks/{19.8da42e8359d72afc2618.js → 19.f03a102365af4315f9db.js} +3 -3
  12. udata/static/chunks/{19.8da42e8359d72afc2618.js.map → 19.f03a102365af4315f9db.js.map} +1 -1
  13. udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.778091d55cd8ea39af6b.js} +2 -2
  14. udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.778091d55cd8ea39af6b.js.map} +1 -1
  15. udata/static/chunks/{9.07515e5187f475bce828.js → 9.033d7e190ca9e226a5d0.js} +3 -3
  16. udata/static/chunks/{9.07515e5187f475bce828.js.map → 9.033d7e190ca9e226a5d0.js.map} +1 -1
  17. udata/static/common.js +1 -1
  18. udata/static/common.js.map +1 -1
  19. udata/tests/metrics/__init__.py +0 -0
  20. udata/tests/metrics/conftest.py +15 -0
  21. udata/tests/metrics/helpers.py +58 -0
  22. udata/tests/metrics/test_metrics.py +67 -0
  23. udata/tests/metrics/test_tasks.py +171 -0
  24. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  25. udata/translations/ar/LC_MESSAGES/udata.po +72 -65
  26. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  27. udata/translations/de/LC_MESSAGES/udata.po +72 -65
  28. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  29. udata/translations/es/LC_MESSAGES/udata.po +72 -65
  30. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  31. udata/translations/fr/LC_MESSAGES/udata.po +72 -65
  32. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  33. udata/translations/it/LC_MESSAGES/udata.po +72 -65
  34. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  35. udata/translations/pt/LC_MESSAGES/udata.po +72 -65
  36. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  37. udata/translations/sr/LC_MESSAGES/udata.po +72 -65
  38. {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/METADATA +2 -1
  39. {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/RECORD +43 -38
  40. {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/LICENSE +0 -0
  41. {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/WHEEL +0 -0
  42. {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/entry_points.txt +0 -0
  43. {udata-10.8.2.dev36809.dist-info → udata-10.8.2.dev36863.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@ import logging
2
2
  from collections import OrderedDict
3
3
  from datetime import datetime, timedelta
4
4
  from typing import Dict, List, Union
5
+ from urllib.parse import urlencode
5
6
 
6
7
  import requests
7
8
  from bson import ObjectId
@@ -68,6 +69,15 @@ def get_metrics_for_model(
68
69
  return [{} for _ in range(len(metrics_labels))]
69
70
 
70
71
 
72
+ def get_download_url(model: str, id: Union[str, ObjectId, None]) -> str:
73
+ api_namespace = model + "s" if model != "site" else model
74
+ base_url = f"{current_app.config['METRICS_API']}/{api_namespace}/data/csv/"
75
+ args = {"metric_month__sort": "asc"}
76
+ if id:
77
+ args[f"{model}_id__exact"] = id
78
+ return f"{base_url}?{urlencode(args)}"
79
+
80
+
71
81
  def compute_monthly_aggregated_metrics(aggregation_res: CommandCursor) -> OrderedDict:
72
82
  monthly_metrics = OrderedDict((month, 0) for month in get_last_13_months())
73
83
  for monthly_count in aggregation_res:
@@ -1,9 +1,152 @@
1
+ import logging
2
+ import time
3
+ from functools import wraps
4
+ from typing import Dict, List
5
+
6
+ import requests
1
7
  from flask import current_app
2
8
 
9
+ from udata.core.dataservices.models import Dataservice
3
10
  from udata.core.metrics.signals import on_site_metrics_computed
4
- from udata.models import Site
11
+ from udata.models import CommunityResource, Dataset, Organization, Reuse, Site, db
5
12
  from udata.tasks import job
6
13
 
14
+ log = logging.getLogger(__name__)
15
+
16
+
17
+ def log_timing(func):
18
+ @wraps(func)
19
+ def timeit_wrapper(*args, **kwargs):
20
+ # Better log if we're using Python 3.9
21
+ name = func.__name__
22
+ model = name.removeprefix("update_") if hasattr(name, "removeprefix") else name
23
+
24
+ log.info(f"Processing {model}…")
25
+ start_time = time.perf_counter()
26
+ result = func(*args, **kwargs)
27
+ total_time = time.perf_counter() - start_time
28
+ log.info(f"Done in {total_time:.4f} seconds.")
29
+ return result
30
+
31
+ return timeit_wrapper
32
+
33
+
34
+ def save_model(model: db.Document, model_id: str, metrics: Dict[str, int]) -> None:
35
+ try:
36
+ result = model.objects(id=model_id).update(
37
+ **{f"set__metrics__{key}": value for key, value in metrics.items()}
38
+ )
39
+
40
+ if result is None:
41
+ log.debug(f"{model.__name__} not found", extra={"id": model_id})
42
+ except Exception as e:
43
+ log.exception(e)
44
+
45
+
46
+ def iterate_on_metrics(target: str, value_keys: List[str], page_size: int = 50) -> dict:
47
+ """
48
+ Yield all elements with not zero values for the keys inside `value_keys`.
49
+ If you pass ['visit', 'download_resource'], it will do a `OR` and get
50
+ metrics with one of the two values not zero.
51
+ """
52
+ yielded = set()
53
+
54
+ for value_key in value_keys:
55
+ url = f"{current_app.config['METRICS_API']}/{target}_total/data/"
56
+ url += f"?{value_key}__greater=1&page_size={page_size}"
57
+
58
+ with requests.Session() as session:
59
+ while url is not None:
60
+ r = session.get(url, timeout=10)
61
+ r.raise_for_status()
62
+ data = r.json()
63
+
64
+ for row in data["data"]:
65
+ if row["__id"] not in yielded:
66
+ yielded.add(row["__id"])
67
+ yield row
68
+
69
+ url = data["links"].get("next")
70
+
71
+
72
+ @log_timing
73
+ def update_resources_and_community_resources():
74
+ for data in iterate_on_metrics("resources", ["download_resource"]):
75
+ if data["dataset_id"] is None:
76
+ save_model(
77
+ CommunityResource,
78
+ data["resource_id"],
79
+ {
80
+ "views": data["download_resource"],
81
+ },
82
+ )
83
+ else:
84
+ Dataset.objects(resources__id=data["resource_id"]).update(
85
+ **{"set__resources__$__metrics__views": data["download_resource"]}
86
+ )
87
+
88
+
89
+ @log_timing
90
+ def update_datasets():
91
+ for data in iterate_on_metrics("datasets", ["visit", "download_resource"]):
92
+ save_model(
93
+ Dataset,
94
+ data["dataset_id"],
95
+ {
96
+ "views": data["visit"],
97
+ "resources_downloads": data["download_resource"],
98
+ },
99
+ )
100
+
101
+
102
+ @log_timing
103
+ def update_dataservices():
104
+ for data in iterate_on_metrics("dataservices", ["visit"]):
105
+ save_model(
106
+ Dataservice,
107
+ data["dataservice_id"],
108
+ {
109
+ "views": data["visit"],
110
+ },
111
+ )
112
+
113
+
114
+ @log_timing
115
+ def update_reuses():
116
+ for data in iterate_on_metrics("reuses", ["visit"]):
117
+ save_model(Reuse, data["reuse_id"], {"views": data["visit"]})
118
+
119
+
120
+ @log_timing
121
+ def update_organizations():
122
+ # We're currently using visit_dataset as global metric for an orga
123
+ for data in iterate_on_metrics("organizations", ["visit_dataset"]):
124
+ save_model(
125
+ Organization,
126
+ data["organization_id"],
127
+ {
128
+ "views": data["visit_dataset"],
129
+ },
130
+ )
131
+
132
+
133
+ def update_metrics_for_models():
134
+ log.info("Starting…")
135
+ update_datasets()
136
+ update_resources_and_community_resources()
137
+ update_dataservices()
138
+ update_reuses()
139
+ update_organizations()
140
+
141
+
142
+ @job("update-metrics", route="low.metrics")
143
+ def update_metrics(self):
144
+ """Update udata objects metrics"""
145
+ if not current_app.config["METRICS_API"]:
146
+ log.error("You need to set METRICS_API to run update-metrics")
147
+ exit(1)
148
+ update_metrics_for_models()
149
+
7
150
 
8
151
  @job("compute-site-metrics")
9
152
  def compute_site_metrics(self):