udata 9.2.5.dev32190__py2.py3-none-any.whl → 9.2.5.dev32222__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 (30) hide show
  1. udata/api/__init__.py +1 -0
  2. udata/core/dataservices/api.py +1 -1
  3. udata/core/dataservices/apiv2.py +29 -0
  4. udata/core/dataservices/search.py +118 -0
  5. udata/core/dataset/search.py +2 -3
  6. udata/core/organization/search.py +2 -3
  7. udata/core/reuse/search.py +2 -3
  8. udata/search/query.py +4 -2
  9. udata/static/chunks/{10.dac55d18d0b4ef3cdacf.js → 10.a99bb538cfbadb38dbcb.js} +3 -3
  10. udata/static/chunks/{10.dac55d18d0b4ef3cdacf.js.map → 10.a99bb538cfbadb38dbcb.js.map} +1 -1
  11. udata/static/chunks/{11.4a20a75f827c5a1125c3.js → 11.bb1c1fb39f740fbbeec0.js} +3 -3
  12. udata/static/chunks/{11.4a20a75f827c5a1125c3.js.map → 11.bb1c1fb39f740fbbeec0.js.map} +1 -1
  13. udata/static/chunks/{13.645dd0b7c0b9210f1b56.js → 13.bef5fdb3e147e94fea99.js} +2 -2
  14. udata/static/chunks/{13.645dd0b7c0b9210f1b56.js.map → 13.bef5fdb3e147e94fea99.js.map} +1 -1
  15. udata/static/chunks/{17.8e19985c4d12a3b7b0c0.js → 17.b91d28f550dc44bc4979.js} +2 -2
  16. udata/static/chunks/{17.8e19985c4d12a3b7b0c0.js.map → 17.b91d28f550dc44bc4979.js.map} +1 -1
  17. udata/static/chunks/{19.825a43c330157e351fca.js → 19.2c615ffee1e807000770.js} +3 -3
  18. udata/static/chunks/{19.825a43c330157e351fca.js.map → 19.2c615ffee1e807000770.js.map} +1 -1
  19. udata/static/chunks/{8.5ee0cf635c848abbfc05.js → 8.291bde987ed97294e4de.js} +2 -2
  20. udata/static/chunks/{8.5ee0cf635c848abbfc05.js.map → 8.291bde987ed97294e4de.js.map} +1 -1
  21. udata/static/chunks/{9.df3c36f8d0d210621fbb.js → 9.985935421e62c97a9f86.js} +3 -3
  22. udata/static/chunks/{9.df3c36f8d0d210621fbb.js.map → 9.985935421e62c97a9f86.js.map} +1 -1
  23. udata/static/common.js +1 -1
  24. udata/static/common.js.map +1 -1
  25. {udata-9.2.5.dev32190.dist-info → udata-9.2.5.dev32222.dist-info}/METADATA +3 -1
  26. {udata-9.2.5.dev32190.dist-info → udata-9.2.5.dev32222.dist-info}/RECORD +30 -28
  27. {udata-9.2.5.dev32190.dist-info → udata-9.2.5.dev32222.dist-info}/LICENSE +0 -0
  28. {udata-9.2.5.dev32190.dist-info → udata-9.2.5.dev32222.dist-info}/WHEEL +0 -0
  29. {udata-9.2.5.dev32190.dist-info → udata-9.2.5.dev32222.dist-info}/entry_points.txt +0 -0
  30. {udata-9.2.5.dev32190.dist-info → udata-9.2.5.dev32222.dist-info}/top_level.txt +0 -0
udata/api/__init__.py CHANGED
@@ -303,6 +303,7 @@ def init_app(app):
303
303
  import udata.core.dataset.api # noqa
304
304
  import udata.core.dataset.apiv2 # noqa
305
305
  import udata.core.dataservices.api # noqa
306
+ import udata.core.dataservices.apiv2 # noqa
306
307
  import udata.core.discussions.api # noqa
307
308
  import udata.core.reuse.api # noqa
308
309
  import udata.core.reuse.apiv2 # noqa
@@ -7,8 +7,8 @@ from flask_login import current_user
7
7
 
8
8
  from udata.api import API, api, fields
9
9
  from udata.api_fields import patch
10
+ from udata.core.dataservices.permissions import OwnablePermission
10
11
  from udata.core.dataset.models import Dataset
11
- from udata.core.dataset.permissions import OwnablePermission
12
12
  from udata.core.followers.api import FollowAPI
13
13
  from udata.rdf import RDF_EXTENSIONS, graph_response, negociate_content
14
14
 
