blob: 0d3a14ab54a8dd34bbb8cda291172a29af947ec4 [file] [log] [blame]
"""A simple function to find the METADATA file and parse it"""
_NAME = "Name: "
_PROVIDES_EXTRA = "Provides-Extra: "
_REQUIRES_DIST = "Requires-Dist: "
_VERSION = "Version: "
_CONSOLE_SCRIPTS = "[console_scripts]"
def whl_metadata(*, install_dir, read_fn, logger):
"""Find and parse the METADATA file in the extracted whl contents dir.
Args:
install_dir: {type}`path` location where the wheel has been extracted.
read_fn: the function used to read files.
logger: the function used to log failures.
Returns:
A struct with parsed values:
* `name`: {type}`str` the name of the wheel.
* `version`: {type}`str` the version of the wheel.
* `requires_dist`: {type}`list[str]` the list of requirements.
* `provides_extra`: {type}`list[str]` the list of extras that this package
provides.
"""
metadata_file = find_whl_metadata(install_dir = install_dir, logger = logger)
contents = read_fn(metadata_file)
entry_points_file = metadata_file.dirname.get_child("entry_points.txt")
if entry_points_file.exists:
entry_points_contents = read_fn(entry_points_file)
else:
entry_points_contents = ""
result = parse_whl_metadata(contents, entry_points_contents)
if not (result.name and result.version):
logger.fail("Failed to parse the wheel METADATA file:\n{}\n{}\n{}".format(
80 * "=",
contents.rstrip("\n"),
80 * "=",
))
return None
return result
def parse_whl_metadata(contents, entry_points_contents = ""):
"""Parse .whl METADATA file
Args:
contents: {type}`str` the contents of the file.
entry_points_contents: {type}`str` the contents of the `entry_points.txt` file if it exists.
Returns:
A struct with parsed values:
* `name`: {type}`str` the name of the wheel.
* `version`: {type}`str` the version of the wheel.
* `requires_dist`: {type}`list[str]` the list of requirements.
* `provides_extra`: {type}`list[str]` the list of extras that this package
provides.
* `entry_points`: {type}`list[struct]` the list of
entry_point metadata.
"""
parsed = {
"name": "",
"provides_extra": [],
"requires_dist": [],
"version": "",
}
for line in contents.strip().split("\n"):
if not line:
# Stop parsing on first empty line, which marks the end of the
# headers containing the metadata.
break
if line.startswith(_NAME):
_, _, value = line.partition(_NAME)
parsed["name"] = value.strip()
elif line.startswith(_VERSION):
_, _, value = line.partition(_VERSION)
parsed["version"] = value.strip()
elif line.startswith(_REQUIRES_DIST):
_, _, value = line.partition(_REQUIRES_DIST)
parsed["requires_dist"].append(value.strip(" "))
elif line.startswith(_PROVIDES_EXTRA):
_, _, value = line.partition(_PROVIDES_EXTRA)
parsed["provides_extra"].append(value.strip(" "))
return struct(
name = parsed["name"],
provides_extra = parsed["provides_extra"],
requires_dist = parsed["requires_dist"],
version = parsed["version"],
entry_points = _parse_entry_points(entry_points_contents),
)
def find_whl_metadata(*, install_dir, logger):
"""Find the whl METADATA file in the install_dir.
Args:
install_dir: {type}`path` location where the wheel has been extracted.
logger: the function used to log failures.
Returns:
{type}`path` The path to the METADATA file.
"""
dist_info = None
for maybe_dist_info in install_dir.readdir():
# first find the ".dist-info" folder
if not (maybe_dist_info.is_dir and maybe_dist_info.basename.endswith(".dist-info")):
continue
dist_info = maybe_dist_info
metadata_file = dist_info.get_child("METADATA")
if metadata_file.exists:
return metadata_file
break
if dist_info:
logger.fail("The METADATA file for the wheel could not be found in '{}/{}'".format(install_dir.basename, dist_info.basename))
else:
logger.fail("The '*.dist-info' directory could not be found in '{}'".format(install_dir.basename))
return None
def _parse_entry_points(contents):
"""parse the entry_points.txt file.
Args:
contents: {type}`str` The contents of the file
Returns:
A list of console_script entry point metadata.
"""
start = False
ret = []
for line in contents.split("\n"):
line = line.rstrip()
if line == _CONSOLE_SCRIPTS:
start = True
continue
if not start:
continue
if start and line.startswith("["):
break
line, _, _comment = line.partition("#")
line = line.strip()
if not line:
continue
name, _, tail = line.partition("=")
# importable.module:object.attr
py_import, _, extras = tail.strip().partition(" ")
module, _, attribute = py_import.partition(":")
ret.append(struct(
name = name.strip(),
module = module.strip(),
attribute = attribute.strip(),
extras = extras.replace(" ", ""),
))
return ret