Changeset View
Standalone View
swh/lister/maven/lister.py
- This file was added.
# Copyright (C) 2021 The Software Heritage developers | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# See the AUTHORS file at the top-level directory of this distribution | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# License: GNU General Public License version 3, or any later version | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# See top-level LICENSE file for more information | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from dataclasses import asdict, dataclass | |||||||||||||||||||||||||||||||||||||||||||||||||||||
import logging | |||||||||||||||||||||||||||||||||||||||||||||||||||||
import re | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from typing import Any, Dict, Iterator, List, Optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from urllib.parse import urljoin | |||||||||||||||||||||||||||||||||||||||||||||||||||||
import requests | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from tenacity.before_sleep import before_sleep_log | |||||||||||||||||||||||||||||||||||||||||||||||||||||
import xmltodict | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from swh.lister.utils import throttling_retry | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from swh.scheduler.interface import SchedulerInterface | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from swh.scheduler.model import ListedOrigin | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from .. import USER_AGENT | |||||||||||||||||||||||||||||||||||||||||||||||||||||
from ..pattern import CredentialsType, Lister | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger = logging.getLogger(__name__) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
@dataclass | |||||||||||||||||||||||||||||||||||||||||||||||||||||
vlorentz: Can you define this type better? A dataclass would be perfect. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsI didn't realise how much work it would involve at first, ahah. Sure it's better for documentation. Done. borisbaldassari: I didn't realise how much work it would involve at first, ahah. Sure it's better for… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
class RepoPage: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Result from a query to a gitlab project api page.""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
origin_type: str | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The type of origin: maven or scm. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url: str | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The URL to access the origin. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
doc: int | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The Doc ID in the Lucene index. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
project: Optional[str] = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""A short string representation of the project. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Used only for type 'scm' """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
time: Optional[int] = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The time of publication of the artefact, as int. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Used only for type 'maven' """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
gid: Optional[str] = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The Maven group ID coordinate. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Used only for type 'maven' """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
aid: Optional[str] = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The Maven artefact ID coordinate. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Used only for type 'maven' """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
version: Optional[str] = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""The Maven version coordinate. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Used only for type 'maven' """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
@dataclass | |||||||||||||||||||||||||||||||||||||||||||||||||||||
class MavenListerState: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actionsyes, keep it. You can drop the question in comment. ardumont: yes, keep it.
It's a required implementation detail for the instantiation of the lister. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""State of the MavenLister""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
last_seen_doc: int = -1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Last doc ID ingested during an incremental pass | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
class MavenLister(Lister[MavenListerState, RepoPage]): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""List origins from a Maven repository. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Maven Central provides artifacts for Java builds. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
It includes POM files and source archives, which we download to get | |||||||||||||||||||||||||||||||||||||||||||||||||||||
the source code of artifacts and links to their scm repository. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
This lister yields origins of types: git/svn/hg or whatever the Artifacts | |||||||||||||||||||||||||||||||||||||||||||||||||||||
use as repository type, plus maven types for the maven loader (tgz, jar).""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
LISTER_NAME = "maven" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actionsthey should not be in caps as they are not constants. vlorentz: they should not be in caps as they are not constants. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def __init__( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
scheduler: SchedulerInterface, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsIt's done with the super() call just below (within the mother class, granted with the netloc but that should be close enough). ardumont: It's done with the super() call just below (within the mother class, granted with the netloc… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url: str, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
index_url: str = None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
instance: Optional[str] = None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
credentials: CredentialsType = None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
incremental: bool = True, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Lister class for Maven repositories. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Args: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url: main URL of the Maven repository, i.e. url of the base index | |||||||||||||||||||||||||||||||||||||||||||||||||||||
used to fetch maven artifacts. For Maven central use | |||||||||||||||||||||||||||||||||||||||||||||||||||||
https://repo1.maven.org/maven2/ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
index_url: the URL to download the exported text indexes from. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Would typically be a local host running the export docker image. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
See README.md in this directory for more information. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
instance: Name of maven instance. Defaults to url's network location | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if unset. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
incremental: bool, defaults to True. Defines if incremental listing | |||||||||||||||||||||||||||||||||||||||||||||||||||||
is activated or not. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.base_url = url | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.index_url = index_url | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.incremental = incremental | |||||||||||||||||||||||||||||||||||||||||||||||||||||
super().__init__( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
scheduler=scheduler, credentials=credentials, url=url, instance=instance, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.session = requests.Session() | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.session.headers.update( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
{"Accept": "application/json", "User-Agent": USER_AGENT,} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def state_from_dict(self, d: Dict[str, Any]) -> MavenListerState: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
return MavenListerState(**d) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def state_to_dict(self, state: MavenListerState) -> Dict[str, Any]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
return asdict(state) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
@throttling_retry(before_sleep=before_sleep_log(logger, logging.WARNING)) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def page_request(self, url: str, params: Dict[str, Any]) -> requests.Response: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info("Fetching URL %s with params %s", url, params) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response = self.session.get(url, params=params) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if response.status_code != 200: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.warning( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"Unexpected HTTP status code %s on %s: %s", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response.status_code, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response.url, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response.content, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response.raise_for_status() | |||||||||||||||||||||||||||||||||||||||||||||||||||||
return response | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_pages(self) -> Iterator[RepoPage]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" Retrieve and parse exported maven indexes to | |||||||||||||||||||||||||||||||||||||||||||||||||||||
identify all pom files and src archives. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsCould you add a comment to give a high-level description of the format returned by this endpoint? (or link to such a description) vlorentz: Could you add a comment to give a high-level description of the format returned by this… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsThe format is described in the README. Adding a link to the documentation inline. borisbaldassari: The format is described in the README. Adding a link to the documentation inline. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsThe link is missing, can you please add it. ardumont: The link is missing, can you please add it. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Example of returned RepoPage's: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# [ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "origin_type": "maven", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "url": "https://maven.xwiki.org/..-5.4.2-sources.jar", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "time": 1626109619335, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "gid": "org.xwiki.platform", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "aid": "xwiki-platform-wikistream-events-xwiki", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "version": "5.4.2" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# }, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "origin_type": "scm", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "url": "scm:git:git://github.com/openengsb/openengsb-framework.git", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# "project": "openengsb-framework", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# }, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# ... | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actions
integrate this into the docstring ^ (dropping the comment) so ardumont: integrate this into the docstring ^ (dropping the comment) so
that makes it into the… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
borisbaldassariAuthorUnsubmitted Done Inline ActionsOf course, it's far better. Thanks. Also added the link to the README.md file in the current directory. borisbaldassari: Of course, it's far better. Thanks.
Also added the link to the README.md file in the current… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Download the main text index file. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info(f"Downloading text index from {self.index_url}.") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
assert self.index_url is not None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# This returns a (possibly huge) text index as described in the lister README. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response = requests.get(self.index_url, stream=True) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
response.raise_for_status() | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Prepare regexes to parse index exports. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Parse doc id. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Example line: "doc 13" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
re_doc = re.compile(r"^doc (?P<doc>\d+)$") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsWhat kind of errors do you get here? vlorentz: What kind of errors do you get here? | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsDecode errors, see https://forge.softwareheritage.org/D6133#inline-45664 Should I add this link to the py file as well for reference? borisbaldassari: Decode errors, see https://forge.softwareheritage.org/D6133#inline-45664
Should I add this… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actions
I agree with you on this. @douardda do you concur? vlorentz: > I have the feeling we should rather fail (throw an exception about decoding and end… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsPlease, add a fixme explaining we should raise instead and let's move on. ardumont: Please, add a fixme explaining we should raise instead and let's move on.
So we can actually… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsOk, thanks. Added a fixme with a link to this very place. borisbaldassari: Ok, thanks.
Added a fixme with a link to this very place. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Parse gid, aid, version, classifier, extension. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Example line: " value al.aldi|sprova4j|0.1.0|sources|jar" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
re_val = re.compile( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
r"^\s{4}value (?P<gid>[^|]+)\|(?P<aid>[^|]+)\|(?P<version>[^|]+)\|" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
+ r"(?P<classifier>[^|]+)\|(?P<ext>[^|]+)$" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Parse last modification time. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Example line: " value jar|1626109619335|14316|2|2|0|jar" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
re_time = re.compile( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
r"^\s{4}value ([^|]+)\|(?P<mtime>[^|]+)\|([^|]+)\|([^|]+)\|([^|]+)" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
+ r"\|([^|]+)\|([^|]+)$" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Read file line by line and process it | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content: Dict = {} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
jar_src: Dict = {} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
doc_id: int = 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url_src = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info("Parsing maven index.") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
deleted_items = 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
iterator = response.iter_lines(chunk_size=1024) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
for line_bytes in iterator: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Read the index text export and get URLs and SCMs. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
line = line_bytes.decode(errors="ignore") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
m_doc = re_doc.match(line) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if m_doc is not None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
doc_id = int(m_doc.group("doc")) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
m_val = re_val.match(line) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if m_val is not None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
(gid, aid, version, classifier, ext) = m_val.groups() | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ext = ext.strip() | |||||||||||||||||||||||||||||||||||||||||||||||||||||
path = "/".join(gid.split(".")) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if classifier == "NA" and ext.lower() == "pom": | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url_path = f"{path}/{aid}/{version}/{aid}-{version}.{ext}" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url_pom = urljoin(self.base_url, url_path,) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id] = {} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["type"] = "scm" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["url"] = url_pom | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actions
ardumont: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"- Storing scm {doc_id} {url_pom}") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actions
log instruction. ardumont: log instruction. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
elif ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
classifier.lower() == "sources" or ("src" in classifier) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) and ext.lower() in ("zip", "jar"): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url_path = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
f"{path}/{aid}/{version}/{aid}-{version}-{classifier}.{ext}" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url_src = urljoin(self.base_url, url_path) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
jar_src["gid"] = gid | |||||||||||||||||||||||||||||||||||||||||||||||||||||
jar_src["aid"] = aid | |||||||||||||||||||||||||||||||||||||||||||||||||||||
jar_src["version"] = version | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
m_time = re_time.match(line) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if m_time is not None and url_src is not None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
time = m_time.group("mtime") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
jar_src["time"] = int(time) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id] = {} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["type"] = "maven" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["url"] = url_src | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["time"] = jar_src["time"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["gid"] = jar_src["gid"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["aid"] = jar_src["aid"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
content[doc_id]["version"] = jar_src["version"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actions
vlorentz: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"- Storing maven {doc_id} {url_src}") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actionsdebug log instruction, please ardumont: debug log instruction, please | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url_src = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# If we meet name del's we need to decrement the doc counter | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# (in case number of artefacts added < number of deleted) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if line == " name del": | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsI have a hard time reading the for logic which as far as i can tell is about parsing the index. It would match what's done in the gnu lister (granted it's easier in the gnu lister but still). ardumont: I have a hard time reading the for logic which as far as i can tell is about parsing the index. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsYes, the parsing is a bit tough, but we don't have much choice, see my comment about the origin of this text file. I certainly could change where the yield occurs, but I did it following douardda's comment in https://forge.softwareheritage.org/D6133#inline-45665 I'm ready to change it, though. No personal strong opinion on that. borisbaldassari: Yes, the parsing is a bit tough, but we don't have much choice, see my comment about the origin… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsWhat if you built a temporary data structure? For example this: doc 0 field 0 name u type string value al.aldi|sprova4j|0.1.0|sources|jar field 1 name m type string value 1626111735737 would be turned into something like this: { "doc 0": { "field 0": { "name": "u", "type": "string", "value": "al.aldi|sprova4j|0.1.0|sources|jar", }, "field 1": { "name": "m", "type": "string", "value": "1626111735737", }, } } This would allow splitting the parser from the rest of the logic vlorentz: What if you built a temporary data structure? For example this:
```
doc 0
field 0
name u… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsYes, that'd be on the path of my initial suggestion about separating parsing and reading logics. Parsing/format conversion is extracted in some easier code to read and maintain Then the listing focus on the incremental listing (or not) logic without having to parse anything, just Note that the lister speed is not that important as long as it yields origins regularly (then it's updating Note that, for my part, this reading improvment can be taken care of in another diff if that's easier to handle... ardumont: Yes, that'd be on the path of my initial suggestion about separating parsing and reading logics. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsSo. Here is my humble proposal to split the parsing to make it more readable. Unfortunately the proposed data structure doesn't exactly work in practice because:
I tried however to keep the same spirit; please allow me to propose a slightly different intermediate version of the data structure: { 0: { 'type': 'maven', 'url': 'https://repo1.maven.org/maven2/al/aldi/sprova4j/0.1.0/sprova4j-0.1.0-sources.jar', 'time': 1626109619335, 'gid': 'al.aldi', 'aid': 'sprova4j', 'version': '0.1.0' }, 1: { 'type': 'scm', 'url': 'https://repo1.maven.org/maven2/al/aldi/sprova4j/0.1.0/sprova4j-0.1.0.pom' }, 2: { 'type': 'maven', 'url': 'https://repo1.maven.org/maven2/al/aldi/sprova4j/0.1.1/sprova4j-0.1.1-sources.jar', 'time': 1626111425534, 'gid': 'al.aldi', 'aid': 'sprova4j', 'version': '0.1.1' }, 3: { 'type': 'scm', 'url': 'https://repo1.maven.org/maven2/al/aldi/sprova4j/0.1.1/sprova4j-0.1.1.pom' } } The resulting parsing code is substantially simpler. Incremental and uniqueness stuff is moved to the second part. In the second part, since we yield jars and scms as we find them, we do not need any more the 2 counters for the incremental recovery: the last_seen_doc is enough. I did not have to change the tests, excepted for:
So it should be ok. Please let me know what you think. borisbaldassari: So. Here is my humble proposal to split the parsing to make it more readable.
Unfortunately… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
deleted_items += 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Data structure example for content: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 0: { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'type': 'maven', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'url': 'https://repo1.maven.org/.../0.1.0/sprova4j-0.1.0-sources.jar', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'time': 1626109619335, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'gid': 'al.aldi', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'aid': 'sprova4j', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'version': '0.1.0' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# }, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 1: { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'type': 'scm', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'url': 'https://repo1.maven.org/.../0.1.0/sprova4j-0.1.0.pom' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# }, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 2: { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'type': 'maven', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'url': 'https://repo1.maven.org/.../0.1.1/sprova4j-0.1.1-sources.jar', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'time': 1626111425534, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'gid': 'al.aldi', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'aid': 'sprova4j', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'version': '0.1.1' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# }, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 3: { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'type': 'scm', | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# 'url': 'https://repo1.maven.org/.../0.1.1/sprova4j-0.1.1.pom' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info(f"Found a grand total of {len(content)} artefacts.") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Now go through the content Dict, starting from the last registered doc, and: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# - for jars: yield them. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline ActionsKeep the same order you did below, first match against maven, else deal with scm: if page["type"] == "maven": ... else: # scm type That's easier to read if you always use the same order in conditionals ardumont: Keep the same order you did below, first match against maven, else deal with scm:
```
if page… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# - for scms: fetch pom files and scan them for scm info. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# If the scm has already been registered during this run: skip, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# else: yield it and remember we yielded it in visited_scms. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
visited_scms: List = [] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info("Start processing entries, yield jars, fetch poms and yield scms..") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
for doc_id in sorted(content): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.incremental | |||||||||||||||||||||||||||||||||||||||||||||||||||||
and self.state | |||||||||||||||||||||||||||||||||||||||||||||||||||||
and self.state.last_seen_doc | |||||||||||||||||||||||||||||||||||||||||||||||||||||
and self.state.last_seen_doc >= doc_id # See [*] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# and (self.state.last_seen_doc - deleted_items) >= doc_id # See [*] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actions
if unused, drop it? ardumont: if unused, drop it? | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# [*] When artefacts are deleted, they are removed from the full list | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# and added at the end of the listing as name=del entries. We need to | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# decrement the state counter to actually analyse new documents | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# occupying deleted documents ids. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||
artefact = content[doc_id] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if artefact["type"] == "scm": | |||||||||||||||||||||||||||||||||||||||||||||||||||||
text = self.page_request(artefact["url"], {}) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actions
ardumont: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
project = xmltodict.parse(text.content.decode()) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if "scm" in project["project"]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if "connection" in project["project"]["scm"]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
scm = project["project"]["scm"]["connection"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if scm not in visited_scms: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if "groupId" in project["project"]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
gid = project["project"]["groupId"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
elif ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"parent" in project["project"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
and "groupId" in project["project"]["parent"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
gid = project["project"]["parent"]["groupId"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||
aid = project["project"]["artifactId"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page = RepoPage(origin_type="scm", url=scm, doc=doc_id) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page.project = f"{gid}.{aid}" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"- Yielding scm {doc_id} {artefact['url']}") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actionslog ;) ardumont: log ;) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
borisbaldassariAuthorUnsubmitted Done Inline Actionshum.. yeah, ok, I got it, sorry.. ;-) borisbaldassari: hum.. yeah, ok, I got it, sorry.. ;-) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
yield page | |||||||||||||||||||||||||||||||||||||||||||||||||||||
visited_scms.append(scm) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.debug(f"No scm.connection in pom {artefact['url']}") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.debug(f"No scm in pom {artefact['url']}") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
except xmltodict.expat.ExpatError as error: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
f"Could not parse POM {artefact['url']} XML: {error}. Next." | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page = RepoPage(origin_type="maven", url=artefact["url"], doc=doc_id) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page.time = artefact["time"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page.gid = artefact["gid"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page.aid = artefact["aid"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
page.version = artefact["version"] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"- Yielding maven {doc_id} {artefact['url']}.") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
yield page | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_origins_from_page(self, page: RepoPage) -> Iterator[ListedOrigin]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Convert a page of Maven repositories into a list of ListedOrigins. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
assert self.lister_obj.id is not None | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if page.origin_type == "maven": | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Origin is considered a maven source archive: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
origin = ListedOrigin( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
lister_id=self.lister_obj.id, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
url=page.url, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
visit_type=page.origin_type, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline ActionsIs it possible to retrieve the last_update as well [1]? What's the timestamp present in the output of the indexer? [1] The last_update is actually used for scheduling purposes. ardumont: Is it possible to retrieve the `last_update` as well [1]?
What's the timestamp present in the… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
borisbaldassariAuthorUnsubmitted Done Inline ActionsThe timestamp is indeed the last update time of the artefact, see the Maven indexer documentation for this release. So I guess it can be used for that purpose, yes. I'll add the parameter (with some parsing) and submit the fix for all 3 following comments too. Thanks. borisbaldassari: The timestamp is indeed the last update time of the artefact, see [the Maven indexer… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
extra_loader_arguments={ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"artifacts": [ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"time": page.time, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"gid": page.gid, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"aid": page.aid, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"version": page.version, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actions
Optional[datetime] is Union[None, datetime] but in shorter form ;) Also to fix the current build failure [1], you need to ensure page.time is not None, hence the suggested assert. [1] 11:12:55 swh/lister/maven/lister.py:360: error: Argument 1 to "parse_date" has incompatible type "Optional[str]"; expected "str" ardumont: Optional[datetime] is Union[None, datetime] but in shorter form ;)
Also to fix the current… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
] | |||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
yield origin | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# If origin is a scm url: detect scm type and yield. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# Note that the official format is: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# scm:git:git://github.com/openengsb/openengsb-framework.git | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# but many, many projects directly put the repo url, so we have to | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Done Inline Actionscan't we make it the same value as last_update in string so as to avoid to have to do the same computation as line 358 in the loader? ardumont: can't we make it the same value as last_update in string so as to avoid to have to do the same… | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# detect the content to match it properly. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
m_scm = re.match(r"^scm:(?P<type>[^:]+):(?P<url>.*)$", page.url) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if m_scm is not None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
scm_type = m_scm.group("type") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
scm_url = m_scm.group("url") | |||||||||||||||||||||||||||||||||||||||||||||||||||||
origin = ListedOrigin( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
lister_id=self.lister_obj.id, url=scm_url, visit_type=scm_type, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actionssame question about last_update (see previous point) ardumont: same question about `last_update` (see previous point) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
yield origin | |||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if page.url.endswith(".git"): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
origin = ListedOrigin( | |||||||||||||||||||||||||||||||||||||||||||||||||||||
lister_id=self.lister_obj.id, url=page.url, visit_type="git", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
ardumontUnsubmitted Done Inline Actionssame question about last_update (see previous point) ardumont: same question about last_update (see previous point) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
yield origin | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def commit_page(self, page: RepoPage) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Update currently stored state using the latest listed doc. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Note: this is a noop for full listing mode | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if self.incremental and self.state: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# We need to differentiate the two state counters according | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# to the type of origin. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if page.doc > self.state.last_seen_doc: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.state.last_seen_doc = page.doc | |||||||||||||||||||||||||||||||||||||||||||||||||||||
def finalize(self) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Finalize the lister state, set update if any progress has been made. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Note: this is a noop for full listing mode | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if self.incremental and self.state: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
last_seen_doc = self.state.last_seen_doc | |||||||||||||||||||||||||||||||||||||||||||||||||||||
scheduler_state = self.get_state_from_scheduler() | |||||||||||||||||||||||||||||||||||||||||||||||||||||
if last_seen_doc and (scheduler_state.last_seen_doc < last_seen_doc): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
self.updated = True |
Can you define this type better? A dataclass would be perfect.