udata 11.0.2.dev16__py3-none-any.whl → 11.0.2.dev18__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 (29) hide show
  1. udata/api_fields.py +99 -45
  2. udata/core/dataservices/models.py +4 -4
  3. udata/core/pages/models.py +2 -2
  4. udata/core/reports/models.py +2 -2
  5. udata/core/reuse/models.py +4 -4
  6. udata/mail.py +1 -1
  7. udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.0f04e49a40a0a381bcce.js} +3 -3
  8. udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.0f04e49a40a0a381bcce.js.map} +1 -1
  9. udata/static/chunks/{13.39e106d56f794ebd06a0.js → 13.d9c1735d14038b94c17e.js} +2 -2
  10. udata/static/chunks/{13.39e106d56f794ebd06a0.js.map → 13.d9c1735d14038b94c17e.js.map} +1 -1
  11. udata/static/chunks/{17.70cbb4a91b002338007e.js → 17.81c57c0dedf812e43013.js} +2 -2
  12. udata/static/chunks/{17.70cbb4a91b002338007e.js.map → 17.81c57c0dedf812e43013.js.map} +1 -1
  13. udata/static/chunks/{19.a348a5fff8fe2801e52a.js → 19.8da42e8359d72afc2618.js} +3 -3
  14. udata/static/chunks/{19.a348a5fff8fe2801e52a.js.map → 19.8da42e8359d72afc2618.js.map} +1 -1
  15. udata/static/chunks/{5.343ca020a2d38cec1a14.js → 5.0fa1408dae4e76b87b2e.js} +3 -3
  16. udata/static/chunks/{5.343ca020a2d38cec1a14.js.map → 5.0fa1408dae4e76b87b2e.js.map} +1 -1
  17. udata/static/chunks/{6.a3b07de9dd2ca2d24e85.js → 6.d663709d877baa44a71e.js} +3 -3
  18. udata/static/chunks/{6.a3b07de9dd2ca2d24e85.js.map → 6.d663709d877baa44a71e.js.map} +1 -1
  19. udata/static/chunks/{8.462bb3029de008497675.js → 8.494b003a94383b142c18.js} +2 -2
  20. udata/static/chunks/{8.462bb3029de008497675.js.map → 8.494b003a94383b142c18.js.map} +1 -1
  21. udata/static/common.js +1 -1
  22. udata/static/common.js.map +1 -1
  23. udata/tests/test_api_fields.py +2 -2
  24. {udata-11.0.2.dev16.dist-info → udata-11.0.2.dev18.dist-info}/METADATA +1 -1
  25. {udata-11.0.2.dev16.dist-info → udata-11.0.2.dev18.dist-info}/RECORD +29 -29
  26. {udata-11.0.2.dev16.dist-info → udata-11.0.2.dev18.dist-info}/WHEEL +0 -0
  27. {udata-11.0.2.dev16.dist-info → udata-11.0.2.dev18.dist-info}/entry_points.txt +0 -0
  28. {udata-11.0.2.dev16.dist-info → udata-11.0.2.dev18.dist-info}/licenses/LICENSE +0 -0
  29. {udata-11.0.2.dev16.dist-info → udata-11.0.2.dev18.dist-info}/top_level.txt +0 -0
udata/api_fields.py CHANGED
@@ -1,39 +1,24 @@
1
- """Enhance a MongoEngine document class to give it super powers by decorating it with @generate_fields.
1
+ """API field generation and metadata management for MongoEngine documents.
2
2
 
3
- The main goal of `generate_fields` is to remove duplication: we used to have fields declaration in
4
- - models.py
5
- - forms.py
6
- - api_fields.py
3
+ This module provides tools to automatically generate Flask-RESTX fields from MongoEngine
4
+ documents, reducing duplication between model definitions and API serialization.
7
5
 
8
- Now they're defined in models.py, and adding the `generate_fields` decorator makes them available in the format we need them for the forms or the API.
6
+ Main components:
7
+ - `@generate_fields`: Decorator that adds API field generation to document classes
8
+ - `field()`: Universal function to add metadata to fields and methods
9
9
 
