udata 10.8.3.dev37185__py2.py3-none-any.whl → 10.8.3.dev37191__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.

@@ -1,11 +1,12 @@
1
1
  import logging
2
2
  from datetime import date
3
- from typing import Generator
3
+ from typing import ClassVar, Generator
4
4
 
5
5
  import lxml.etree as ET
6
6
  from flask import current_app
7
7
  from rdflib import Graph
8
8
  from rdflib.namespace import RDF
9
+ from typing_extensions import override
9
10
 
10
11
  from udata.core.dataservices.rdf import dataservice_from_rdf
11
12
  from udata.core.dataset.rdf import dataset_from_rdf
@@ -55,9 +56,6 @@ URIS_TO_REPLACE = {
55
56
  }
56
57
 
57
58
 
58
- SAFE_PARSER = ET.XMLParser(resolve_entities=False)
59
-
60
-
61
59
  def extract_graph(source, target, node, specs):
62
60
  for p, o in source.predicate_objects(node):
63
61
  target.add((node, p, o))
@@ -240,104 +238,165 @@ class DcatBackend(BaseBackend):
240
238
  return node
241
239
  raise ValueError(f"Unable to find dataset with DCT.identifier:{item.remote_id}")
242
240
 
243
- def next_record_if_should_continue(self, start, search_results):
244
- next_record = int(search_results.attrib["nextRecord"])
245
- matched_count = int(search_results.attrib["numberOfRecordsMatched"])
246
- returned_count = int(search_results.attrib["numberOfRecordsReturned"])
247
241
 
248
- # Break conditions copied gratefully from
249
- # noqa https://github.com/geonetwork/core-geonetwork/blob/main/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/csw/Harvester.java#L338-L369
250
- break_conditions = (
251
- # standard CSW: A value of 0 means all records have been returned.
252
- next_record == 0,
253
- # Misbehaving CSW server returning a next record > matched count
254
- next_record > matched_count,
255
- # No results returned already
256
- returned_count == 0,
257
- # Current next record is lower than previous one
258
- next_record < start,
259
- # Enough items have been harvested already
260
- self.max_items and len(self.job.items) >= self.max_items,
261
- )
242
+ class CswDcatBackend(DcatBackend):
243
+ """
244
+ CSW harvester fetching records as DCAT.
245
+ The parsing of items is then the same as for the DcatBackend.
246
+ """
262
247
 
263
- if any(break_conditions):
264
- return None
265
- else:
266
- return next_record
248
+ display_name = "CSW-DCAT"
267
249
 