@@ -0,0 +1,29 @@
1
+ from flask import request
2
+
3
+ from udata import search
4
+ from udata.api import API, apiv2
5
+ from udata.core.dataservices.models import Dataservice, HarvestMetadata
6
+ from udata.utils import multi_to_dict
7
+
8
+ from .search import DataserviceSearch
9
+
10
+ apiv2.inherit("DataservicePage", Dataservice.__page_fields__)
11
+ apiv2.inherit("Dataservice (read)", Dataservice.__read_fields__)
12
+ apiv2.inherit("HarvestMetadata (read)", HarvestMetadata.__read_fields__)
13
+
14
+ ns = apiv2.namespace("dataservices", "Dataservice related operations")
15
+
16
+ search_parser = DataserviceSearch.as_request_parser()
17
+
18
+
19
+ @ns.route("/search/", endpoint="dataservice_search")
20
+ class DataserviceSearchAPI(API):
21
+ """Dataservices collection search endpoint"""
22
+
23
+ @apiv2.doc("search_dataservices")
24
+ @apiv2.expect(search_parser)
25
+ @apiv2.marshal_with(Dataservice.__page_fields__)
26
+ def get(self):
27
+ """Search all dataservices"""
28
+ search_parser.parse_args()
29
+ return search.query(DataserviceSearch, **multi_to_dict(request.args))
@@ -0,0 +1,118 @@
1
+ import datetime
2
+
3
+ from bson.objectid import ObjectId
4
+ from flask_restx.inputs import boolean
5
+
6
+ from udata.api import api
7
+ from udata.api.parsers import ModelApiParser
8
+ from udata.models import Dataservice, Organization, User
9
+ from udata.search import (
10
+ BoolFilter,
11
+ Filter,
12
+ ModelSearchAdapter,
13
+ ModelTermsFilter,
14
+ register,
15
+ )
16
+ from udata.utils import to_iso_datetime
17
+
18
+ __all__ = ("DataserviceSearch",)
19
+
20
+ DEFAULT_SORTING = "-created_at"
21
+
22
+
23
+ class DataserviceApiParser(ModelApiParser):
24
+ sorts = {
25
+ "created": "created_at",
26
+ }
27
+
28
+ def __init__(self):
29
+ super().__init__()
30
+ self.parser.add_argument("tag", type=str, location="args")
31
+ self.parser.add_argument("organization", type=str, location="args")
32
+ self.parser.add_argument("is_restricted", type=bool, location="args")
33
+
34
+ @staticmethod
35
+ def parse_filters(dataservices, args):
36
+ if args.get("q"):
37
+ # Following code splits the 'q' argument by spaces to surround
38
+ # every word in it with quotes before rebuild it.
39
+ # This allows the search_text method to tokenise with an AND
40
+ # between tokens whereas an OR is used without it.
41
+ phrase_query = " ".join([f'"{elem}"' for elem in args["q"].split(" ")])
42
+ dataservices = dataservices.search_text(phrase_query)
43
+ if args.get("tag"):
44
+ dataservices = dataservices.filter(tags=args["tag"])
45
+ if args.get("organization"):
46
+ if not ObjectId.is_valid(args["organization"]):
47
+ api.abort(400, "Organization arg must be an identifier")
48
+ dataservices = dataservices.filter(organization=args["organization"])
49
+ if "is_restricted" in args:
50
+ dataservices = dataservices.filter(is_restricted=boolean(args["is_restricted"]))
51
+ return dataservices
52
+
53
+
54
+ @register
55
+ class DataserviceSearch(ModelSearchAdapter):
56
+ model = Dataservice
57
+ search_url = "dataservices/"
58
+
59
+ sorts = {
60
+ "created": "created_at",
61
+ }
62
+
63
+ filters = {
64
+ "tag": Filter(),
65
+ "organization": ModelTermsFilter(model=Organization),
66
+ "archived": BoolFilter(),
67
+ }
68
+
69
+ @classmethod
70
+ def is_indexable(cls, dataservice: Dataservice) -> bool:
71
+ return dataservice.deleted_at is None and not dataservice.private
72
+
73
+ @classmethod
74
+ def mongo_search(cls, args):
75
+ dataservices = Dataservice.objects.visible()
76
+ dataservices = DataserviceApiParser.parse_filters(dataservices, args)
77
+
78
+ sort = (
79
+ cls.parse_sort(args["sort"])
80
+ or ("$text_score" if args["q"] else None)
81
+ or DEFAULT_SORTING
82
+ )
83
+ return dataservices.order_by(sort).paginate(args["page"], args["page_size"])
84
+
85
+ @classmethod
86
+ def serialize(cls, dataservice: Dataservice) -> dict:
87
+ organization = None
88
+ owner = None
89
+ if dataservice.organization:
90
+ org = Organization.objects(id=dataservice.organization.id).first()
91
+ organization = {
92
+ "id": str(org.id),
93
+ "name": org.name,
94
+ "public_service": 1 if org.public_service else 0,
95
+ "followers": org.metrics.get("followers", 0),
96
+ }
97
+ elif dataservice.owner:
98
+ owner = User.objects(id=dataservice.owner.id).first()
99
+ extras = {}
100
+ for key, value in dataservice.extras.items():
101
+ extras[key] = to_iso_datetime(value) if isinstance(value, datetime.datetime) else value
102
+
103
+ return {
104
+ "id": str(dataservice.id),
105
+ "title": dataservice.title,
106
+ "description": dataservice.description,
107
+ "base_api_url": dataservice.base_api_url,
108
+ "created_at": to_iso_datetime(dataservice.created_at),
109
+ "archived": to_iso_datetime(dataservice.archived_at)
110
+ if dataservice.archived_at
111
+ else None,
112
+ "organization": organization,
113
+ "owner": str(owner.id) if owner else None,
114
+ "tags": dataservice.tags,
115
+ "extras": extras,
116
+ "followers": dataservice.metrics.get("followers", 0),
117
+ "is_restricted": dataservice.is_restricted or False,
118
+ }
@@ -52,7 +52,7 @@ class DatasetSearch(ModelSearchAdapter):
52
52
 