10
- - default_filterable_field: which field in this document should be the default filter, eg when filtering by Badge, you're actually filtering on `Badge.kind`
11
- - searchable: boolean, if True, the document can be full-text searched using MongoEngine text search
12
- - additional_sorts: add more sorts than the already available ones based on fields (see below). Eg, sort by metrics.
13
- - additional_filters: filter on a field of a field (aka "join"), eg filter on `Reuse__organization__badge=PUBLIC_SERVICE`.
10
+ The `@generate_fields` decorator parameters:
11
+ - default_filterable_field: Default field for filtering (e.g., Badge.kind)
12
+ - searchable: Enables full-text search with param `q` via MongoEngine on indexed text fields
13
+ - additional_sorts: Custom sort options beyond field-based sorts
14
+ - additional_filters: Cross-document filtering (e.g., Reuse__organization__badge)
14
15
 
16
+ Generated attributes on decorated classes:
17
+ - ref_fields: Minimal fields for embedded/referenced documents
18
+ - read_fields: All fields returned when querying a document
19
+ - write_fields: Fields accepted when creating/updating a document
15
20
 
16
- On top of those functionalities added to the document by the `@generate_fields` decorator parameters,
17
- the document fields are parsed and enhanced if they are wrapped in the `field` helper.
18
-
19
- - sortable: boolean, if True, it'll be available in the list of sort options
20
- - show_as_ref: add to the list of `ref_fields` (see below)
21
- - readonly: don't add this field to the `write_fields`
22
- - markdown: use Mardown to format this field instead of plain old text
23
- - filterable: this field can be filtered on. It's either an empty dictionnary, either {`key`: `field_name`} if the `field_name` to use is different from the original field, eg `dataset` instead of `datasets`.
24
- - description: use as the info on the field in the swagger forms.
25
- - check: provide a function to validate the content of the field.
26
- - thumbnail_info: add additional info for a thumbnail, eg `{ "size": BIGGEST_IMAGE_SIZE }`.
27
-
28
- You may also use the `@function_field` decorator to treat a document method as a field.
29
-
30
-
31
- The following fields are added on the document class once decorated:
32
-
33
- - ref_fields: list of fields to return when embedding/referencing a document, eg when querying Reuse.organization, only return a subset of the org fields
34
- - read_fields: all of the fields to return when querying a document
35
- - write_fields: list of fields to provide when creating a document, eg when creating a Reuse, we only provide organization IDs, not all the org fields
36
-
21
+ For field-specific metadata, see the `field()` function documentation.
37
22
  """
38
23
 
39
24
  import functools
@@ -401,7 +386,7 @@ def generate_fields(**kwargs) -> Callable:
401
386
 
402
387
  # The goal of this loop is to fetch all functions (getters) of the class
403
388
  # If a function has an `__additional_field_info__` attribute it means
404
- # it has been decorated with `@function_field()` and should be included
389
+ # it has been decorated with `@field()` and should be included
405
390
  # in the API response.
406
391
  for method_name in dir(cls):
407
392
  if method_name == "objects":
@@ -558,22 +543,91 @@ def generate_fields(**kwargs) -> Callable:
558
543
  return wrapper
559
544
 
560
545
 
561
- def function_field(**info) -> Callable:
562
- def inner(func):
563
- func.__additional_field_info__ = info
564
- return func
565
-
566
- return inner
567
-
546
+ def field(
547
+ inner=None,
548
+ sortable: bool | str | None = None,
549
+ filterable: dict[str, Any] | None = None,
550
+ readonly: bool | None = None,
551
+ show_as_ref: bool | None = None,
552
+ markdown: bool | None = None,
553
+ description: str | None = None,
554
+ auditable: bool | None = None,
555
+ checks: list[Callable] | None = None,
556
+ attribute: str | None = None,
557
+ thumbnail_info: dict[str, Any] | None = None,
558
+ example: str | None = None,
559
+ nested_fields: dict[str, Any] | None = None,
560
+ inner_field_info: dict[str, Any] | None = None,
561
+ size: int | None = None,
562
+ is_thumbnail: bool | None = None,
563
+ href: Callable | None = None,
564
+ generic: bool | None = None,
565
+ generic_key: str | None = None,
566
+ convert_to: Callable | None = None,
567
+ allow_null: bool | None = None,
568
+ **kwargs: Any, # Accept any additional parameters, forward to flask rest x constructor.
569
+ ):
570
+ """Universal field decorator/wrapper for API field metadata.
571
+
572
+ Can be used in two ways:
573
+
574
+ 1. As a wrapper for MongoEngine fields:
575
+ title = field(db.StringField(required=True),
576
+ sortable=True,
577
+ description="The title of the item")
578
+
579
+ 2. As a decorator for computed fields:
580
+ @field(description="Link to the API endpoint", show_as_ref=True)
581
+ def uri(self):
582
+ return f"/api/items/{self.id}"
583
+
584
+ Args:
585
+ inner: The MongoEngine field to wrap (or None when used as decorator)
586
+ sortable: If True, field can be sorted. If str, use as custom sort key
587
+ filterable: Filter configuration dict
588
+ readonly: If True, exclude from write_fields
589
+ show_as_ref: If True, include in ref_fields
590
+ markdown: If True, use Markdown formatter
591
+ description: Field description for Swagger
592
+ auditable: If False, exclude from audit trail
593
+ checks: List of validation functions
594
+ attribute: Custom attribute name for serialization
595
+ thumbnail_info: Thumbnail configuration dict
596
+ example: Example value for documentation
597
+ nested_fields: RestX model for nested objects
598
+ inner_field_info: Additional info for list inner fields
599
+ size: Image size for thumbnails
600
+ is_thumbnail: If True, this is a thumbnail field
601
+ href: Function to generate API link
602
+ generic: If True, handle generic embedded documents
603
+ generic_key: Key for generic type discrimination
604
+ convert_to: Custom converter for RestX
605
+ allow_null: If True, field can be null
606
+ **kwargs: Any additional parameters not explicitly defined
607
+
608
+ Returns:
609
+ When used as wrapper: The field with __additional_field_info__ attached.
610
+ When used as decorator: A decorator function.
611
+ """
612
+ # Build field_info from non-None parameters, excluding 'inner' and 'kwargs'
613
+ field_info = {
614
+ k: v for k, v in locals().items() if v is not None and k not in ("inner", "kwargs")
615
+ }
568
616
 
569
- def field(inner, **kwargs):
570
- """Simple wrapper to make a document field visible for the API.
617
+ # Add any extra kwargs passed
618
+ field_info.update(kwargs)
571
619
 