250
+ # CSW_REQUEST is based on:
251
+ # - Request syntax from spec [1] and example requests [1] [2].
252
+ # - Sort settings to ensure stable paging [3].
253
+ # - Filter settings to only retrieve record types currently mapped in udata.
254
+ #
255
+ # If you modify the request, make sure:
256
+ # - `typeNames` and `outputSchema` are consistent. You'll likely want to keep "gmd:MD_Metadata",
257
+ # since "csw:Record" contains less information.
258
+ # - `typeNames` and namespaces in `csw:Query` (`Filter`, `SortBy`, ...) are consistent, although
259
+ # they are ignored on some servers [4] [5].
260
+ # - It works on real catalogs! Not many servers implement the whole spec.
261
+ #
262
+ # References:
263
+ # [1] OpenGIS Catalogue Services Specification 2.0.2 – ISO Metadata Application Profile: Corrigendum
264
+ # https://portal.ogc.org/files/80534
265
+ # [2] GeoNetwork - CSW test requests
266
+ # https://github.com/geonetwork/core-geonetwork/tree/3.10.4/web/src/main/webapp/xml/csw/test
267
+ # [3] Udata - Support csw dcat harvest
268
+ # https://github.com/opendatateam/udata/pull/2800#discussion_r1129053500
269
+ # [4] GeoNetwork - GetRecords ignores namespaces for Filter/SortBy fields
270
+ # https://github.com/geonetwork/core-geonetwork/blob/3.10.4/csw-server/src/main/java/org/fao/geonet/kernel/csw/services/getrecords/FieldMapper.java#L92
271
+ # [5] GeoNetwork - GetRecords ignores `typeNames`
272
+ # https://github.com/geonetwork/core-geonetwork/blob/3.10.4/csw-server/src/main/java/org/fao/geonet/kernel/csw/services/getrecords/CatalogSearcher.java#L194
273
+ CSW_REQUEST: ClassVar[str] = """
274
+ <csw:GetRecords xmlns:apiso="http://www.opengis.net/cat/csw/apiso/1.0"
275
+ xmlns:csw="http://www.opengis.net/cat/csw/2.0.2"
276
+ xmlns:ogc="http://www.opengis.net/ogc"
277
+ service="CSW" version="2.0.2" outputFormat="application/xml"
278
+ resultType="results" startPosition="{start}" maxRecords="25"
279
+ outputSchema="{output_schema}">
280
+ <csw:Query typeNames="gmd:MD_Metadata">
281
+ <csw:ElementSetName>full</csw:ElementSetName>
282
+ <csw:Constraint version="1.1.0">
283
+ <ogc:Filter>
284
+ <ogc:Or>
285
+ <ogc:PropertyIsEqualTo>
286
+ <ogc:PropertyName>apiso:type</ogc:PropertyName>
287
+ <ogc:Literal>dataset</ogc:Literal>
288
+ </ogc:PropertyIsEqualTo>
289
+ <ogc:PropertyIsEqualTo>
290
+ <ogc:PropertyName>apiso:type</ogc:PropertyName>
291
+ <ogc:Literal>nonGeographicDataset</ogc:Literal>
292
+ </ogc:PropertyIsEqualTo>
293
+ <ogc:PropertyIsEqualTo>
294
+ <ogc:PropertyName>apiso:type</ogc:PropertyName>
295
+ <ogc:Literal>series</ogc:Literal>
296
+ </ogc:PropertyIsEqualTo>
297
+ <ogc:PropertyIsEqualTo>
298
+ <ogc:PropertyName>apiso:type</ogc:PropertyName>
299
+ <ogc:Literal>service</ogc:Literal>
300
+ </ogc:PropertyIsEqualTo>
301
+ </ogc:Or>
302
+ </ogc:Filter>
303
+ </csw:Constraint>
304
+ <ogc:SortBy>
305
+ <ogc:SortProperty>
306
+ <ogc:PropertyName>apiso:identifier</ogc:PropertyName>
307
+ <ogc:SortOrder>ASC</ogc:SortOrder>
308
+ </ogc:SortProperty>
309
+ </ogc:SortBy>
310
+ </csw:Query>
311
+ </csw:GetRecords>
312
+ """
268
313
 
269
- class CswDcatBackend(DcatBackend):
270
- display_name = "CSW-DCAT"
314
+ CSW_OUTPUT_SCHEMA = "http://www.w3.org/ns/dcat#"
271
315
 
272
- DCAT_SCHEMA = "http://www.w3.org/ns/dcat#"
316
+ def __init__(self, *args, **kwargs):
317
+ super().__init__(*args, **kwargs)
318
+ self.xml_parser = ET.XMLParser(resolve_entities=False)
273
319
 
274
320
  def walk_graph(self, url: str, fmt: str) -> Generator[tuple[int, Graph], None, None]:
275
321
  """
276
322
  Yield all RDF pages as `Graph` from the source
277
323
  """
