Changeset View
Changeset View
Standalone View
Standalone View
swh/loader/core/loader.py
# Copyright (C) 2015-2022 The Software Heritage developers | # Copyright (C) 2015-2022 The Software Heritage developers | |||||||||||||||||
# See the AUTHORS file at the top-level directory of this distribution | # See the AUTHORS file at the top-level directory of this distribution | |||||||||||||||||
# License: GNU General Public License version 3, or any later version | # License: GNU General Public License version 3, or any later version | |||||||||||||||||
# See top-level LICENSE file for more information | # See top-level LICENSE file for more information | |||||||||||||||||
import datetime | import datetime | |||||||||||||||||
import hashlib | import hashlib | |||||||||||||||||
import logging | import logging | |||||||||||||||||
import os | import os | |||||||||||||||||
from typing import Any, Dict, Iterable, Optional | from typing import Any, Dict, Iterable, List, Optional | |||||||||||||||||
import sentry_sdk | ||||||||||||||||||
from swh.core.config import load_from_envvar | from swh.core.config import load_from_envvar | |||||||||||||||||
from swh.loader.core.metadata_fetchers import CredentialsType, get_fetchers_for_lister | ||||||||||||||||||
from swh.loader.exception import NotFound | from swh.loader.exception import NotFound | |||||||||||||||||
from swh.model.model import ( | from swh.model.model import ( | |||||||||||||||||
BaseContent, | BaseContent, | |||||||||||||||||
Content, | Content, | |||||||||||||||||
Directory, | Directory, | |||||||||||||||||
Origin, | Origin, | |||||||||||||||||
OriginVisit, | OriginVisit, | |||||||||||||||||
OriginVisitStatus, | OriginVisitStatus, | |||||||||||||||||
RawExtrinsicMetadata, | ||||||||||||||||||
Release, | Release, | |||||||||||||||||
Revision, | Revision, | |||||||||||||||||
Sha1Git, | Sha1Git, | |||||||||||||||||
SkippedContent, | SkippedContent, | |||||||||||||||||
Snapshot, | Snapshot, | |||||||||||||||||
) | ) | |||||||||||||||||
from swh.storage import get_storage | from swh.storage import get_storage | |||||||||||||||||
from swh.storage.interface import StorageInterface | from swh.storage.interface import StorageInterface | |||||||||||||||||
Show All 19 Lines | class BaseLoader: | |||||||||||||||||
Some class examples: | Some class examples: | |||||||||||||||||
- :class:`SvnLoader` | - :class:`SvnLoader` | |||||||||||||||||
- :class:`GitLoader` | - :class:`GitLoader` | |||||||||||||||||
- :class:`PyPILoader` | - :class:`PyPILoader` | |||||||||||||||||
- :class:`NpmLoader` | - :class:`NpmLoader` | |||||||||||||||||
Args: | ||||||||||||||||||
lister_name: Name of the lister which triggered this load. | ||||||||||||||||||
If provided, the loader will try to use the forge's API to retrieve extrinsic | ||||||||||||||||||
metadata | ||||||||||||||||||
lister_instance: Name of the lister instance which triggered this load. | ||||||||||||||||||
Must be None iff lister_name is, but it may be the empty string for listers | ||||||||||||||||||
with a single instance. | ||||||||||||||||||
""" | """ | |||||||||||||||||
visit_type: str | visit_type: str | |||||||||||||||||
origin: Origin | origin: Origin | |||||||||||||||||
loaded_snapshot_id: Optional[Sha1Git] | loaded_snapshot_id: Optional[Sha1Git] | |||||||||||||||||
def __init__( | def __init__( | |||||||||||||||||
self, | self, | |||||||||||||||||
storage: StorageInterface, | storage: StorageInterface, | |||||||||||||||||
origin_url: str, | origin_url: str, | |||||||||||||||||
logging_class: Optional[str] = None, | logging_class: Optional[str] = None, | |||||||||||||||||
save_data_path: Optional[str] = None, | save_data_path: Optional[str] = None, | |||||||||||||||||
max_content_size: Optional[int] = None, | max_content_size: Optional[int] = None, | |||||||||||||||||
lister_name: Optional[str] = None, | ||||||||||||||||||
vlorentz: Should I rename `lister_instance` to `lister_instance_name` for consistency with https://forge. | ||||||||||||||||||
Done Inline Actionsah actually I *need* to do that vlorentz: ah actually I *need* to do that | ||||||||||||||||||
lister_instance: Optional[str] = None, | ||||||||||||||||||
metadata_fetcher_credentials: CredentialsType = None, | ||||||||||||||||||
): | ): | |||||||||||||||||
super().__init__() | if lister_name == "": | |||||||||||||||||
raise ValueError("lister_name must not be the empty string") | ||||||||||||||||||
if lister_name is None and lister_instance is not None: | ||||||||||||||||||
raise ValueError( | ||||||||||||||||||
f"lister_name is None but lister_instance is {lister_instance!r}" | ||||||||||||||||||
) | ||||||||||||||||||
if lister_name is not None and lister_instance is None: | ||||||||||||||||||
raise ValueError( | ||||||||||||||||||
f"lister_instance is None but lister_name is {lister_name!r}" | ||||||||||||||||||
Not Done Inline Actions
or something ^? ardumont: or something ^? | ||||||||||||||||||
Done Inline ActionsI did this initially, but I prefer two distinct error messages; they will make issues easier to pinpoint. (and btw, XOR can be written more shortly in Python: if (lister_name is None) != (lister_instance_name is None):) vlorentz: I did this initially, but I prefer two distinct error messages; they will make issues easier to… | ||||||||||||||||||
Not Done Inline Actionsack neat xor trick ;) ardumont: ack
neat xor trick ;) | ||||||||||||||||||
) | ||||||||||||||||||
self.storage = storage | self.storage = storage | |||||||||||||||||
self.origin = Origin(url=origin_url) | self.origin = Origin(url=origin_url) | |||||||||||||||||
self.max_content_size = int(max_content_size) if max_content_size else None | self.max_content_size = int(max_content_size) if max_content_size else None | |||||||||||||||||
self.lister_name = lister_name | ||||||||||||||||||
self.lister_instance = lister_instance | ||||||||||||||||||
self.metadata_fetcher_credentials = metadata_fetcher_credentials or {} | ||||||||||||||||||
if logging_class is None: | if logging_class is None: | |||||||||||||||||
logging_class = "%s.%s" % ( | logging_class = "%s.%s" % ( | |||||||||||||||||
self.__class__.__module__, | self.__class__.__module__, | |||||||||||||||||
self.__class__.__name__, | self.__class__.__name__, | |||||||||||||||||
) | ) | |||||||||||||||||
self.log = logging.getLogger(logging_class) | self.log = logging.getLogger(logging_class) | |||||||||||||||||
▲ Show 20 Lines • Show All 211 Lines • ▼ Show 20 Lines | def load(self) -> Dict[str, str]: | |||||||||||||||||
assert ( | assert ( | |||||||||||||||||
self.visit.visit | self.visit.visit | |||||||||||||||||
), "The method `_store_origin_visit` should set the visit (OriginVisit)" | ), "The method `_store_origin_visit` should set the visit (OriginVisit)" | |||||||||||||||||
self.log.info( | self.log.info( | |||||||||||||||||
"Load origin '%s' with type '%s'", self.origin.url, self.visit.type | "Load origin '%s' with type '%s'", self.origin.url, self.visit.type | |||||||||||||||||
) | ) | |||||||||||||||||
try: | try: | |||||||||||||||||
metadata = self.build_extrinsic_origin_metadata() | ||||||||||||||||||
self.load_metadata_objects(metadata) | ||||||||||||||||||
except Exception as e: | ||||||||||||||||||
sentry_sdk.capture_exception(e) | ||||||||||||||||||
# Do not fail the whole task if this is the only failure | ||||||||||||||||||
self.log.exception( | ||||||||||||||||||
"Failure while loading extrinsic origin metadata.", | ||||||||||||||||||
extra={ | ||||||||||||||||||
"swh_task_args": [], | ||||||||||||||||||
"swh_task_kwargs": { | ||||||||||||||||||
"origin": self.origin.url, | ||||||||||||||||||
"lister_name": self.lister_name, | ||||||||||||||||||
"lister_instance": self.lister_instance, | ||||||||||||||||||
}, | ||||||||||||||||||
}, | ||||||||||||||||||
) | ||||||||||||||||||
try: | ||||||||||||||||||
self.prepare() | self.prepare() | |||||||||||||||||
while True: | while True: | |||||||||||||||||
more_data_to_fetch = self.fetch_data() | more_data_to_fetch = self.fetch_data() | |||||||||||||||||
self.store_data() | self.store_data() | |||||||||||||||||
if not more_data_to_fetch: | if not more_data_to_fetch: | |||||||||||||||||
break | break | |||||||||||||||||
Show All 15 Lines | def load(self) -> Dict[str, str]: | |||||||||||||||||
status = "partial" if self.loaded_snapshot_id else "failed" | status = "partial" if self.loaded_snapshot_id else "failed" | |||||||||||||||||
task_status = "failed" | task_status = "failed" | |||||||||||||||||
self.log.exception( | self.log.exception( | |||||||||||||||||
"Loading failure, updating to `%s` status", | "Loading failure, updating to `%s` status", | |||||||||||||||||
status, | status, | |||||||||||||||||
extra={ | extra={ | |||||||||||||||||
"swh_task_args": [], | "swh_task_args": [], | |||||||||||||||||
"swh_task_kwargs": {"origin": self.origin.url}, | "swh_task_kwargs": { | |||||||||||||||||
"origin": self.origin.url, | ||||||||||||||||||
"lister_name": self.lister_name, | ||||||||||||||||||
"lister_instance": self.lister_instance, | ||||||||||||||||||
}, | ||||||||||||||||||
}, | }, | |||||||||||||||||
) | ) | |||||||||||||||||
visit_status = OriginVisitStatus( | visit_status = OriginVisitStatus( | |||||||||||||||||
origin=self.origin.url, | origin=self.origin.url, | |||||||||||||||||
visit=self.visit.visit, | visit=self.visit.visit, | |||||||||||||||||
type=self.visit_type, | type=self.visit_type, | |||||||||||||||||
date=now(), | date=now(), | |||||||||||||||||
status=status, | status=status, | |||||||||||||||||
snapshot=self.loaded_snapshot_id, | snapshot=self.loaded_snapshot_id, | |||||||||||||||||
) | ) | |||||||||||||||||
self.storage.origin_visit_status_add([visit_status]) | self.storage.origin_visit_status_add([visit_status]) | |||||||||||||||||
self.post_load(success=False) | self.post_load(success=False) | |||||||||||||||||
return {"status": task_status} | return {"status": task_status} | |||||||||||||||||
finally: | finally: | |||||||||||||||||
self.flush() | self.flush() | |||||||||||||||||
self.cleanup() | self.cleanup() | |||||||||||||||||
return self.load_status() | return self.load_status() | |||||||||||||||||
def load_metadata_objects( | ||||||||||||||||||
self, metadata_objects: List[RawExtrinsicMetadata] | ||||||||||||||||||
) -> None: | ||||||||||||||||||
if not metadata_objects: | ||||||||||||||||||
return | ||||||||||||||||||
authorities = {mo.authority for mo in metadata_objects} | ||||||||||||||||||
self.storage.metadata_authority_add(list(authorities)) | ||||||||||||||||||
fetchers = {mo.fetcher for mo in metadata_objects} | ||||||||||||||||||
self.storage.metadata_fetcher_add(list(fetchers)) | ||||||||||||||||||
self.storage.raw_extrinsic_metadata_add(metadata_objects) | ||||||||||||||||||
def build_extrinsic_origin_metadata(self) -> List[RawExtrinsicMetadata]: | ||||||||||||||||||
"""Builds a list of full RawExtrinsicMetadata objects, using | ||||||||||||||||||
a metadata fetcher returned by :func:`get_fetcher_classes`.""" | ||||||||||||||||||
if self.lister_name is None: | ||||||||||||||||||
self.log.debug("lister_not provided, skipping extrinsic origin metadata") | ||||||||||||||||||
return [] | ||||||||||||||||||
assert self.lister_instance, "lister_instance is None, but lister_name is not" | ||||||||||||||||||
metadata = [] | ||||||||||||||||||
for cls in get_fetchers_for_lister(self.lister_name): | ||||||||||||||||||
metadata_fetcher = cls( | ||||||||||||||||||
origin=self.origin, | ||||||||||||||||||
lister_name=self.lister_name, | ||||||||||||||||||
lister_instance=self.lister_instance, | ||||||||||||||||||
credentials=self.metadata_fetcher_credentials, | ||||||||||||||||||
) | ||||||||||||||||||
metadata.extend(metadata_fetcher.get_origin_metadata()) | ||||||||||||||||||
return metadata | ||||||||||||||||||
class DVCSLoader(BaseLoader): | class DVCSLoader(BaseLoader): | |||||||||||||||||
"""This base class is a pattern for dvcs loaders (e.g. git, mercurial). | """This base class is a pattern for dvcs loaders (e.g. git, mercurial). | |||||||||||||||||
Those loaders are able to load all the data in one go. For example, the | Those loaders are able to load all the data in one go. For example, the | |||||||||||||||||
loader defined in swh-loader-git :class:`BulkUpdater`. | loader defined in swh-loader-git :class:`BulkUpdater`. | |||||||||||||||||
For other loaders (stateful one, (e.g :class:`SWHSvnLoader`), | For other loaders (stateful one, (e.g :class:`SWHSvnLoader`), | |||||||||||||||||
▲ Show 20 Lines • Show All 74 Lines • Show Last 20 Lines |
Should I rename lister_instance to lister_instance_name for consistency with https://forge.softwareheritage.org/source/swh-scheduler/browse/master/swh/scheduler/model.py$107-108 ?