From 3e28af2b50c0cdf4fc529cff37e99cc0b01f15d2 Mon Sep 17 00:00:00 2001 From: Pascal Repond Date: Tue, 17 Mar 2026 15:30:41 +0100 Subject: [PATCH] refactor: remove RERODOC harvesting pipeline and legacy URL support RERO DOC repository is now closed. Remove the MARC21 transformation pipeline, CLI commands, loader schema, rerod PID minting, /rerodoc/ redirection routes, OAI source config, and all associated tests. Co-Authored-by: Pascal Repond --- data/oai_sources.json | 8 - sonar/modules/documents/cli/documents.py | 4 +- sonar/modules/documents/cli/rerodoc.py | 138 - .../documents/dojson/rerodoc/__init__.py | 16 - .../modules/documents/dojson/rerodoc/model.py | 990 ------- .../documents/dojson/rerodoc/model.py_hesso | 1168 -------- .../documents/dojson/rerodoc/overdo.py | 141 - .../documents/loaders/schemas/factory.py | 4 +- .../documents/loaders/schemas/rerodoc.py | 61 - sonar/modules/documents/minters.py | 19 +- sonar/theme/views.py | 53 +- tests/conftest.py | 3 +- tests/ui/conftest.py | 4 +- tests/ui/data/harvested_record.xml | 132 +- .../documents/cli/test_documents_cli_ark.py | 2 +- .../cli/test_documents_cli_rerodoc.py | 57 - .../dojson/rerodoc/test_rerodoc_model.py | 2587 ----------------- .../dojson/rerodoc/test_rerodoc_overdo.py | 114 - tests/ui/documents/test_documents_api.py | 20 +- .../ui/documents/test_documents_receivers.py | 12 +- tests/ui/test_views.py | 41 +- .../loaders/test_loader_schema_factory.py | 14 +- .../documents/loaders/test_rerodoc_loader.py | 220 -- uv.lock | 554 ++-- 24 files changed, 341 insertions(+), 6021 deletions(-) delete mode 100644 sonar/modules/documents/cli/rerodoc.py delete mode 100644 sonar/modules/documents/dojson/rerodoc/__init__.py delete mode 100644 sonar/modules/documents/dojson/rerodoc/model.py delete mode 100644 sonar/modules/documents/dojson/rerodoc/model.py_hesso delete mode 100644 sonar/modules/documents/dojson/rerodoc/overdo.py delete mode 100644 sonar/modules/documents/loaders/schemas/rerodoc.py delete mode 100644 tests/ui/documents/cli/test_documents_cli_rerodoc.py delete mode 100644 tests/ui/documents/dojson/rerodoc/test_rerodoc_model.py delete mode 100644 tests/ui/documents/dojson/rerodoc/test_rerodoc_overdo.py delete mode 100644 tests/unit/documents/loaders/test_rerodoc_loader.py diff --git a/data/oai_sources.json b/data/oai_sources.json index 707bf3c0a..03b8493ae 100644 --- a/data/oai_sources.json +++ b/data/oai_sources.json @@ -1,12 +1,4 @@ [ - { - "key": "rerodoc", - "name": "rerodoc", - "url": "http://doc.rero.ch/oai2d", - "metadataprefix": "marcxml", - "comment": "", - "setspecs": "unisi unifr nl-epfl nl-ethz nl-fachhochschulen nl-lib4ri nl-unibas nl-unibe nl-unifr nl-unige nl-unil nl-unilu nl-unine nl-unisg nl-usi nl-uzh bge mhnge baage bmuge imvge mhsge hepbejune unifr" - }, { "key": "archive_ouverte_unige", "name": "Archive ouverte UNIGE", diff --git a/sonar/modules/documents/cli/documents.py b/sonar/modules/documents/cli/documents.py index cdea9e199..2841b7c96 100644 --- a/sonar/modules/documents/cli/documents.py +++ b/sonar/modules/documents/cli/documents.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -18,7 +18,6 @@ import click from .oai import oai -from .rerodoc import rerodoc from .urn import urn @@ -27,6 +26,5 @@ def documents(): """Commands for documents.""" -documents.add_command(rerodoc) documents.add_command(urn) documents.add_command(oai) diff --git a/sonar/modules/documents/cli/rerodoc.py b/sonar/modules/documents/cli/rerodoc.py deleted file mode 100644 index 5a599ef4f..000000000 --- a/sonar/modules/documents/cli/rerodoc.py +++ /dev/null @@ -1,138 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""RERODOC specific CLI commands.""" - -import csv -import re - -import click -from flask import current_app -from flask.cli import with_appcontext -from invenio_db import db - -from sonar.modules.documents.api import DocumentIndexer, DocumentRecord - - -@click.group() -def rerodoc(): - """RERODOC specific commands.""" - - -@rerodoc.command("update-file-permissions") -@click.argument("permissions_file", type=click.File("r")) -@click.option("-c", "--chunk-size", type=int, default=500, help="Chunk size for bulk indexing.") -@with_appcontext -def update_file_permissions(permissions_file, chunk_size): - """Update file permission with information given by input file. - - :param permissions_file: CSV file containing files permissions. - """ - indexer = DocumentIndexer(record_cls=DocumentRecord) - - def save_records(ids): - """Save current records set into database and re-index. - - :param ids: List of records to save - """ - db.session.commit() - indexer.bulk_index(ids) - indexer.process_bulk_queue() - - try: - with open(permissions_file.name) as file: - reader = csv.reader(file, delimiter=",") - - # header - header = next(reader) - - # check number of columns - if len(header) != 3: - raise Exception("CSV file seems to be not well formatted.") - - # To store ids for bulk indexing - ids = [] - - for row in reader: - try: - # try to load corresponding record - record = DocumentRecord.get_record_by_identifier([{"type": "bf:Local", "value": row[0]}]) - - # No record found, skipping.. - if not record: - raise Exception(f"Record {row[0]} not found") - - file_name = f"{row[2]}.pdf" - - # File not found in record, skipping - if file_name not in record.files: - raise Exception(f"File {file_name} not found in record {row[0]}") - - record_file = record.files[file_name] - - # permissions contains a status - matches = re.search(r"status:(\w+)$", row[1]) - if matches: - # If status if RERO or INTERNAL, file must not be - # displayed, otherwise file is not accessible outside - # organisation - record_file["access"] = "coar:c_16ec" # restricted access - if matches.group(1) in ["RERO", "INTERNAL"]: - record_file["restricted_outside_organisation"] = False - else: - record_file["restricted_outside_organisation"] = True - else: - # permissions contains a date - matches = re.search( - r"allow roles \/\.\*,(\w+),\.\*\/\n\s+.+(\d{4}-" - r"\d{2}-\d{2})", - row[1], - ) - - if matches: - # file is accessible inside organisation - if matches.group(1) != "INTERNAL": - record_file["restricted_outside_organisation"] = True - # file is not accessible - else: - record_file["restricted_outside_organisation"] = False - - record_file["access"] = "coar:c_f1cf" # embargoed access - record_file["embargo_date"] = matches.group(2) - - record.commit() - db.session.flush() - ids.append(str(record.id)) - - current_app.logger.warning(f"Restriction added for file {file_name} in record {record['pid']}.") - - # Bulk save and index - if len(ids) % chunk_size == 0: - save_records(ids) - ids = [] - - except Exception as exception: - click.secho(str(exception), fg="yellow") - - # save remaining records - save_records(ids) - - click.secho("Process finished", fg="green") - - except Exception as exception: - click.secho( - f"An error occured during file process: {exception}", - fg="red", - ) diff --git a/sonar/modules/documents/dojson/rerodoc/__init__.py b/sonar/modules/documents/dojson/rerodoc/__init__.py deleted file mode 100644 index da7f08d42..000000000 --- a/sonar/modules/documents/dojson/rerodoc/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""DOJSON RERODOC transformation for SONAR.""" diff --git a/sonar/modules/documents/dojson/rerodoc/model.py b/sonar/modules/documents/dojson/rerodoc/model.py deleted file mode 100644 index 6629708b4..000000000 --- a/sonar/modules/documents/dojson/rerodoc/model.py +++ /dev/null @@ -1,990 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""RERODOC MARC21 model definition.""" - -import hashlib -import re - -from dojson import utils -from flask import current_app -from invenio_db import db - -from sonar.modules.collections.api import Record as CollectionRecord -from sonar.modules.documents.dojson.rerodoc.overdo import Overdo -from sonar.modules.organisations.api import OrganisationRecord -from sonar.modules.subdivisions.api import Record as SubdivisionRecord -from sonar.modules.subdivisions.api import RecordSearch -from sonar.modules.utils import remove_trailing_punctuation - -overdo = Overdo() - -TYPE_MAPPINGS = { - "PREPRINT|": "coar:c_816b", - "POSTPRINT|ART_JOURNAL": "coar:c_6501", - "POSTPRINT|ART_INBOOK": "coar:c_3248", - "POSTPRINT|ART_INPROC": "coar:c_5794", - "BOOK|": "coar:c_2f33", - "DISSERTATION|DISS_MASTER": "coar:c_bdcc", - "DISSERTATION|DISS_BACHELOR": "coar:c_7a1f", - "DISSERTATION|DISS_CONT_EDU": "coar:c_46ec", - "THESIS|": "coar:c_db06", - "THESIS|TH_PHD": "coar:c_db06", - "THESIS|TH_HABILIT": "coar:c_46ec", - "MAP|": "coar:c_12cc", - "REPORT|": "coar:c_18ws", - "NEWSPAPER|": "coar:c_2fe3", - "JOURNAL|": "coar:c_0640", - "PRINT_MEDIA|": "coar:c_2fe3", - "AUDIO|": "coar:c_18cc", - "IMAGE|": "coar:c_ecc8", - "PARTITION|": "coar:c_18cw", -} - - -@overdo.over("type", "^980") -@utils.ignore_value -def marc21_to_type_and_organisation(self, key, value): - """Get document type and organisation from 980 field.""" - subdivision_name = None - - # organisation - if value.get("b"): - organisation = value.get("b").lower() - - # Specific transformation for `unisi`, because the real acronym is - # `usi`. - if organisation == "unisi": - organisation = "usi" - - # Specific transformation for `hep bejune`, in order to fill out - # custom fields `Filière` (`customField1`) and `Titre obtenu` - # (`customField2`) - if organisation == "hepbejune" and value.get("f"): - document_subtype = value.get("f").lower() - custom_field1 = "" - custom_field2 = "" - if document_subtype == "diss_bachelor": - custom_field1 = "Enseignement primaire" - custom_field2 = "Bachelor of Arts in Pre-Primary and Primary Education" - elif document_subtype == "diss_master": - custom_field1 = "Enseignement secondaire" - custom_field2 = "Master of Arts or of Science in Secondary Education" - if custom_field1: - self["customField1"] = [custom_field1] - if custom_field2: - self["customField2"] = [custom_field2] - - # Specific transformation for `hepfr`, which should be imported as - # a faculty AND a subdivision of FOLIA/unifr - if organisation == "hepfr": - organisation = "unifr" - self["organisation"] = [{"$ref": OrganisationRecord.get_ref_link("organisations", organisation)}] - # `hepfr` is a faculty of FOLIA/unifr - self["customField1"] = ["HEP|PH FR"] - # `hepfr` is a subdivision of FOLIA/unifr - subdivision_name = "HEP Fribourg" - # Store subdivision - # TODO: avoid possible clashes between subdivision - # names in different languages - result = ( - RecordSearch() - .filter("term", organisation__pid=organisation) - .filter("term", name__value__raw=subdivision_name) - .source(includes="pid") - .scan() - ) - subdivision_pid = next(result).pid - # If the subdivision exists, assign it to the record - if subdivision_pid: - self["subdivisions"] = [{"$ref": SubdivisionRecord.get_ref_link("subdivisions", subdivision_pid)}] - - # Specific transformation for `bpuge` and `mhnge`, because the real - # acronym is `vge`. - subdivision_name = None - - if organisation in ["bpuge", "mhnge", "baage", "bmuge", "imvge", "mhsge"]: - subdivision_name = "bge" if organisation == "bpuge" else organisation - organisation = "vge" - - if organisation not in overdo.registererd_organisations: - overdo.create_organisation(organisation) - overdo.registererd_organisations.append(organisation) - - self["organisation"] = [{"$ref": OrganisationRecord.get_ref_link("organisations", organisation)}] - - if subdivision_name: - # Store subdivision - hash_key = hashlib.md5((subdivision_name + organisation).encode()).hexdigest() - - subdivision_pid = SubdivisionRecord.get_pid_by_hash_key(hash_key) - - # No subdivision found - if not subdivision_pid: - subdivision = SubdivisionRecord.create( - { - "name": [{"language": "eng", "value": subdivision_name}], - "organisation": {"$ref": OrganisationRecord.get_ref_link("organisations", organisation)}, - "hashKey": hash_key, - } - ) - subdivision.commit() - subdivision.reindex() - db.session.commit() - subdivision_pid = subdivision["pid"] - - self["subdivisions"] = [{"$ref": SubdivisionRecord.get_ref_link("subdivisions", subdivision_pid)}] - - # get doc type by mapping - key = value.get("a", "") + "|" + value.get("f", "") - if key not in TYPE_MAPPINGS: - current_app.logger.warning(f'Document type not found in mapping for type "{key}"') - return - - # Store types to records - self["documentType"] = TYPE_MAPPINGS[key] - - return - - -@overdo.over("language", "^041") -@utils.for_each_value -@utils.ignore_value -def marc21_to_language(self, key, value): - """Get languages.""" - if not value.get("a"): - return - - language = self.get("language", []) - - for code in utils.force_list(value.get("a")): - language.append({"type": "bf:Language", "value": code}) - - self["language"] = language - - return - - -@overdo.over("title", "^245..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_title_245(self, key, value): - """Get title.""" - main_title = value.get("a") - language = value.get("9", "eng") - subtitle = value.get("b") - - if not main_title: - return None - - title = { - "type": "bf:Title", - "mainTitle": [{"value": main_title.rstrip(":"), "language": language}], - } - - if subtitle: - title["subtitle"] = [{"value": subtitle, "language": language}] - - return title - - -@overdo.over("title", "^246..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_title_246(self, key, value): - """Get title.""" - main_title = value.get("a") - language = value.get("9", "eng") - - if not main_title: - return - - title = self.get("title", [{"type": "bf:Title", "mainTitle": []}]) - - # Add title 246 to last title in mainTitle propert - title[-1]["mainTitle"].append({"value": main_title, "language": language}) - - self["title"] = title - - return - - -@overdo.over("editionStatement", "^250..") -@utils.ignore_value -def marc21_to_edition_statement(self, key, value): - """Get edition statement data.""" - if not value.get("a") or not value.get("b"): - return None - - return { - "editionDesignation": {"value": value.get("a")}, - "responsibility": {"value": value.get("b")}, - } - - -@overdo.over("provisionActivity", "^260..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_provision_activity_field_260(self, key, value): - """Get provision activity data from field 260.""" - provision_activity = self.get("provisionActivity", []) - - # Only if there is a date - if value.get("c"): - publication = {"type": "bf:Publication", "statement": []} - - # Place - if value.get("a"): - publication["statement"].append({"type": "bf:Place", "label": [{"value": value.get("a")}]}) - - # Agent - if value.get("b"): - publication["statement"].append( - { - "type": "bf:Agent", - "label": [{"value": remove_trailing_punctuation(value.get("b"))}], - } - ) - - years = value.get("c").split("-") - - # Start date - if years and re.match(r"^\d{4}$", years[0]): - publication["startDate"] = years[0] - - publication["statement"].append({"type": "Date", "label": [{"value": value.get("c")}]}) - - # End date - if len(years) > 1 and re.match(r"^\d{4}$", years[1]): - publication["endDate"] = years[1] - - provision_activity.append(publication) - - # Manufacture - if value.get("e") or value.get("f"): - manufacture = {"type": "bf:Manufacture", "statement": []} - - if value.get("e"): - manufacture["statement"].append( - { - "type": "bf:Place", - "label": [{"value": remove_trailing_punctuation(value.get("e"))}], - } - ) - - if value.get("f"): - manufacture["statement"].append({"type": "bf:Agent", "label": [{"value": value.get("f")}]}) - - provision_activity.append(manufacture) - - # Re-assign provision activity - if provision_activity: - self["provisionActivity"] = provision_activity - - return - - -@overdo.over("provisionActivity", "^269..") -@utils.ignore_value -def marc21_to_provision_activity_field_269(self, key, value): - """Get provision activity data from field 269.""" - # 260$c has priority to this date - if overdo.blob_record.get("260__", {}).get("c"): - return - - # No date, skipping - if not value.get("c"): - return - - # Assign start date - match = re.search(r"^[0-9]{4}(-[0-9]{2}-[0-9]{2})?$", value.get("c")) - - # Date does not match "YYYY" or "YYYY-MM-DD" - if not match: - return - - add_provision_activity_start_date(self, value.get("c")) - - return - - -@overdo.over("formats", "^300..") -@utils.ignore_value -def marc21_to_description(self, key, value): - """Get extent, otherMaterialCharacteristics, formats. - - extent: 300$a (the first one if many) - otherMaterialCharacteristics: 300$b (the first one if many) - formats: 300 [$c repetitive] - """ - if value.get("a") and not self.get("extent"): - self["extent"] = remove_trailing_punctuation(overdo.not_repetitive(value, "a")) - - if value.get("b") and self.get("otherMaterialCharacteristics", []) == []: - self["otherMaterialCharacteristics"] = remove_trailing_punctuation(overdo.not_repetitive(value, "b")) - - if value.get("c"): - formats = self.get("formats") - - if not formats: - data = value.get("c") - formats = list(utils.force_list(data)) - - return formats - - return None - - -@overdo.over("series", "^490..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_series(self, key, value): - """Get series. - - series.name: [490$a repetitive] - series.number: [490$v repetitive] - """ - series = {} - - name = value.get("a") - if name: - series["name"] = ", ".join(utils.force_list(name)) - - number = value.get("v") - if number: - series["number"] = ", ".join(utils.force_list(number)) - - return series - - -@overdo.over("abstracts", "^520..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_abstract(self, key, value): - """Get abstract.""" - abstract = value.get("a") - language = value.get("9", "eng") - - if not abstract: - return - - if language == "fr": - language = "fre" - - abstracts_data = self.get("abstracts", []) - abstracts_data.append({"value": abstract, "language": language}) - - self["abstracts"] = abstracts_data - - return - - -@overdo.over("identifiedBy", "001") -@utils.ignore_value -def marc21_to_identified_by_from_001(self, key, value): - """Get identifier from field 001.""" - identified_by = self.get("identifiedBy", []) - - identified_by.append({"type": "bf:Local", "source": "RERO DOC", "value": value}) - - return identified_by - - -@overdo.over("identifiedBy", "^020..") -@utils.ignore_value -def marc21_to_identified_by_from_020(self, key, value): - """Get identifier from field 020.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a"): - return None - - identified_by.append({"type": "bf:Isbn", "value": value.get("a")}) - - return identified_by - - -@overdo.over("identifiedBy", "^0247.") -@utils.ignore_value -def marc21_to_identified_by_from_024(self, key, value): - """Get identifier from field 024.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a") or value.get("2") != "urn": - return None - - identified_by.append({"type": "bf:Urn", "value": value.get("a")}) - - return identified_by - - -@overdo.over("identifiedBy", "^027..") -@utils.ignore_value -def marc21_to_identified_by_from_027(self, key, value): - """Get identifier from field 027.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a"): - return None - - identified_by.append({"type": "bf:Strn", "value": value.get("a")}) - - return identified_by - - -@overdo.over("identifiedBy", "^035..") -@utils.ignore_value -def marc21_to_identified_by_from_035(self, key, value): - """Get identifier from field 035.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a"): - return None - - identified_by.append({"type": "bf:Local", "source": "RERO", "value": value.get("a")}) - - return identified_by - - -@overdo.over("identifiedBy", "^037..") -@utils.ignore_value -def marc21_to_identified_by_from_037(self, key, value): - """Get identifier from field 037.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a"): - return None - - identified_by.append( - { - "type": "bf:Local", - "source": "Swissbib", - "value": value.get("a").replace("swissbib.ch:", "").strip(), - } - ) - - return identified_by - - -@overdo.over("identifiedBy", "^088..") -@utils.ignore_value -def marc21_to_identified_by_from_088(self, key, value): - """Get identifier from field 088.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a"): - return None - - identified_by.append({"type": "bf:ReportNumber", "value": value.get("a")}) - - return identified_by - - -@overdo.over("identifiedBy", "^091..") -@utils.ignore_value -def marc21_to_identified_by_from_091(self, key, value): - """Get identifier from field 091.""" - identified_by = self.get("identifiedBy", []) - - if not value.get("a") or value.get("b") != "pmid": - return None - - identified_by.append({"type": "bf:Local", "value": value.get("a"), "source": "PMID"}) - - return identified_by - - -@overdo.over("notes", "^500..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_notes(self, key, value): - """Get notes.""" - return overdo.not_repetitive(value, "a") - - -@overdo.over("subjects", "^600..|695..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_subjects(self, key, value): - """Get subjects.""" - if not value.get("a"): - return None - - subjects = {"label": {"value": [item for item in value.get("a").split(" ; ") if item]}} - - # If field is 695 and no language is available - if key == "695__": - if not value.get("9"): - return None - - subjects["label"]["language"] = value.get("9") - - # If field is 600 and no source is available - if key == "600__": - if not value.get("2"): - return None - - subjects["source"] = value.get("2") - - return subjects - - -@overdo.over("files", "^856..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_files(self, key, value): - """Get files.""" - key = value.get("f") - url = value.get("u") - mime_type = value.get("q", "text/plain") - - if not key or not url: - return None - - # TODO: Check why this type of file exists. Real example with rerodoc ID - # 29085 - if mime_type == "pdt/download": - current_app.logger.warning( - f"File {key} for record {self['identifiedBy']} has a strange pdt/download mime " - "type, skipping import of file..." - ) - return None - - url = url.strip() - - # Retreive file order - # If order not set we put a value to 99 for easily point theses files - order = 99 - if value.get("y"): - match = re.search(r"order:([0-9]+)$", value.get("y")) - if match: - order = int(match.group(1)) - - return { - "key": key, - "url": url, - "label": value.get("z", key), - "type": "file", - "order": order, - } - - -@overdo.over("otherEdition", "^775..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_other_edition(self, key, value): - """Get other edition.""" - electronic_locator = value.get("o") - public_note = value.get("g") - - if not electronic_locator or not public_note: - return None - - # if the value matches a DOI, apply `identifiedBy[type:bf:Doi]` - matches = re.search(r"(?P10\.\d{4,9}/[-._;()/:a-zA-Z0-9]+)", value.get("o")) - if matches and matches.group("doi"): - identified_by = self.get("identifiedBy", []) - identified_by.append({"type": "bf:Doi", "value": matches.group("doi")}) - self["identifiedBy"] = identified_by - return None - return { - "document": {"electronicLocator": electronic_locator}, - "publicNote": public_note, - } - - -@overdo.over("collections", "^982..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_specific_collection(self, key, value): - """Extract collection for record.""" - if not value.get("a"): - return None - - # No organisation found, the collection is not imported. - if not self.get("organisation"): - return None - - organisation_pid = OrganisationRecord.get_pid_by_ref_link(self["organisation"][0]["$ref"]) - - hash_key = hashlib.md5((value.get("a") + organisation_pid).encode()).hexdigest() - - collection_pid = CollectionRecord.get_pid_by_hash_key(hash_key) - - # No collection found - if not collection_pid: - collection = CollectionRecord.create( - { - "name": [{"language": "eng", "value": value.get("a")}], - "organisation": {"$ref": self["organisation"][0]["$ref"]}, - "hashKey": hash_key, - } - ) - collection.commit() - collection.reindex() - db.session.commit() - collection_pid = collection["pid"] - - return {"$ref": CollectionRecord.get_ref_link("collections", collection_pid)} - - -@overdo.over("classification", "^080..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_classification_field_080(self, key, value): - """Get classification data from field 080.""" - if not value.get("a"): - return None - - return {"type": "bf:ClassificationUdc", "classificationPortion": value.get("a")} - - -@overdo.over("classification", "^084..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_classification_field_084(self, key, value): - """Get classification data from field 084.""" - if not value.get("a") or value.get("2") != "ddc": - return None - - return {"type": "bf:ClassificationDdc", "classificationPortion": value.get("a")} - - -@overdo.over("contentNote", "^505..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_content_note(self, key, value): - """Extract collection for record.""" - return value.get("a") - - -@overdo.over("dissertation", "^502..") -@utils.ignore_value -def marc21_to_dissertation_field_502(self, key, value): - """Extract dissertation degree.""" - record = overdo.blob_record - if value.get("a"): - dissertation = self.get("dissertation", {}) - dissertation["degree"] = value.get("a") - self["dissertation"] = dissertation - - # try to parse the thesis note more precisely - matches = re.match( - r"^(?P[^:]+) : (?P[^,]+) ?[,:] (?P\d{4})( ; .*)?$", - value.get("a"), - ) - if matches: - if matches.group("degree"): - dissertation["degree"] = matches.group("degree") - if matches.group("grantingInstitution"): - dissertation["grantingInstitution"] = matches.group("grantingInstitution") - if matches.group("date"): - dissertation["date"] = matches.group("date") - - # Specific transformation for `hep bejune`, in order to fill out - # custom fields `Filière` (`customField1`) and `Titre obtenu` - # (`customField2`) - organisation = record.get("980__", {}).get("b") - if organisation and organisation.lower() == "hepbejune": - degree = matches.group("degree").lower() - custom_field1 = None - custom_field2 = None - if "mémoire de master spécialisé" in degree: - custom_field1 = "Enseignement spécialisé" - custom_field2 = "Master of Arts in special needs education, orientation enseignement spécialisé" - elif "mémoire de master" in degree: - custom_field1 = "Enseignement secondaire" - custom_field2 = "Master of Arts or of Science in Secondary Education" - elif "mémoire de bachelor" in degree: - custom_field1 = "Enseignement primaire" - custom_field2 = "Bachelor of Arts in Pre-Primary and Primary Education" - if custom_field1: - self["customField1"] = [custom_field1] - if custom_field2: - self["customField2"] = [custom_field2] - - # Try to get start date and store in provision activity - # 260$c and 269$c have priority to this date - if record.get("260__", {}).get("c") or record.get("269__", {}).get("c") or record.get("773__", {}).get("g"): - return - - # No date, skipping - if not value.get("9"): - return - - # Match either 2019 or 2019-01-01 - match = re.search(r"^[0-9]{4}(-[0-9]{2}-[0-9]{2})?$", value.get("9")) - - if not match: - return - - add_provision_activity_start_date(self, value.get("9")) - - return - - -@overdo.over("dissertation", "^508..") -@utils.ignore_value -def marc21_to_dissertation_field_508(self, key, value): - """Extract dissertation note.""" - if not value.get("a"): - return - - dissertation = self.get("dissertation", {}) - dissertation["jury_note"] = value.get("a") - - self["dissertation"] = dissertation - - return - - -@overdo.over("usageAndAccessPolicy", "^540..") -@utils.ignore_value -def marc21_to_usage_and_access_policy(self, key, value): - """Extract usage and access policy.""" - if not value.get("a"): - return None - - return {"label": value.get("a"), "license": "License undefined"} - - -@overdo.over("contribution", "^100..") -@utils.ignore_value -def marc21_to_contribution_field_100(self, key, value): - """Extract contribution from field 100.""" - if not value.get("a"): - return - - contribution = self.get("contribution", []) - - data = { - "agent": {"type": "bf:Person", "preferred_name": value.get("a")}, - "role": ["cre"], - } - - # Affiliation - if value.get("u"): - data["affiliation"] = value.get("u") - - # Date of birth - date of death - date_of_birth, date_of_death = overdo.extract_date(value.get("d")) - - if date_of_birth: - data["agent"]["date_of_birth"] = date_of_birth - - if date_of_death: - data["agent"]["date_of_death"] = date_of_death - - contribution.append(data) - self["contribution"] = contribution - - return - - -@overdo.over("contribution", "^700..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_contribution_field_700(self, key, value): - """Extract contribution from field 100.""" - if not value.get("a"): - return - - contribution = self.get("contribution", []) - - role = overdo.get_contributor_role(value.get("e")) - - if not role: - raise Exception(f"No role found for contributor {value}") - - data = { - "agent": {"type": "bf:Person", "preferred_name": value.get("a")}, - "role": [role], - } - - # Affiliation - if value.get("u"): - data["affiliation"] = value.get("u") - - # Date of birth - date of death - date_of_birth, date_of_death = overdo.extract_date(value.get("d")) - - if date_of_birth: - data["agent"]["date_of_birth"] = date_of_birth - - if date_of_death: - data["agent"]["date_of_death"] = date_of_death - - contribution.append(data) - self["contribution"] = contribution - - return - - -@overdo.over("contribution", "^710..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_contribution_field_710(self, key, value): - """Extract contribution from field 710.""" - if not value.get("a"): - return - - contribution = self.get("contribution", []) - contribution.append( - { - "agent": {"type": "bf:Organization", "preferred_name": value.get("a")}, - "role": ["ctb"], - } - ) - self["contribution"] = contribution - - return - - -@overdo.over("contribution", "^711..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_contribution_field_711(self, key, value): - """Extract contribution from field 711.""" - if not value.get("a"): - return - - contribution = self.get("contribution", []) - - data = { - "agent": {"type": "bf:Meeting", "preferred_name": value.get("a")}, - "role": ["cre"], - } - - # Place - if value.get("c"): - data["agent"]["place"] = value.get("c") - - # Date - if value.get("d"): - data["agent"]["date"] = value.get("d") - - # Number - if value.get("n"): - data["agent"]["number"] = value.get("n") - - contribution.append(data) - self["contribution"] = contribution - - return - - -@overdo.over("customField1", "^918..") -@utils.ignore_value -def marc21_to_faculty_and_department(self, key, value): - """Extract faculty and department for UNIFR.""" - record = overdo.blob_record - org = record.get("980__", {}).get("b") - if org and org == "UNIFR": - faculty = value.get("a") - if faculty: - self["customField1"] = [faculty] - dep = value.get("c") - if dep: - self["customField2"] = [dep] - return - - -@overdo.over("partOf", "^773..") -@utils.for_each_value -@utils.ignore_value -def marc21_to_part_of(self, key, value): - """Extract related document for record.""" - if not value.get("g"): - return None - - # Split value for getting numbering values - numbering = value.get("g").split("/") - - # Numbering year - if not numbering[0]: - return None - - data = {"numberingYear": numbering[0]} - - # Volume - if len(numbering) > 1 and numbering[1]: - data["numberingVolume"] = numbering[1] - - # Issue - if len(numbering) > 2 and numbering[2]: - data["numberingIssue"] = numbering[2] - - # Pages - if len(numbering) > 3 and numbering[3] and numbering[3] != "-": - data["numberingPages"] = numbering[3] - - document = {} - - # Title is found - if value.get("t"): - document["title"] = value.get("t") - - # Contribution - if value.get("c"): - document["contribution"] = list(value.get("c").split(";")) - - record = overdo.blob_record - - # Publication based on document sub type - sub_type = record.get("980__", {}).get("f") - if value.get("d") or sub_type == "ART_INBOOK": - document["publication"] = {} - - if value.get("d"): - document["publication"]["statement"] = value.get("d") - - if sub_type == "ART_INBOOK": - document["publication"]["startDate"] = numbering[0] - - # If no field 260$c or 269$c, store start date - if not record.get("260__", {}).get("c") and not record.get("269__", {}).get("c"): - add_provision_activity_start_date(self, numbering[0]) - - if document: - data["document"] = document - - return data - - -def add_provision_activity_start_date(data, date): - """Add start date for provision activity. - - :param data: Data dictionary. - :param date: Date to add. - """ - provisition_activity = data.get("provisionActivity", []) - - def get_publication(): - """Get stored publication.""" - for key, item in enumerate(provisition_activity): - if item["type"] == "bf:Publication": - return provisition_activity.pop(key) - - return {"type": "bf:Publication", "startDate": None} - - publication = get_publication() - - publication["startDate"] = date - - # Inject publiction into provision activity - provisition_activity.append(publication) - - # Re-assign provisionActivity - data["provisionActivity"] = provisition_activity diff --git a/sonar/modules/documents/dojson/rerodoc/model.py_hesso b/sonar/modules/documents/dojson/rerodoc/model.py_hesso deleted file mode 100644 index 436f009eb..000000000 --- a/sonar/modules/documents/dojson/rerodoc/model.py_hesso +++ /dev/null @@ -1,1168 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""RERODOC MARC21 model definition.""" - -import hashlib -import re - -from dojson import utils -from flask import current_app -from invenio_db import db - -from sonar.modules.collections.api import Record as CollectionRecord -from sonar.modules.documents.dojson.rerodoc.overdo import Overdo -from sonar.modules.organisations.api import OrganisationRecord -from sonar.modules.subdivisions.api import Record as SubdivisionRecord -from sonar.modules.subdivisions.api import RecordSearch -from sonar.modules.utils import remove_trailing_punctuation - -overdo = Overdo() - -TYPE_MAPPINGS = { - 'PREPRINT|': 'coar:c_816b', - 'POSTPRINT|ART_JOURNAL': 'coar:c_6501', - 'POSTPRINT|ART_INBOOK': 'coar:c_3248', - 'POSTPRINT|ART_INPROC': 'coar:c_5794', - 'BOOK|': 'coar:c_2f33', - 'DISSERTATION|DISS_MASTER': 'coar:c_bdcc', - 'DISSERTATION|DISS_BACHELOR': 'coar:c_7a1f', - 'DISSERTATION|DISS_CONT_EDU': 'advanced_studies_thesis', - 'THESIS|': 'coar:c_db06', - 'THESIS|TH_PHD': 'coar:c_db06', - 'THESIS|TH_HABILIT': 'coar:c_46ec', - 'MAP|': 'coar:c_12cc', - 'REPORT|': 'coar:c_18ws', - 'NEWSPAPER|': 'coar:c_2fe3', - 'JOURNAL|': 'coar:c_0640', - 'PRINT_MEDIA|': 'coar:c_2fe3', - 'AUDIO|': 'coar:c_18cc', - 'IMAGE|': 'coar:c_ecc8', - 'PARTITION|': 'coar:c_18cw' -} - - -@overdo.over('type', '^980') -@utils.ignore_value -def marc21_to_type_and_organisation(self, key, value): - """Get document type and organisation from 980 field.""" - subdivision_name = None - record = overdo.blob_record - - # organisation - if value.get('b'): - organisation = value.get('b').lower() - - # Specific transformation for `unisi`, because the real acronym is - # `usi`. - if organisation == 'unisi': - organisation = 'usi' - - # Specific transformation for `hep bejune`, in order to fill out - # custom fields `Filière` (`customField1`) and `Titre obtenu` - # (`customField2`) - if organisation == 'hepbejune' and value.get('f'): - document_subtype = value.get('f').lower() - customField1 = '' - customField2 = '' - if document_subtype == 'diss_bachelor': - customField1 = 'Enseignement primaire' - customField2 = 'Bachelor of Arts in Pre-Primary and Primary Education' - elif document_subtype == 'diss_master': - customField1 = 'Enseignement secondaire' - customField2 = 'Master of Arts or of Science in Secondary Education' - if customField1: - self['customField1'] = [customField1] - if customField2: - self['customField2'] = [customField2] - - # Specific transformation for `hepfr`, which should be imported as - # a faculty AND a subdivision of FOLIA/unifr - if organisation == 'hepfr': - organisation = 'unifr' - self['organisation'] = [{ - '$ref': - OrganisationRecord.get_ref_link('organisations', organisation) - }] - # `hepfr` is a faculty of FOLIA/unifr - self['customField1'] = ['HEP|PH FR'] - # `hepfr` is a subdivision of FOLIA/unifr - subdivision_name = 'HEP Fribourg' - # Store subdivision - # TODO: avoid possible clashes between subdivision - # names in different languages - result = RecordSearch()\ - .filter('term', organisation__pid=organisation)\ - .filter('term', name__value__raw=subdivision_name)\ - .source(includes='pid').scan() - subdivision_pid = next(result).pid - # If the subdivision exists, assign it to the record - if subdivision_pid: - self['subdivisions'] = [{ - '$ref': - SubdivisionRecord.get_ref_link('subdivisions', subdivision_pid) - }] - - # Specific transformation for `hesso` - 11 institutions in RERO DOC - # are imported to a new `hesso` institution in SONAR - if organisation in [ - 'heascne', 'heasju', 'hedsfr', 'hegge', 'hedsge', - 'hetsge', 'hetsvs', 'hegtvs', 'hedsvs', 'heivs', 'esdsvs' - ]: - # include the original organisation code (from RERO DOC) as a note, - # for later reference - notes = [] - if 'notes' in self: - notes = self['notes'] - institution = None - code = None - - # replace the original RERO DOC institution codes and names - # by their current equivalent - # NB: records from 'esdsvs' are imported as 'hetsvs' - inst_mapping = { - 'heascne': ('hearc-cor', 'Haute Ecole Arc Conservation-Restauration Neuchâtel'), - 'heasju': ('hearc-san', 'Haute Ecole Arc Santé'), - 'hedsfr': ('hedsfr', 'Haute école de santé Fribourg'), - 'hegge': ('hegge', 'Haute école de gestion Genève'), - 'hedsge': ('hedsge', 'Haute école de santé Genève'), - 'hetsge': ('hetsge', 'Haute école de travail social Genève'), - 'hetsvs': ('hetsvs', 'Haute Ecole de Travail Social Valais'), - 'esdsvs': ('hetsvs', 'Haute Ecole de Travail Social Valais'), - 'hegtvs': ('hegvs', 'Haute Ecole de Gestion Valais'), - 'hedsvs': ('hedsvs', 'Haute Ecole de Santé Valais'), - 'heivs': ('heivs', 'Haute Ecole d\'Ingénierie Valais') - } - (code, institution) = inst_mapping.get(organisation, (None, None)) - - # reject deprecated institution names - depracated_names = ( - 'Haute Ecole Arc Conservation-Restauration', - 'Haute Ecole de Santé de Fribourg', - 'Haute école de gestion de Genève', - 'Haute école de travail social de Genève', - 'Haute Ecole de Travail Social', - 'Haute Ecole de Gestion & Tourisme', - 'Haute Ecole d\'Ingénierie', - ) - - # include the institution name as note - if not institution: - # get it from source MARC if not found in the dict - institution = record.get('919__', {}).get('a') - if (institution and institution not in notes and - institution not in depracated_names): - notes.append(institution) - # include faculty and department information as notes, as well - faculty = record.get('918__', {}).get('a') - if (faculty and faculty not in notes and - faculty not in depracated_names): - notes.append(faculty) - department = record.get('918__', {}).get('c') - if department and department not in notes: - notes.append(department) - # add a note with a code identifiying the source institution - notes.append('hesso:' + code) - self['notes'] = notes - # from now on, the organisation code is `hesso` - organisation = 'hesso' - - - # Specific transformation for `bpuge` and `mhnge`, because the real - # acronym is `vge`. - subdivision_name = None - - if organisation in [ - 'bpuge', 'mhnge', 'baage', 'bmuge', 'imvge', 'mhsge' - ]: - subdivision_name = 'bge' if organisation == 'bpuge' else organisation - organisation = 'vge' - - if organisation not in overdo.registererd_organisations: - overdo.create_organisation(organisation) - overdo.registererd_organisations.append(organisation) - - self['organisation'] = [{ - '$ref': - OrganisationRecord.get_ref_link('organisations', organisation) - }] - - if subdivision_name: - # Store subdivision - hash_key = hashlib.md5( - (subdivision_name + organisation).encode()).hexdigest() - - subdivision_pid = SubdivisionRecord.get_pid_by_hash_key(hash_key) - - # No subdivision found - if not subdivision_pid: - subdivision = SubdivisionRecord.create({ - 'name': [{ - 'language': 'eng', - 'value': subdivision_name - }], - 'organisation': { - '$ref': - OrganisationRecord.get_ref_link( - 'organisations', organisation) - }, - 'hashKey': - hash_key - }) - subdivision.commit() - subdivision.reindex() - db.session.commit() - subdivision_pid = subdivision['pid'] - - self['subdivisions'] = [{ - '$ref': - SubdivisionRecord.get_ref_link('subdivisions', subdivision_pid) - }] - - # get doc type by mapping - key = value.get('a', '') + '|' + value.get('f', '') - if key not in TYPE_MAPPINGS: - current_app.logger.warning(f'Document type not found in mapping for type "{key}"') - return None - - # Store types to records - self['documentType'] = TYPE_MAPPINGS[key] - - return None - - -@overdo.over('language', '^041') -@utils.for_each_value -@utils.ignore_value -def marc21_to_language(self, key, value): - """Get languages.""" - if not value.get('a'): - return None - - language = self.get('language', []) - - codes = utils.force_list(value.get('a')) - - for code in codes: - language.append({'type': 'bf:Language', 'value': code}) - - self['language'] = language - - return None - - -@overdo.over('title', '^245..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_title_245(self, key, value): - """Get title.""" - main_title = value.get('a') - language = value.get('9', 'eng') - subtitle = value.get('b') - - if not main_title: - return None - - title = { - 'type': 'bf:Title', - 'mainTitle': [{ - 'value': main_title.rstrip(':'), - 'language': language - }] - } - - if subtitle: - title['subtitle'] = [{'value': subtitle, 'language': language}] - - return title - - -@overdo.over('title', '^246..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_title_246(self, key, value): - """Get title.""" - main_title = value.get('a') - language = value.get('9', 'eng') - - if not main_title: - return None - - title = self.get('title', [{'type': 'bf:Title', 'mainTitle': []}]) - - # Add title 246 to last title in mainTitle propert - title[-1]['mainTitle'].append({'value': main_title, 'language': language}) - - self['title'] = title - - return None - - -@overdo.over('editionStatement', '^250..') -@utils.ignore_value -def marc21_to_edition_statement(self, key, value): - """Get edition statement data.""" - if not value.get('a'): - return None - - editionStatement = { - 'editionDesignation': { - 'value': value.get('a') - } - } - if value.get('b'): - editionStatement['responsibility'] = { - 'value': value.get('b') - } - return editionStatement - - -@overdo.over('provisionActivity', '^260..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_provision_activity_field_260(self, key, value): - """Get provision activity data from field 260.""" - provision_activity = self.get('provisionActivity', []) - - # Only if there is a date - if value.get('c'): - publication = {'type': 'bf:Publication', 'statement': []} - - # Place - if value.get('a'): - publication['statement'].append({ - 'type': - 'bf:Place', - 'label': [{ - 'value': value.get('a') - }] - }) - - # Agent - if value.get('b'): - publication['statement'].append({ - 'type': - 'bf:Agent', - 'label': [{ - 'value': remove_trailing_punctuation(value.get('b')) - }] - }) - - years = value.get('c').split('-') - - # Start date - if years and re.match(r'^\d{4}$', years[0]): - publication['startDate'] = years[0] - - publication['statement'].append({ - 'type': - 'Date', - 'label': [{ - 'value': value.get('c') - }] - }) - - # End date - if len(years) > 1 and re.match(r'^\d{4}$', years[1]): - publication['endDate'] = years[1] - - provision_activity.append(publication) - - # Manufacture - if value.get('e') or value.get('f'): - manufacture = {'type': 'bf:Manufacture', 'statement': []} - - if value.get('e'): - manufacture['statement'].append({ - 'type': - 'bf:Place', - 'label': [{ - 'value': remove_trailing_punctuation(value.get('e')) - }] - }) - - if value.get('f'): - manufacture['statement'].append({ - 'type': - 'bf:Agent', - 'label': [{ - 'value': value.get('f') - }] - }) - - provision_activity.append(manufacture) - - # Re-assign provision activity - if provision_activity: - self['provisionActivity'] = provision_activity - - return None - - -@overdo.over('provisionActivity', '^269..') -@utils.ignore_value -def marc21_to_provision_activity_field_269(self, key, value): - """Get provision activity data from field 269.""" - # 260$c has priority to this date - if overdo.blob_record.get('260__', {}).get('c'): - return None - - # No date, skipping - if not value.get('c'): - return None - - # Assign start date - match = re.search(r'^[0-9]{4}(-[0-9]{2}-[0-9]{2})?$', value.get('c')) - - # Date does not match "YYYY" or "YYYY-MM-DD" - if not match: - return None - - add_provision_activity_start_date(self, value.get('c')) - - return None - - -@overdo.over('formats', '^300..') -@utils.ignore_value -def marc21_to_description(self, key, value): - """Get extent, otherMaterialCharacteristics, formats. - - extent: 300$a (the first one if many) - otherMaterialCharacteristics: 300$b (the first one if many) - formats: 300 [$c repetitive] - """ - if value.get('a'): - if not self.get('extent'): - self['extent'] = remove_trailing_punctuation( - overdo.not_repetitive(value, 'a')) - - if value.get('b'): - if self.get('otherMaterialCharacteristics', []) == []: - self['otherMaterialCharacteristics'] = remove_trailing_punctuation( - overdo.not_repetitive(value, 'b')) - - if value.get('c'): - formats = self.get('formats') - - if not formats: - data = value.get('c') - formats = list(utils.force_list(data)) - - return formats - - return None - - -@overdo.over('series', '^490..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_series(self, key, value): - """Get series. - - series.name: [490$a repetitive] - series.number: [490$v repetitive] - """ - series = {} - - name = value.get('a') - if name: - series['name'] = ', '.join(utils.force_list(name)) - - number = value.get('v') - if number: - series['number'] = ', '.join(utils.force_list(number)) - - return series - - -@overdo.over('abstracts', '^520..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_abstract(self, key, value): - """Get abstract.""" - abstract = value.get('a') - language = value.get('9', 'eng') - - if not abstract: - return None - - if language == 'fr': - language = 'fre' - - abstracts_data = self.get('abstracts', []) - abstracts_data.append({'value': abstract, 'language': language}) - - self['abstracts'] = abstracts_data - - return None - - -@overdo.over('identifiedBy', '001') -@utils.ignore_value -def marc21_to_identified_by_from_001(self, key, value): - """Get identifier from field 001.""" - identified_by = self.get('identifiedBy', []) - - identified_by.append({ - 'type': 'bf:Local', - 'source': 'RERO DOC', - 'value': value - }) - - return identified_by - - -@overdo.over('identifiedBy', '^020..') -@utils.ignore_value -def marc21_to_identified_by_from_020(self, key, value): - """Get identifier from field 020.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a'): - return None - - identified_by.append({'type': 'bf:Isbn', 'value': value.get('a')}) - - return identified_by - - -@overdo.over('identifiedBy', '^0247.') -@utils.ignore_value -def marc21_to_identified_by_from_024(self, key, value): - """Get identifier from field 024.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a') or value.get('2') != 'urn': - return None - - identified_by.append({'type': 'bf:Urn', 'value': value.get('a')}) - - return identified_by - - -@overdo.over('identifiedBy', '^027..') -@utils.ignore_value -def marc21_to_identified_by_from_027(self, key, value): - """Get identifier from field 027.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a'): - return None - - identified_by.append({'type': 'bf:Strn', 'value': value.get('a')}) - - return identified_by - - -@overdo.over('identifiedBy', '^035..') -@utils.ignore_value -def marc21_to_identified_by_from_035(self, key, value): - """Get identifier from field 035.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a'): - return None - - identified_by.append({ - 'type': 'bf:Local', - 'source': 'RERO', - 'value': value.get('a') - }) - - return identified_by - - -@overdo.over('identifiedBy', '^037..') -@utils.ignore_value -def marc21_to_identified_by_from_037(self, key, value): - """Get identifier from field 037.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a'): - return None - - identified_by.append({ - 'type': - 'bf:Local', - 'source': - 'Swissbib', - 'value': - value.get('a').replace('swissbib.ch:', '').strip() - }) - - return identified_by - - -@overdo.over('identifiedBy', '^088..') -@utils.ignore_value -def marc21_to_identified_by_from_088(self, key, value): - """Get identifier from field 088.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a'): - return None - - identified_by.append({'type': 'bf:ReportNumber', 'value': value.get('a')}) - - return identified_by - - -@overdo.over('identifiedBy', '^091..') -@utils.ignore_value -def marc21_to_identified_by_from_091(self, key, value): - """Get identifier from field 091.""" - identified_by = self.get('identifiedBy', []) - - if not value.get('a') or value.get('b') != 'pmid': - return None - - identified_by.append({ - 'type': 'bf:Local', - 'value': value.get('a'), - 'source': 'PMID' - }) - - return identified_by - - -@overdo.over('notes', '^500..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_notes(self, key, value): - """Get notes.""" - return overdo.not_repetitive(value, 'a') - - -@overdo.over('subjects', '^600..|695..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_subjects(self, key, value): - """Get subjects.""" - if not value.get('a'): - return None - - subject_values = [] - - for item in value.get('a').split(' ; '): - if item: - subject_values.append(item) - - subjects = {'label': {'value': subject_values}} - - # If field is 695 and no language is available - if key == '695__': - if not value.get('9'): - return None - - subjects['label']['language'] = value.get('9') - - # If field is 600 and no source is available - if key == '600__': - if not value.get('2'): - return None - - subjects['source'] = value.get('2') - - return subjects - - -@overdo.over('files', '^856..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_files(self, key, value): - """Get files.""" - key = value.get('f') - url = value.get('u') - mime_type = value.get('q', 'text/plain') - - if not key or not url: - return None - - # TODO: Check why this type of file exists. Real example with rerodoc ID - # 29085 - if mime_type == 'pdt/download': - current_app.logger.warning( - f"File {key} for record {self['identifiedBy']} has a strange pdt/download mime " - "type, skipping import of file..." - ) - return None - - url = url.strip() - - # Retreive file order - # If order not set we put a value to 99 for easily point theses files - order = 99 - if value.get('y'): - match = re.search(r'order:([0-9]+)$', value.get('y')) - if match: - order = int(match.group(1)) - - data = { - 'key': key, - 'url': url, - 'label': value.get('z', key), - 'type': 'file', - 'order': order - } - - return data - - -@overdo.over('otherEdition', '^775..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_other_edition(self, key, value): - """Get other edition.""" - electronic_locator = value.get('o') - public_note = value.get('g') - - if not electronic_locator or not public_note: - return None - - # if the value matches a DOI, apply `identifiedBy[type:bf:Doi]` - matches = re.search(r'(?P10\.\d{4,9}/[-._;()/:a-zA-Z0-9]+)', value.get('o')) - if matches and matches.group('doi'): - identified_by = self.get('identifiedBy', []) - identified_by.append({ - 'type': 'bf:Doi', - 'value': matches.group('doi') - }) - self['identifiedBy'] = identified_by - return None - else: - return { - 'document': { - 'electronicLocator': electronic_locator - }, - 'publicNote': public_note - } - - -@overdo.over('collections', '^982..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_specific_collection(self, key, value): - """Extract collection for record.""" - if not value.get('a'): - return None - - # No organisation found, the collection is not imported. - if not self.get('organisation'): - return None - - organisation_pid = OrganisationRecord.get_pid_by_ref_link( - self['organisation'][0]['$ref']) - - hash_key = hashlib.md5( - (value.get('a') + organisation_pid).encode()).hexdigest() - - collection_pid = CollectionRecord.get_pid_by_hash_key(hash_key) - - # No collection found - if not collection_pid: - collection = CollectionRecord.create({ - 'name': [{ - 'language': 'eng', - 'value': value.get('a') - }], - 'organisation': { - '$ref': self['organisation'][0]['$ref'] - }, - 'hashKey': - hash_key - }) - collection.commit() - collection.reindex() - db.session.commit() - collection_pid = collection['pid'] - - return { - '$ref': CollectionRecord.get_ref_link('collections', collection_pid) - } - - -@overdo.over('classification', '^080..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_classification_field_080(self, key, value): - """Get classification data from field 080.""" - if not value.get('a'): - return None - - return { - 'type': 'bf:ClassificationUdc', - 'classificationPortion': value.get('a') - } - - -@overdo.over('classification', '^084..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_classification_field_084(self, key, value): - """Get classification data from field 084.""" - if not value.get('a') or value.get('2') != 'ddc': - return None - - return { - 'type': 'bf:ClassificationDdc', - 'classificationPortion': value.get('a') - } - - -@overdo.over('contentNote', '^505..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_content_note(self, key, value): - """Extract content note for record.""" - return value.get('a') - - -@overdo.over('dissertation', '^502..') -@utils.ignore_value -def marc21_to_dissertation_field_502(self, key, value): - """Extract dissertation degree.""" - record = overdo.blob_record - if value.get('a'): - dissertation = self.get('dissertation', {}) - dissertation['degree'] = value.get('a') - self['dissertation'] = dissertation - - # try to parse the thesis note more precisely - matches = re.match(r'^(?P[^:]+) : (?P[^,]+) ?[,:] (?P\d{4})( ; .*)?$', value.get('a')) - if matches: - if matches.group("degree"): - dissertation['degree'] = matches.group("degree") - if matches.group("grantingInstitution"): - dissertation['grantingInstitution'] = matches.group("grantingInstitution") - if matches.group("date"): - dissertation['date'] = matches.group("date") - - # Specific transformation for `hep bejune`, in order to fill out - # custom fields `Filière` (`customField1`) and `Titre obtenu` - # (`customField2`) - organisation = record.get('980__', {}).get('b') - if (organisation and organisation.lower() == 'hepbejune'): - degree = matches.group("degree").lower() - customField1 = None - customField2 = None - if 'mémoire de master spécialisé' in degree: - customField1 = 'Enseignement spécialisé' - customField2 = 'Master of Arts in special needs ' \ - 'education, orientation enseignement spécialisé' - elif 'mémoire de master' in degree: - customField1 = 'Enseignement secondaire' - customField2 = 'Master of Arts or of Science ' \ - 'in Secondary Education' - elif 'mémoire de bachelor' in degree: - customField1 = 'Enseignement primaire' - customField2 = 'Bachelor of Arts in Pre-Primary and ' \ - 'Primary Education' - if customField1: - self['customField1'] = [customField1] - if customField2: - self['customField2'] = [customField2] - - # Try to get start date and store in provision activity - # 260$c and 269$c have priority to this date - if (record.get('260__', {}).get('c') or record.get('269__', {}).get('c') or - record.get('773__', {}).get('g')): - return None - - # No date, skipping - if not value.get('9'): - return None - - # Match either 2019 or 2019-01-01 - match = re.search(r'^[0-9]{4}(-[0-9]{2}-[0-9]{2})?$', value.get('9')) - - if not match: - return None - - add_provision_activity_start_date(self, value.get('9')) - - return None - - -@overdo.over('dissertation', '^508..') -@utils.ignore_value -def marc21_to_dissertation_field_508(self, key, value): - """Extract dissertation note.""" - if not value.get('a'): - return None - - dissertation = self.get('dissertation', {}) - dissertation['jury_note'] = value.get('a') - - self['dissertation'] = dissertation - - return None - - -@overdo.over('usageAndAccessPolicy', '^540..') -@utils.ignore_value -def marc21_to_usage_and_access_policy(self, key, value): - """Extract usage and access policy.""" - if not value.get('a'): - return None - - return {'label': value.get('a'), 'license': 'License undefined'} - - -@overdo.over('contribution', '^100..') -@utils.ignore_value -def marc21_to_contribution_field_100(self, key, value): - """Extract contribution from field 100.""" - if not value.get('a'): - return None - - contribution = self.get('contribution', []) - - data = { - 'agent': { - 'type': 'bf:Person', - 'preferred_name': value.get('a') - }, - 'role': ['cre'] - } - - # Affiliation - if value.get('u'): - data['affiliation'] = value.get('u') - - # Date of birth - date of death - date_of_birth, date_of_death = overdo.extract_date(value.get('d')) - - if date_of_birth: - data['agent']['date_of_birth'] = date_of_birth - - if date_of_death: - data['agent']['date_of_death'] = date_of_death - - contribution.append(data) - self['contribution'] = contribution - - return None - - -@overdo.over('contribution', '^700..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_contribution_field_700(self, key, value): - """Extract contribution from field 100.""" - if not value.get('a'): - return None - - contribution = self.get('contribution', []) - - role = overdo.get_contributor_role(value.get('e')) - - if not role: - raise Exception(f'No role found for contributor {value}') - - data = { - 'agent': { - 'type': 'bf:Person', - 'preferred_name': value.get('a') - }, - 'role': [role] - } - - # Affiliation - if value.get('u'): - data['affiliation'] = value.get('u') - - # Date of birth - date of death - date_of_birth, date_of_death = overdo.extract_date(value.get('d')) - - if date_of_birth: - data['agent']['date_of_birth'] = date_of_birth - - if date_of_death: - data['agent']['date_of_death'] = date_of_death - - contribution.append(data) - self['contribution'] = contribution - - return None - - -@overdo.over('contribution', '^710..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_contribution_field_710(self, key, value): - """Extract contribution from field 710.""" - if not value.get('a'): - return None - - contribution = self.get('contribution', []) - contribution.append({ - 'agent': { - 'type': 'bf:Organization', - 'preferred_name': value.get('a') - }, - 'role': ['ctb'] - }) - self['contribution'] = contribution - - return None - - -@overdo.over('contribution', '^711..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_contribution_field_711(self, key, value): - """Extract contribution from field 711.""" - if not value.get('a'): - return None - - contribution = self.get('contribution', []) - - data = { - 'agent': { - 'type': 'bf:Meeting', - 'preferred_name': value.get('a') - }, - 'role': ['cre'] - } - - # Place - if value.get('c'): - data['agent']['place'] = value.get('c') - - # Date - if value.get('d'): - data['agent']['date'] = value.get('d') - - # Number - if value.get('n'): - data['agent']['number'] = value.get('n') - - contribution.append(data) - self['contribution'] = contribution - - return None - - -@overdo.over('customField1', '^918..') -@utils.ignore_value -def marc21_to_faculty_and_department(self, key, value): - """Extract faculty and department for UNIFR.""" - record = overdo.blob_record - org = record.get('980__', {}).get('b') - if org and org == 'UNIFR': - faculty = value.get('a') - if faculty: - self['customField1'] = [faculty] - dep = value.get('c') - if dep: - self['customField2'] = [dep] - return None - - -@overdo.over('partOf', '^773..') -@utils.for_each_value -@utils.ignore_value -def marc21_to_part_of(self, key, value): - """Extract related document for record.""" - if not value.get('g'): - return None - - # Split value for getting numbering values - numbering = value.get('g').split('/') - - # Numbering year - if not numbering[0]: - return None - - data = {'numberingYear': numbering[0]} - - # Volume - if len(numbering) > 1 and numbering[1]: - data['numberingVolume'] = numbering[1] - - # Issue - if len(numbering) > 2 and numbering[2]: - data['numberingIssue'] = numbering[2] - - # Pages - if len(numbering) > 3 and numbering[3] and numbering[3] != '-': - data['numberingPages'] = numbering[3] - - document = {} - - # Title is found - if value.get('t'): - document['title'] = value.get('t') - - # Contribution - if value.get('c'): - contributors = [] - for contributor in value.get('c').split(';'): - if contributor: - contributors.append(contributor) - - if contributors: - document['contribution'] = contributors - - record = overdo.blob_record - - # Publication based on document sub type - sub_type = record.get('980__', {}).get('f') - if value.get('d') or sub_type == 'ART_INBOOK': - document['publication'] = {} - - if value.get('d'): - document['publication']['statement'] = value.get('d') - - if sub_type == 'ART_INBOOK': - document['publication']['startDate'] = numbering[0] - - # If no field 260$c or 269$c, store start date - if (not record.get('260__', {}).get('c') and - not record.get('269__', {}).get('c')): - add_provision_activity_start_date(self, numbering[0]) - - if document: - data['document'] = document - - return data - - -def add_provision_activity_start_date(data, date): - """Add start date for provision activity. - - :param data: Data dictionary. - :param date: Date to add. - """ - provisition_activity = data.get('provisionActivity', []) - - def get_publication(): - """Get stored publication.""" - for key, item in enumerate(provisition_activity): - if item['type'] == 'bf:Publication': - return provisition_activity.pop(key) - - return {'type': 'bf:Publication', 'startDate': None} - - publication = get_publication() - - publication['startDate'] = date - - # Inject publiction into provision activity - provisition_activity.append(publication) - - # Re-assign provisionActivity - data['provisionActivity'] = provisition_activity diff --git a/sonar/modules/documents/dojson/rerodoc/overdo.py b/sonar/modules/documents/dojson/rerodoc/overdo.py deleted file mode 100644 index 3e635871b..000000000 --- a/sonar/modules/documents/dojson/rerodoc/overdo.py +++ /dev/null @@ -1,141 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Overdo specialized class for RERODOC DOJSON transformation.""" - -from flask import current_app - -from sonar.modules.documents.dojson.overdo import Overdo as BaseOverdo -from sonar.modules.organisations.api import OrganisationRecord - - -class Overdo(BaseOverdo): - """Overdo specialized class for RERODOC DOJSON transformation.""" - - registererd_organisations = [] - - @staticmethod - def create_organisation(organisation_key): - """Create organisation if not existing and return it. - - :param str organisation_key: Key (PID) of the organisation. - """ - if not organisation_key: - raise Exception("No key provided") - - # Get organisation record from database - organisation = OrganisationRecord.get_record_by_pid(organisation_key) - - if not organisation: - # Create organisation record - organisation = OrganisationRecord.create( - { - "code": organisation_key, - "name": organisation_key, - "isShared": False, - "isDedicated": False, - }, - with_bucket=True, - dbcommit=True, - ) - organisation.reindex() - - def do(self, blob, ignore_missing=True, exception_handlers=None): - """Do transformation.""" - result = super().do(blob, ignore_missing=ignore_missing, exception_handlers=exception_handlers) - - # Verify data integrity - self.verify(result) - - # Add default license if not set. - if not result.get("usageAndAccessPolicy"): - default_license = "License undefined" - if ( - result.get("organisation") - and result["organisation"][0]["$ref"] == "https://sonar.ch/api/organisations/hepbejune" - ): - default_license = "CC BY-NC-SA" - - result["usageAndAccessPolicy"] = {"license": default_license} - - return result - - def get_contributor_role(self, role_700=None): - """Return contributor role. - - :param role_700: String, role found in field 700$e - :returns: String containing the mapped role or None - """ - if role_700 in ["Dir.", "Codir."]: - return "dgs" - - if role_700 == "Libr./Impr.": - return "prt" - - if role_700 == "joint author": - return "cre" - - if not role_700: - doc_type = self.blob_record.get("980__", {}).get("a") - - if not doc_type: - return None - - if doc_type in ["PREPRINT", "POSTPRINT", "DISSERTATION", "REPORT"]: - return "cre" - - if doc_type in [ - "BOOK", - "THESIS", - "MAP", - "JOURNAL", - "PARTITION", - "AUDIO", - "IMAGE", - ]: - return "ctb" - - return None - - def verify(self, result): - """Verify record data integrity after processing. - - :param result: Record data - """ - - def is_pa_mandatory(): - """Check if record types make provision activity mandatory.""" - document_type = result.get("documentType") - - if not document_type: - return True - - return document_type not in [ - "coar:c_beb9", - "coar:c_6501", - "coar:c_998f", - "coar:c_dcae04bc", - "coar:c_3e5a", - "coar:c_5794", - "coar:c_6670", - ] - - self.result_ok = True - - # Check if provision activity is set, but it's optional depending - # on record types - if "provisionActivity" not in result and is_pa_mandatory(): - self.result_ok = False - current_app.logger.warning(f"No provision activity found in record {result}") diff --git a/sonar/modules/documents/loaders/schemas/factory.py b/sonar/modules/documents/loaders/schemas/factory.py index 738b33cab..8eff08f00 100644 --- a/sonar/modules/documents/loaders/schemas/factory.py +++ b/sonar/modules/documents/loaders/schemas/factory.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -17,14 +17,12 @@ from .archive_ouverte_unige import ArchiveOuverteUnigeSchema from .boris import BorisSchema -from .rerodoc import RerodocSchema class LoaderSchemaFactory: """Factory for creating a loader schema.""" schemas = { - "rerodoc": RerodocSchema, "archive_ouverte_unige": ArchiveOuverteUnigeSchema, "boris": BorisSchema, } diff --git a/sonar/modules/documents/loaders/schemas/rerodoc.py b/sonar/modules/documents/loaders/schemas/rerodoc.py deleted file mode 100644 index cf27d2266..000000000 --- a/sonar/modules/documents/loaders/schemas/rerodoc.py +++ /dev/null @@ -1,61 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""RERODOC schema.""" - -from invenio_records_rest.schemas.fields import SanitizedUnicode -from marshmallow import fields, pre_dump - -from sonar.modules.documents.dojson.rerodoc.model import overdo - -from .marc21 import Marc21Schema - - -class RerodocSchema(Marc21Schema): - """RERODOC schema.""" - - documentType = SanitizedUnicode() - title = fields.List(fields.Dict()) - partOf = fields.List(fields.Dict()) - abstracts = fields.List(fields.Dict()) - contribution = fields.List(fields.Dict()) - organisation = fields.List(fields.Dict()) - language = fields.List(fields.Dict()) - copyrightDate = fields.List(fields.String()) - editionStatement = fields.Dict() - provisionActivity = fields.List(fields.Dict()) - extent = SanitizedUnicode() - otherMaterialCharacteristics = SanitizedUnicode() - formats = fields.List(SanitizedUnicode()) - additionalMaterials = SanitizedUnicode() - series = fields.List(fields.Dict()) - notes = fields.List(fields.String()) - identifiedBy = fields.List(fields.Dict()) - subjects = fields.List(fields.Dict()) - classification = fields.List(fields.Dict()) - collections = fields.List(fields.Dict()) - dissertation = fields.Dict() - otherEdition = fields.List(fields.Dict()) - usageAndAccessPolicy = fields.Dict() - files = fields.List(fields.Dict()) - subdivisions = fields.List(fields.Dict()) - customField1 = fields.List(fields.String()) - customField2 = fields.List(fields.String()) - customField3 = fields.List(fields.String()) - - @pre_dump - def process(self, obj, **kwargs): - """All the process is done by overdo.""" - return overdo.do(obj) diff --git a/sonar/modules/documents/minters.py b/sonar/modules/documents/minters.py index 378def10e..07bcff134 100644 --- a/sonar/modules/documents/minters.py +++ b/sonar/modules/documents/minters.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -19,7 +19,6 @@ from invenio_oaiserver.minters import oaiid_minter from invenio_oaiserver.provider import OAIIDProvider from invenio_pidstore.errors import PIDAlreadyExists, PIDDoesNotExistError -from invenio_pidstore.models import PersistentIdentifier, PIDStatus from sonar.modules.ark.api import Ark @@ -48,26 +47,12 @@ def id_minter(record_uuid, data, provider, pid_key="pid", object_type="rec"): def external_minters(record_uuid, data, pid_key="pid"): """External minters. - RERO DOC and ARK. + ARK. :param record_uuid: Record UUID. :param data: Record data. :param pid_key: PID key. - :returns: Created PID object. """ - for identifier in data.get("identifiedBy", []): - if identifier.get("source") == "RERO DOC": - try: - pid = PersistentIdentifier.create( - "rerod", - identifier["value"], - object_type="rec", - object_uuid=record_uuid, - status=PIDStatus.REGISTERED, - ) - pid.redirect(PersistentIdentifier.get("doc", data[pid_key])) - except PIDAlreadyExists: - pass new_data = current_app.extensions.get("invenio-records").replace_refs(data.get("organisation", [{}])[0]) naan = new_data.get("arkNAAN") diff --git a/sonar/theme/views.py b/sonar/theme/views.py index db310bac7..4e6fcf38f 100644 --- a/sonar/theme/views.py +++ b/sonar/theme/views.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -42,14 +42,12 @@ from flask_menu import current_menu from invenio_jsonschemas import current_jsonschemas from invenio_jsonschemas.errors import JSONSchemaNotFound -from invenio_pidstore.models import PersistentIdentifier from sonar.jsonschemas.factory import JSONSchemaFactory from sonar.modules.collections.permissions import ( RecordPermission as CollectionPermission, ) from sonar.modules.deposits.permissions import DepositPermission -from sonar.modules.documents.api import DocumentRecord from sonar.modules.documents.permissions import DocumentPermission from sonar.modules.organisations.api import OrganisationSearch from sonar.modules.organisations.permissions import OrganisationPermission @@ -124,55 +122,6 @@ def error(): raise Exception("this is an error for test purposes") -@blueprint.route("/rerodoc/") -@blueprint.route("/rerodoc//files/") -def rerodoc_redirection(pid, filename=None): - """Redirection to document with identifier from RERODOC. - - :param pid: PID from RERODOC. - :returns: A redirection to record's detail page or 404 if not found. - """ - try: - pid = PersistentIdentifier.get("rerod", pid) - except Exception: - abort(404) - - # Files URLs does not contains view - if filename: - return redirect( - url_for( - "invenio_records_ui.doc_files", - pid_value=pid.get_redirect().pid_value, - filename=filename, - ) - ) - doc_pid = pid.get_redirect().pid_value - doc = DocumentRecord.get_record_by_pid(doc_pid) - if doc: - doc = doc.resolve() - orgs = doc.get("organisation", []) - # In case of multiple organisations we redirect to the global view - if len(orgs) == 1: - org = orgs.pop() - # Only for dedicated or shared - if org.get("isDedicated") or org.get("isShared"): - return redirect( - url_for( - "invenio_records_ui.doc", - view=org.get("code"), - pid_value=pid.get_redirect().pid_value, - ) - ) - global_view = current_app.config.get("SONAR_APP_DEFAULT_ORGANISATION") - return redirect( - url_for( - "invenio_records_ui.doc", - view=global_view, - pid_value=pid.get_redirect().pid_value, - ) - ) - - @blueprint.route("/manage/") @blueprint.route("/manage/") @can_access_manage_view diff --git a/tests/conftest.py b/tests/conftest.py index 58fb43c89..b4a0acf82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -433,7 +433,6 @@ def document_json(app, db, bucket_location, organisation): return { "identifiedBy": [ {"value": "oai:doc.rero.ch:20050302172954-WU", "type": "bf:Identifier"}, - {"value": "111111", "type": "bf:Local", "source": "RERO DOC"}, {"value": "R003415713", "type": "bf:Local", "source": "RERO"}, ], "language": [{"value": "eng", "type": "bf:Language"}], diff --git a/tests/ui/conftest.py b/tests/ui/conftest.py index 9d2c75082..4df80e3c4 100644 --- a/tests/ui/conftest.py +++ b/tests/ui/conftest.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -23,7 +23,7 @@ @pytest.fixture(scope="module") def harvested_record(): - """Return test XML output file path.""" + """Return test XML record.""" with open(os.path.join(os.path.dirname(__file__), "data", "harvested_record.xml")) as file: yield file.read() diff --git a/tests/ui/data/harvested_record.xml b/tests/ui/data/harvested_record.xml index b269ec2a0..b8a95c62b 100644 --- a/tests/ui/data/harvested_record.xml +++ b/tests/ui/data/harvested_record.xml @@ -1,128 +1,6 @@ - -
- oai:doc.rero.ch:20120503160026-MV - 2013-06-17T11:29:40Z - postprint - cdu1 - rero_explore - article - unifr -
- - - 00000coc 2200000uu 4500 - 29085 - 20150420164351.0 - - oai:doc.rero.ch:20120503160026-MV - article - rero_explore - cdu1 - postprint - unifr - - - eng - - - 1 - - - Betschart, Christof - - - Was ist Lebenskraft? - eng - Edith Steins anthropologischer Beitrag in 'Psychische - Kausalität' (Teil 2) - - - What is Lifeforce? Edith Stein's Anthropological - Contribution in 'Psychic Causality' (Part 2) - eng - - - Dieser Folge-Artikel im Edith Stein Jahrbuch 2010 fragt nach - der Lebenskraft in Edith Steins Abhandlung „Psychische Kausalität“ (1918 - geschrieben, 1922 publiziert). Es geht besonders um den Wandel der Lebenskraft - im Hinblick auf ihre Umsetzung im sinnlichen und geistigen Bereich, sowie ihre - Erneuerung durch die Welt der Werte, durch die intersubjektiven Beziehungen und - durch Gott. Ein eigener Abschnitt ist der Frage nach ganz aufgebrauchter - Lebenskraft gewidmet. Im Ausblick wird die Lebenskraft im Sinne eines Potenzials - zu sinnlicher und geistiger Betätigung interpretiert und eine Auseinandersetzung - mit der Tiefenpsychologie Sigmund Freuds angestrebt. - ger - - - force vitale ; phénoménologie ; conscience phénoménologique - ; réalité psychique ; idéalisme-réalisme ; dépression ; neurologie ; psychologie - des profondeurs ; vécu religieux ; monde des valeurs ; Edith Stein ; Edmund - Husserl - fre - - - Lebenskraft ; Betätigungspotenzial ; Phänomenologie ; - Bewusstsein ; Psyche ; Idealismus-Realismus ; Depression ; Neurologie ; - Tiefenpsychologie ; religiöses Erlebnis ; Wertewelt ; Burnout ; Edith Stein ; - Edmund Husserl - ger - - - vital force ; life force ; phenomenology ; phenomenological - conscience ; psyche ; idealism-realism ; depression ; neurology ; psychology of - Sigmund Freud ; world of values ; burnout ; Edith Stein ; Edmund Husserl - eng - - - forza vitale ; fenomenologia ; coscienza fenomenologica ; - psiche ; idealismo-realismo ; depressione ; neurologia ; psicologia freudiana ; - vissuto religioso ; mondo dei valori ; burnout ; Edith Stein ; Edmund Husserl - ita - - - Edith Stein Jahrbuch - 2010/16//33-64 - Echter - - - Betschart_Jahrbuch_2010.pdf - pdt/download - 126302 - - http://doc.rero.ch/lm.php?url=1000,43,2,20120503160026-MV/Betschart_Jahrbuch_2010.pdf - 2012-05-03 16:03:50 - Texte intégral - - - Betschart_Jahrbuch_2010.pdf - application/pdf - 126302 - - http://doc.rero.ch/record/29085/files/Betschart_Jahrbuch_2010.pdf - order:1 - Texte intégral - - - Faculté de théologie - Décanat, Av. de l'Europe 20, 1700 Fribourg - - - Université de Fribourg - Fribourg - doc.support@rero.ch - - - POSTPRINT - UNIFR - ART_JOURNAL - - - 20120503160026-MV - - - + + 123456 + + Test record + diff --git a/tests/ui/documents/cli/test_documents_cli_ark.py b/tests/ui/documents/cli/test_documents_cli_ark.py index abcb302c9..a7aed23aa 100644 --- a/tests/ui/documents/cli/test_documents_cli_ark.py +++ b/tests/ui/documents/cli/test_documents_cli_ark.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -"""Test documents RERODOC cli commands.""" +"""Test ARK cli commands.""" from click.testing import CliRunner diff --git a/tests/ui/documents/cli/test_documents_cli_rerodoc.py b/tests/ui/documents/cli/test_documents_cli_rerodoc.py deleted file mode 100644 index d359e4345..000000000 --- a/tests/ui/documents/cli/test_documents_cli_rerodoc.py +++ /dev/null @@ -1,57 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Test documents RERODOC cli commands.""" - -from click.testing import CliRunner - -import sonar.modules.documents.cli.rerodoc as cli - - -def test_update_file_permissions(app, script_info, document_with_file): - """Test update file permissions.""" - runner = CliRunner() - - # Not existing input file - result = runner.invoke( - cli.update_file_permissions, - ["./tests/ui/documents/data/not_existing.csv", "-c", "1"], - obj=script_info, - ) - assert "Error: Invalid value for 'PERMISSIONS_FILE'" in result.output - - # Invalid input file - result = runner.invoke( - cli.update_file_permissions, - ["./tests/ui/documents/data/invalid.csv", "-c", "1"], - obj=script_info, - ) - assert "CSV file seems to be not well formatted." in result.output - - # File cannot be parsed - result = runner.invoke( - cli.update_file_permissions, - ["./tests/ui/documents/data/permissions_file.pdf", "-c", "1"], - obj=script_info, - ) - assert "An error occured during file process" in result.output - - # OK - result = runner.invoke( - cli.update_file_permissions, - ["./tests/ui/documents/data/permissions_file.csv", "-c", "1"], - obj=script_info, - ) - assert "Process finished" in result.output diff --git a/tests/ui/documents/dojson/rerodoc/test_rerodoc_model.py b/tests/ui/documents/dojson/rerodoc/test_rerodoc_model.py deleted file mode 100644 index 50b6bcf15..000000000 --- a/tests/ui/documents/dojson/rerodoc/test_rerodoc_model.py +++ /dev/null @@ -1,2587 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""DOJSON module tests.""" - -import pytest -from dojson.contrib.marc21.utils import create_record - -from sonar.modules.documents.dojson.rerodoc.model import overdo - - -def test_marc21_to_type_and_organisation(app, bucket_location): - """Test type and organisation.""" - - # Type only - marc21xml = """ - - - BOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("documentType") == "coar:c_2f33" - assert not data.get("organisation") - - # Type and organisation - marc21xml = """ - - - BOOK - TEST - - - 1966 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("documentType") == "coar:c_2f33" - assert data.get("organisation") == [{"$ref": "https://sonar.ch/api/organisations/test"}] - - # Type, subtype and organisation - marc21xml = """ - - - POSTPRINT - TEST - ART_JOURNAL - - - 1966 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("documentType") == "coar:c_6501" - assert data.get("organisation") == [{"$ref": "https://sonar.ch/api/organisations/test"}] - - # Organisation only - marc21xml = """ - - - TEST - - - 1966 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("documentType") - assert data.get("organisation") == [{"$ref": "https://sonar.ch/api/organisations/test"}] - - # Specific conversion for unisi - marc21xml = """ - - - UNISI - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("documentType") - assert data.get("organisation") == [{"$ref": "https://sonar.ch/api/organisations/usi"}] - - # Specific conversion for bpuge - marc21xml = """ - - - BPUGE - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("documentType") - assert data.get("organisation") == [{"$ref": "https://sonar.ch/api/organisations/vge"}] - assert len(data["subdivisions"]) == 1 - - # Specific conversion for mhnge - marc21xml = """ - - - MHNGE - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("documentType") - assert data.get("organisation") == [{"$ref": "https://sonar.ch/api/organisations/vge"}] - assert len(data["subdivisions"]) == 1 - - -def test_marc21_to_title_245(app): - """Test dojson marc21_to_title.""" - - # One title with subtitle - marc21xml = """ - - - Main title - eng - Subtitle - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [ - { - "type": "bf:Title", - "mainTitle": [{"value": "Main title", "language": "eng"}], - "subtitle": [{"value": "Subtitle", "language": "eng"}], - } - ] - - # Multiple titles with subtitles - marc21xml = """ - - - Main title - eng - Subtitle - - - Titre principal - fre - Sous-titre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [ - { - "type": "bf:Title", - "mainTitle": [{"value": "Main title", "language": "eng"}], - "subtitle": [{"value": "Subtitle", "language": "eng"}], - }, - { - "type": "bf:Title", - "mainTitle": [{"value": "Titre principal", "language": "fre"}], - "subtitle": [{"value": "Sous-titre", "language": "fre"}], - }, - ] - - # One title without subtitle - marc21xml = """ - - - Main title - eng - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [{"type": "bf:Title", "mainTitle": [{"value": "Main title", "language": "eng"}]}] - - # No title - marc21xml = """ - - - eng - Subtitle - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("title") - - # No language - marc21xml = """ - - - Main title - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [{"type": "bf:Title", "mainTitle": [{"value": "Main title", "language": "eng"}]}] - - # Multiple title with one without title - marc21xml = """ - - - Main title - eng - Subtitle - - - fre - Sous-titre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [ - { - "type": "bf:Title", - "mainTitle": [{"value": "Main title", "language": "eng"}], - "subtitle": [{"value": "Subtitle", "language": "eng"}], - } - ] - - -def test_marc21_to_title_246(app): - """Test dojson marc21_to_title.""" - - # One title 246 without 245 - marc21xml = """ - - - Main title - eng - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [{"type": "bf:Title", "mainTitle": [{"value": "Main title", "language": "eng"}]}] - - # One title 246 without $a and without 245 - marc21xml = """ - - - eng - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("title") - - # One title 246 without language and without 245 - marc21xml = """ - - - Main title - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [{"type": "bf:Title", "mainTitle": [{"value": "Main title", "language": "eng"}]}] - - # One title 246 with one 245 title - marc21xml = """ - - - Main title 245 - eng - - - Main title 246 - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [ - { - "type": "bf:Title", - "mainTitle": [ - {"value": "Main title 245", "language": "eng"}, - {"value": "Main title 246", "language": "fre"}, - ], - } - ] - - # One title 246 with multiple 245 title - marc21xml = """ - - - Main title 245 1 - eng - - - Main title 245 2 - eng - - - Main title 246 - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("title") == [ - { - "type": "bf:Title", - "mainTitle": [{"value": "Main title 245 1", "language": "eng"}], - }, - { - "type": "bf:Title", - "mainTitle": [ - {"value": "Main title 245 2", "language": "eng"}, - {"value": "Main title 246", "language": "fre"}, - ], - }, - ] - - -def test_marc21_to_language(app): - """Test language transformation.""" - # OK - marc21xml = """ - - - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("language") == [{"type": "bf:Language", "value": "fre"}] - - # Multiple $a - marc21xml = """ - - - eng - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("language") == [ - {"type": "bf:Language", "value": "eng"}, - {"type": "bf:Language", "value": "fre"}, - ] - - # Multiple 041 - marc21xml = """ - - - eng - - - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("language") == [ - {"type": "bf:Language", "value": "eng"}, - {"type": "bf:Language", "value": "fre"}, - ] - - # Not $a - marc21xml = """ - - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("language") - - -def test_marc21_to_provision_activity_field_260(app): - """Test provision activity with field 260.""" - # OK - marc21xml = """ - - - Lausanne - 1798-1799 - Bulletin officiel du Directoire, - Lausanne : - Henri Vincent - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [ - { - "type": "bf:Publication", - "startDate": "1798", - "endDate": "1799", - "statement": [ - {"label": [{"value": "Lausanne"}], "type": "bf:Place"}, - { - "label": [{"value": "Bulletin officiel du Directoire"}], - "type": "bf:Agent", - }, - {"label": [{"value": "1798-1799"}], "type": "Date"}, - ], - }, - { - "type": "bf:Manufacture", - "statement": [ - {"label": [{"value": "Lausanne"}], "type": "bf:Place"}, - {"label": [{"value": "Henri Vincent"}], "type": "bf:Agent"}, - ], - }, - ] - - # No start date --> throw error - marc21xml = """ - - - Lausanne - Bulletin officiel du Directoire, - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") - - # No provision activity and wrong type --> throw error - marc21xml = """ - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") - - # No provision activity and right types --> ok provision activity is not - # mandatory - marc21xml = """ - - - POSTPRINT - ART_JOURNAL - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") - - # Without end date - marc21xml = """ - - - Lausanne - 1798 - Bulletin officiel du Directoire, - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity")[0]["startDate"] == "1798" - assert "endDate" not in data.get("provisionActivity")[0] - - # Wrong start date - marc21xml = """ - - - Lausanne - [1798?] - Bulletin officiel du Directoire, - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert "startDate" not in data.get("provisionActivity")[0] - - # Wrong end date - marc21xml = """ - - - Lausanne - 1798- - Bulletin officiel du Directoire, - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert "endDate" not in data.get("provisionActivity")[0] - - -def test_marc21_to_provision_activity_field_269(app): - """Test provision activity with field 269.""" - # One field - marc21xml = """ - - - 1966 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [{"startDate": "1966", "type": "bf:Publication"}] - - # One field with full date - marc21xml = """ - - - 1966-01-01 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [{"startDate": "1966-01-01", "type": "bf:Publication"}] - - # Date does not match "YYYY" OR "YYYY-MM-DD" - marc21xml = """ - - - 1966-1999 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") - - # Multiple fields - marc21xml = """ - - - 1966 - - - 2005 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [{"startDate": "2005", "type": "bf:Publication"}] - - # No field $c - marc21xml = """ - - - 1966 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") - - -def test_marc21_to_provision_activity_all(app): - """Test provision activity with both 260 and 269 fields.""" - marc21xml = """ - - - Lausanne - 1798-1799 - Bulletin officiel du Directoire, - Lausanne : - Henri Vincent - - - 1700 - - - Thèse 2 - 2020 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [ - { - "type": "bf:Publication", - "startDate": "1798", - "endDate": "1799", - "statement": [ - {"label": [{"value": "Lausanne"}], "type": "bf:Place"}, - { - "label": [{"value": "Bulletin officiel du Directoire"}], - "type": "bf:Agent", - }, - {"label": [{"value": "1798-1799"}], "type": "Date"}, - ], - }, - { - "type": "bf:Manufacture", - "statement": [ - {"label": [{"value": "Lausanne"}], "type": "bf:Place"}, - {"label": [{"value": "Henri Vincent"}], "type": "bf:Agent"}, - ], - }, - ] - - -def test_marc21_to_edition_statement(app): - """Test edition statement dojson from field 250.""" - # OK - marc21xml = """ - - - Reproduction numérique - René Wetzel - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("editionStatement") == { - "editionDesignation": {"value": "Reproduction numérique"}, - "responsibility": {"value": "René Wetzel"}, - } - - # Without field $a - marc21xml = """ - - - René Wetzel - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("editionStatement") - - # Without field $b - marc21xml = """ - - - Reproduction numérique - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("editionStatement") - - # Multiple --> keep only one value - marc21xml = """ - - - Reproduction numérique 1 - John Doe - - - Reproduction numérique 2 - René Wetzel - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("editionStatement") == { - "editionDesignation": {"value": "Reproduction numérique 2"}, - "responsibility": {"value": "René Wetzel"}, - } - - -# extent: 300$a (the first one if many) -# otherMaterialCharacteristics: 300$b (the first one if many) -# formats: 300 [$c repetitive] -def test_marc21_to_description(app): - """Test dojson extent, otherMaterialCharacteristics, formats.""" - - marc21xml = """ - - - 116 p. - ill. - 22 cm - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("extent") == "116 p." - assert data.get("otherMaterialCharacteristics") == "ill." - assert data.get("formats") == ["22 cm"] - - marc21xml = """ - - - 116 p. - ill. - 22 cm - 12 x 15 - - - 200 p. - ill. - 19 cm - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("extent") == "116 p." - assert data.get("otherMaterialCharacteristics") == "ill." - assert data.get("formats") == ["22 cm", "12 x 15"] - - marc21xml = """ - - - 116 p. - ill. - 22 cm - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("extent") == "116 p." - assert data.get("otherMaterialCharacteristics") == "ill." - - -# series.name: [490$a repetitive] -# series.number: [490$v repetitive] -def test_marc21_to_series(app): - """Test dojson series.""" - - marc21xml = """ - - - Collection One - 5 - - - Collection Two - 123 - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("series") == [ - {"name": "Collection One", "number": "5"}, - {"name": "Collection Two", "number": "123"}, - ] - - -def test_marc21_to_abstract(app): - """Test dojson abstract.""" - - # One abstract without language - marc21xml = """ - - - Abstract - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("abstracts") == [{"value": "Abstract", "language": "eng"}] - - # One abstract with language - marc21xml = """ - - - Résumé - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("abstracts") == [{"value": "Résumé", "language": "fre"}] - - # Multiple abstracts - marc21xml = """ - - - Abstract - eng - - - Résumé - fre - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("abstracts") == [ - {"value": "Abstract", "language": "eng"}, - {"value": "Résumé", "language": "fre"}, - ] - - # Without abstract - marc21xml = """ - - - eng - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("abstracts") - - # Special case with lang --> fr - marc21xml = """ - - - Résumé - fr - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("abstracts") == [{"value": "Résumé", "language": "fre"}] - - -# notes: [500$a repetitive] -def test_marc21_to_notes(app): - """Test dojson notes.""" - - marc21xml = """ - - - note 1 - - - note 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("notes") == ["note 1", "note 2"] - - -# subjects: 6xx [duplicates could exist between several vocabularies, -# if possible deduplicate] -def test_marc21_to_subjects(app): - """Test dojson subjects.""" - - marc21xml = """ - - - eng - subject 1 ; subject 2 - - - fre - sujet 1 ; sujet 2 - - - rero - subject 600 1 ; subject 600 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("subjects") == [ - {"label": {"language": "eng", "value": ["subject 1", "subject 2"]}}, - {"label": {"language": "fre", "value": ["sujet 1", "sujet 2"]}}, - {"label": {"value": ["subject 600 1", "subject 600 2"]}, "source": "rero"}, - ] - - # 600 without $a - marc21xml = """ - - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("subjects") - - # 600 without source - marc21xml = """ - - - subject 600 1 ; subject 600 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("subjects") - - # 695 without language - marc21xml = """ - - - subject 1 ; subject 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("subjects") - - -def test_marc21_to_identified_by_from_001(app): - """Test identifiedBy from 001.""" - - marc21xml = """ - - 327171 - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:Local", "source": "RERO DOC", "value": "327171"}] - - marc21xml = "" - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_020(app): - """Test identifiedBy from 020.""" - - marc21xml = """ - - - 9783796539138 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:Isbn", "value": "9783796539138"}] - - # Without code $a - marc21xml = """ - - - 9783796539138 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_024(app): - """Test identifiedBy from 024.""" - - marc21xml = """ - - - urn:nbn:ch:rero-002-118667 - urn - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:Urn", "value": "urn:nbn:ch:rero-002-118667"}] - - # Without code $a - marc21xml = """ - - - urn - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - # Without code $2 - marc21xml = """ - - - urn:nbn:ch:rero-002-118667 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - # $2 is a falsy value - marc21xml = """ - - - urn:nbn:ch:rero-002-118667 - falsy_value - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - # Without ind1 == 7 - marc21xml = """ - - - urn:nbn:ch:rero-002-118667 - falsy_value - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_027(app): - """Test identifiedBy from 027.""" - - marc21xml = """ - - - 9789027223951 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:Strn", "value": "9789027223951"}] - - # Without code $a - marc21xml = """ - - - 9789027223951 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_035(app): - """Test identifiedBy from 035.""" - - marc21xml = """ - - - R008966083 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:Local", "source": "RERO", "value": "R008966083"}] - - # Without code $a - marc21xml = """ - - - R008966083 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_037(app): - """Test identifiedBy from 037.""" - - marc21xml = """ - - - - swissbib.ch:(NATIONALLICENCE)springer-10.1007/s00209-014-1344-0 - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [ - { - "type": "bf:Local", - "source": "Swissbib", - "value": "(NATIONALLICENCE)springer-10.1007/s00209-014-1344-0", - } - ] - - # Without code $a - marc21xml = """ - - - - swissbib.ch:(NATIONALLICENCE)springer-10.1007/s00209-014-1344-0 - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_088(app): - """Test identifiedBy from 088.""" - - marc21xml = """ - - - 25 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:ReportNumber", "value": "25"}] - - # Without code $a - marc21xml = """ - - - 25 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_from_091(app): - """Test identifiedBy from 091.""" - - marc21xml = """ - - - 24638240 - pmid - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [{"type": "bf:Local", "value": "24638240", "source": "PMID"}] - - # Without code $a - marc21xml = """ - - - pmid - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - # Without code $b - marc21xml = """ - - - 24638240 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - # Invalid code $b - marc21xml = """ - - - 24638240 - fake - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("identifiedBy") - - -def test_marc21_to_identified_by_full(app): - """Test full identified by.""" - marc21xml = """ - - 327171 - - 9783796539138 - - - urn:nbn:ch:rero-002-118667 - urn - - - 9789027223951 - - - R008966083 - - - - swissbib.ch:(NATIONALLICENCE)springer-10.1007/s00209-014-1344-0 - - - - 25 - - - 24638240 - pmid - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("identifiedBy") == [ - {"type": "bf:Local", "source": "RERO DOC", "value": "327171"}, - {"type": "bf:Isbn", "value": "9783796539138"}, - {"type": "bf:Urn", "value": "urn:nbn:ch:rero-002-118667"}, - {"type": "bf:Strn", "value": "9789027223951"}, - {"type": "bf:Local", "source": "RERO", "value": "R008966083"}, - { - "type": "bf:Local", - "source": "Swissbib", - "value": "(NATIONALLICENCE)springer-10.1007/s00209-014-1344-0", - }, - {"type": "bf:ReportNumber", "value": "25"}, - {"type": "bf:Local", "value": "24638240", "source": "PMID"}, - ] - - -def test_marc21_to_files(app): - """Test getting files from field 856.""" - # Only one file - marc21xml = """ - - - file.pdf - application/pdf - 1467377 - - http://some.url/file.pdf - - order:1 - Dépliant de l'exposition - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert len(data.get("files")) == 1 - assert data.get("files")[0]["key"] == "file.pdf" - assert data.get("files")[0]["url"] == "http://some.url/file.pdf" - assert data.get("files")[0]["label"] == "Dépliant de l'exposition" - assert data.get("files")[0]["order"] == 1 - - # Not key - marc21xml = """ - - - application/pdf - 1467377 - - http://some.url/file.pdf - - order:1 - Dépliant de l'exposition - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("files") - - # Not URL - marc21xml = """ - - - file.pdf - application/pdf - 1467377 - order:1 - Dépliant de l'exposition - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("files") - - # Wrong mime type - marc21xml = """ - - 327171 - - file.pdf - pdt/download - 1467377 - - http://some.url/file.pdf - - order:1 - Dépliant de l'exposition - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("files") - - -def test_marc21_to_other_edition(app): - """Test other edition extraction.""" - # One other edition - marc21xml = """ - - - http://domain.com/url - version publiée - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("otherEdition") == [ - { - "document": {"electronicLocator": "http://domain.com/url"}, - "publicNote": "version publiée", - } - ] - - # Multiple other editions - marc21xml = """ - - - http://domain.com/url - version publiée - - - http://domain.com/url-2 - version publiée - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("otherEdition") == [ - { - "document": {"electronicLocator": "http://domain.com/url"}, - "publicNote": "version publiée", - }, - { - "document": {"electronicLocator": "http://domain.com/url-2"}, - "publicNote": "version publiée", - }, - ] - - # No electronic location - marc21xml = """ - - - version publiée - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("otherEdition") - - # No public note - marc21xml = """ - - - http://domain.com/url - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("otherEdition") - - # Incorrect DOIs - they do get collected in `otherEdition` - # instead of `identifiedBy` - marc21xml = """ - - - http://dx.doi.org/10.1130%2F0091-7613(2002)030%3C0655:CWCIAP%3E2.0.CO%3B2 - version publiée - - - http://dx.doi.org/0.1021/jp0558775 - version publiée - - - http://dx.doi.org/1017/S0031182010000296 - version publiée - - - http://dx.doi.org/1039/B926873A - version publiée - - - http://dx.doi.org/0.1016/j.str.2012.09.019 - version publiée - - - https://doi.org/10.1111%2Fj.1467-9280.2009.02364.x - version publiée - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("otherEdition") == [ - { - "document": { - "electronicLocator": "http://dx.doi.org/10.1130%2F0091-7613(2002)030%3C0655:CWCIAP%3E2.0.CO%3B2" - }, - "publicNote": "version publiée", - }, - { - "document": {"electronicLocator": "http://dx.doi.org/0.1021/jp0558775"}, - "publicNote": "version publiée", - }, - { - "document": {"electronicLocator": "http://dx.doi.org/1017/S0031182010000296"}, - "publicNote": "version publiée", - }, - { - "document": {"electronicLocator": "http://dx.doi.org/1039/B926873A"}, - "publicNote": "version publiée", - }, - { - "document": {"electronicLocator": "http://dx.doi.org/0.1016/j.str.2012.09.019"}, - "publicNote": "version publiée", - }, - { - "document": {"electronicLocator": "https://doi.org/10.1111%2Fj.1467-9280.2009.02364.x"}, - "publicNote": "version publiée", - }, - ] - assert not data.get("identifiedBy") - - # Well-formed DOIs - they get collected in `identifiedBy` - # instead of`otherEdition` - marc21xml = """ - - - http://dx.doi.org/10.1002/1521-3773(20020104)41:1 - version publiée - - - 10.1016/j.apergo.2008.03.002 - version publiée - - - : https://doi.pangaea.de/10.1594/PANGAEA.914883 - version publiée - - - https://www.brepolsonline.net/doi/abs/10.1484/J.BPM.5.110808 - version publiée - - - https://doi.org710.35662/unine-thesis-2747 - version publiée - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("otherEdition") - assert data.get("identifiedBy") == [ - {"type": "bf:Doi", "value": "10.1002/1521-3773(20020104)41:1"}, - {"type": "bf:Doi", "value": "10.1016/j.apergo.2008.03.002"}, - {"type": "bf:Doi", "value": "10.1594/PANGAEA.914883"}, - {"type": "bf:Doi", "value": "10.1484/J.BPM.5.110808"}, - {"type": "bf:Doi", "value": "10.35662/unine-thesis-2747"}, - ] - - -def test_marc21_to_specific_collection(app, bucket_location): - """Test extracting collection from file 982.""" - # No code a - marc21xml = """ - - - Treize étoiles - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("collections") - - # Not field 982 - marc21xml = """ - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("collections") - - # No organisation - marc21xml = """ - - - Treize étoiles - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("collections") - - # OK - marc21xml = """ - - - test-org - - - Treize étoiles - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data["collections"] - - # Multiple collections - marc21xml = """ - - - test-org - - - Collection 1 - - - Collection 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert len(data["collections"]) == 2 - - -def test_marc21_to_classification_from_field_080(app): - """Test classification from field 080.""" - # OK - marc21xml = """ - - - 82 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("classification") == [{"type": "bf:ClassificationUdc", "classificationPortion": "82"}] - - # Not $a record - marc21xml = """ - - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("classification") - - -def test_marc21_to_classification_from_field_084(app): - """Test classification from field 084.""" - # OK - marc21xml = """ - - - 610 - ddc - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("classification") == [{"type": "bf:ClassificationDdc", "classificationPortion": "610"}] - - # Not $a record - marc21xml = """ - - - ddc - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("classification") - - # Not $2 record - marc21xml = """ - - - 610 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("classification") - - -def test_marc21_to_classification_from_all(app): - """Test classification from all field.""" - marc21xml = """ - - - 82 - - - 610 - ddc - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("classification") == [ - {"type": "bf:ClassificationUdc", "classificationPortion": "82"}, - {"type": "bf:ClassificationDdc", "classificationPortion": "610"}, - ] - - -def test_marc21_to_content_note(app): - """Test extracting content notes from field 505.""" - # OK - marc21xml = """ - - - La comtesse de Mortane - - - Voyage de campagne - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contentNote") == ["La comtesse de Mortane", "Voyage de campagne"] - - # No field $a - marc21xml = """ - - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("contentNote") - - -def test_marc21_to_dissertation_field_502(app): - """Test extracting dissertation degree from field 502.""" - # OK - marc21xml = """ - - - Thèse de doctorat - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("dissertation") == {"degree": "Thèse de doctorat"} - - # thesis note decomposition - marc21xml = """ - - Thèse de doctorat : Université de Fribourg, 2010 ; Nr. 1671 - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("dissertation") == { - "degree": "Thèse de doctorat", - "grantingInstitution": "Université de Fribourg", - "date": "2010", - } - - # Multiple --> keep always last value - marc21xml = """ - - - Thèse de doctorat - - - Last degree - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("dissertation") == {"degree": "Last degree"} - - # Without $a - marc21xml = """ - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("dissertation") - - -def test_marc21_to_dissertation_field_508(app): - """Test extracting dissertation notes from field 508.""" - # OK - marc21xml = """ - - - Magna cum laude - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("dissertation") == {"jury_note": "Magna cum laude"} - - # Multiple - marc21xml = """ - - - Note 1 - - - Note 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("dissertation") == {"jury_note": "Note 2"} - - # Without $a - marc21xml = """ - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("dissertation") - - -def test_marc21_to_dissertation_all(app): - """Test extracting dissertation notes and degree.""" - # OK - marc21xml = """ - - - Thèse de doctorat - - - Magna cum laude - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("dissertation") == { - "degree": "Thèse de doctorat", - "jury_note": "Magna cum laude", - } - - -def test_marc21_to_usage_and_access_policy(app): - """Test extracting usage and access policy.""" - # OK - marc21xml = """ - - - Springer-Verlag Berlin - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("usageAndAccessPolicy") == { - "label": "Springer-Verlag Berlin", - "license": "License undefined", - } - - # Multiple - marc21xml = """ - - - Usage 1 - - - Usage 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("usageAndAccessPolicy") == { - "label": "Usage 2", - "license": "License undefined", - } - - # Without $a - marc21xml = """ - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("usageAndAccessPolicy") == {"license": "License undefined"} - - # Without 540 - marc21xml = """ - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("usageAndAccessPolicy") == {"license": "License undefined"} - - -def test_marc21_to_contribution_field_100(app): - """Test extracting contribution from field 100.""" - # OK - marc21xml = """ - - - Romagnani, Andrea - 1980-2010 - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Person", - "preferred_name": "Romagnani, Andrea", - "date_of_birth": "1980", - "date_of_death": "2010", - }, - "role": ["cre"], - "affiliation": "University of Bern, Switzerland", - } - ] - - # Not $a - marc21xml = """ - - - 1980-2010 - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("contribution") - - # Date does not match - marc21xml = """ - - - Romagnani, Andrea - zzzz - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - with pytest.raises(Exception) as exception: - data = overdo.do(marc21json) - assert str(exception.value) == 'Date "zzzz" is not recognized' - - # Only birth date - marc21xml = """ - - - Romagnani, Andrea - 1980- - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Person", - "preferred_name": "Romagnani, Andrea", - "date_of_birth": "1980", - }, - "role": ["cre"], - "affiliation": "University of Bern, Switzerland", - } - ] - - # Only birth date, variant 2 - marc21xml = """ - - - Romagnani, Andrea - 1980 - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Person", - "preferred_name": "Romagnani, Andrea", - "date_of_birth": "1980", - }, - "role": ["cre"], - "affiliation": "University of Bern, Switzerland", - } - ] - - # Only birth date, variant 3 - marc21xml = """ - - - Romagnani, Andrea - 1980-04-04 - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Person", - "preferred_name": "Romagnani, Andrea", - "date_of_birth": "1980-04-04", - }, - "role": ["cre"], - "affiliation": "University of Bern, Switzerland", - } - ] - - -def test_marc21_to_contribution_field_700(app): - """Test extracting contribution from field 700.""" - # OK - marc21xml = """ - - - Piguet, Etienne - 1980-2010 - Dir. - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Person", - "preferred_name": "Piguet, Etienne", - "date_of_birth": "1980", - "date_of_death": "2010", - }, - "role": ["dgs"], - "affiliation": "University of Bern, Switzerland", - } - ] - - # Not $a - marc21xml = """ - - - 1980-2010 - Dir. - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("contribution") - - # Only birth date - marc21xml = """ - - - Piguet, Etienne - 1980- - Dir. - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Person", - "preferred_name": "Piguet, Etienne", - "date_of_birth": "1980", - }, - "role": ["dgs"], - "affiliation": "University of Bern, Switzerland", - } - ] - - # Role from field 980, but not existing - marc21xml = """ - - - Piguet, Etienne - 1980- - University of Bern, Switzerland - - - """ - marc21json = create_record(marc21xml) - with pytest.raises(Exception) as exception: - data = overdo.do(marc21json) - assert str(exception.value).startswith("No role found for contributor") - - # Role from field 980, but 980 is not mapped - marc21xml = """ - - - Piguet, Etienne - - - NOT EXISTING - - - """ - marc21json = create_record(marc21xml) - with pytest.raises(Exception) as exception: - data = overdo.do(marc21json) - assert str(exception.value).startswith("No role found for contributor") - - # Role from field 980, but $a is not existing - marc21xml = """ - - - Piguet, Etienne - - - - - """ - marc21json = create_record(marc21xml) - with pytest.raises(Exception) as exception: - data = overdo.do(marc21json) - assert str(exception.value).startswith("No role found for contributor") - - # Role from field 980, found 'cre' - marc21xml = """ - - - Piguet, Etienne - - - POSTPRINT - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution")[0]["role"] == ["cre"] - - # Role from field 980, found 'ctb' - marc21xml = """ - - - Piguet, Etienne - - - BOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution")[0]["role"] == ["ctb"] - - # Role 'prt' found - marc21xml = """ - - - Piguet, Etienne - Libr./Impr. - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution")[0]["role"] == ["prt"] - - # Role for joint author - marc21xml = """ - - - Piguet, Etienne - joint author - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution")[0]["role"] == ["cre"] - - -def test_marc21_to_contribution_field_710(app): - """Test extracting contribution from field 710.""" - # OK - marc21xml = """ - - - Musée d'art et d'histoire - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Organization", - "preferred_name": "Musée d'art et d'histoire", - }, - "role": ["ctb"], - } - ] - - # No $a - marc21xml = """ - - - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("contribution") - - -def test_marc21_to_contribution_field_711(app): - """Test extracting contribution from field 711.""" - # OK - marc21xml = """ - - - Theologisches Forum Christentum - Stuttgart-Hohenheim - 2004 - 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("contribution") == [ - { - "agent": { - "type": "bf:Meeting", - "preferred_name": "Theologisches Forum Christentum", - "place": "Stuttgart-Hohenheim", - "date": "2004", - "number": "2", - }, - "role": ["cre"], - } - ] - - # Not $a - marc21xml = """ - - - Stuttgart-Hohenheim - 2004 - 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("contribution") - - -def test_marc21_to_part_of(app): - """Test extracting is part of from field 773.""" - # With sub type of ART INBOOK - marc21xml = """ - - - Belser, Eva Maria - Mehr oder weniger Staat? - 2015/// - Stämpfli Verlag, Bern - - - ART_INBOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("partOf") == [ - { - "numberingYear": "2015", - "document": { - "title": "Mehr oder weniger Staat?", - "contribution": ["Belser, Eva Maria"], - "publication": { - "statement": "Stämpfli Verlag, Bern", - "startDate": "2015", - }, - }, - } - ] - assert data.get("provisionActivity") == [{"startDate": "2015", "type": "bf:Publication"}] - - # With sub type is not ART INBOOK - marc21xml = """ - - - Optics Express - 2020/28/6/8200-8210 - Optical Society of America - - - ART_JOURNAL - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("partOf") == [ - { - "numberingYear": "2020", - "numberingVolume": "28", - "numberingIssue": "6", - "numberingPages": "8200-8210", - "document": { - "title": "Optics Express", - "publication": {"statement": "Optical Society of America"}, - }, - } - ] - assert data.get("provisionActivity") == [{"startDate": "2020", "type": "bf:Publication"}] - - # Without $g - marc21xml = """ - - - Belser, Eva Maria - Mehr oder weniger Staat? - Stämpfli Verlag, Bern - - - ART_INBOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("partOf") - assert not data.get("provisionActivity") - - # Without empty numbering year - marc21xml = """ - - - ///- - Belser, Eva Maria - Mehr oder weniger Staat? - Stämpfli Verlag, Bern - - - ART_INBOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("partOf") - assert not data.get("provisionActivity") - - # Without numbering year - marc21xml = """ - - - Belser, Eva Maria - - Mehr oder weniger Staat? - Stämpfli Verlag, Bern - - - ART_INBOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("partOf") - assert not data.get("provisionActivity") - - # Without title - marc21xml = """ - - - Belser, Eva Maria - 2015/// - Stämpfli Verlag, Bern - - - ART_INBOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("partOf") == [ - { - "numberingYear": "2015", - "document": { - "contribution": ["Belser, Eva Maria"], - "publication": { - "startDate": "2015", - "statement": "Stämpfli Verlag, Bern", - }, - }, - } - ] - assert data.get("provisionActivity") == [{"startDate": "2015", "type": "bf:Publication"}] - - # Without $c - marc21xml = """ - - - Mehr oder weniger Staat? - 2015/// - Stämpfli Verlag, Bern - - - ART_INBOOK - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("partOf") == [ - { - "numberingYear": "2015", - "document": { - "title": "Mehr oder weniger Staat?", - "publication": { - "statement": "Stämpfli Verlag, Bern", - "startDate": "2015", - }, - }, - } - ] - assert data.get("provisionActivity") == [{"startDate": "2015", "type": "bf:Publication"}] - - # Without document - marc21xml = """ - - - 2015/// - - - ART_JOURNAL - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("partOf") == [{"numberingYear": "2015"}] - assert data.get("provisionActivity") == [{"startDate": "2015", "type": "bf:Publication"}] - - -def test_start_date_priorities(app): - """Test start date priorities for provision activity.""" - # Four potential start dates. - marc21xml = """ - - - Lausanne - 1798-1799 - Bulletin officiel du Directoire, - - - 2015/// - - - 1966 - - - Thèse 1 - 2020 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data["provisionActivity"][0]["startDate"] == "1798" - - # Start dates from 269$c must be taken. - marc21xml = """ - - - 1966 - - - Thèse 1 - 2020 - - - 2015/// - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data["provisionActivity"][0]["startDate"] == "1966" - - # Start dates from 773$g must be taken. - marc21xml = """ - - - Thèse 1 - 2020 - - - 2015/// - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data["provisionActivity"][0]["startDate"] == "2015" - - # Only 502$9 - marc21xml = """ - - - Thèse 1 - 2020 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data["provisionActivity"][0]["startDate"] == "2020" - - -def test_marc21_to_provision_activity_field_502(app): - """Test provision activity with field 502.""" - # One field - marc21xml = """ - - - Thèse 1 - 2020 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [{"startDate": "2020", "type": "bf:Publication"}] - - # One field with full date - marc21xml = """ - - - Thèse 1 - 2020-09-09 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [{"startDate": "2020-09-09", "type": "bf:Publication"}] - - # Date does not match "YYYY" OR "YYYY-MM-DD" - marc21xml = """ - - - Thèse 1 - 2010-2020 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") - - # Multiple fields - marc21xml = """ - - - Thèse 1 - 2010 - - - Thèse 2 - 2020 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert data.get("provisionActivity") == [{"startDate": "2020", "type": "bf:Publication"}] - - # No field $9 - marc21xml = """ - - - Thèse 2 - - - """ - marc21json = create_record(marc21xml) - data = overdo.do(marc21json) - assert not data.get("provisionActivity") diff --git a/tests/ui/documents/dojson/rerodoc/test_rerodoc_overdo.py b/tests/ui/documents/dojson/rerodoc/test_rerodoc_overdo.py deleted file mode 100644 index 314530b45..000000000 --- a/tests/ui/documents/dojson/rerodoc/test_rerodoc_overdo.py +++ /dev/null @@ -1,114 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Test RERODOC overdo.""" - -import pytest - -from sonar.modules.documents.dojson.rerodoc.overdo import Overdo -from sonar.modules.organisations.api import OrganisationRecord - - -def test_create_organisation(app, bucket_location): - """Test create organisation.""" - Overdo.create_organisation("test") - - # Organisation creation OK - organisation = OrganisationRecord.get_record_by_pid("test") - assert organisation - assert organisation["pid"] == "test" - - # No organisation key provided - with pytest.raises(Exception) as exception: - Overdo.create_organisation(None) - assert str(exception.value) == "No key provided" - - -def test_extract_date(): - """Test date extraction.""" - # No date provided - assert Overdo.extract_date(None) == (None, None) - - # Full first date - assert Overdo.extract_date("1980-01-01") == ("1980-01-01", None) - - # Full first date, variant - assert Overdo.extract_date("01-01-1980") == ("01-01-1980", None) - - # First year only - assert Overdo.extract_date("1980") == ("1980", None) - - # First year only, variant with dash - assert Overdo.extract_date("1980-") == ("1980", None) - - # Start and end year - assert Overdo.extract_date("1980-2010") == ("1980", "2010") - - # Error on date format - with pytest.raises(Exception) as exception: - assert Overdo.extract_date("AAAA") - assert str(exception.value) == 'Date "AAAA" is not recognized' - - -def test_get_contributor_role(): - """Test contributor role mapping.""" - overdo = Overdo() - overdo.blob_record = {} - - # dgs - assert overdo.get_contributor_role("Dir.") == "dgs" - - # prt - assert overdo.get_contributor_role("Libr./Impr.") == "prt" - - # joint author - assert overdo.get_contributor_role("joint author") == "cre" - - # with role but no mapping found - assert not overdo.get_contributor_role("not-mapped") - - # no role, no doc type - assert not overdo.get_contributor_role(None) - - # no role, doc type mapped to 'cre' - overdo.blob_record = {"980__": {"a": "PREPRINT"}} - assert overdo.get_contributor_role(None) == "cre" - - # no role, doc type mapped to 'ctb' - overdo.blob_record = {"980__": {"a": "BOOK"}} - assert overdo.get_contributor_role(None) == "ctb" - - -def test_verify(app): - """Test verify result.""" - overdo = Overdo() - overdo.blob_record = {} - - # No provision activity and no type - overdo.verify({}) - assert not overdo.result_ok - - # No provision activity and type make it mandatory - overdo.verify({"documentType": "coar:c_816b"}) - assert not overdo.result_ok - - # Provision activity is present in result - overdo.blob_record = {} - overdo.verify({"provisionActivity": {}}) - assert overdo.result_ok - - # No provision activity and provision activity is optional - overdo.verify({"documentType": "coar:c_beb9"}) - assert overdo.result_ok diff --git a/tests/ui/documents/test_documents_api.py b/tests/ui/documents/test_documents_api.py index 1bb351c74..310df5d07 100644 --- a/tests/ui/documents/test_documents_api.py +++ b/tests/ui/documents/test_documents_api.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -28,26 +28,12 @@ def test_get_record_by_identifier(app, db, document): """Test getting record by its identifier.""" # Record found - record = DocumentRecord.get_record_by_identifier( - [ - {"value": "111111", "type": "bf:Local", "source": "RERO DOC"}, - {"value": "R003415713", "type": "bf:Local", "source": "RERO"}, - ] - ) + record = DocumentRecord.get_record_by_identifier([{"value": "R003415713", "type": "bf:Local", "source": "RERO"}]) assert record["pid"] == document["pid"] # Not matching complete identifier record = DocumentRecord.get_record_by_identifier( - [ - {"value": "111111", "type": "bf:Local", "source": "Unmatching"}, - {"value": "R003415713", "type": "bf:Local", "source": "RERO"}, - ] - ) - assert not record - - # Mixing identifier data - record = DocumentRecord.get_record_by_identifier( - [{"value": "R003415713", "type": "bf:Local", "source": "RERO DOC"}] + [{"value": "R003415713", "type": "bf:Local", "source": "Unmatching"}] ) assert not record diff --git a/tests/ui/documents/test_documents_receivers.py b/tests/ui/documents/test_documents_receivers.py index fe0bec72b..a04b90ca2 100644 --- a/tests/ui/documents/test_documents_receivers.py +++ b/tests/ui/documents/test_documents_receivers.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -29,17 +29,17 @@ def test_transform_harvested_records(app, bucket_location, capsys, harvested_record): """Test harvested record transformation.""" - transform_harvested_records(None, [harvested_record], name="rerodoc", max="1") + transform_harvested_records(None, [harvested_record], name="archive_ouverte_unige", max="1") captured = capsys.readouterr() assert captured.out.find("1 records harvested") != -1 # Max set to 0 --> import all - transform_harvested_records(None, [harvested_record], name="rerodoc", max="0") + transform_harvested_records(None, [harvested_record], name="archive_ouverte_unige", max="0") captured = capsys.readouterr() assert captured.out.find("1 records harvested") != -1 # Not an import - transform_harvested_records(None, [harvested_record], name="rerodoc", max="1", action="not-existing") + transform_harvested_records(None, [harvested_record], name="archive_ouverte_unige", max="1", action="not-existing") captured = capsys.readouterr() assert captured.out == "" @@ -61,10 +61,10 @@ def test_export_json(app, bucket_location, monkeypatch, harvested_record): data_directory = join(app.instance_path, "data") - export_json(None, [harvested_record], clean_file=False, name="rerodoc", action="not-existing") + export_json(None, [harvested_record], clean_file=False, name="archive_ouverte_unige", action="not-existing") assert not exists(data_directory) - export_json(None, [harvested_record], clean_file=False, name="rerodoc", action="export") + export_json(None, [harvested_record], clean_file=False, name="archive_ouverte_unige", action="export") assert len(listdir(data_directory)) == 1 shutil.rmtree(data_directory) diff --git a/tests/ui/test_views.py b/tests/ui/test_views.py index d4187aad0..c3c4f0b21 100644 --- a/tests/ui/test_views.py +++ b/tests/ui/test_views.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -318,45 +318,6 @@ def test_record_image_url(client): assert record_image_url(record, "org", "test2.jpg") == "/organisations/1/files/test2.jpg" -def test_rerodoc_redirection(client, app, document, organisation): - """Test redirection with RERODOC identifier.""" - global_view = app.config.get("SONAR_APP_DEFAULT_ORGANISATION") - # does not exist - res = client.get(url_for("sonar.rerodoc_redirection", pid="NOT-EXISTING")) - assert res.status_code == 404 - - # Files - res = client.get(url_for("sonar.rerodoc_redirection", pid="111111", filename="test.pdf")) - assert res.status_code == 302 - assert res.location.find(f"/documents/{document['pid']}/files/test.pdf") != -1 - - def changeorg(key, value): - organisation[key] = value - organisation.commit() - organisation.dbcommit() - # Note: this is not needed as all is done from the db - # organisation.reindex() - - # No dedicated - changeorg("isShared", False) - res = client.get(url_for("sonar.rerodoc_redirection", pid="111111")) - assert res.status_code == 302 - assert res.location.find(f"/{global_view}/documents/{document['pid']}") != -1 - - # Dedicated - changeorg("isDedicated", True) - res = client.get(url_for("sonar.rerodoc_redirection", pid="111111")) - assert res.status_code == 302 - assert res.location.find(f"/{organisation['code']}/documents/{document['pid']}") != -1 - - # Shared - changeorg("isDedicated", False) - changeorg("isShared", True) - res = client.get(url_for("sonar.rerodoc_redirection", pid="111111")) - assert res.status_code == 302 - assert res.location.find(f"/{organisation['code']}/documents/{document['pid']}") != -1 - - def test_format_date(app): """Test date formatting.""" assert format_date("1984-05-01 14:30:00", "%d/%m/%Y %H:%M") == "01/05/1984 16:30" diff --git a/tests/unit/documents/loaders/test_loader_schema_factory.py b/tests/unit/documents/loaders/test_loader_schema_factory.py index c13d80dce..45aef6d4e 100644 --- a/tests/unit/documents/loaders/test_loader_schema_factory.py +++ b/tests/unit/documents/loaders/test_loader_schema_factory.py @@ -1,5 +1,5 @@ # Swiss Open Access Repository -# Copyright (C) 2021 RERO +# Copyright (C) 2021-2026 RERO # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -13,19 +13,19 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -"""Test BORIS record loader.""" +"""Test loader schema factory.""" import pytest +from sonar.modules.documents.loaders.schemas.archive_ouverte_unige import ArchiveOuverteUnigeSchema from sonar.modules.documents.loaders.schemas.factory import LoaderSchemaFactory -from sonar.modules.documents.loaders.schemas.rerodoc import RerodocSchema def test_loader_schema_factory(): """Test loader schema factory.""" - schema = LoaderSchemaFactory.create("rerodoc") - assert isinstance(schema, RerodocSchema) + schema = LoaderSchemaFactory.create("archive_ouverte_unige") + assert isinstance(schema, ArchiveOuverteUnigeSchema) with pytest.raises(Exception) as exception: - schema = LoaderSchemaFactory.create("not-existing") - assert str(exception) == 'No schema defined for key "not-existing"' + LoaderSchemaFactory.create("not-existing") + assert str(exception.value) == 'No schema defined for key "not-existing"' diff --git a/tests/unit/documents/loaders/test_rerodoc_loader.py b/tests/unit/documents/loaders/test_rerodoc_loader.py deleted file mode 100644 index 66aacb60e..000000000 --- a/tests/unit/documents/loaders/test_rerodoc_loader.py +++ /dev/null @@ -1,220 +0,0 @@ -# Swiss Open Access Repository -# Copyright (C) 2021 RERO -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Test BORIS record loader.""" - -from sonar.modules.documents.loaders.schemas.rerodoc import RerodocSchema - - -def test_rerodoc_loader(app, organisation): - """Test RERODOC record loader.""" - xml = """ - -
- oai:doc.rero.ch:289209 - 2017-08-02T19:31:33Z - nl-uzh - article - rero_explore - postprint -
- - - 00000coc 2200000uu 4500 - 289209 - 20170803135013.0 - - 10.1093/mnras/stu2500 - doi - - - oai:doc.rero.ch:289209 - nl-uzh - article - rero_explore - postprint - - - swissbib.ch:(NATIONALLICENCE)oxford-10.1093/mnras/stu2500 - - - eng - - - 52 - - - Capelo, Pedro R. - Department of Astronomy, University of Michigan, Ann Arbor, MI 48109, USA - - - Growth and activity of black holes in galaxy mergers with varying mass ratios - - - We study supermassive black holes (BHs) in merging galaxies - - - © 2015 The Authors Published by Oxford University Press on behalf of the Royal Astronomical Society - - - galaxies: active ; galaxies: interactions ; galaxies: nuclei - - - Volonteri, Marta - Department of Astronomy, University of Michigan, Ann Arbor, MI 48109, USA - - - Dotti, Massimo - Dipartimento di Fisica G. Occhialini, Università degli Studi di Milano Bicocca, Piazza della Scienza 3, I-20126 Milano, Italy - - - Bellovary, Jillian M. - Department of Physics and Astronomy, Vanderbilt University, Nashville, TN 37235, USA - - - Mayer, Lucio - Institute for Computational Science, University of Zürich, Winterthurerstrasse 190, CH-8057 Zürich, Switzerland - - - Governato, Fabio - Department of Astronomy, University of Washington, Box 351580, Seattle, WA 98195, USA - - - 0035-8711 - Monthly Notices of the Royal Astronomical Society - 2015/447/3/2123-2143 - Oxford University Press - - - Publisher's version - https://doi.org/10.1093/mnras/stu2500 - - - Alternative version - http://www-geol.unine.ch/GEOMAGNETISME/tract.html - - - Consortium of Swiss Academic Libraries - Zurich - doc.support@rero.ch - - - POSTPRINT - org - ART_JOURNAL - - - National Licences: uzh - - - -
- """ # nopep8 - assert RerodocSchema().dump(xml) == { - "identifiedBy": [ - {"type": "bf:Local", "source": "RERO DOC", "value": "289209"}, - { - "type": "bf:Local", - "source": "Swissbib", - "value": "(NATIONALLICENCE)oxford-10.1093/mnras/stu2500", - }, - {"type": "bf:Doi", "value": "10.1093/mnras/stu2500"}, - ], - "collections": [{"$ref": "https://sonar.ch/api/collections/1"}], - "usageAndAccessPolicy": { - "label": "© 2015 The Authors Published by Oxford University Press on " - "behalf of the Royal Astronomical Society", - "license": "License undefined", - }, - "abstracts": [ - { - "value": "We study supermassive black holes (BHs) in merging galaxies", - "language": "eng", - } - ], - "partOf": [ - { - "numberingYear": "2015", - "numberingVolume": "447", - "numberingIssue": "3", - "numberingPages": "2123-2143", - "document": { - "title": "Monthly Notices of the Royal Astronomical Society", - "publication": {"statement": "Oxford University Press"}, - }, - } - ], - "provisionActivity": [{"type": "bf:Publication", "startDate": "2015"}], - "otherEdition": [ - { - "document": {"electronicLocator": "http://www-geol.unine.ch/GEOMAGNETISME/tract.html"}, - "publicNote": "Alternative version", - } - ], - "language": [{"type": "bf:Language", "value": "eng"}], - "documentType": "coar:c_6501", - "organisation": [{"$ref": "https://sonar.ch/api/organisations/org"}], - "classification": [{"type": "bf:ClassificationUdc", "classificationPortion": "52"}], - "contribution": [ - { - "agent": {"type": "bf:Person", "preferred_name": "Capelo, Pedro R."}, - "role": ["cre"], - "affiliation": "Department of Astronomy, University of Michigan, Ann Arbor, MI 48109, USA", - }, - { - "agent": {"type": "bf:Person", "preferred_name": "Volonteri, Marta"}, - "role": ["cre"], - "affiliation": "Department of Astronomy, University of Michigan, Ann Arbor, MI 48109, USA", - }, - { - "agent": {"type": "bf:Person", "preferred_name": "Dotti, Massimo"}, - "role": ["cre"], - "affiliation": "Dipartimento di Fisica G. Occhialini, Università degli Studi di " - "Milano Bicocca, Piazza della Scienza 3, I-20126 Milano, Italy", - }, - { - "agent": { - "type": "bf:Person", - "preferred_name": "Bellovary, Jillian M.", - }, - "role": ["cre"], - "affiliation": "Department of Physics and Astronomy, Vanderbilt University, Nashville, TN 37235, USA", - }, - { - "agent": {"type": "bf:Person", "preferred_name": "Mayer, Lucio"}, - "role": ["cre"], - "affiliation": "Institute for Computational Science, University of Zürich, " - "Winterthurerstrasse 190, CH-8057 Zürich, Switzerland", - }, - { - "agent": {"type": "bf:Person", "preferred_name": "Governato, Fabio"}, - "role": ["cre"], - "affiliation": "Department of Astronomy, University of Washington, Box 351580, Seattle, WA 98195, USA", - }, - ], - "title": [ - { - "type": "bf:Title", - "mainTitle": [ - { - "value": "Growth and activity of black holes in galaxy mergers with varying mass ratios", - "language": "eng", - } - ], - } - ], - } diff --git a/uv.lock b/uv.lock index de36261f0..4e1604797 100644 --- a/uv.lock +++ b/uv.lock @@ -118,11 +118,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -140,7 +140,8 @@ version = "1.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, - { name = "edtf" }, + { name = "edtf", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "edtf", version = "5.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f9/25/b2ca3f471b91d37f59ec4304b5f98bdb6001e174a431dda34cf98b2a217c/babel-edtf-1.2.1.tar.gz", hash = "sha256:1ed0c454e123ed7579510d9531eae5af4399272b0dff897d3d42f68e9bed8d8e", size = 18138, upload-time = "2024-11-07T13:05:22.818Z" } wheels = [ @@ -302,7 +303,8 @@ version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "os_name == 'nt'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.10.2'" }, { name = "packaging", version = "24.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "packaging", version = "26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyproject-hooks" }, @@ -354,7 +356,7 @@ wheels = [ [package.optional-dependencies] filecache = [ - { name = "filelock", version = "3.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "filelock", version = "3.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] [[package]] @@ -468,75 +470,75 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/21/a2b1505639008ba2e6ef03733a81fc6cfd6a07ea6139a2b76421230b8dad/charset_normalizer-3.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765", size = 283319, upload-time = "2026-03-06T06:00:26.433Z" }, - { url = "https://files.pythonhosted.org/packages/70/67/df234c29b68f4e1e095885c9db1cb4b69b8aba49cf94fac041db4aaf1267/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990", size = 189974, upload-time = "2026-03-06T06:00:28.222Z" }, - { url = "https://files.pythonhosted.org/packages/df/7f/fc66af802961c6be42e2c7b69c58f95cbd1f39b0e81b3365d8efe2a02a04/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2", size = 207866, upload-time = "2026-03-06T06:00:29.769Z" }, - { url = "https://files.pythonhosted.org/packages/c9/23/404eb36fac4e95b833c50e305bba9a241086d427bb2167a42eac7c4f7da4/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765", size = 203239, upload-time = "2026-03-06T06:00:31.086Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2f/8a1d989bfadd120c90114ab33e0d2a0cbde05278c1fc15e83e62d570f50a/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d", size = 196529, upload-time = "2026-03-06T06:00:32.608Z" }, - { url = "https://files.pythonhosted.org/packages/a5/0c/c75f85ff7ca1f051958bb518cd43922d86f576c03947a050fbedfdfb4f15/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8", size = 184152, upload-time = "2026-03-06T06:00:33.93Z" }, - { url = "https://files.pythonhosted.org/packages/f9/20/4ed37f6199af5dde94d4aeaf577f3813a5ec6635834cda1d957013a09c76/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412", size = 195226, upload-time = "2026-03-06T06:00:35.469Z" }, - { url = "https://files.pythonhosted.org/packages/28/31/7ba1102178cba7c34dcc050f43d427172f389729e356038f0726253dd914/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2", size = 192933, upload-time = "2026-03-06T06:00:36.83Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/f86443ab3921e6a60b33b93f4a1161222231f6c69bc24fb18f3bee7b8518/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1", size = 185647, upload-time = "2026-03-06T06:00:38.367Z" }, - { url = "https://files.pythonhosted.org/packages/82/44/08b8be891760f1f5a6d23ce11d6d50c92981603e6eb740b4f72eea9424e2/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4", size = 209533, upload-time = "2026-03-06T06:00:41.931Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/df114f23406199f8af711ddccfbf409ffbc5b7cdc18fa19644997ff0c9bb/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f", size = 195901, upload-time = "2026-03-06T06:00:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/07/83/71ef34a76fe8aa05ff8f840244bda2d61e043c2ef6f30d200450b9f6a1be/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550", size = 204950, upload-time = "2026-03-06T06:00:45.202Z" }, - { url = "https://files.pythonhosted.org/packages/58/40/0253be623995365137d7dc68e45245036207ab2227251e69a3d93ce43183/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2", size = 198546, upload-time = "2026-03-06T06:00:46.481Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5c/5f3cb5b259a130895ef5ae16b38eaf141430fa3f7af50cd06c5d67e4f7b2/charset_normalizer-3.4.5-cp310-cp310-win32.whl", hash = "sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475", size = 132516, upload-time = "2026-03-06T06:00:47.924Z" }, - { url = "https://files.pythonhosted.org/packages/a5/c3/84fb174e7770f2df2e1a2115090771bfbc2227fb39a765c6d00568d1aab4/charset_normalizer-3.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05", size = 142906, upload-time = "2026-03-06T06:00:49.389Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b2/6f852f8b969f2cbd0d4092d2e60139ab1af95af9bb651337cae89ec0f684/charset_normalizer-3.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064", size = 133258, upload-time = "2026-03-06T06:00:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, - { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, - { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, - { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, - { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, - { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, - { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, - { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, - { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, - { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, - { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, - { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, - { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, - { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, - { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, - { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/be/76/96dec962aa996081c48f544d5e9e97322006a1e67e8f76bad41f3fb0b151/charset_normalizer-3.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:259cd1ca995ad525f638e131dbcc2353a586564c038fc548a3fe450a91882139", size = 283220, upload-time = "2026-03-06T06:02:53.024Z" }, - { url = "https://files.pythonhosted.org/packages/cc/80/050c340587611be9743eff02d1ca34b5fc76a4356849dcb74dfd898d6d87/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a28afb04baa55abf26df544e3e5c6534245d3daa5178bc4a8eeb48202060d0e", size = 189988, upload-time = "2026-03-06T06:02:54.448Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a3/bb6caf9f5544ccaaca5c7e387fa868868d3420bcb03e8bc30f37be2e8a72/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff95a9283de8a457e6b12989de3f9f5193430f375d64297d323a615ea52cbdb3", size = 207786, upload-time = "2026-03-06T06:02:55.808Z" }, - { url = "https://files.pythonhosted.org/packages/ee/50/e56713141f2fdb3a4d46092425d58dc97a48e1e10ce321ac6ba43862aacf/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:708c7acde173eedd4bfa4028484426ba689d2103b28588c513b9db2cd5ecde9c", size = 203556, upload-time = "2026-03-06T06:02:57.31Z" }, - { url = "https://files.pythonhosted.org/packages/22/34/ed0cfd388dd9106725afc2beb036adbaa167fc0b5a9ee8cd3940757fb060/charset_normalizer-3.4.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa92ec1102eaff840ccd1021478af176a831f1bccb08e526ce844b7ddda85c22", size = 196552, upload-time = "2026-03-06T06:02:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8b/da4a4c3d26c539fdd777cfbd2c0d83e77e1218879517ef91c4ece7238563/charset_normalizer-3.4.5-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:5fea359734b140d0d6741189fea5478c6091b54ffc69d7ce119e0a05637d8c99", size = 184289, upload-time = "2026-03-06T06:03:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/d3/05/9f67c1f94ea9ae1e08c8fa2182b1f5411732e18643e7080fc8c10ba1e021/charset_normalizer-3.4.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e545b51da9f9af5c67815ca0eb40676c0f016d0b0381c86f20451e35696c5f95", size = 195282, upload-time = "2026-03-06T06:03:02.161Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/aaf84a2e37e75470640e965d6619c6d9a521eb7c8aa097f2586907859198/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:30987f4a8ed169983f93e1be8ffeea5214a779e27ed0b059835c7afe96550ad7", size = 192889, upload-time = "2026-03-06T06:03:03.629Z" }, - { url = "https://files.pythonhosted.org/packages/eb/94/9b714873baf9a841613e8b49a5a3cd77d985d2c6c80f5038a5057395ebac/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:149ec69866c3d6c2fb6f758dbc014ecb09f30b35a5ca90b6a8a2d4e54e18fdfe", size = 185738, upload-time = "2026-03-06T06:03:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/ab/e5/bf57e1a9210a6ba78c740d66d05165a55b2cbeca29a83b8c659c9eb2d6c6/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:530beedcec9b6e027e7a4b6ce26eed36678aa39e17da85e6e03d7bd9e8e9d7c9", size = 209458, upload-time = "2026-03-06T06:03:06.54Z" }, - { url = "https://files.pythonhosted.org/packages/65/91/3c8cb46d840840f2593028fd708ea50695f8f61e1c490530ef1cce824f56/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:14498a429321de554b140013142abe7608f9d8ccc04d7baf2ad60498374aefa2", size = 195792, upload-time = "2026-03-06T06:03:08Z" }, - { url = "https://files.pythonhosted.org/packages/b0/43/783be5c6932fa8846a98313a2242fbcfe0c06c1c0ac2d6856b99d93069eb/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2820a98460c83663dd8ec015d9ddfd1e4879f12e06bb7d0500f044fb477d2770", size = 204829, upload-time = "2026-03-06T06:03:09.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/7d/138b5311c32fd24396321db796538cc748287c92da5e6fc1996babc06f99/charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:aa2f963b4da26daf46231d9b9e0e2c9408a751f8f0d0f44d2de56d3caf51d294", size = 198558, upload-time = "2026-03-06T06:03:11.585Z" }, - { url = "https://files.pythonhosted.org/packages/9c/87/ddd8bbdd703707c019fe9d14b678011627e6c5131dfdefe42aff151d718c/charset_normalizer-3.4.5-cp39-cp39-win32.whl", hash = "sha256:82cc7c2ad42faec8b574351f8bc2a0c049043893853317bd9bb309f5aba6cb5a", size = 132370, upload-time = "2026-03-06T06:03:13.327Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/d7cd28ae6d4dd47170b95153986789d69af4d5844f640edbc5138e4a70a2/charset_normalizer-3.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:92263f7eca2f4af326cd20de8d16728d2602f7cfea02e790dcde9d83c365d7cc", size = 142877, upload-time = "2026-03-06T06:03:15.041Z" }, - { url = "https://files.pythonhosted.org/packages/9c/26/8d68681566f288998eb36a0c60dd2c5c8aa93ee67b0d7e3dc72606650828/charset_normalizer-3.4.5-cp39-cp39-win_arm64.whl", hash = "sha256:014837af6fabf57121b6254fa8ade10dceabc3528b27b721a64bbc7b8b1d4eb4", size = 133186, upload-time = "2026-03-06T06:03:16.476Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" }, + { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" }, + { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" }, + { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" }, + { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/41/85/580dbaa12ab31041ed7df59f0bebc8893514fc21da6c05c3a1c1707d118f/charset_normalizer-3.4.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e", size = 298620, upload-time = "2026-03-15T18:52:57.332Z" }, + { url = "https://files.pythonhosted.org/packages/67/2c/1e55af3a5e2f52e44396d5c5b731e0ae4f3bb92915ff09a610fb2f4497eb/charset_normalizer-3.4.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17", size = 200106, upload-time = "2026-03-15T18:52:59.2Z" }, + { url = "https://files.pythonhosted.org/packages/10/42/0f2f51a1d16caa45fbf384fd337d4242df1a5b313babee211381d2d39a96/charset_normalizer-3.4.6-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778", size = 220539, upload-time = "2026-03-15T18:53:01.019Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0c/4e10996c740eec0f4ae8afbbbfa25f66e8479c4b6ee9cff1ca366a4f6c04/charset_normalizer-3.4.6-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe", size = 215821, upload-time = "2026-03-15T18:53:02.621Z" }, + { url = "https://files.pythonhosted.org/packages/46/73/205ae7644ebb581a7c6fa9c3751e283606e145f0e6f066003c66aafc9973/charset_normalizer-3.4.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a", size = 207917, upload-time = "2026-03-15T18:53:04.413Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ca/18f7dcf19afdab8097aeb2feb8b3809bb4b6ee356cb720abf5263d79406a/charset_normalizer-3.4.6-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297", size = 194513, upload-time = "2026-03-15T18:53:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6a/e7e3e204c8d79832a091e00b24595af1d5d9800d37dc1f67a6b264cc99a6/charset_normalizer-3.4.6-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687", size = 205612, upload-time = "2026-03-15T18:53:07.494Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ae/2169ebcea2851c5460c7a21993a0f87028be3c3e60899cb36251e1135cf5/charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4", size = 203519, upload-time = "2026-03-15T18:53:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/43/a0/6a49a925b9c225fe35dffeac5c76f68996b814c637e9d7213718f96be109/charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833", size = 195411, upload-time = "2026-03-15T18:53:10.542Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/a26b0a18e52b1a0f11f53c2c400ed062f386ac227a64ae4be4c5a64699be/charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5", size = 221653, upload-time = "2026-03-15T18:53:12.394Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3a/ed1d3b5bb55e3634bd5c31cedbe4fff79d0e5b8d9a062f663a757a07760d/charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b", size = 205650, upload-time = "2026-03-15T18:53:13.934Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/c75819eea5ceeefc49bae329327bb91e81adc346e2a9873d9fdb9e77cde6/charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9", size = 216919, upload-time = "2026-03-15T18:53:15.44Z" }, + { url = "https://files.pythonhosted.org/packages/0f/42/6e91bf8b15f67b7c957091138a36057a083e60703cc27848d5e36ca1eb03/charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597", size = 210101, upload-time = "2026-03-15T18:53:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/99/ff/101af2605e66a7ee59961d7f9e1060df7c92e8ea54208a02ab881422c24e/charset_normalizer-3.4.6-cp39-cp39-win32.whl", hash = "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54", size = 144136, upload-time = "2026-03-15T18:53:19.152Z" }, + { url = "https://files.pythonhosted.org/packages/1d/da/de5942dfbf21f28c19e9202267dabf7bc73f195465d020a3a60054520cc5/charset_normalizer-3.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8", size = 154210, upload-time = "2026-03-15T18:53:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/06/df/1b780a25b86d22b1d736f6ac883afd38ffdf30ddc18e5dc0e82211f493f1/charset_normalizer-3.4.6-cp39-cp39-win_arm64.whl", hash = "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8", size = 143225, upload-time = "2026-03-15T18:53:22.072Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] @@ -734,60 +736,60 @@ toml = [ [[package]] name = "coverage" -version = "7.13.4" +version = "7.13.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, - { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, - { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, - { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, - { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, - { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, - { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, - { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -822,7 +824,7 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "coverage", version = "7.13.4", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.13.5", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, { name = "requests", marker = "python_full_version >= '3.10'" }, { name = "typer", marker = "python_full_version >= '3.10'" }, ] @@ -897,7 +899,7 @@ wheels = [ [[package]] name = "cyclonedx-python-lib" -version = "11.6.0" +version = "11.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", @@ -911,9 +913,9 @@ dependencies = [ { name = "sortedcontainers", marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/ed/54ecfa25fc145c58bf4f98090f7b6ffe5188d0759248c57dde44427ea239/cyclonedx_python_lib-11.6.0.tar.gz", hash = "sha256:7fb85a4371fa3a203e5be577ac22b7e9a7157f8b0058b7448731474d6dea7bf0", size = 1408147, upload-time = "2025-12-02T12:28:46.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/0d/64f02d3fd9c116d6f50a540d04d1e4f2e3c487f5062d2db53733ddb25917/cyclonedx_python_lib-11.7.0.tar.gz", hash = "sha256:fb1bc3dedfa31208444dbd743007f478ab6984010a184e5bd466bffd969e936e", size = 1411174, upload-time = "2026-03-17T15:19:16.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/1b/534ad8a5e0f9470522811a8e5a9bc5d328fb7738ba29faf357467a4ef6d0/cyclonedx_python_lib-11.6.0-py3-none-any.whl", hash = "sha256:94f4aae97db42a452134dafdddcfab9745324198201c4777ed131e64c8380759", size = 511157, upload-time = "2025-12-02T12:28:44.158Z" }, + { url = "https://files.pythonhosted.org/packages/30/09/fe0e3bc32bd33707c519b102fc064ad2a2ce5a1b53e2be38b86936b476b1/cyclonedx_python_lib-11.7.0-py3-none-any.whl", hash = "sha256:02fa4f15ddbba21ac9093039f8137c0d1813af7fe88b760c5dcd3311a8da2178", size = 513041, upload-time = "2026-03-17T15:19:14.369Z" }, ] [[package]] @@ -1045,15 +1047,35 @@ wheels = [ name = "edtf" version = "5.0.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "pyparsing" }, - { name = "python-dateutil" }, + { name = "pyparsing", marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/24/0a9053bea41c2860046416f3d136283233ce307adb218d406dede64a8fac/edtf-5.0.0.tar.gz", hash = "sha256:7393c570b4838c8cbc05b0687c6ea0578039ba007c8ce125206f44f18f2dea5d", size = 45617, upload-time = "2024-07-18T08:01:52.882Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/08/746096971fae3585928a631912bdd4393f5ab5696706bd34cbdcccf763fc/edtf-5.0.0-py3-none-any.whl", hash = "sha256:b38ca29fa166a5c628b899a76e73a9f4cc732da565b85e737af4c0e457775f5e", size = 40319, upload-time = "2024-07-18T08:01:50.873Z" }, ] +[[package]] +name = "edtf" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "pyparsing", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/23/e9f69255f67f55b03e2705bfd7a579a01c26146b9ea98f9cdb07d8156b4d/edtf-5.0.1-py3-none-any.whl", hash = "sha256:d3ed8c1307330d7563ae9efc4193988acddeb60dcfb740d4fe0abe739ef0944a", size = 36776, upload-time = "2026-03-15T03:22:56.53Z" }, +] + [[package]] name = "elasticsearch" version = "7.13.4" @@ -1139,16 +1161,16 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.0" +version = "3.25.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] [[package]] @@ -1159,7 +1181,7 @@ dependencies = [ { name = "blinker" }, { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "itsdangerous" }, { name = "jinja2" }, { name = "markupsafe" }, @@ -1546,40 +1568,40 @@ wheels = [ [[package]] name = "fonttools" -version = "4.61.1" +version = "4.62.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, - { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, - { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, - { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, - { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, - { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/ff/532ed43808b469c807e8cb6b21358da3fe6fd51486b3a8c93db0bb5d957f/fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c", size = 2873740, upload-time = "2026-03-13T13:52:11.822Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/2318d2b430562da7227010fb2bb029d2fa54d7b46443ae8942bab224e2a0/fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a", size = 2417649, upload-time = "2026-03-13T13:52:14.605Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/40f15523b5188598018e7956899fed94eb7debec89e2dd70cb4a8df90492/fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3", size = 4935213, upload-time = "2026-03-13T13:52:17.399Z" }, + { url = "https://files.pythonhosted.org/packages/42/09/7dbe3d7023f57d9b580cfa832109d521988112fd59dddfda3fddda8218f9/fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23", size = 4892374, upload-time = "2026-03-13T13:52:20.175Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2d/84509a2e32cb925371560ef5431365d8da2183c11d98e5b4b8b4e42426a5/fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d", size = 4911856, upload-time = "2026-03-13T13:52:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/a5/80/df28131379eed93d9e6e6fccd3bf6e3d077bebbfe98cc83f21bbcd83ed02/fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae", size = 5031712, upload-time = "2026-03-13T13:52:25.14Z" }, + { url = "https://files.pythonhosted.org/packages/3d/03/3c8f09aad64230cd6d921ae7a19f9603c36f70930b00459f112706f6769a/fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed", size = 1507878, upload-time = "2026-03-13T13:52:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f53f626f8f3e89f4cadd8fc08f3452c8fd182c951ad5caa35efac22b29ab/fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9", size = 1556766, upload-time = "2026-03-13T13:52:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] @@ -1610,7 +1632,7 @@ resolution-markers = [ ] dependencies = [ { name = "defusedxml", marker = "python_full_version >= '3.10'" }, - { name = "fonttools", version = "4.61.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fonttools", version = "4.62.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pillow", version = "12.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/f2/72feae0b2827ed38013e4307b14f95bf0b3d124adfef4d38a7d57533f7be/fpdf2-2.8.7.tar.gz", hash = "sha256:7060ccee5a9c7ab0a271fb765a36a23639f83ef8996c34e3d46af0a17ede57f9", size = 362351, upload-time = "2026-02-28T05:39:16.456Z" } @@ -1807,28 +1829,48 @@ wheels = [ [[package]] name = "idutils" -version = "1.5.1" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "isbnlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/be/114efec47bd7b4b6e7ba4819e27e0ff1cba13bbd3c829bae5a31d12f82a5/idutils-1.5.1.tar.gz", hash = "sha256:d010750b10c0f516509614fc2997da619ab95585f9f2fe3bc149e5819d3acfd7", size = 35001, upload-time = "2025-12-16T15:57:44.826Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/ea/97f631ea64e756bbbd4b17c3e8173033321fb39527da8bfd5b5b8332fdd4/idutils-1.6.0.tar.gz", hash = "sha256:dfffe30a6abdba85b1710ef3bb469ae586f72a3db3f36217b82213ed8de32f85", size = 35905, upload-time = "2026-03-19T07:49:10.553Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/d4/0b075ed2e75ffbb4ec5ac927be4f10a5c349d8e5642f0499e40e931a1c06/idutils-1.5.1-py2.py3-none-any.whl", hash = "sha256:bbeb5dd8d7d803e69a32d825ae2d3dd1bf33b271cc06de06e3e83f2b93fc74c5", size = 24080, upload-time = "2025-12-16T15:57:43.456Z" }, + { url = "https://files.pythonhosted.org/packages/5f/25/eb953116f129414fb21f563c0253efda09fbc482507cede686005a5026a4/idutils-1.6.0-py2.py3-none-any.whl", hash = "sha256:fe84d9d837c16f1ec9062687fa8bdc133fcbdab6365f894511e67b2aaa1849d7", size = 24654, upload-time = "2026-03-19T07:49:09.07Z" }, ] [[package]] name = "importlib-metadata" version = "8.7.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "zipp", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + [[package]] name = "importlib-resources" version = "6.5.2" @@ -1984,7 +2026,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "flask" }, - { name = "importlib-metadata" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "importlib-resources" }, { name = "itsdangerous" }, { name = "markupsafe" }, @@ -2019,7 +2062,7 @@ dependencies = [ { name = "invenio-base" }, { name = "msgpack" }, { name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "redis", version = "7.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "redis", version = "7.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/0b/33f7e233501a833706c25892ccb3132133285a23762fdbe4e25ad5a688f7/invenio_celery-2.2.0.tar.gz", hash = "sha256:348f9fadd2af2b33ec8cef61e0c926a9d12995c1687bf74d1de579440ad7f046", size = 21141, upload-time = "2025-07-03T21:30:49.923Z" } wheels = [ @@ -2041,7 +2084,7 @@ wheels = [ [[package]] name = "invenio-db" -version = "2.3.0" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic", version = "1.16.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -2053,9 +2096,9 @@ dependencies = [ { name = "sqlalchemy-continuum" }, { name = "sqlalchemy-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b8/13946c58a059e0c42bf69cdf606d359078065be857df8bfff729d556860e/invenio_db-2.3.0.tar.gz", hash = "sha256:b85f7739e15b48a0e9373b4e6428c92ab9fcb92a45d91425d6a33ae653146796", size = 22289, upload-time = "2026-02-25T08:42:45.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/f0/bd62981ea87b484757fb40c627d086fe65353b22be3451852ab79f9c4667/invenio_db-2.4.0.tar.gz", hash = "sha256:e4d509e0f22850c6b190979878d45b400d33d09943c879ea49884fb10cc2a587", size = 22108, upload-time = "2026-03-12T22:08:26.296Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/86/2bc3ca5504fa3ff22264c0946197fcc395333af77f88c0fc0ad93ef048c2/invenio_db-2.3.0-py2.py3-none-any.whl", hash = "sha256:6a7348c20a3ae419716a09002b589f62690b4460962f5b4a0e054f87a00d0918", size = 22274, upload-time = "2026-02-25T08:42:44.78Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/86e676d5a543baca9de4a2b542f8e23d7d2dd264e42cdf0de02ab3511b77/invenio_db-2.4.0-py2.py3-none-any.whl", hash = "sha256:cff42013df5acfa0cdd8538787ab16961b5e7f01ab323d75f313b1f6bf9ced72", size = 21797, upload-time = "2026-03-12T22:08:25.255Z" }, ] [package.optional-dependencies] @@ -2212,7 +2255,8 @@ dependencies = [ { name = "flask-oauthlib-invenio" }, { name = "flask-wtf" }, { name = "future" }, - { name = "importlib-metadata" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "invenio-accounts" }, { name = "invenio-base" }, { name = "invenio-i18n" }, @@ -2258,7 +2302,8 @@ version = "2.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "base32-lib" }, - { name = "importlib-metadata" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "importlib-resources" }, { name = "invenio-base" }, { name = "invenio-i18n" }, @@ -2298,7 +2343,7 @@ dependencies = [ { name = "invenio-base" }, { name = "invenio-celery" }, { name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "redis", version = "7.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "redis", version = "7.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/02/e81070f10cfbb1c821caa68853b77116bdb41ada95c5640f3f3e26e62a53/invenio_queues-1.0.2.tar.gz", hash = "sha256:47b70d71459b5057bb3fd8aafa45a467c8ec099ddf01d9c065e0fb8a80c23ba1", size = 10926, upload-time = "2025-11-04T09:45:53.12Z" } wheels = [ @@ -2494,7 +2539,7 @@ wheels = [ [[package]] name = "invenio-theme" -version = "4.5.0" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask-menu" }, @@ -2503,9 +2548,9 @@ dependencies = [ { name = "invenio-i18n" }, { name = "jsmin" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/55/487b727ca7c34b370f6a6632ecdd039473a7deef3e9d0a9667cfebf7f61a/invenio_theme-4.5.0.tar.gz", hash = "sha256:840027c16ddd9b2d3e9d156c38ee0c3b106925f515da172e9940db3de54182bc", size = 4438293, upload-time = "2025-12-05T15:12:44.597Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/2b/afe6f4e155defbb07c6cbfb9967dce6633b21a0788bc4469110cf063eac5/invenio_theme-4.6.0.tar.gz", hash = "sha256:370be32b5453516030644d4e0916f8024079e5dc85d106a8b3d38a4c31c0ff90", size = 4438771, upload-time = "2026-03-09T10:57:35.467Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/1e/851d36dbf13ee6787d729a190fce80f177ba0dfc6840e3024f1582a3b9fc/invenio_theme-4.5.0-py2.py3-none-any.whl", hash = "sha256:69ea8a010fe6b0720b9d15f10fbadb17f9c37cc241af76d3a5d2779ac1798091", size = 4567262, upload-time = "2025-12-05T15:12:42.792Z" }, + { url = "https://files.pythonhosted.org/packages/b7/26/6f393d68c25fb419314a0c34d0f68569be78bb98329351f2781481bccdef/invenio_theme-4.6.0-py2.py3-none-any.whl", hash = "sha256:9186944490ccb8c78997b958e00ddc7f66c9876ef6beb4eb9e228a85a829e84b", size = 4567436, upload-time = "2026-03-09T10:57:33.456Z" }, ] [[package]] @@ -2658,7 +2703,7 @@ resolution-markers = [ "python_full_version < '3.10'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1e/82/fa43935523efdfcce6abbae9da7f372b627b27142c3419fcf13bf5b0c397/isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481", size = 824325, upload-time = "2025-10-01T16:26:45.027Z" } wheels = [ @@ -2723,7 +2768,8 @@ name = "jsonpatch" version = "1.33" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jsonpointer" }, + { name = "jsonpointer", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jsonpointer", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } wheels = [ @@ -2734,11 +2780,28 @@ wheels = [ name = "jsonpointer" version = "3.0.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] +[[package]] +name = "jsonpointer" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/48/bf/9ecc036fbc15cf4153ea6ed4dbeed31ef043f762cccc9d44a534be8319b0/jsonpointer-3.1.0.tar.gz", hash = "sha256:f9b39abd59ba8c1de8a4ff16141605d2a8dacc4dd6cf399672cf237bfe47c211", size = 9000, upload-time = "2026-03-20T21:47:09.982Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/25/cebb241a435cbf4626b5ea096d8385c04416d7ca3082a15299b746e248fa/jsonpointer-3.1.0-py3-none-any.whl", hash = "sha256:f82aa0f745001f169b96473348370b43c3f581446889c41c807bab1db11c8e7b", size = 7651, upload-time = "2026-03-20T21:47:08.792Z" }, +] + [[package]] name = "jsonref" version = "1.1.0" @@ -2750,15 +2813,16 @@ wheels = [ [[package]] name = "jsonresolver" -version = "0.5.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "jsonschema-specifications" }, { name = "pluggy" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/ef/256abb36bc5a371a4bad078f1883e64bfa21385c6962548d324ff9fcadf2/jsonresolver-0.5.0.tar.gz", hash = "sha256:32bdc7aff09d5c64276e0711c6b9fa4c4b1bda979b1b02a8185d79487ba9fff5", size = 13689, upload-time = "2026-01-27T12:10:00.328Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/c2/5d7ee99eaf8cb1f2b97ef295332921a8dd3903f0eafb15559e04312bc200/jsonresolver-0.5.1.tar.gz", hash = "sha256:dc48f19f3e329af6d30a696c53fb087edf95d97c4615bccd27f517e683503864", size = 13788, upload-time = "2026-03-20T07:27:53.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/fd/0cacf8a2f37952e589b55919e46df50ed24ae1612e0b1d7a1ca74709f017/jsonresolver-0.5.0-py2.py3-none-any.whl", hash = "sha256:e39be56d675a1756dc1fa56036b499aeaef79f22caeb0d220397a7aac5c4bbc2", size = 13594, upload-time = "2026-01-27T12:09:58.943Z" }, + { url = "https://files.pythonhosted.org/packages/19/57/45bde3c362518fd3a8078a32d704edeb94a61ad814302a246b351016f492/jsonresolver-0.5.1-py2.py3-none-any.whl", hash = "sha256:5736bca6678cde4ef501c09a9b7754467d11863bab89af6d2cee45adc49f689d", size = 13634, upload-time = "2026-03-20T07:27:52.682Z" }, ] [[package]] @@ -2820,7 +2884,7 @@ resolution-markers = [ "python_full_version < '3.10'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "python-dateutil", marker = "python_full_version < '3.10'" }, { name = "pyzmq", marker = "python_full_version < '3.10'" }, @@ -3237,7 +3301,7 @@ resolution-markers = [ "python_full_version < '3.10'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } wheels = [ @@ -3345,7 +3409,8 @@ dependencies = [ { name = "babel-edtf" }, { name = "bleach", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version < '3.10'" }, { name = "bleach", version = "6.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version >= '3.10'" }, - { name = "edtf" }, + { name = "edtf", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "edtf", version = "5.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "ftfy" }, { name = "geojson" }, { name = "idutils" }, @@ -3612,7 +3677,7 @@ dependencies = [ { name = "bleach", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version < '3.10'" }, { name = "bleach", version = "6.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version >= '3.10'" }, { name = "defusedxml" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, @@ -3976,7 +4041,7 @@ resolution-markers = [ ] dependencies = [ { name = "cachecontrol", version = "0.14.4", source = { registry = "https://pypi.org/simple" }, extra = ["filecache"], marker = "python_full_version >= '3.10'" }, - { name = "cyclonedx-python-lib", version = "11.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "cyclonedx-python-lib", version = "11.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "packaging", version = "26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pip-api", marker = "python_full_version >= '3.10'" }, { name = "pip-requirements-parser", marker = "python_full_version >= '3.10'" }, @@ -4272,11 +4337,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.11.0" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] [package.optional-dependencies] @@ -4304,22 +4372,22 @@ wheels = [ [[package]] name = "pymupdf" -version = "1.27.1" +version = "1.27.2.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/0c/40dda0cc4bd2220a2ef75f8c53dd7d8ed1e29681fcb3df75db6ee9677a7e/pymupdf-1.27.1.tar.gz", hash = "sha256:4afbde0769c336717a149ab0de3330dcb75378f795c1a8c5af55c1a628b17d55", size = 85303479, upload-time = "2026-02-12T08:29:17.682Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/32/f6b645c51d79a188a4844140c5dabca7b487ad56c4be69c4bc782d0d11a9/pymupdf-1.27.2.2.tar.gz", hash = "sha256:ea8fdc3ab6671ca98f629d5ec3032d662c8cf1796b146996b7ad306ac7ed3335", size = 85354380, upload-time = "2026-03-20T09:47:58.386Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/19/fde6ea4712a904b65e8f41124a0e4233879b87a770fe6a8ce857964de6d5/pymupdf-1.27.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bee9f95512f9556dbf2cacfd1413c61b29a55baa07fa7f8fc83d221d8419888a", size = 23986707, upload-time = "2026-02-11T15:03:24.025Z" }, - { url = "https://files.pythonhosted.org/packages/75/c2/070dff91ad3f1bc16fd6c6ceff23495601fcce4c92d28be534417596418a/pymupdf-1.27.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3de95a0889395b0966fafd11b94980b7543a816e89dd1c218597a08543ac3415", size = 23263493, upload-time = "2026-02-11T15:03:45.528Z" }, - { url = "https://files.pythonhosted.org/packages/8e/db/937377f4b3e0fbf6273c17436a49f7db17df1a46b1be9e26653b6fafc0e1/pymupdf-1.27.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2c9d9353b840040cbc724341f4095fb7e2cc1a12a9147d0ec1a0a79f5d773147", size = 24317651, upload-time = "2026-02-11T22:33:38.967Z" }, - { url = "https://files.pythonhosted.org/packages/72/d5/c701cf2d0cdd6e5d6bca3ca9188d7f5d7ce3ae67dd1368d658cd4bae2707/pymupdf-1.27.1-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:aeaed76e72cbc061149a825ab0811c5f4752970c56591c2938c5042ec06b26e1", size = 24945742, upload-time = "2026-02-11T15:04:06.21Z" }, - { url = "https://files.pythonhosted.org/packages/2b/29/690202b38b93cf77b73a29c25a63a2b6f3fcb36b1f75006e50b8dee7c108/pymupdf-1.27.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4f1837554134fb45d390a44de8844b2ca9b6c901c82ccc90b340e3b7f3b126ca", size = 25167965, upload-time = "2026-02-11T22:36:35.478Z" }, - { url = "https://files.pythonhosted.org/packages/8a/81/f937e6aa606fd263c3a45d0ff0f0bbdbf3fb779933091fc0f6179513cc93/pymupdf-1.27.1-cp310-abi3-win32.whl", hash = "sha256:fa33b512d82c6c4852edadf57f22d5f27d16243bb33dac0fbe4eb0f281c5b17e", size = 18006253, upload-time = "2026-02-12T13:48:07.129Z" }, - { url = "https://files.pythonhosted.org/packages/3e/99/fe4a7752990bf65277718fffbead4478de9afd1c7288d7a6d643f79a6fa7/pymupdf-1.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:4b6268dff3a9d713034eba5c2ffce0da37c62443578941ac5df433adcde57b2f", size = 19236703, upload-time = "2026-02-11T15:04:19.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/88/d01992a50165e22dec057a1129826846c547feb4ba07f42720ac030ce438/pymupdf-1.27.2.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:800f43e60a6f01f644343c2213b8613db02eaf4f4ba235b417b3351fa99e01c0", size = 23987563, upload-time = "2026-03-19T12:35:42.989Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0e/9f526bc1d49d8082eff0d1547a69d541a0c5a052e71da625559efaba46a6/pymupdf-1.27.2.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e2e4299ef1ac0c9dff9be096cbd22783699673abecfa7c3f73173ae06421d73", size = 23263089, upload-time = "2026-03-20T09:44:16.982Z" }, + { url = "https://files.pythonhosted.org/packages/42/be/984f0d6343935b5dd30afaed6be04fc753146bf55709e63ef28bf9ef7497/pymupdf-1.27.2.2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5e3d54922db1c7da844f1208ac1db05704770988752311f81dd36694ae0a07b", size = 24318817, upload-time = "2026-03-20T09:44:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/22/8e/85e9d9f11dbf34036eb1df283805ef6b885f2005a56d6533bb58ab0b8a11/pymupdf-1.27.2.2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:892698c9768457eb0991c102c96a856c0a7062539371df5e6bee0816f3ef498e", size = 24948135, upload-time = "2026-03-20T09:44:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/db/e6/386edb017e5b93f1ab0bf6653ae32f3dd8dfc834ed770212e10ca62f4af9/pymupdf-1.27.2.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b4bbfa6ef347fade678771a93f6364971c51a2cdc44cd2400dc4eeed1ddb4e6", size = 25169585, upload-time = "2026-03-20T09:45:05.393Z" }, + { url = "https://files.pythonhosted.org/packages/ba/fd/f1ebe24fcd31aaea8b85b3a7ac4c3fc96e20388be5466ace27c9a3c546d9/pymupdf-1.27.2.2-cp310-abi3-win32.whl", hash = "sha256:0b8e924433b7e0bd46be820899300259235997d5a747638471fb2762baa8ee30", size = 18008861, upload-time = "2026-03-20T09:45:21.353Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b6/2a9a8556000199bbf80a5915dcd15d550d1e5288894316445c54726aaf53/pymupdf-1.27.2.2-cp310-abi3-win_amd64.whl", hash = "sha256:09bb53f9486ccb5297030cbc2dbdae845ba1c3c5126e96eb2d16c4f118de0b5b", size = 19238032, upload-time = "2026-03-20T09:45:37.941Z" }, ] [[package]] @@ -4412,17 +4480,17 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, - { name = "coverage", version = "7.13.4", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.13.5", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -4478,7 +4546,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "check-manifest" }, { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "coverage", version = "7.13.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.13.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "docker-services-cli" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -4953,7 +5021,7 @@ wheels = [ [[package]] name = "redis" -version = "7.2.1" +version = "7.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", @@ -4963,9 +5031,9 @@ resolution-markers = [ dependencies = [ { name = "async-timeout", marker = "python_full_version >= '3.10' and python_full_version < '3.11.3'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/31/1476f206482dd9bc53fdbbe9f6fbd5e05d153f18e54667ce839df331f2e6/redis-7.2.1.tar.gz", hash = "sha256:6163c1a47ee2d9d01221d8456bc1c75ab953cbda18cfbc15e7140e9ba16ca3a5", size = 4906735, upload-time = "2026-02-25T20:05:18.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/82/4d1a5279f6c1251d3d2a603a798a1137c657de9b12cfc1fba4858232c4d2/redis-7.3.0.tar.gz", hash = "sha256:4d1b768aafcf41b01022410b3cc4f15a07d9b3d6fe0c66fc967da2c88e551034", size = 4928081, upload-time = "2026-03-06T18:18:16.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/98/1dd1a5c060916cf21d15e67b7d6a7078e26e2605d5c37cbc9f4f5454c478/redis-7.2.1-py3-none-any.whl", hash = "sha256:49e231fbc8df2001436ae5252b3f0f3dc930430239bfeb6da4c7ee92b16e5d33", size = 396057, upload-time = "2026-02-25T20:05:16.533Z" }, + { url = "https://files.pythonhosted.org/packages/f0/28/84e57fce7819e81ec5aa1bd31c42b89607241f4fb1a3ea5b0d2dbeaea26c/redis-7.3.0-py3-none-any.whl", hash = "sha256:9d4fcb002a12a5e3c3fbe005d59c48a2cc231f87fbb2f6b70c2d89bb64fec364", size = 404379, upload-time = "2026-03-06T18:18:14.583Z" }, ] [[package]] @@ -5081,7 +5149,7 @@ dependencies = [ { name = "poethepoet", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "poethepoet", version = "0.42.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pymupdf", version = "1.26.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pymupdf", version = "1.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pymupdf", version = "1.27.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/0e/5b103716694ef4146b9792958e97b4b09c0d46326c2097c1d8c5d7f8c501/rero_invenio_files-1.2.0.tar.gz", hash = "sha256:2b1b8ddfa03e46f9e052630a9b275091a992e35cd9d6e06f1fa78b67607e69f1", size = 1454460, upload-time = "2025-09-02T14:38:45.923Z" } wheels = [ @@ -5295,27 +5363,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, - { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, - { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, - { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, - { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, - { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] @@ -5822,21 +5890,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, - { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, - { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, - { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, - { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, - { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +version = "6.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, ] [[package]]