278
- body = """<csw:GetRecords xmlns:csw="http://www.opengis.net/cat/csw/2.0.2"
279
- xmlns:gmd="http://www.isotc211.org/2005/gmd"
280
- service="CSW" version="2.0.2" resultType="results"
281
- startPosition="{start}" maxPosition="200"
282
- outputSchema="{schema}">
283
- <csw:Query typeNames="gmd:MD_Metadata">
284
- <csw:ElementSetName>full</csw:ElementSetName>
285
- <ogc:SortBy xmlns:ogc="http://www.opengis.net/ogc">
286
- <ogc:SortProperty>
287
- <ogc:PropertyName>identifier</ogc:PropertyName>
288
- <ogc:SortOrder>ASC</ogc:SortOrder>
289
- </ogc:SortProperty>
290
- </ogc:SortBy>
291
- </csw:Query>
292
- </csw:GetRecords>"""
293
- headers = {"Content-Type": "application/xml"}
294
-
295
324
  page_number = 0
296
325
  start = 1
297
326
 
298
- response = self.post(
299
- url, data=body.format(start=start, schema=self.DCAT_SCHEMA), headers=headers
300
- )
301
- response.raise_for_status()
302
- content = response.content
303
- tree = ET.fromstring(content, parser=SAFE_PARSER)
304
- if tree.tag == "{" + OWS_NAMESPACE + "}ExceptionReport":
305
- raise ValueError(f"Failed to query CSW:\n{content}")
306
- while tree is not None:
327
+ while True:
328
+ data = self.CSW_REQUEST.format(output_schema=self.CSW_OUTPUT_SCHEMA, start=start)
329
+ response = self.post(url, data=data, headers={"Content-Type": "application/xml"})
330
+ response.raise_for_status()
331
+
332
+ content = response.content
333
+ tree = ET.fromstring(content, parser=self.xml_parser)
334
+ if tree.tag == "{" + OWS_NAMESPACE + "}ExceptionReport":
335
+ raise ValueError(f"Failed to query CSW:\n{content}")
336
+
307
337
  search_results = tree.find("csw:SearchResults", {"csw": CSW_NAMESPACE})
308
- if search_results is None:
338
+ if not search_results:
309
339
  log.error(f"No search results found for {url} on page {page_number}")
310
- break
311
- for child in search_results:
340
+ return
341
+
342
+ for result in search_results:
312
343
  subgraph = Graph(namespace_manager=namespace_manager)
313
- subgraph.parse(data=ET.tostring(child), format=fmt)
344
+ doc = ET.tostring(self.as_dcat(result))
345
+ subgraph.parse(data=doc, format=fmt)
346
+
347
+ if not subgraph.subjects(
348
+ RDF.type, [DCAT.Dataset, DCAT.DatasetSeries, DCAT.DataService]
349
+ ):
350
+ raise ValueError("Failed to fetch CSW content")
314
351
 
315
352
  yield page_number, subgraph
353
+
316
354
  if self.has_reached_max_items():
317
355
  return
318
356
 
319
- next_record = self.next_record_if_should_continue(start, search_results)
320
- if not next_record:
321
- break
322
-
323
- start = next_record
324
357
  page_number += 1
358
+ start = self.next_position(start, search_results)
359
+ if not start:
360
+ return
325
361
 
326
- tree = ET.fromstring(
327
- self.post(
328
- url, data=body.format(start=start, schema=self.DCAT_SCHEMA), headers=headers
329
- ).content,
330
- parser=SAFE_PARSER,
331
- )
362
+ def as_dcat(self, tree: ET._Element) -> ET._Element:
363
+ """
364
+ Return the input tree as a DCAT tree.
365
+ For CswDcatBackend, this method return the incoming tree as-is, since it's already DCAT.
366
+ For subclasses of CswDcatBackend, this method should convert the incoming tree to DCAT.
367
+ """
368
+ return tree
369
+
370
+ def next_position(self, start: int, search_results: ET._Element) -> int | None:
371
+ next_record = int(search_results.attrib["nextRecord"])
372
+ matched_count = int(search_results.attrib["numberOfRecordsMatched"])
373
+ returned_count = int(search_results.attrib["numberOfRecordsReturned"])
374
+
375
+ # Break conditions copied gratefully from
376
+ # noqa https://github.com/geonetwork/core-geonetwork/blob/main/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/csw/Harvester.java#L338-L369
377
+ should_break = (
378
+ # A value of 0 means all records have been returned (standard CSW)
379
+ (next_record == 0)
380
+ # Misbehaving CSW server returning a next record > matched count
381
+ or (next_record > matched_count)
382
+ # No results returned already
383
+ or (returned_count == 0)
384
+ # Current next record is lower than previous one
385
+ or (next_record < start)
386
+ # Enough items have been harvested already
387
+ or self.has_reached_max_items()
388
+ )
389
+ return None if should_break else next_record
332
390
 
