udata 10.0.1.dev32359__py2.py3-none-any.whl → 10.0.1.dev32405__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.

udata/api_fields.py CHANGED
@@ -94,6 +94,8 @@ def convert_db_to_field(key, field, info) -> tuple[Callable | None, Callable | N
94
94
  params["min_length"] = field.min_length
95
95
  params["max_length"] = field.max_length
96
96
  params["enum"] = field.choices
97
+ if field.validation:
98
+ params["validation"] = validation_to_type(field.validation)
97
99
  elif isinstance(field, mongo_fields.ObjectIdField):
98
100
  constructor = restx_fields.String
99
101
  elif isinstance(field, mongo_fields.FloatField):
@@ -287,7 +289,7 @@ def generate_fields(**kwargs) -> Callable:
287
289
  if not isinstance(
288
290
  field, mongo_fields.ReferenceField | mongo_fields.LazyReferenceField
289
291
  ):
290
- raise Exception("Cannot use additional_filters on not a ref.")
292
+ raise Exception("Cannot use additional_filters on a field that is not a ref.")
291
293
 
292
294
  ref_model: db.Document = field.document_type
293
295
 
@@ -678,9 +680,34 @@ def compute_filter(column: str, field, info, filterable) -> dict:
678
680
  filterable["type"] = boolean
679
681
  else:
680
682
  filterable["type"] = str
683
+ if field.validation:
684
+ filterable["type"] = validation_to_type(field.validation)
681
685
 
682
686
  filterable["choices"] = info.get("choices", None)
683
687
  if hasattr(field, "choices") and field.choices:
684
688
  filterable["choices"] = field.choices
685
689
 
686
690
  return filterable
691
+
692
+
693
+ def validation_to_type(validation: Callable) -> Callable:
694
+ """Convert a mongo field's validation function to a ReqParser's type.
695
+
696
+ In flask_restx.ReqParser, validation is done by setting the param's type to
697
+ a callable that will either raise, or return the parsed value.
698
+
699
+ In mongo, a field's validation function cannot return anything, so this
700
+ helper wraps the mongo field's validation to return the value if it validated.
701
+ """
702
+ from udata.models import db
703
+
704
+ def wrapper(value: str) -> str:
705
+ try:
706
+ validation(value)
707
+ except db.ValidationError:
708
+ raise
709
+ return value
710
+
711
+ wrapper.__schema__ = {"type": "string", "format": "my-custom-format"}
712
+
713
+ return wrapper
@@ -15,8 +15,13 @@ BADGES = {
15
15
  }
16
16
 
17
17
 
18
+ def validate_badge(value):
19
+ if value not in Fake.__badges__.keys():
20
+ raise db.ValidationError("Unknown badge type")
21
+
22
+
18
23
  class FakeBadge(Badge):
19
- kind = db.StringField(required=True, choices=list(BADGES.keys()))
24
+ kind = db.StringField(required=True, validation=validate_badge)
20
25
 
21
26
 
22
27
  class FakeBadgeMixin(BadgeMixin):
@@ -34,12 +39,6 @@ class BadgeMixinTest(DBTestMixin, TestCase):
34
39
  fake = Fake.objects.create()
35
40
  self.assertIsInstance(fake.badges, (list, tuple))
36
41
 
37
- def test_choices(self):
38
- """It should have a choice list on the badge field."""
39
- self.assertEqual(
40
- Fake._fields["badges"].field.document_type.kind.choices, list(Fake.__badges__.keys())
41
- )
42
-
43
42
  def test_get_badge_found(self):
44
43
  """It allow to get a badge by kind if present"""
45
44
  fake = Fake.objects.create()
@@ -155,3 +154,19 @@ class BadgeMixinTest(DBTestMixin, TestCase):
155
154
  with self.assertRaises(db.ValidationError):
156
155
  fake = Fake.objects.create()
157
156
  fake.add_badge("unknown")
157
+
158
+ def test_validation(self):
159
+ """It should validate default badges as well as extended ones"""
160
+ # Model badges can be extended in plugins, for example in udata-front
161
+ # for french only badges.
162
+ Fake.__badges__["new"] = "new"
163
+
164
+ fake = FakeBadge(kind="test")
165
+ fake.validate()
166
+
167
+ fake = FakeBadge(kind="new")
168
+ fake.validate()
169
+
170
+ with self.assertRaises(db.ValidationError):
171
+ fake = FakeBadge(kind="doesnotexist")
172
+ fake.validate()
@@ -128,6 +128,7 @@ class Dataservice(WithMetrics, Owned, db.Document):
128
128
  description = field(db.StringField(default=""), description="In markdown")
129
129
  base_api_url = field(db.URLField(), sortable=True)
130
130
  endpoint_description_url = field(db.URLField())
131
+ business_documentation_url = field(db.URLField())
131
132
  authorization_request_url = field(db.URLField())
132
133
  availability = field(db.FloatField(min=0, max=100), example="99.99")
133
134
  rate_limiting = field(db.StringField())
@@ -521,8 +521,13 @@ class Resource(ResourceMixin, WithMetrics, db.EmbeddedDocument):
521
521
  self.dataset.save(*args, **kwargs)
522
522
 
523
523
 
524
+ def validate_badge(value):
525
+ if value not in Dataset.__badges__.keys():
526
+ raise db.ValidationError("Unknown badge type")
527
+
528
+
524
529
  class DatasetBadge(Badge):
525
- kind = db.StringField(required=True, choices=list(BADGES.keys()))
530
+ kind = db.StringField(required=True, validation=validate_badge)
526
531
 
527
532
 
528
533
  class DatasetBadgeMixin(BadgeMixin):
@@ -95,8 +95,13 @@ class OrganizationQuerySet(db.BaseQuerySet):
95
95
  return self(badges__kind=kind)
96
96
 
97
97
 
98
+ def validate_badge(value):
99
+ if value not in Organization.__badges__.keys():
100
+ raise db.ValidationError("Unknown badge type")
101
+
102
+
98
103
  class OrganizationBadge(Badge):
99
- kind = db.StringField(required=True, choices=list(BADGES.keys()))
104
+ kind = db.StringField(required=True, validation=validate_badge)
100
105
 
101
106
 
102
107
  class OrganizationBadgeMixin(BadgeMixin):
@@ -35,8 +35,13 @@ def check_url_does_not_exists(url):
35
35
  raise FieldValidationError(_("This URL is already registered"), field="url")
36
36
 
37
37
 
38
+ def validate_badge(value):
39
+ if value not in Reuse.__badges__.keys():
40
+ raise db.ValidationError("Unknown badge type")
41
+
42
+
38
43
  class ReuseBadge(Badge):
39
- kind = db.StringField(required=True, choices=list(BADGES.keys()))
44
+ kind = db.StringField(required=True, validation=validate_badge)
40
45
 
41
46
 
42
47
  class ReuseBadgeMixin(BadgeMixin):
@@ -4,10 +4,10 @@ import udata.core.organization.constants as org_constants
4
4
  from udata.core.dataset.factories import DatasetFactory, HiddenDatasetFactory
5
5
  from udata.core.followers.signals import on_follow, on_unfollow
6
6
  from udata.core.organization.factories import OrganizationFactory
7
- from udata.core.organization.models import Organization
7
+ from udata.core.organization.models import Organization, OrganizationBadge
8
8
  from udata.core.reuse.factories import ReuseFactory, VisibleReuseFactory
9
9
  from udata.core.user.factories import UserFactory
10
- from udata.models import Dataset, Follow, Member, Reuse
10
+ from udata.models import Dataset, Follow, Member, Reuse, db
11
11
  from udata.tests.helpers import assert_emit
12
12
 
13
13
  from .. import DBTestMixin, TestCase
@@ -71,3 +71,21 @@ class OrganizationModelTest(TestCase, DBTestMixin):
71
71
  associations = list(Organization.objects.with_badge(org_constants.ASSOCIATION))
72
72
  assert len(associations) == 1
73
73
  assert org_certified_association in associations
74
+
75
+
76
+ class OrganizationBadgeTest(DBTestMixin, TestCase):
77
+ # Model badges can be extended in plugins, for example in udata-front
78
+ # for french only badges.
79
+ Organization.__badges__["new"] = "new"
80
+
81
+ def test_validation(self):
82
+ """It should validate default badges as well as extended ones"""
83
+ badge = OrganizationBadge(kind="public-service")
84
+ badge.validate()
85
+
86
+ badge = OrganizationBadge(kind="new")
87
+ badge.validate()
88
+
89
+ with self.assertRaises(db.ValidationError):
90
+ badge = OrganizationBadge(kind="doesnotexist")
91
+ badge.validate()
@@ -5,9 +5,10 @@ from udata.core.dataset.factories import DatasetFactory
5
5
  from udata.core.discussions.factories import DiscussionFactory
6
6
  from udata.core.organization.factories import OrganizationFactory
7
7
  from udata.core.reuse.factories import ReuseFactory, VisibleReuseFactory
8
+ from udata.core.reuse.models import Reuse, ReuseBadge
8
9
  from udata.core.user.factories import UserFactory
9
10
  from udata.i18n import gettext as _
10
- from udata.models import Reuse
11
+ from udata.models import db
11
12
  from udata.tests.helpers import assert_emit
12
13
 
13
14
  from .. import DBTestMixin, TestCase
@@ -124,3 +125,18 @@ class ReuseModelTest(TestCase, DBTestMixin):
124
125
  reuse.private = True
125
126
  reuse.save()
126
127
  self.assertEqual(reuse.private, True)
128
+
129
+
130
+ class ReuseBadgeTest(DBTestMixin, TestCase):
131
+ # Model badges can be extended in plugins, for example in udata-front
132
+ # for french only badges.
133
+ Reuse.__badges__["new"] = "new"
134
+
135
+ def test_validation(self):
136
+ """It should validate default badges as well as extended ones"""
137
+ badge = ReuseBadge(kind="new")
138
+ badge.validate()
139
+
140
+ with self.assertRaises(db.ValidationError):
141
+ badge = ReuseBadge(kind="doesnotexist")
142
+ badge.validate()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 10.0.1.dev32359
3
+ Version: 10.0.1.dev32405
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -141,6 +141,8 @@ It is collectively taken care of by members of the
141
141
  ## Current (in progress)
142
142
 
143
143
  - Add more comments and types in the `api_field.py` "lib" [#3174](https://github.com/opendatateam/udata/pull/3174)
144
+ - Allow overriding of badges (for example in plugins like udata-front) [#3191](https://github.com/opendatateam/udata/pull/3191)
145
+ - Add business documentation url property on dataservice [#3193](https://github.com/opendatateam/udata/pull/3193)
144
146
 
145
147
  ## 10.0.0 (2024-11-07)
146
148
 
@@ -1,7 +1,7 @@
1
1
  tasks/__init__.py,sha256=nubUI6ljumym4uv6NvAJEkWHtsdurFpEGSq-AxDWYDM,8153
2
2
  tasks/helpers.py,sha256=70fS9tI_m0DTWmKx9Zl5-LG-nxdz_ZaPyvvsFkN2r48,1091
3
3
  udata/__init__.py,sha256=VLpep3pCtT5bA1tTfgPooU04eEqTfDj4l1Ji5umePX0,102
4
- udata/api_fields.py,sha256=z1L_ykNO6GoZP0i-RvlYTv6GwoP0NtGFdfZMLWe8qTc,28784
4
+ udata/api_fields.py,sha256=BG4VhH5rBO-FmfDGuX7LO8R-7LL5EE6o1eDP2MDnpS4,29723
5
5
  udata/app.py,sha256=xjk2D3EgboYBpTwBwdIxd2klt2yMoWMyCrkry5fz0LA,7292
6
6
  udata/assets.py,sha256=H5Hrc2vnKM0IFLyWfLXmJ2Kj35w1i8W1D8Cgy8_cUj4,657
7
7
  udata/cors.py,sha256=gPTIXnO5nWziCKAHqU9GNuBKaIzr7RRtbjZBd2RdE2k,3155
@@ -72,7 +72,7 @@ udata/core/badges/signals.py,sha256=gD68zbdL-mM0epJp8MbkBhqcgqKd2_U-pEEUpwoII7w,
72
72
  udata/core/badges/tasks.py,sha256=gkp3gfXGb8-uqpb9U9P2aPz2Eb_g3Lol0K5IRsaJ2yg,432
73
73
  udata/core/badges/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
74
  udata/core/badges/tests/test_commands.py,sha256=dSM8VV9CByPJYv11BUlfh4WIV2KLy7GceBXJ0DFSKpY,1276
75
- udata/core/badges/tests/test_model.py,sha256=FL4bU8urgm7raq9xaoPsUV1MhmM6q4-oNqkpw3Volrg,4731
75
+ udata/core/badges/tests/test_model.py,sha256=FRyh4RkVHaL3cA9UROvD5fRXBB1GfPZ824k2-F4fHBg,5143
76
76
  udata/core/contact_point/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  udata/core/contact_point/api.py,sha256=hK5fanw-QZvenqu2XQW5xKmhPNPGK6xplUWqaMy_Il4,1951
78
78
  udata/core/contact_point/api_fields.py,sha256=IECdDFG9OzbQP8USXhUKAbEdVWiA6xO4ffcEB4zTTWQ,1073
@@ -83,7 +83,7 @@ udata/core/dataservices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
83
83
  udata/core/dataservices/api.py,sha256=XybQOFfnYU2SVQ-LXW4jXmCfIDuF6oEc3VuDfMCiRj0,7371
84
84
  udata/core/dataservices/apiv2.py,sha256=XIqJq58uLtxtt52iKYo7Fl0ZXv9Sz72uA7JIGwP8Qos,995
85
85
  udata/core/dataservices/factories.py,sha256=LDk8vvG0zhW8J-ZX5LoJQDU13pqeIyjQ05muuMro_eA,876
86
- udata/core/dataservices/models.py,sha256=p9tn-YTrd01WHHke49EMx3msH9Fu49XtdQsII9sAJXE,8488
86
+ udata/core/dataservices/models.py,sha256=8XhhlhGzIr8S3n7yVbqo4Z8b_cieUKUwNNp4-dSbbqs,8542
87
87
  udata/core/dataservices/permissions.py,sha256=98zM_R4v2ZtRubflB7ajaVQz-DVc-pZBMgtKUYy34oI,169
88
88
  udata/core/dataservices/rdf.py,sha256=PTye9_DeeOdpSa48NkcQNqRR15_LeUsvkjJ21nvLAhI,5428
89
89
  udata/core/dataservices/search.py,sha256=htjh3sq3tDuD2qwgRgZsW6ClrHLF6hMwYO4nfBO0Hr4,4171
@@ -101,7 +101,7 @@ udata/core/dataset/events.py,sha256=bSM0nFEX14r4JHc-bAM-7OOuD3JAxUIpw9GgXbOsUyw,
101
101
  udata/core/dataset/exceptions.py,sha256=uKiayLSpSzsnLvClObS6hOO0qXEqvURKN7_w8eimQNU,498
102
102
  udata/core/dataset/factories.py,sha256=fRDWDlybR_ud4pDs1-ntWuYHKtV9LMHeBOBp2SmTT6M,9006
103
103
  udata/core/dataset/forms.py,sha256=H2oeAD8XckMugnwUcyuKv7o1Xq1rEIlLz9FtxujqbIE,6196
104
- udata/core/dataset/models.py,sha256=1x5zfKQAHBTCxhxz4sl-EK9BEVYsUVyBORULOg55FTU,37382
104
+ udata/core/dataset/models.py,sha256=9iaZ072KauirHZDH1SwIazOFxNjCg1MyBWepouKH3Ig,37511
105
105
  udata/core/dataset/permissions.py,sha256=zXQ6kU-Ni3Pl5tDtat-ZPupug9InsNeCN7xRLc2Vcrc,1097
106
106
  udata/core/dataset/preview.py,sha256=IwCqiNTjjXbtA_SSKF52pwnzKKEz0GyYM95QNn2Dkog,2561
107
107
  udata/core/dataset/rdf.py,sha256=YqsRPrrcwWaV13hflpcEOaN3VXpWW3X8VRtUT2qJH60,26383
@@ -149,7 +149,7 @@ udata/core/organization/csv.py,sha256=j6BJvXE8_6c-IWOPOg0C-j9KiCushoG1FQxzKVBDj2
149
149
  udata/core/organization/factories.py,sha256=g8ubBcz79xbjvpunZ02IDOakFg1KE6cXjNkE9vFyFAc,624
150
150
  udata/core/organization/forms.py,sha256=tscDb1_yOpbTx3ahl8ttA7oDkX9jIyzLc4gOf6WbN3s,3552
151
151
  udata/core/organization/metrics.py,sha256=UlHOBOph9s_QW-X7dkEivVITdyZ0ku9kLKl7BmSpGTI,778
152
- udata/core/organization/models.py,sha256=NLxw5fp0GzUxdjH_Hvu4F0OXa7Y7inkqrfgPiZoIPjI,8811
152
+ udata/core/organization/models.py,sha256=pOVtlWgXQ_HxWZASJ7W5vusove-gqZ6o2TNsP2IbAjA,8945
153
153
  udata/core/organization/notifications.py,sha256=i_36-l2y7fOGmnBmr5NDWmGGmrGRaCWbU-6XS4c2wQs,917
154
154
  udata/core/organization/permissions.py,sha256=GD-9TMtRppVCPaC1ysXYrONvGJV-ArzAOXm2XMKf9yo,1256
155
155
  udata/core/organization/rdf.py,sha256=GmWliHFf356cUiXfNuK56b-O-zRNQqxLKJhNULGVcKA,1940
@@ -178,7 +178,7 @@ udata/core/reuse/constants.py,sha256=JgDBrjOKSt9q0auv9rjzbGsch83H-Oi8YXAKeI5hO4o
178
178
  udata/core/reuse/csv.py,sha256=4TcQbk4kWN_HbnpEUfgfW9_x7ZqNDk5s4YuJ839p3EE,896
179
179
  udata/core/reuse/factories.py,sha256=GrQqYTIvwQrwkvJrbTr38-2faFW_PC99gn3yOVpgFec,850
180
180
  udata/core/reuse/metrics.py,sha256=sVh7BlW3OKRvFDHFyD4pPUV91jOOhj8qeWbBkLPn5Gg,176
181
- udata/core/reuse/models.py,sha256=YitJhFFvyxG5XGt76Y7Q_kfiA_ixxtRu9b3dVceXBDM,8290
181
+ udata/core/reuse/models.py,sha256=ZB5KKpqYqwghOMqynT_wL6OwIozt5UQYrpNqLEahPQA,8417
182
182
  udata/core/reuse/permissions.py,sha256=j-ancS7gvLl5vJu0TNYqpYD-2So-UzoDE4IHLxRoMGg,621
183
183
  udata/core/reuse/search.py,sha256=y1DwXYkBMBwuhn62CULkU1NNo89IYp0Ae7U01jcnjBY,3137
184
184
  udata/core/reuse/signals.py,sha256=nDrEUpYKN0AdYiEbrR0z3nzXzjaRcD8SAMutwIDsQPM,155
@@ -668,11 +668,11 @@ udata/tests/frontend/test_markdown.py,sha256=8E0XjByzmsY-RGdF2ESoL9Rklkfz3vcdJWK
668
668
  udata/tests/organization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
669
669
  udata/tests/organization/test_csv_adapter.py,sha256=vgTVbPMqKipcdg7wi99pHk9ZNhqtuU-yneEaVW3pZmU,1564
670
670
  udata/tests/organization/test_notifications.py,sha256=PGmME5BbYVtcPffeSE1sx47J6iAfm4RDAZIRv_MXDFE,1366
671
- udata/tests/organization/test_organization_model.py,sha256=JIYgVmgs71r9KCplyEOdHC8qSncr4ER9fZWiGgL5hEs,3060
671
+ udata/tests/organization/test_organization_model.py,sha256=clRck_ZNSIBI0VaugrInK6dUkw3yBaly6TtVqmLzfEY,3684
672
672
  udata/tests/organization/test_organization_rdf.py,sha256=v_gC4BI0hCFKDkK0CMmZO6PBwqe--pc0OLhyftZpQeg,8086
673
673
  udata/tests/organization/test_organization_tasks.py,sha256=ehPirwfLqaEcnJ9TLk3jGw3Xh-CVx1WXhvIDFYrT2iQ,2845
674
674
  udata/tests/reuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
675
- udata/tests/reuse/test_reuse_model.py,sha256=ro_p0GmDvkaP6jQmCxDDLiGBFetDInjn9McnHRglWZY,3989
675
+ udata/tests/reuse/test_reuse_model.py,sha256=Q862yC-kXE2cM8on-2-B7JEeP9rLgAENi9z3OpDvt5M,4530
676
676
  udata/tests/reuse/test_reuse_task.py,sha256=4gMsKHjGafb6kpRKnqF167Htd-Ff0tRD4iQajW1e_lg,1380
677
677
  udata/tests/search/__init__.py,sha256=ub8kS6vG9EjzkJ-9sAR7j1eLSYpud1Gzry4MQ8fpgxM,1493
678
678
  udata/tests/search/test_adapter.py,sha256=mi-M53JQSPYxU8ahkqtfyIrIyhz_cC_ATdLbWWa3onM,6941
@@ -705,9 +705,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=TpMlhwj07t7CPkzXn-qmHG4aTq6ZT7
705
705
  udata/translations/pt/LC_MESSAGES/udata.po,sha256=vRqSEt9Jds--Wic-hYMkdjYaWzeHFnUn3wD5XvQL-qE,44877
706
706
  udata/translations/sr/LC_MESSAGES/udata.mo,sha256=bc-BmgT91ZJJB9DKc5YDsHqyxBhOjDJdbOy-UPo-NjA,28163
707
707
  udata/translations/sr/LC_MESSAGES/udata.po,sha256=BKVmr4M88pfgZMU4sHIyDObV5M9E8ezZ9m7jviy2PE4,51369
708
- udata-10.0.1.dev32359.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
709
- udata-10.0.1.dev32359.dist-info/METADATA,sha256=hPcO_cGu0iLmqjkyWoUmvQcqLk1CSeJp4NxsYsACGho,134253
710
- udata-10.0.1.dev32359.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
711
- udata-10.0.1.dev32359.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
712
- udata-10.0.1.dev32359.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
713
- udata-10.0.1.dev32359.dist-info/RECORD,,
708
+ udata-10.0.1.dev32405.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
709
+ udata-10.0.1.dev32405.dist-info/METADATA,sha256=kO-jT8H6rlN7tnOKx3tuIh4Bw5U8X6Nf_Rq8ygFzmtg,134495
710
+ udata-10.0.1.dev32405.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
711
+ udata-10.0.1.dev32405.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
712
+ udata-10.0.1.dev32405.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
713
+ udata-10.0.1.dev32405.dist-info/RECORD,,