53
53
  @classmethod
54
54
  def mongo_search(cls, args):
55
- datasets = Dataset.objects(archived=None, deleted=None, private=False)
55
+ datasets = Dataset.objects.visible()
56
56
  datasets = DatasetApiParser.parse_filters(datasets, args)
57
57
 
58
58
  sort = (
@@ -60,8 +60,7 @@ class DatasetSearch(ModelSearchAdapter):
60
60
  or ("$text_score" if args["q"] else None)
61
61
  or DEFAULT_SORTING
62
62
  )
63
- offset = (args["page"] - 1) * args["page_size"]
64
- return datasets.order_by(sort).skip(offset).limit(args["page_size"]), datasets.count()
63
+ return datasets.order_by(sort).paginate(args["page"], args["page_size"])
65
64
 
66
65
  @classmethod
67
66
  def serialize(cls, dataset):
@@ -34,7 +34,7 @@ class OrganizationSearch(search.ModelSearchAdapter):
34
34
 
35
35
  @classmethod
36
36
  def mongo_search(cls, args):
37
- orgs = Organization.objects(deleted=None)
37
+ orgs = Organization.objects.visible()
38
38
  orgs = OrgApiParser.parse_filters(orgs, args)
39
39
 
40
40
  sort = (
@@ -42,8 +42,7 @@ class OrganizationSearch(search.ModelSearchAdapter):
42
42
  or ("$text_score" if args["q"] else None)
43
43
  or DEFAULT_SORTING
44
44
  )
45
- offset = (args["page"] - 1) * args["page_size"]
46
- return orgs.order_by(sort).skip(offset).limit(args["page_size"]), orgs.count()
45
+ return orgs.order_by(sort).paginate(args["page"], args["page_size"])
47
46
 
48
47
  @classmethod
49
48
  def serialize(cls, organization):
@@ -44,7 +44,7 @@ class ReuseSearch(ModelSearchAdapter):
44
44
 
45
45
  @classmethod
46
46
  def mongo_search(cls, args):
47
- reuses = Reuse.objects(deleted=None, private__ne=True)
47
+ reuses = Reuse.objects.visible()
48
48
  reuses = ReuseApiParser.parse_filters(reuses, args)
49
49
 
50
50
  sort = (
@@ -52,8 +52,7 @@ class ReuseSearch(ModelSearchAdapter):
52
52
  or ("$text_score" if args["q"] else None)
53
53
  or DEFAULT_SORTING
54
54
  )
55
- offset = (args["page"] - 1) * args["page_size"]
56
- return reuses.order_by(sort).skip(offset).limit(args["page_size"]), reuses.count()
55
+ return reuses.order_by(sort).paginate(args["page"], args["page_size"])
57
56
 
58
57
  @classmethod
59
58
  def serialize(cls, reuse: Reuse) -> dict:
udata/search/query.py CHANGED
@@ -54,8 +54,10 @@ class SearchQuery:
54
54
  "sort": self.sort,
55
55
  }
56
56
  query_args.update(self._filters)
57
- result, total = self.adapter.mongo_search(query_args)
58
- return SearchResult(query=self, mongo_objects=result, total=total, **query_args)
57
+ result = self.adapter.mongo_search(query_args)
58
+ return SearchResult(
59
+ query=self, mongo_objects=list(result), total=result.total, **query_args
60
+ )
59
61
 
60
62
  def to_url(self, url=None, replace=False, **kwargs):
61
63
  """Serialize the query into an URL"""