333
391
 
334
- class CswIso19139DcatBackend(DcatBackend):
392
+ class CswIso19139DcatBackend(CswDcatBackend):
335
393
  """
336
- An harvester that takes CSW ISO 19139 as input and transforms it to DCAT using SEMIC GeoDCAT-AP XSLT.
394
+ CSW harvester fetching records as ISO-19139 and using XSLT to convert them to DCAT.
337
395
  The parsing of items is then the same as for the DcatBackend.
338
396
  """
339
397
 
340
398
  display_name = "CSW-ISO-19139"
399
+
341
400
  extra_configs = (
342
401
  HarvestExtraConfig(
343
402
  _("Remote URL prefix"),
@@ -347,94 +406,14 @@ class CswIso19139DcatBackend(DcatBackend):
347
406
  ),
348
407
  )
349
408
 
350
- ISO_SCHEMA = "http://www.isotc211.org/2005/gmd"
351
-
352
- def walk_graph(self, url: str, fmt: str) -> Generator[tuple[int, Graph], None, None]:
353
- """
354
- Yield all RDF pages as `Graph` from the source
355
-
356
- Parse CSW graph querying ISO schema.
357
- Use SEMIC GeoDCAT-AP XSLT to map it to a correct version.
358
- See https://github.com/SEMICeu/iso-19139-to-dcat-ap for more information on the XSLT.
359
- """
360
- # Load XSLT
361
- xsl_url = current_app.config["HARVEST_ISO19139_XSL_URL"]
362
- xsl = ET.fromstring(self.get(xsl_url).content, parser=SAFE_PARSER)
363
- transform = ET.XSLT(xsl)
364
-
365
- # Start querying and parsing graph
366
- # Filter on dataset or serie records
367
- body = """<csw:GetRecords xmlns:csw="http://www.opengis.net/cat/csw/2.0.2"
368
- xmlns:gmd="http://www.isotc211.org/2005/gmd"
369
- service="CSW" version="2.0.2" resultType="results"
370
- startPosition="{start}" maxPosition="10"
371
- outputSchema="{schema}">
372
- <csw:Query typeNames="csw:Record">
373
- <csw:ElementSetName>full</csw:ElementSetName>
374
- <csw:Constraint version="1.1.0">
375
- <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
376
- <ogc:Or xmlns:ogc="http://www.opengis.net/ogc">
377
- <ogc:PropertyIsEqualTo>
378
- <ogc:PropertyName>dc:type</ogc:PropertyName>
379
- <ogc:Literal>dataset</ogc:Literal>
380
- </ogc:PropertyIsEqualTo>
381
- <ogc:PropertyIsEqualTo>
382
- <ogc:PropertyName>dc:type</ogc:PropertyName>
383
- <ogc:Literal>service</ogc:Literal>
384
- </ogc:PropertyIsEqualTo>
385
- <ogc:PropertyIsEqualTo>
386
- <ogc:PropertyName>dc:type</ogc:PropertyName>
387
- <ogc:Literal>series</ogc:Literal>
388
- </ogc:PropertyIsEqualTo>
389
- </ogc:Or>
390
- </ogc:Filter>
391
- </csw:Constraint>
392
- </csw:Query>
393
- </csw:GetRecords>"""
394
- headers = {"Content-Type": "application/xml"}
395
-
396
- page_number = 0
397
- start = 1
398
-
399
- response = self.post(
400
- url, data=body.format(start=start, schema=self.ISO_SCHEMA), headers=headers
401
- )
402
- response.raise_for_status()
403
-
404
- tree_before_transform = ET.fromstring(response.content, parser=SAFE_PARSER)
405
- # Disabling CoupledResourceLookUp to prevent failure on xlink:href
406
- # https://github.com/SEMICeu/iso-19139-to-dcat-ap/blob/master/documentation/HowTo.md#parameter-coupledresourcelookup
407
- tree = transform(tree_before_transform, CoupledResourceLookUp="'disabled'")
408
-
409
- while tree:
410
- # We query the tree before the transformation because the XSLT remove the search results
411
- # infos (useful for pagination)
412
- search_results = tree_before_transform.find("csw:SearchResults", {"csw": CSW_NAMESPACE})
413
- if search_results is None:
414
- log.error(f"No search results found for {url} on page {page_number}")
415
- break
416
-
417
- subgraph = Graph(namespace_manager=namespace_manager)
418
- subgraph.parse(ET.tostring(tree), format=fmt)
419
-
420
- if not subgraph.subjects(RDF.type, DCAT.Dataset):
421
- raise ValueError("Failed to fetch CSW content")
409
+ CSW_OUTPUT_SCHEMA = "http://www.isotc211.org/2005/gmd"
422
410
 
