-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Homepage project urls #5
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,50 @@ things, with as minimal dependencies as possible: | |
1. Support just enough metadata to be able to look up deps. | ||
2. Do "the thing that pip does" when deciding what dist-info dir to look at. | ||
|
||
# Usage | ||
|
||
Example snippet to show how to get the metadata from a wheel. | ||
|
||
```python | ||
from zipfile import ZipFile | ||
from metadata_please import basic_metadata_from_wheel | ||
|
||
zf = ZipFile('somepkg.whl') | ||
print(basic_metadata_from_wheel(zf, "somepkg")) | ||
``` | ||
|
||
### Output | ||
|
||
``` | ||
BasicMetadata( | ||
reqs=[ | ||
'build', | ||
'setuptools', | ||
'pip', | ||
'imperfect<1', | ||
'tomlkit<1', | ||
'click~=8.0', | ||
'GitPython~=3.1.18', | ||
'metatron==0.60.0', | ||
'pkginfo~=1.9', | ||
'pyyaml~=6.0', | ||
'runez~=5.2', | ||
'pathspec<1', | ||
'virtualenv<20.21', | ||
'tox~=3.28', | ||
'requests~=2.27', | ||
'urllib3~=1.26' | ||
], | ||
provides_extra=frozenset(), | ||
name='pynt', | ||
requires_python='>=3.6', | ||
url='https://stash.corp.netflix.com/projects/NFPY/repos/pynt/browse', | ||
project_urls={} | ||
) | ||
``` | ||
|
||
The metadata can be extracted from a `wheel`, `sdist` (zip or tarball). Check [`__init__.py`](metadata_please/__init__.py) file for all available functions. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also "most straightforward source checkouts" |
||
|
||
# Version Compat | ||
|
||
Usage of this library should work back to 3.7, but development (and mypy | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,10 @@ | |
|
||
Notably, does not read nontrivial setup.py or attempt to emulate anything that can't be read staticly. | ||
""" | ||
|
||
import ast | ||
import re | ||
from dataclasses import asdict | ||
from pathlib import Path | ||
|
||
try: | ||
|
@@ -81,6 +83,54 @@ def from_pep621_checkout(path: Path) -> bytes: | |
for i in v: | ||
buf.append("Requires-Dist: " + merge_extra_marker(extra_name, i) + "\n") | ||
|
||
name = doc.get("project", {}).get("name") | ||
if name: | ||
buf.append(f"Name: {name}\n") | ||
|
||
# Version | ||
version = doc.get("project", {}).get("version") | ||
if version: | ||
buf.append(f"Version: {version}\n") | ||
|
||
# Requires-Python | ||
requires_python = doc.get("project", {}).get("requires-python") | ||
if requires_python: | ||
buf.append(f"Requires-Python: {requires_python}\n") | ||
|
||
# Project-URL | ||
urls = doc.get("project", {}).get("urls") | ||
if urls: | ||
for k, v in urls.items(): | ||
buf.append(f"Project-URL: {k}={v}\n") | ||
|
||
# Author | ||
authors = doc.get("project", {}).get("authors") | ||
if authors: | ||
for author in authors: | ||
try: | ||
buf.append(f"Author: {author.get('name')}\n") | ||
except AttributeError: | ||
pass | ||
try: | ||
buf.append(f"Author-Email: {author.get('email')}\n") | ||
except AttributeError: | ||
pass | ||
|
||
# Summary | ||
summary = doc.get("project", {}).get("description") | ||
if summary: | ||
buf.append(f"Summary: {summary}\n") | ||
|
||
# Description | ||
description = doc.get("project", {}).get("readme") | ||
if description: | ||
buf.append(f"Description: {description}\n") | ||
|
||
# Keywords | ||
keywords = doc.get("project", {}).get("keywords") | ||
if keywords: | ||
buf.append(f"Keywords: {keywords}\n") | ||
|
||
return "".join(buf).encode("utf-8") | ||
|
||
|
||
|
@@ -193,6 +243,45 @@ def from_poetry_checkout(path: Path) -> bytes: | |
f"Requires-Dist: {vi}{constraints}{merge_extra_marker(k, markers)}" | ||
) | ||
|
||
name = doc.get("tool", {}).get("poetry", {}).get("name") | ||
if name: | ||
buf.append(f"Name: {name}\n") | ||
|
||
# Version | ||
version = doc.get("tool", {}).get("poetry", {}).get("version") | ||
if version: | ||
buf.append(f"Version: {version}\n") | ||
|
||
# Requires-Python | ||
requires_python = doc.get("tool", {}).get("poetry", {}).get("requires-python") | ||
if requires_python: | ||
buf.append(f"Requires-Python: {requires_python}\n") | ||
|
||
# Project-URL | ||
url = doc.get("tool", {}).get("poetry", {}).get("homepage") | ||
if url: | ||
buf.append(f"Home-Page: {url}\n") | ||
|
||
# Author | ||
authors = doc.get("tool", {}).get("poetry", {}).get("authors") | ||
if authors: | ||
buf.append(f"Author: {authors}\n") | ||
|
||
# Summary | ||
summary = doc.get("tool", {}).get("poetry", {}).get("description") | ||
if summary: | ||
buf.append(f"Summary: {summary}\n") | ||
|
||
# Description | ||
description = doc.get("tool", {}).get("poetry", {}).get("readme") | ||
if description: | ||
buf.append(f"Description: {description}\n") | ||
|
||
# Keywords | ||
keywords = doc.get("tool", {}).get("poetry", {}).get("keywords") | ||
if keywords: | ||
buf.append(f"Keywords: {keywords}\n") | ||
|
||
return "".join(buf).encode("utf-8") | ||
|
||
|
||
|
@@ -206,6 +295,55 @@ def from_setup_cfg_checkout(path: Path) -> bytes: | |
rc.read_string(data) | ||
|
||
buf: list[str] = [] | ||
try: | ||
buf.append(f"Name: {rc.get('metadata', 'name')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Requires-Python | ||
try: | ||
buf.append(f"Requires-Python: {rc.get('options', 'python_requires')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Home-Page | ||
try: | ||
buf.append(f"Home-Page: {rc.get('metadata', 'url')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Author | ||
try: | ||
buf.append(f"Author: {rc.get('metadata', 'author')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Author-Email | ||
try: | ||
buf.append(f"Author-Email: {rc.get('metadata', 'author_email')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Summary | ||
try: | ||
buf.append(f"Summary: {rc.get('metadata', 'description')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Description | ||
try: | ||
buf.append(f"Description: {rc.get('metadata', 'long_description')}\n") | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
# Description-Content-Type | ||
try: | ||
buf.append( | ||
f"Description-Content-Type: {rc.get('metadata', 'long_description_content_type')}\n" | ||
) | ||
except (NoOptionError, NoSectionError): | ||
pass | ||
|
||
try: | ||
for dep in rc.get("options", "install_requires").splitlines(): | ||
dep = dep.strip() | ||
|
@@ -229,6 +367,8 @@ def from_setup_cfg_checkout(path: Path) -> bytes: | |
"Requires-Dist: " + merge_extra_marker(extra_name, i) + "\n" | ||
) | ||
|
||
# TODO name requires_python url project_urls | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO can go |
||
|
||
return "".join(buf).encode("utf-8") | ||
|
||
|
||
|
@@ -252,6 +392,7 @@ def from_setup_py_checkout(path: Path) -> bytes: | |
raise ValueError("Complex setup call can't extract reqs") | ||
for dep in r: | ||
buf.append(f"Requires-Dist: {dep}\n") | ||
|
||
er = v.setup_call_args.get("extras_require") | ||
if er: | ||
if er is UNKNOWN: | ||
|
@@ -262,6 +403,31 @@ def from_setup_py_checkout(path: Path) -> bytes: | |
for i in deps: | ||
buf.append("Requires-Dist: " + merge_extra_marker(extra_name, i) + "\n") | ||
|
||
n = v.setup_call_args.get("name") | ||
if n: | ||
if n is UNKNOWN: | ||
raise ValueError("Complex setup call can't extract name") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think long-term these shouldn't raise; the snippets above do that because finding deps was the original use of this. It doesn't matter for your artifact-validation use case though. |
||
buf.append(f"Name: {n}\n") | ||
|
||
n = v.setup_call_args.get("python_requires") | ||
if n: | ||
if n is UNKNOWN: | ||
raise ValueError("Complex setup call can't extract python_requires") | ||
buf.append(f"Requires-Python: {n}\n") | ||
|
||
n = v.setup_call_args.get("url") | ||
if n: | ||
if n is UNKNOWN: | ||
raise ValueError("Complex setup call can't extract url") | ||
buf.append(f"Home-Page: {n}\n") | ||
|
||
n = v.setup_call_args.get("project_urls") | ||
if n: | ||
if n is UNKNOWN: | ||
raise ValueError("Complex setup call can't extract project_urls") | ||
for k, v in n.items(): | ||
buf.append(f"Project-URL: {k}={v}\n") | ||
|
||
return "".join(buf).encode("utf-8") | ||
|
||
|
||
|
@@ -270,6 +436,11 @@ def basic_metadata_from_source_checkout(path: Path) -> BasicMetadata: | |
|
||
|
||
if __name__ == "__main__": # pragma: no cover | ||
import json | ||
import sys | ||
|
||
print(basic_metadata_from_source_checkout(Path(sys.argv[1]))) | ||
md = basic_metadata_from_source_checkout(Path(sys.argv[1])) | ||
if md.reqs or md.name: | ||
print(json.dumps(asdict(md), default=list)) | ||
else: | ||
sys.exit(1) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,16 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Sequence | ||
from typing import Mapping, Sequence | ||
|
||
|
||
class MemoryZipFile: | ||
def __init__(self, names: Sequence[str], read_value: bytes = b"foo") -> None: | ||
self.names = names | ||
self.read_value = read_value | ||
def __init__(self, mock_files: Mapping[str, bytes] = {}) -> None: | ||
self.mock_files = mock_files | ||
self.files_read: list[str] = [] | ||
|
||
def namelist(self) -> Sequence[str]: | ||
return self.names[:] | ||
return list(self.mock_files.keys()) | ||
|
||
def read(self, filename: str) -> bytes: | ||
self.files_read.append(filename) | ||
return self.read_value | ||
return self.mock_files[filename] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you happy with this example? Maybe use requests or something more recognizable instead?