udata 10.1.4.dev34377__py2.py3-none-any.whl → 10.1.4.dev34401__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 (27) hide show
  1. udata/core/dataset/apiv2.py +26 -4
  2. udata/core/dataset/models.py +38 -3
  3. udata/routing.py +13 -2
  4. udata/settings.py +4 -0
  5. udata/static/chunks/{11.b6f741fcc366abfad9c4.js → 11.51d706fb9521c16976bc.js} +3 -3
  6. udata/static/chunks/{11.b6f741fcc366abfad9c4.js.map → 11.51d706fb9521c16976bc.js.map} +1 -1
  7. udata/static/chunks/{13.2d06442dd9a05d9777b5.js → 13.39e106d56f794ebd06a0.js} +2 -2
  8. udata/static/chunks/{13.2d06442dd9a05d9777b5.js.map → 13.39e106d56f794ebd06a0.js.map} +1 -1
  9. udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js → 17.70cbb4a91b002338007e.js} +2 -2
  10. udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js.map → 17.70cbb4a91b002338007e.js.map} +1 -1
  11. udata/static/chunks/{19.f03a102365af4315f9db.js → 19.a348a5fff8fe2801e52a.js} +3 -3
  12. udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.a348a5fff8fe2801e52a.js.map} +1 -1
  13. udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.343ca020a2d38cec1a14.js} +3 -3
  14. udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.343ca020a2d38cec1a14.js.map} +1 -1
  15. udata/static/chunks/{6.d663709d877baa44a71e.js → 6.a3b07de9dd2ca2d24e85.js} +3 -3
  16. udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.a3b07de9dd2ca2d24e85.js.map} +1 -1
  17. udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.462bb3029de008497675.js} +2 -2
  18. udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.462bb3029de008497675.js.map} +1 -1
  19. udata/static/common.js +1 -1
  20. udata/static/common.js.map +1 -1
  21. udata/tests/apiv2/test_datasets.py +21 -0
  22. {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/METADATA +2 -2
  23. {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/RECORD +27 -27
  24. {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/LICENSE +0 -0
  25. {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/WHEEL +0 -0
  26. {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/entry_points.txt +0 -0
  27. {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@ import logging
2
2
 
3
3
  import mongoengine
4
4
  from flask import abort, request, url_for
5
+ from flask_login import current_user
5
6
  from flask_restx import marshal
6
7
 
7
8
  from udata import search
@@ -11,7 +12,7 @@ from udata.core.organization.api_fields import member_user_with_email_fields
11
12
  from udata.core.spatial.api_fields import geojson
12
13
  from udata.utils import get_by
13
14
 
14
- from .api import ResourceMixin
15
+ from .api import DEFAULT_SORTING, DatasetApiParser, ResourceMixin
15
16
  from .api_fields import (
16
17
  badge_fields,
17
18
  catalog_schema_fields,
@@ -128,7 +129,7 @@ dataset_fields = apiv2.model(
128
129
  _external=True,
129
130
  ),
130
131
  "type": "GET",
131
- "total": len(o.resources),
132
+ "total": o.resources_len, # :ResourcesLengthProperty may call MongoDB to fetch the length if resources were not fetched
132
133
  },
133
134
  description="Link to the dataset resources",
134
135
  ),
@@ -155,7 +156,7 @@ dataset_fields = apiv2.model(
155
156
  ),
156
157
  "frequency_date": fields.ISODateTime(
157
158
  description=(
158
- "Next expected update date, you will be notified " "once that date is reached."
159
+ "Next expected update date, you will be notified once that date is reached."
159
160
  )
160
161
  ),
161
162
  "harvest": fields.Nested(
@@ -276,7 +277,28 @@ class DatasetSearchAPI(API):
276
277
  abort(500, "Internal search service error")
277
278
 
278
279
 
279
- @ns.route("/<dataset:dataset>/", endpoint="dataset", doc=common_doc)
280
+ dataset_parser = DatasetApiParser()
281
+
282
+
283
+ @ns.route("/", endpoint="datasets")
284
+ class DatasetListAPI(API):
285
+ """Datasets collection endpoint"""
286
+
287
+ @apiv2.doc("list_datasets")
288
+ @apiv2.expect(dataset_parser.parser)
289
+ @apiv2.marshal_with(dataset_page_fields)
290
+ def get(self):
291
+ """List or search all datasets"""
292
+ args = dataset_parser.parse()
293
+ datasets = Dataset.objects.exclude("resources").visible_by_user(
294
+ current_user, mongoengine.Q(private__ne=True, archived=None, deleted=None)
295
+ )
296
+ datasets = dataset_parser.parse_filters(datasets, args)
297
+ sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
298
+ return datasets.order_by(sort).paginate(args["page"], args["page_size"])
299
+
300
+
301
+ @ns.route("/<dataset_without_resources:dataset>/", endpoint="dataset", doc=common_doc)
280
302
  @apiv2.response(404, "Dataset not found")
281
303
  @apiv2.response(410, "Dataset has been deleted")
282
304
  class DatasetAPI(API):
@@ -2,6 +2,7 @@ import logging
2
2
  import re
3
3
  from datetime import datetime, timedelta
4
4
  from pydoc import locate
5
+ from typing import Self
5
6
  from urllib.parse import urlparse
6
7
 
7
8
  import requests
@@ -11,7 +12,7 @@ from flask import current_app
11
12
  from mongoengine import DynamicEmbeddedDocument
12
13
  from mongoengine import ValidationError as MongoEngineValidationError
13
14
  from mongoengine.fields import DateTimeField
14
- from mongoengine.signals import post_save, pre_save
15
+ from mongoengine.signals import post_save, pre_init, pre_save
15
16
  from stringdist import rdlevenshtein
16
17
  from werkzeug.utils import cached_property
17
18
 
@@ -617,6 +618,31 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
617
618
 
618
619
  verbose_name = _("dataset")
619
620
 
621
+ missing_resources = False
622
+
623
+ @cached_property
624
+ def resources_len(self):
625
+ # :ResourcesLengthProperty
626
+ # If we've excluded the resources from the Mongo request we need
627
+ # to do a new Mongo request to fetch the resources length manually.
628
+ # If the resources are already present, just return the `len()` of the array.
629
+ if not self.missing_resources:
630
+ return len(self.resources)
631
+
632
+ pipeline = [{"$project": {"_id": 1, "resources_len": {"$size": "$resources"}}}]
633
+ data = Dataset.objects(id=self.id).aggregate(pipeline)
634
+
635
+ return next(data)["resources_len"]
636
+
637
+ @classmethod
638
+ def pre_init(cls, sender, document: Self, values, **kwargs):
639
+ # MongoEngine loses the information about raw values during the __init__ function
640
+ # Here we catch the raw values from the database (or from the creation of the object)
641
+ # and we check if resources were returned (sometimes we exclude `resources` from the
642
+ # Mongo request to improve perfs)
643
+ # This is used in :ResourcesLengthProperty
644
+ document.missing_resources = "resources" not in values
645
+
620
646
  @classmethod
621
647
  def pre_save(cls, sender, document, **kwargs):
622
648
  cls.before_save.send(document)
@@ -941,9 +967,17 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
941
967
  "url": endpoint_for("datasets.show", "api.dataset", dataset=self, _external=True),
942
968
  "name": self.title,
943
969
  "keywords": ",".join(self.tags),
944
- "distribution": [resource.json_ld for resource in self.resources],
970
+ "distribution": [
971
+ resource.json_ld
972
+ for resource in self.resources[: current_app.config["MAX_RESOURCES_IN_JSON_LD"]]
973
+ ],
945
974
  # Theses values are not standard
946
- "contributedDistribution": [resource.json_ld for resource in self.community_resources],
975
+ "contributedDistribution": [
976
+ resource.json_ld
977
+ for resource in self.community_resources[
978
+ : current_app.config["MAX_RESOURCES_IN_JSON_LD"]
979
+ ]
980
+ ],
947
981
  "extras": [get_json_ld_extra(*item) for item in self.extras.items()],
948
982
  }
949
983
 
@@ -995,6 +1029,7 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
995
1029
  self.save()
996
1030
 
997
1031
 
1032
+ pre_init.connect(Dataset.pre_init, sender=Dataset)
998
1033
  pre_save.connect(Dataset.pre_save, sender=Dataset)
999
1034
  post_save.connect(Dataset.post_save, sender=Dataset)
1000
1035
 
udata/routing.py CHANGED
@@ -77,6 +77,9 @@ class ModelConverter(BaseConverter):
77
77
  def has_redirected_slug(self):
78
78
  return self.has_slug and self.model.slug.follow
79
79
 
80
+ def get_excludes(self):
81
+ return []
82
+
80
83
  def quote(self, value):
81
84
  if self.has_slug:
82
85
  return self.model.slug.slugify(value)
@@ -85,13 +88,13 @@ class ModelConverter(BaseConverter):
85
88
 
86
89
  def to_python(self, value):
87
90
  try:
88
- return self.model.objects.get_or_404(id=value)
91
+ return self.model.objects.exclude(*self.get_excludes()).get_or_404(id=value)
89
92
  except (NotFound, ValidationError):
90
93
  pass
91
94
  try:
92
95
  quoted = self.quote(value)
93
96
  query = db.Q(slug=value) | db.Q(slug=quoted)
94
- obj = self.model.objects(query).get()
97
+ obj = self.model.objects(query).exclude(*self.get_excludes()).get()
95
98
  except (InvalidQueryError, self.model.DoesNotExist):
96
99
  # If the model doesn't have a slug or matching slug doesn't exist.
97
100
  if self.has_redirected_slug:
@@ -121,6 +124,13 @@ class DatasetConverter(ModelConverter):
121
124
  model = models.Dataset
122
125
 
123
126
 
127
+ class DatasetWithoutResourcesConverter(ModelConverter):
128
+ model = models.Dataset
129
+
130
+ def get_excludes(self):
131
+ return ["resources"]
132
+
133
+
124
134
  class DataserviceConverter(ModelConverter):
125
135
  model = Dataservice
126
136
 
@@ -226,6 +236,7 @@ def init_app(app):
226
236
  app.url_map.converters["pathlist"] = PathListConverter
227
237
  app.url_map.converters["uuid"] = UUIDConverter
228
238
  app.url_map.converters["dataset"] = DatasetConverter
239
+ app.url_map.converters["dataset_without_resources"] = DatasetWithoutResourcesConverter
229
240
  app.url_map.converters["dataservice"] = DataserviceConverter
230
241
  app.url_map.converters["crid"] = CommunityResourceConverter
231
242
  app.url_map.converters["org"] = OrganizationConverter
udata/settings.py CHANGED
@@ -570,6 +570,10 @@ class Defaults(object):
570
570
  ###########################################################################
571
571
  TABULAR_API_DATASERVICE_ID = None
572
572
 
573
+ # JSON-LD settings
574
+ ###########################################################################
575
+ MAX_RESOURCES_IN_JSON_LD = 20
576
+
573
577
 
574
578
  class Testing(object):
575
579
  """Sane values for testing. Should be applied as override"""