423
- yield page_number, subgraph
424
- if self.has_reached_max_items():
425
- return
426
-
427
- next_record = self.next_record_if_should_continue(start, search_results)
428
- if not next_record:
429
- break
430
-
431
- start = next_record
432
- page_number += 1
433
-
434
- response = self.post(
435
- url, data=body.format(start=start, schema=self.ISO_SCHEMA), headers=headers
436
- )
437
- response.raise_for_status()
411
+ def __init__(self, *args, **kwargs):
412
+ super().__init__(*args, **kwargs)
413
+ xslt_url = current_app.config["HARVEST_ISO19139_XSLT_URL"]
414
+ xslt = ET.fromstring(self.get(xslt_url).content, parser=self.xml_parser)
415
+ self.transform = ET.XSLT(xslt)
438
416
 
439
- tree_before_transform = ET.fromstring(response.content, parser=SAFE_PARSER)
440
- tree = transform(tree_before_transform, CoupledResourceLookUp="'disabled'")
417
+ @override
418
+ def as_dcat(self, tree: ET._Element) -> ET._Element:
419
+ return self.transform(tree, CoupledResourceLookUp="'disabled'")
@@ -899,7 +899,7 @@ class CswIso19139DcatBackendTest:
899
899
  with open(os.path.join(CSW_DCAT_FILES_DIR, "XSLT.xml"), "r") as f:
900
900
  xslt = f.read()
901
901
  url = mock_csw_pagination(rmock, "geonetwork/srv/eng/csw.rdf", "geonetwork-iso-page-{}.xml")
902
- rmock.get(current_app.config.get("HARVEST_ISO19139_XSL_URL"), text=xslt)
902
+ rmock.get(current_app.config.get("HARVEST_ISO19139_XSLT_URL"), text=xslt)
903
903
  org = OrganizationFactory()
904
904
  source = HarvestSourceFactory(
905
905
  backend="csw-iso-19139",
udata/settings.py CHANGED
@@ -283,7 +283,7 @@ class Defaults(object):
283
283
  HARVEST_GRAPHS_S3_BUCKET = None # If the catalog is bigger than `HARVEST_MAX_CATALOG_SIZE_IN_MONGO` store the graph inside S3 instead of MongoDB
284
284
  HARVEST_GRAPHS_S3_FILENAME_PREFIX = "" # Useful to store the graphs inside a subfolder of the bucket. For example by setting `HARVEST_GRAPHS_S3_FILENAME_PREFIX = 'graphs/'`
285
285
 
286
- HARVEST_ISO19139_XSL_URL = "https://raw.githubusercontent.com/SEMICeu/iso-19139-to-dcat-ap/refs/heads/geodcat-ap-2.0.0/iso-19139-to-dcat-ap.xsl"
286
+ HARVEST_ISO19139_XSLT_URL = "https://raw.githubusercontent.com/SEMICeu/iso-19139-to-dcat-ap/refs/heads/geodcat-ap-2.0.0/iso-19139-to-dcat-ap.xsl"
287
287
 
288
288
  # S3 connection details
289
289
  S3_URL = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 10.8.3.dev37185
3
+ Version: 10.8.3.dev37191
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -145,6 +145,8 @@ It is collectively taken care of by members of the
145
145
 
146
146
  - Count dataservices and reuses for datasets based on signal [#3335](https://github.com/opendatateam/udata/pull/3335)
147
147
  :warning: the job `update-datasets-reuses-metrics` disappears, you should unschedule it before installing this version
148
+ - Consolidate CSW harvesting logic [#3378](https://github.com/opendatateam/udata/pull/3378)
149
+ :warning: Config setting `HARVEST_ISO19139_XSL_URL` is renamed to `HARVEST_ISO19139_XSLT_URL`.
148
150
  - Cache dcat harvest pages to avoid two rounds of requests [#3398](https://github.com/opendatateam/udata/pull/3398)
149
151
  - Ignore Dataset.accessService when processing DataService [#3399](https://github.com/opendatateam/udata/pull/3399)
150
152
  - Add dataset field `description_short` [#3397](https://github.com/opendatateam/udata/pull/3397)
@@ -13,7 +13,7 @@ udata/mail.py,sha256=FMGHcDAjHvk86iDUwBmVXpx3vbAb2c-j5C3BRnh9IYQ,2670
13
13
  udata/rdf.py,sha256=NUuE-zCAmyFBlKpNEXIO98uIfup_JS-qggqP1_Z8RJ8,18307
14
14
  udata/routing.py,sha256=ryxVjhUyEjyP-xBNyRtubGlGJwskpMYdY7tSj9Aej8w,7930
15
15
  udata/sentry.py,sha256=ekcxqUSqxfM98TtvCsPaOoX5i2l6PEcYt7kb4l3od-Q,3223
16
- udata/settings.py,sha256=uf2EfhZ4Ps1u6FW03xobVjxUBwthqsXyj5n2dgNi8yg,19667
16
+ udata/settings.py,sha256=hKYOF3kHONuPGRkypE_dqHAyQLuxN85FjK--7l5abho,19668
17
17
  udata/sitemap.py,sha256=oRRWoPI7ZsFFnUAOqGT1YuXFFKHBe8EcRnUCNHD7xjM,979
18
18
  udata/tags.py,sha256=ydq4uokd6bzdeGVSpEXASVtGvDfO2LfQs9mptvvKJCM,631
19
19
  udata/tasks.py,sha256=yTYBJG5bzEChX27p3MSqurSji84rg7w7OUvK4vuPRfY,5080
@@ -300,7 +300,7 @@ udata/harvest/signals.py,sha256=3AhFHMPIFH5vz01NX5ycR_RWH14MXFWnCT6__LSa-QI,1338
300
300
  udata/harvest/tasks.py,sha256=id5YmHIhnkgez0LVC1fNg_6Yz1Sp0jrvQ1caslVOWdY,1722
301
301
  udata/harvest/backends/__init__.py,sha256=QjoFfBJfpw_xgk5YYWI1SgKJOMEmTMlxSfW79GNkSTI,459
302
302
  udata/harvest/backends/base.py,sha256=2wyfw83e3xGQcHnQI-z26g1dg-uVtWcDgzsBk7iGX3Y,17480
303
- udata/harvest/backends/dcat.py,sha256=aBdCiKcmZoDmjsmZVP_S1DW5MO0h1Um8Zqj8knJwN-k,18804
303
+ udata/harvest/backends/dcat.py,sha256=_wBqD-hJIKZWMJZStHvE6p6yl2-YZ7ktcoa1PvshW34,17749
304
304
  udata/harvest/backends/maaf.py,sha256=N7ty8ZWO9pfKPtZRk1wTaJ5pY6qi-0-GtF1p8jiYiY4,8102
305
305
  udata/harvest/backends/maaf.xsd,sha256=vEyG8Vqw7Yn_acjRdXjqUJgxOj4pv8bibep-FX-f3BQ,18322
306
306
  udata/harvest/backends/ckan/__init__.py,sha256=JE7Qa7kX7Yd8OvmJnAO_NupZe0tqYyhhkgJ-iGNxX64,35
@@ -314,7 +314,7 @@ udata/harvest/tests/person.jsonld,sha256=I7Ynh-PQlNeD51I1LrCgYOEjhL-WBeb65xzIE_s
314
314
  udata/harvest/tests/test_actions.py,sha256=oXNTwDuSHtlVANRcsNyq3J33u4XU_jM9o67FbZjLCys,24860
315
315
  udata/harvest/tests/test_api.py,sha256=gSuICkPy3KVRUhHAyudXVf_gLwiB7SoriUp3DLXWDdA,21611
316
316
  udata/harvest/tests/test_base_backend.py,sha256=ow8ecGtD836mUqyPWYjkS5nx0STyT5RMLgBdDyOhts4,19233
317
- udata/harvest/tests/test_dcat_backend.py,sha256=xe8X4Q88-Y50IQYT3Q9lz2rcFeb2ADIg1wLz-I7-ewc,43835
317
+ udata/harvest/tests/test_dcat_backend.py,sha256=I8D6nKVYCXmS-pJb0OoeROtUXFI4Ua3HPO_VI0j-vZA,43836
318
318
  udata/harvest/tests/test_filters.py,sha256=PT2qopEIoXsqi8MsNDRuhNH7jGXiQo8r0uJrCOUd4aM,2465
319
319
  udata/harvest/tests/test_models.py,sha256=f9NRR2_S4oZFgF8qOumg0vv-lpnEBJbI5vNtcwFdSqM,831
320
320
  udata/harvest/tests/test_notifications.py,sha256=MMzTzkv-GXMNFeOwAi31rdTsAXyLCLOSna41zOtaJG0,816
@@ -758,9 +758,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=ogtEGsjwIkOEtzrI49e57izi-NUim5
758
758
  udata/translations/pt/LC_MESSAGES/udata.po,sha256=1omSlqdMEdwYtzHGalXrtptVk14mlb6JS_8xR8BsznU,48428
759
759
  udata/translations/sr/LC_MESSAGES/udata.mo,sha256=V7unDjO31PNLiRhfEkYU3pU47wWWct-otdL0GDma0S4,28692
760
760
  udata/translations/sr/LC_MESSAGES/udata.po,sha256=wVBipfpPl-DmYeVm5wrlzWWM3oEvuruDVbJHx4I1p8s,55170
761
- udata-10.8.3.dev37185.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
762
- udata-10.8.3.dev37185.dist-info/METADATA,sha256=cQX5a7XSYkkv2rL8MrpOvosALh1CSYEzBFP0uNFh2eY,153630
763
- udata-10.8.3.dev37185.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
764
- udata-10.8.3.dev37185.dist-info/entry_points.txt,sha256=v2u12qO11i2lyLNIp136WmLJ-NHT-Kew3Duu8J-AXPM,614
765
- udata-10.8.3.dev37185.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
766
- udata-10.8.3.dev37185.dist-info/RECORD,,
761
+ udata-10.8.3.dev37191.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
762
+ udata-10.8.3.dev37191.dist-info/METADATA,sha256=MZpUlHzcf_z5Jatwr_gpUTTYn9QlUKiIxUiuY7yUMAo,153819
763
+ udata-10.8.3.dev37191.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
764
+ udata-10.8.3.dev37191.dist-info/entry_points.txt,sha256=v2u12qO11i2lyLNIp136WmLJ-NHT-Kew3Duu8J-AXPM,614
765
+ udata-10.8.3.dev37191.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
766
+ udata-10.8.3.dev37191.dist-info/RECORD,,