572
- We can pass additional arguments that will be forwarded to the RestX field constructor.
620
+ if inner is None:
621
+ # Used as a decorator for methods
622
+ def decorator(func):
623
+ func.__additional_field_info__ = field_info
624
+ return func
573
625
 
574
- """
575
- inner.__additional_field_info__ = kwargs
576
- return inner
626
+ return decorator
627
+ else:
628
+ # Used as a field wrapper
629
+ inner.__additional_field_info__ = field_info
630
+ return inner
577
631
 
578
632
 
579
633
  def patch(obj, request) -> type:
@@ -7,7 +7,7 @@ from mongoengine.signals import post_save
7
7
 
8
8
  import udata.core.contact_point.api_fields as contact_api_fields
9
9
  from udata.api import api, fields
10
- from udata.api_fields import field, function_field, generate_fields
10
+ from udata.api_fields import field, generate_fields
11
11
  from udata.core.activity.models import Auditable
12
12
  from udata.core.dataservices.constants import (
13
13
  DATASERVICE_ACCESS_AUDIENCE_CONDITIONS,
@@ -284,7 +284,7 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
284
284
  auditable=False,
285
285
  )
286
286
 
287
- @function_field(description="Link to the API endpoint for this dataservice")
287
+ @field(description="Link to the API endpoint for this dataservice")
288
288
  def self_api_url(self, **kwargs):
289
289
  return url_for(
290
290
  "api.dataservice",
@@ -292,7 +292,7 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
292
292
  **self._self_api_url_kwargs(**kwargs),
293
293
  )
294
294
 
295
- @function_field(description="Link to the udata web page for this dataservice", show_as_ref=True)
295
+ @field(description="Link to the udata web page for this dataservice", show_as_ref=True)
296
296
  def self_web_url(self, **kwargs):
297
297
  return cdata_url(f"/dataservices/{self._link_id(**kwargs)}/", **kwargs)
298
298
 
@@ -309,7 +309,7 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
309
309
  return self.private or self.deleted_at or self.archived_at
310
310
 
311
311
  @property
312
- @function_field(
312
+ @field(
313
313
  nested_fields=dataservice_permissions_fields,
314
314
  )
315
315
  def permissions(self):
@@ -1,5 +1,5 @@
1
1
  from udata.api import api, fields
2
- from udata.api_fields import field, function_field, generate_fields
2
+ from udata.api_fields import field, generate_fields
3
3
  from udata.core.activity.models import Auditable
4
4
  from udata.core.dataservices.models import Dataservice
5
5
  from udata.core.dataset.api_fields import dataset_fields
@@ -91,7 +91,7 @@ class Page(Auditable, Owned, Datetimed, db.Document):
91
91
  )
92
92
 
93
93
  @property
94
- @function_field(
94
+ @field(
95
95
  nested_fields=page_permissions_fields,
96
96
  )
97
97
  def permissions(self):
@@ -4,7 +4,7 @@ from bson import DBRef
4
4
  from flask import url_for
5
5
  from mongoengine import DO_NOTHING, NULLIFY, signals
6
6
 
7
- from udata.api_fields import field, function_field, generate_fields
7
+ from udata.api_fields import field, generate_fields
8
8
  from udata.core.user.api_fields import user_ref_fields
9
9
  from udata.core.user.models import User
10
10
  from udata.mongo import db
@@ -46,7 +46,7 @@ class Report(db.Document):
46
46
  readonly=True,
47
47
  )
48
48
 
49
- @function_field(description="Link to the API endpoint for this report")
49
+ @field(description="Link to the API endpoint for this report")
50
50
  def self_api_url(self):
51
51
  return url_for("api.report", report=self, _external=True)
52
52
 
@@ -3,7 +3,7 @@ from flask import url_for
3
3
  from mongoengine.signals import post_save, pre_save
4
4
  from werkzeug.utils import cached_property
5
5
 
6
- from udata.api_fields import field, function_field, generate_fields
6
+ from udata.api_fields import field, generate_fields
7
7
  from udata.core.activity.models import Auditable
8
8
  from udata.core.dataset.api_fields import dataset_fields
9
9
  from udata.core.linkable import Linkable
@@ -197,16 +197,16 @@ class Reuse(db.Datetimed, Auditable, WithMetrics, ReuseBadgeMixin, Linkable, Own
197
197
  "api.reuse", reuse=self._link_id(**kwargs), **self._self_api_url_kwargs(**kwargs)
198
198
  )
199
199
 
200
- @function_field(description="Link to the API endpoint for this reuse", show_as_ref=True)
200
+ @field(description="Link to the API endpoint for this reuse", show_as_ref=True)
201
201
  def uri(self, *args, **kwargs):
202
202
  return self.self_api_url(*args, **kwargs)
203
203
 
204
- @function_field(description="Link to the udata web page for this reuse", show_as_ref=True)
204
+ @field(description="Link to the udata web page for this reuse", show_as_ref=True)
205
205
  def page(self, *args, **kwargs):
206
206
  return self.self_web_url(*args, **kwargs)
207
207
 
208
208
  @property
209
- @function_field(
209
+ @field(
210
210
  nested_fields=reuse_permissions_fields,
211
211
  )
212
212
  def permissions(self):
udata/mail.py CHANGED
@@ -49,7 +49,7 @@ def send(subject, recipients, template_base, **kwargs):
49
49
 
50
50
  debug = current_app.config.get("DEBUG", False)
51
51
  send_mail = current_app.config.get("SEND_MAIL", not debug)
52
- connection = send_mail and mail.connect or dummyconnection
52
+ connection = mail.connect if send_mail else dummyconnection
53
53
  extras = get_mail_campaign_dict()
54
54
 
55
55
  with connection() as conn: