depsdev 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
depsdev/__main__.py CHANGED
@@ -1,6 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import sys
5
+
6
+ from depsdev.cli.purl import get_extractor
7
+ from depsdev.cli.vuln import main_helper
8
+
9
+ try:
10
+ import typer
11
+ except ImportError:
12
+ msg = (
13
+ "The 'cli' optional dependency is not installed. "
14
+ "Please install it with 'pip install depsdev[cli]'."
15
+ )
16
+ print(msg, file=sys.stderr)
17
+ raise SystemExit(1) from None
18
+
4
19
  import os
5
20
  from textwrap import dedent
6
21
  from typing import TYPE_CHECKING
@@ -14,7 +29,6 @@ if TYPE_CHECKING:
14
29
  P = ParamSpec("P")
15
30
  R = TypeVar("R")
16
31
 
17
-
18
32
  logging.basicConfig(
19
33
  level=logging.ERROR,
20
34
  format="[%(asctime)s] [%(levelname)-7s] [%(name)s] %(message)s",
@@ -38,7 +52,9 @@ def to_sync() -> Callable[[Callable[P, R]], Callable[P, R]]:
38
52
 
39
53
  from rich import print_json
40
54
 
41
- print_json(data=asyncio.run(func(*args, **kwargs))) # type: ignore[arg-type]
55
+ result: object = asyncio.run(func(*args, **kwargs)) # type: ignore[arg-type]
56
+ if result is not None:
57
+ print_json(data=result)
42
58
  except Exception:
43
59
  logger.exception("An error occurred while executing the command.")
44
60
  raise SystemExit(1) from None
@@ -50,25 +66,14 @@ def to_sync() -> Callable[[Callable[P, R]], Callable[P, R]]:
50
66
  return decorator
51
67
 
52
68
 
53
- def main() -> None:
69
+ def create_app() -> typer.Typer:
54
70
  """
55
71
  Main entry point for the CLI.
56
72
  """
57
-
58
- try:
59
- import typer
60
- except ImportError:
61
- msg = (
62
- "The 'cli' optional dependency is not installed. "
63
- "Please install it with 'pip install depsdev[cli]'."
64
- )
65
- logger.error(msg) # noqa: TRY400
66
- raise SystemExit(1) from None
67
-
68
73
  alpha = os.environ.get("DEPSDEV_V3_ALPHA", "false").lower() in ("true", "1", "yes")
69
74
 
70
75
  app = typer.Typer(
71
- name="depsdev",
76
+ name="api",
72
77
  no_args_is_help=True,
73
78
  rich_markup_mode="rich",
74
79
  help=dedent(
@@ -125,7 +130,50 @@ def main() -> None:
125
130
  app.command(rich_help_panel="v3alpha")(to_sync()(client_v3_alpha.purl_lookup_batch))
126
131
  app.command(rich_help_panel="v3alpha")(to_sync()(client_v3_alpha.query_container_images))
127
132
 
128
- return app()
133
+ return app
134
+
135
+
136
+ main = typer.Typer(
137
+ name="depsdev",
138
+ no_args_is_help=True,
139
+ rich_markup_mode="rich",
140
+ )
141
+
142
+ main.add_typer(
143
+ create_app(),
144
+ name="api",
145
+ )
146
+
147
+
148
+ @main.command(name="purl", rich_help_panel="Utils")
149
+ def purl(filename: str) -> None:
150
+ """
151
+ Extract package URLs from various formats.
152
+ """
153
+ extractor = get_extractor(filename)
154
+
155
+ for purl in extractor.extract(filename):
156
+ print(purl)
157
+
158
+
159
+ main.command(name="vuln", rich_help_panel="Utils")(to_sync()(main_helper))
160
+
161
+
162
+ @main.command()
163
+ @to_sync()
164
+ async def report(filename: str) -> None:
165
+ """
166
+ Show vulnerabilities for packages in a file.
167
+
168
+ Example usage:
169
+ depsdev report requirements.txt
170
+ depsdev report pom.xml
171
+ depsdev report Pipfile.lock
172
+ """
173
+ filename = os.path.abspath(filename)
174
+ extractor = get_extractor(filename)
175
+ packages = extractor.extract(filename)
176
+ await main_helper([x.to_string() for x in packages])
129
177
 
130
178
 
131
179
  if __name__ == "__main__":
depsdev/_version.py CHANGED
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '0.0.3'
21
- __version_tuple__ = version_tuple = (0, 0, 3)
31
+ __version__ = version = '0.0.5'
32
+ __version_tuple__ = version_tuple = (0, 0, 5)
33
+
34
+ __commit_id__ = commit_id = None
depsdev/base.py ADDED
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+ from dataclasses import field
6
+ from typing import TYPE_CHECKING
7
+ from urllib.parse import quote
8
+
9
+ import httpx
10
+
11
+ if TYPE_CHECKING:
12
+ from httpx._types import QueryParamTypes
13
+ from typing_extensions import Literal
14
+
15
+ from depsdev.v3 import Incomplete
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class BaseClient:
22
+ base_url: str
23
+ timeout: float = 5.0
24
+ client: httpx.AsyncClient = field(init=False, repr=False)
25
+
26
+ def __post_init__(self) -> None:
27
+ self.client = httpx.AsyncClient(base_url=self.base_url, timeout=self.timeout)
28
+
29
+ async def _requests(
30
+ self,
31
+ url: str = "",
32
+ method: Literal["GET", "POST"] = "GET",
33
+ params: QueryParamTypes | None = None,
34
+ json: object | None = None,
35
+ ) -> Incomplete:
36
+ logger.info(locals())
37
+ response = await self.client.request(method=method, url=url, params=params, json=json)
38
+ if not response.is_success:
39
+ logger.error(
40
+ "Request failed with status code %s: %s", response.status_code, response.text
41
+ )
42
+ response.raise_for_status()
43
+ return response.json()
44
+
45
+ @staticmethod
46
+ def url_escape(string: str) -> str:
47
+ return quote(string, safe="")
File without changes
depsdev/cli/purl.py ADDED
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ import json
5
+ import logging
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from typing import TYPE_CHECKING
10
+
11
+ from packageurl import PackageURL
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Iterable
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class MavenExtractor:
20
+ @classmethod
21
+ def extract(cls, filename: str) -> Iterable[PackageURL]:
22
+ yield from (cls.parse_single_line(x) for x in cls._clean(cls._get_source(filename)))
23
+
24
+ @staticmethod
25
+ def _get_source(filename: str) -> Iterable[str]:
26
+ """
27
+ Read lines from stdin or a file.
28
+ """
29
+ if not filename.endswith("pom.xml"):
30
+ logger.error("Invalid POM file: %s. It should end with 'pom.xml'.", filename)
31
+ raise SystemExit(1)
32
+ result = subprocess.run(
33
+ ["mvn", "dependency:tree"], # noqa: S607
34
+ check=False,
35
+ capture_output=True,
36
+ text=True,
37
+ cwd=os.path.dirname(os.path.abspath(filename)),
38
+ )
39
+ if result.returncode != 0:
40
+ print(result.stderr, file=sys.stderr)
41
+ raise SystemExit(1)
42
+ yield from result.stdout.splitlines()
43
+
44
+ @staticmethod
45
+ def parse_single_line(line: str) -> PackageURL:
46
+ """
47
+ Parse a single line of Maven dependency output and return a PackageURL object.
48
+ """
49
+ package, *rest = line.split()
50
+ _is_optional = bool(rest)
51
+ group, artifact, _type, version, *_classifier = package.split(":")
52
+ return PackageURL(
53
+ type="maven",
54
+ namespace=group,
55
+ name=artifact,
56
+ version=version,
57
+ qualifiers=None,
58
+ subpath=None,
59
+ )
60
+
61
+ @staticmethod
62
+ def _clean(lines: Iterable[str]) -> Iterable[str]:
63
+ stage1 = (x.rstrip() for x in lines if x.strip())
64
+ stage2 = itertools.dropwhile(lambda x: not x.startswith("[INFO] --- "), stage1)
65
+ stage3 = itertools.takewhile(
66
+ lambda x: not x.startswith(
67
+ "[INFO] ------------------------------------------------------------------------"
68
+ ),
69
+ stage2,
70
+ )
71
+ stage4 = itertools.islice(stage3, 1, None) # Skip the first line
72
+ stage5 = (x[7:] for x in stage4)
73
+ yield from (x.split("- ", maxsplit=1)[-1] for x in stage5)
74
+
75
+
76
+ class PipfileLockExtractor:
77
+ @classmethod
78
+ def extract(cls, filename: str) -> Iterable[PackageURL]:
79
+ """
80
+ Extracts package URLs from a Pipfile.lock.
81
+ """
82
+ if not filename.endswith("Pipfile.lock"):
83
+ logger.error("Invalid Pipfile.lock: %s. It should end with 'Pipfile.lock'.", filename)
84
+ raise SystemExit(1)
85
+ with open(filename) as f:
86
+ data = json.load(f)
87
+ for package_name, package_info in data.get("default", {}).items():
88
+ version: str | None = package_info.get("version")
89
+ if version:
90
+ yield PackageURL(
91
+ type="pypi",
92
+ namespace=None,
93
+ name=package_name,
94
+ version=version[2:],
95
+ qualifiers=None,
96
+ subpath=None,
97
+ )
98
+ else:
99
+ logger.warning("Package %s has no version specified.", package_name)
100
+
101
+
102
+ class RequirementsExtractor:
103
+ @classmethod
104
+ def extract(cls, filename: str) -> Iterable[PackageURL]:
105
+ """
106
+ Extracts package URLs from a requirements.txt file.
107
+ """
108
+ if not filename.endswith("requirements.txt"):
109
+ logger.error(
110
+ "Invalid requirements file: %s. It should end with 'requirements.txt'.", filename
111
+ )
112
+ raise SystemExit(1)
113
+ with open(filename) as f:
114
+ for line in f:
115
+ _line = line.strip()
116
+ if not _line or _line.startswith(("#", "-r ", "-i ")):
117
+ continue
118
+ parts = _line.split(";")[0].split("==")
119
+ if len(parts) == 2: # noqa: PLR2004
120
+ name, version = parts
121
+ version = version.strip(" \\")
122
+ yield PackageURL(
123
+ type="pypi",
124
+ namespace=None,
125
+ name=name,
126
+ version=version,
127
+ qualifiers=None,
128
+ subpath=None,
129
+ )
130
+
131
+
132
+ def get_extractor(filename: str) -> MavenExtractor | PipfileLockExtractor | RequirementsExtractor:
133
+ """
134
+ Returns the appropriate extractor based on the file extension.
135
+ """
136
+ if filename.endswith("pom.xml"):
137
+ return MavenExtractor()
138
+ if filename.endswith("Pipfile.lock"):
139
+ return PipfileLockExtractor()
140
+ if filename.endswith("requirements.txt"):
141
+ return RequirementsExtractor()
142
+ logger.error("Unsupported file format: %s", filename)
143
+ raise SystemExit(1)
depsdev/cli/vuln.py ADDED
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import itertools
5
+ import logging
6
+ from typing import TYPE_CHECKING
7
+
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from depsdev.osv import OSVClientV1
12
+
13
+ if TYPE_CHECKING:
14
+ from depsdev.osv import OSVVulnerability
15
+ from depsdev.osv import V1Query
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def get_version_fix(vuln: OSVVulnerability) -> str | None:
21
+ for affected in vuln.get("affected", []):
22
+ for _range in affected.get("ranges", []):
23
+ for event in _range.get("events", []):
24
+ if "fixed" in event:
25
+ return event["fixed"]
26
+ return None
27
+
28
+
29
+ async def get_vulns(purls: list[str], osv_client: OSVClientV1) -> dict[str, list[OSVVulnerability]]:
30
+ queries: list[V1Query] = [
31
+ {
32
+ "package": {"purl": purl},
33
+ }
34
+ for purl in purls
35
+ ]
36
+ result = await osv_client.querybatch({"queries": queries})
37
+ r = {k: [x["id"] for x in v["vulns"]] for k, v in zip(purls, result["results"]) if v}
38
+ all_result = await asyncio.gather(
39
+ *[osv_client.get_vuln(vuln_id) for vuln_id in itertools.chain.from_iterable(r.values())]
40
+ )
41
+ look_up = {vuln["id"]: vuln for vuln in all_result}
42
+ return {purl: [look_up[vuln_id] for vuln_id in vuln_ids] for purl, vuln_ids in r.items()}
43
+
44
+
45
+ async def main_helper(packages: list[str]) -> int:
46
+ """Main function to analyze packages for vulnerabilities."""
47
+
48
+ console = Console()
49
+
50
+ console.print(f"Analysing {len(packages)} packages...")
51
+
52
+ osv_client = OSVClientV1()
53
+
54
+ results = await get_vulns(packages, osv_client)
55
+ console.print(f"Found {len(results)} packages with advisories.")
56
+
57
+ for purl, advisories in results.items():
58
+ table = Table(title=purl)
59
+
60
+ table.add_column("Id")
61
+ table.add_column("Summary", style="cyan", no_wrap=True)
62
+ table.add_column("Fixed", style="magenta")
63
+
64
+ for vuln in advisories:
65
+ table.add_row(
66
+ f"[link=https://github.com/advisories/{vuln['id']}]{vuln['id']}[/link]",
67
+ vuln.get("summary"),
68
+ get_version_fix(vuln) or "unknown",
69
+ )
70
+ console.print(table)
71
+ return 0
depsdev/osv.py ADDED
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from depsdev.base import BaseClient
8
+
9
+ if TYPE_CHECKING:
10
+ from typing import Any
11
+ from typing import Literal
12
+
13
+ from typing_extensions import NotRequired
14
+ from typing_extensions import TypedDict
15
+
16
+ class OSVEvent(TypedDict):
17
+ introduced: NotRequired[str]
18
+ fixed: NotRequired[str]
19
+ limit: NotRequired[str]
20
+ lastAffected: NotRequired[str]
21
+
22
+ class OSVRange(TypedDict):
23
+ type: NotRequired[
24
+ Literal[
25
+ "UNSPECIFIED",
26
+ "GIT",
27
+ "SEMVER",
28
+ "ECOSYSTEM",
29
+ ]
30
+ ]
31
+ repo: NotRequired[str]
32
+ events: NotRequired[list[OSVEvent]]
33
+
34
+ class OSVPackage(TypedDict):
35
+ name: NotRequired[str]
36
+ ecosystem: NotRequired[str]
37
+ purl: NotRequired[str]
38
+
39
+ class OSVCredit(TypedDict):
40
+ name: NotRequired[str]
41
+ contact: NotRequired[list[str]]
42
+ type: NotRequired[
43
+ Literal[
44
+ "UNSPECIFIED",
45
+ "OTHER",
46
+ "FINDER",
47
+ "REPORTER",
48
+ "ANALYST",
49
+ "COORDINATOR",
50
+ "REMEDIATION_DEVELOPER",
51
+ "REMEDIATION_REVIEWER",
52
+ "REMEDIATION_VERIFIER",
53
+ "TOOL",
54
+ "SPONSOR",
55
+ ]
56
+ ]
57
+
58
+ class OSVSeverity(TypedDict):
59
+ type: NotRequired[
60
+ Literal[
61
+ "UNSPECIFIED",
62
+ "CVSS_V4",
63
+ "CVSS_V3",
64
+ "CVSS_V2",
65
+ ]
66
+ ]
67
+ score: NotRequired[str]
68
+
69
+ class OSVReference(TypedDict):
70
+ type: NotRequired[
71
+ Literal[
72
+ "NONE",
73
+ "WEB",
74
+ "ADVISORY",
75
+ "REPORT",
76
+ "FIX",
77
+ "PACKAGE",
78
+ "ARTICLE",
79
+ "EVIDENCE",
80
+ ]
81
+ ]
82
+ url: NotRequired[str]
83
+
84
+ class OSVAffected(TypedDict):
85
+ package: NotRequired[OSVPackage]
86
+ ranges: NotRequired[list[OSVRange]]
87
+ versions: NotRequired[list[str]]
88
+ ecosystemSpecific: NotRequired[dict[str, Any]]
89
+ databaseSpecific: NotRequired[dict[str, Any]]
90
+ severity: NotRequired[list[OSVSeverity]]
91
+
92
+ class OSVVulnerability(TypedDict):
93
+ id: str
94
+ summary: str
95
+ schemaVersion: NotRequired[str]
96
+ published: NotRequired[str]
97
+ modified: NotRequired[str]
98
+ withdrawn: NotRequired[str]
99
+ aliases: NotRequired[list[str]]
100
+ related: NotRequired[list[str]]
101
+ details: NotRequired[str]
102
+ affected: NotRequired[list[OSVAffected]]
103
+ references: NotRequired[list[OSVReference]]
104
+ databaseSpecific: NotRequired[dict[str, Any]]
105
+ severity: NotRequired[list[OSVSeverity]]
106
+ credits: NotRequired[list[OSVCredit]]
107
+
108
+ class V1VulnerabilityList(TypedDict):
109
+ vulns: NotRequired[list[OSVVulnerability]]
110
+ nextPageToken: NotRequired[str]
111
+
112
+ class V1Query(TypedDict):
113
+ commit: NotRequired[str]
114
+ version: NotRequired[str]
115
+ package: NotRequired[OSVPackage]
116
+ page_token: NotRequired[str]
117
+
118
+ #########################################
119
+
120
+ class V1Batchquery(TypedDict):
121
+ queries: list[V1Query]
122
+
123
+ class QueryBatchResult(TypedDict):
124
+ vulns: list[dict[Literal["id", "modified"], str]]
125
+ next_page_token: NotRequired[str]
126
+
127
+ class QueryBatchResponse(TypedDict):
128
+ results: list[QueryBatchResult]
129
+
130
+
131
+ logger = logging.getLogger(__name__)
132
+
133
+
134
+ @dataclass
135
+ class OSVClientV1(BaseClient):
136
+ base_url: str = "https://api.osv.dev"
137
+
138
+ async def query(self, query: V1Query) -> V1VulnerabilityList:
139
+ """
140
+ Lists vulnerabilities for given package and version. May also be queried by commit hash.
141
+
142
+ POST /v1/query
143
+ """
144
+ return await self._requests(method="POST", url="/v1/query", json=query) # type:ignore[return-value]
145
+
146
+ async def querybatch(self, query: V1Batchquery) -> QueryBatchResponse:
147
+ """
148
+ Query for multiple packages (by either package and version or git commit hash) at once. Returns vulnerability ids and modified field only. The response ordering will be guaranteed to match the input.
149
+
150
+ POST /v1/querybatc
151
+ """ # noqa: E501
152
+ return await self._requests(method="POST", url="/v1/querybatch", json=query) # type:ignore[return-value]
153
+
154
+ async def get_vuln(self, vuln_id: str) -> OSVVulnerability:
155
+ """
156
+ Returns vulnerability information for a given vulnerability id.
157
+
158
+ GET /v1/vulns/{id}
159
+ """
160
+ return await self._requests(method="GET", url=f"/v1/vulns/{self.url_escape(vuln_id)}") # type:ignore[return-value]
161
+
162
+ # async def import_findings(self) -> Incomplete:
163
+ # """
164
+ # Something like this:
165
+ # """
166
+ # return await self._requests(method="GET", url="/v1experimental/importfindings")
167
+
168
+ # async def determine_version(self) -> Incomplete:
169
+ # """
170
+ # Something like this:
171
+ # """
172
+ # return await self._requests(method="POST", url="/v1experimental/determineversion")
173
+
174
+
175
+ if __name__ == "__main__":
176
+ logging.basicConfig(level=logging.INFO)
177
+ client = OSVClientV1()
178
+ import asyncio
179
+ import json
180
+
181
+ query: V1Query = {
182
+ "package": {"name": "jinja2", "ecosystem": "PyPI"},
183
+ "version": "2.4.1",
184
+ }
185
+ loop = asyncio.get_event_loop()
186
+ a = client.query(query)
187
+ # a = client.get_vuln("GHSA-3mc7-4q67-w48m")
188
+ # # {"name": "org.yaml:snakeyaml", "version": "1.19", "system": "MAVEN"}
189
+ # a = client.query(
190
+ # {"package": {"name": "org.yaml:snakeyaml", "ecosystem": "MAVEN"}, "version": "1.19"}
191
+ # )
192
+ a = client.query({"package": {"purl": "pkg:maven/org.yaml/snakeyaml@1.19"}})
193
+
194
+ result = loop.run_until_complete(a)
195
+ print(json.dumps(result)) # For demonstration purposes, print the result
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: depsdev
3
+ Version: 0.0.5
4
+ Summary: Python wrapper for https://deps.dev/ API
5
+ Project-URL: Documentation, https://github.com/FlavioAmurrioCS/depsdev#readme
6
+ Project-URL: Issues, https://github.com/FlavioAmurrioCS/depsdev/issues
7
+ Project-URL: Source, https://github.com/FlavioAmurrioCS/depsdev
8
+ Author-email: Flavio Amurrio <25621374+FlavioAmurrioCS@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE.txt
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: httpx
23
+ Requires-Dist: packageurl-python
24
+ Provides-Extra: cli
25
+ Requires-Dist: rich; extra == 'cli'
26
+ Requires-Dist: typer-slim; extra == 'cli'
27
+ Provides-Extra: tests
28
+ Requires-Dist: pytest; extra == 'tests'
29
+ Requires-Dist: pytest-asyncio; extra == 'tests'
30
+ Requires-Dist: rich; extra == 'tests'
31
+ Requires-Dist: tomli; (python_version < '3.11') and extra == 'tests'
32
+ Requires-Dist: typer-slim; extra == 'tests'
33
+ Provides-Extra: types
34
+ Requires-Dist: mypy; extra == 'types'
35
+ Requires-Dist: pyrefly; extra == 'types'
36
+ Requires-Dist: pyright[nodejs]; extra == 'types'
37
+ Requires-Dist: pytest; extra == 'types'
38
+ Requires-Dist: pytest-asyncio; extra == 'types'
39
+ Requires-Dist: rich; extra == 'types'
40
+ Requires-Dist: tomli; (python_version < '3.11') and extra == 'types'
41
+ Requires-Dist: ty; extra == 'types'
42
+ Requires-Dist: typer-slim; extra == 'types'
43
+ Requires-Dist: typing-extensions; extra == 'types'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # depsdev
47
+
48
+ [![PyPI - Version](https://img.shields.io/pypi/v/depsdev.svg)](https://pypi.org/project/depsdev)
49
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/depsdev.svg)](https://pypi.org/project/depsdev)
50
+ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/FlavioAmurrioCS/depsdev/main.svg)](https://results.pre-commit.ci/latest/github/FlavioAmurrioCS/depsdev/main)
51
+
52
+ -----
53
+
54
+ ## Table of Contents
55
+
56
+ - [depsdev](#depsdev)
57
+ - [Table of Contents](#table-of-contents)
58
+ - [Overview](#overview)
59
+ - [Installation](#installation)
60
+ - [CLI Usage](#cli-usage)
61
+ - [Report mode](#report-mode)
62
+ - [License](#license)
63
+
64
+ ## Overview
65
+
66
+ Thin Python wrapper (async-first) around the public [deps.dev REST API](https://deps.dev) plus an optional Typer-based CLI. Provides straightforward methods mapping closely to the documented endpoints; responses are returned as decoded JSON (dict / list). Alpha endpoints can be enabled via `DEPSDEV_V3_ALPHA=true` and may change without notice.
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ pip install depsdev # library only
72
+ pipx install depsdev[cli] # CLI
73
+ uv tool install depsdev[cli] # CLI
74
+ ```
75
+
76
+ ## CLI Usage
77
+
78
+ ```bash
79
+ [flavio@Mac ~/dev/github.com/FlavioAmurrioCS/depsdev][main ✗]
80
+ $ depsdev --help
81
+
82
+ Usage: depsdev [OPTIONS] COMMAND [ARGS]...
83
+
84
+ ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
85
+ │ --install-completion [bash|zsh|fish|powershell|pwsh] Install completion for the specified shell. │
86
+ │ --show-completion [bash|zsh|fish|powershell|pwsh] Show completion for the specified shell, to copy it or customize the installation. │
87
+ │ --help Show this message and exit. │
88
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
89
+ ╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
90
+ │ report Show vulnerabilities for packages in a file. │
91
+ │ api A CLI tool to interact with the https://docs.deps.dev/api/ │
92
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
93
+ ╭─ Utils ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
94
+ │ purl Extract package URLs from various formats. │
95
+ │ vuln Main function to analyze packages for vulnerabilities. │
96
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
97
+
98
+ ```
99
+
100
+ ### Report mode
101
+
102
+ Parses depedency file and reports the vulnerabilities and the version where it was fixed.
103
+
104
+ ```bash
105
+ [flavio@Mac ~/dev/github.com/FlavioAmurrioCS/depsdev][main ✗]
106
+ $ depsdev report --help
107
+
108
+ Usage: depsdev report [OPTIONS] FILENAME
109
+
110
+ Show vulnerabilities for packages in a file.
111
+
112
+ Example usage:
113
+ depsdev report requirements.txt
114
+ depsdev report pom.xml
115
+ depsdev report Pipfile.lock
116
+
117
+ ╭─ Arguments ────────────────────────────────────────────────╮
118
+ │ * filename TEXT [required] │
119
+ ╰────────────────────────────────────────────────────────────╯
120
+ ╭─ Options ──────────────────────────────────────────────────╮
121
+ │ --help Show this message and exit. │
122
+ ╰────────────────────────────────────────────────────────────╯
123
+
124
+ [flavio@Mac ~/dev/github.com/FlavioAmurrioCS/depsdev][main ✗]
125
+ $ uv export > requirements.txt
126
+ Resolved 34 packages in 6ms
127
+
128
+ [flavio@Mac ~/dev/github.com/FlavioAmurrioCS/depsdev][main ✗]
129
+ $ depsdev report requirements.txt
130
+ Analysing 10 packages...
131
+ Found 1 packages with advisories.
132
+ pkg:pypi/idna@3.6
133
+ ┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
134
+ ┃ Id ┃ Summary ┃ Fixed ┃
135
+ ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
136
+ │ GHSA-jjg7-2v4v-x38h │ Internationalized Domain Names in Applications (IDNA) vulnerable to denial of service from specially crafted inputs to idna.encode │ 3.7 │
137
+ │ PYSEC-2024-60 │ │ 1d365e17e10d72d0b7876316fc7b9… │
138
+ └─────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────────────────┘
139
+ ```
140
+
141
+ ## License
142
+
143
+ `depsdev` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,16 @@
1
+ depsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ depsdev/__main__.py,sha256=BefEx7haQe2q0GuzIjg7aIeDBElmf4HCOenflZJiKc4,5649
3
+ depsdev/_version.py,sha256=YRV1ohn6CdKEhsUOmFFMmr5UTjMv4Ydw3WJGxF2BHBs,704
4
+ depsdev/base.py,sha256=knP1QrgtLQbwgZYqe5B-QNecYpBGQCLsQeIQRprPQKk,1314
5
+ depsdev/osv.py,sha256=AWP3E1_LmUTbXGmA15yZazTrEU-uPf1f5TW69o8LW04,5983
6
+ depsdev/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ depsdev/v3.py,sha256=vLkOYzT3wx5YuEnjwI1t7mcXXCbGmSVsoK4V1GbUGLc,7896
8
+ depsdev/v3alpha.py,sha256=KUl8Fq9mLExAfrU5T43vAc1dlRBbugN3H2Eg4Tv5XxE,13908
9
+ depsdev/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ depsdev/cli/purl.py,sha256=vl6Ix3r0mgvuy8MxQ_ayVYDmLNpok3qLqsXIMlJ9IgI,4967
11
+ depsdev/cli/vuln.py,sha256=sTdeFpthSEhavzbfvSZ5PxBQltDGdVBcmscPZO9dkS8,2206
12
+ depsdev-0.0.5.dist-info/METADATA,sha256=UekH3aUH-CYSIkTWQQGeoOC9JL-Hf4nzSCGq-Rgxxgw,10947
13
+ depsdev-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ depsdev-0.0.5.dist-info/entry_points.txt,sha256=yVCFtXda2xhj-7SmKEw7ynA_8QJrmKi9tORDw2uco9Q,50
15
+ depsdev-0.0.5.dist-info/licenses/LICENSE.txt,sha256=jpNC8_qYxlJENCgo7GKooe4rsIx-t_wIWl7ngr03F2k,1131
16
+ depsdev-0.0.5.dist-info/RECORD,,
@@ -1,72 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: depsdev
3
- Version: 0.0.3
4
- Summary: Python wrapper for https://deps.dev/ API
5
- Project-URL: Documentation, https://github.com/FlavioAmurrioCS/depsdev#readme
6
- Project-URL: Issues, https://github.com/FlavioAmurrioCS/depsdev/issues
7
- Project-URL: Source, https://github.com/FlavioAmurrioCS/depsdev
8
- Author-email: Flavio Amurrio <25621374+FlavioAmurrioCS@users.noreply.github.com>
9
- License-Expression: MIT
10
- License-File: LICENSE.txt
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Programming Language :: Python
13
- Classifier: Programming Language :: Python :: 3.9
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Programming Language :: Python :: 3.14
19
- Classifier: Programming Language :: Python :: Implementation :: CPython
20
- Classifier: Programming Language :: Python :: Implementation :: PyPy
21
- Requires-Python: >=3.9
22
- Requires-Dist: httpx
23
- Provides-Extra: cli
24
- Requires-Dist: rich; extra == 'cli'
25
- Requires-Dist: typer-slim; extra == 'cli'
26
- Provides-Extra: tests
27
- Requires-Dist: pytest; extra == 'tests'
28
- Requires-Dist: pytest-asyncio; extra == 'tests'
29
- Requires-Dist: rich; extra == 'tests'
30
- Requires-Dist: tomli; (python_version < '3.11') and extra == 'tests'
31
- Requires-Dist: typer-slim; extra == 'tests'
32
- Provides-Extra: types
33
- Requires-Dist: mypy; extra == 'types'
34
- Requires-Dist: pyrefly; extra == 'types'
35
- Requires-Dist: pyright[nodejs]; extra == 'types'
36
- Requires-Dist: pytest; extra == 'types'
37
- Requires-Dist: pytest-asyncio; extra == 'types'
38
- Requires-Dist: rich; extra == 'types'
39
- Requires-Dist: tomli; (python_version < '3.11') and extra == 'types'
40
- Requires-Dist: ty; extra == 'types'
41
- Requires-Dist: typer-slim; extra == 'types'
42
- Requires-Dist: typing-extensions; extra == 'types'
43
- Description-Content-Type: text/markdown
44
-
45
- # depsdev
46
-
47
- [![PyPI - Version](https://img.shields.io/pypi/v/depsdev.svg)](https://pypi.org/project/depsdev)
48
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/depsdev.svg)](https://pypi.org/project/depsdev)
49
- [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/FlavioAmurrioCS/depsdev/main.svg)](https://results.pre-commit.ci/latest/github/FlavioAmurrioCS/depsdev/main)
50
-
51
- -----
52
-
53
- ## Table of Contents
54
-
55
- - [Overview](#overview)
56
- - [Installation](#installation)
57
- - [License](#license)
58
-
59
- ## Overview
60
-
61
- Thin Python wrapper (async-first) around the public [deps.dev REST API](https://deps.dev) plus an optional Typer-based CLI. Provides straightforward methods mapping closely to the documented endpoints; responses are returned as decoded JSON (dict / list). Alpha endpoints can be enabled via `DEPSDEV_V3_ALPHA=true` and may change without notice.
62
-
63
- ## Installation
64
-
65
- ```bash
66
- pip install depsdev # library only
67
- pip install depsdev[cli] # library + CLI
68
- ```
69
-
70
- ## License
71
-
72
- `depsdev` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -1,11 +0,0 @@
1
- depsdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- depsdev/__main__.py,sha256=tBzB1uTUuMYu61aW4QpgdClhf4C7IgQ8EQzT_K21JXE,4578
3
- depsdev/_version.py,sha256=7Bz8oZwdLt8FP3QYrq7kiW8zbcYkC4dpOMGsCBlZ_8Y,511
4
- depsdev/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- depsdev/v3.py,sha256=vLkOYzT3wx5YuEnjwI1t7mcXXCbGmSVsoK4V1GbUGLc,7896
6
- depsdev/v3alpha.py,sha256=KUl8Fq9mLExAfrU5T43vAc1dlRBbugN3H2Eg4Tv5XxE,13908
7
- depsdev-0.0.3.dist-info/METADATA,sha256=eYFZho9-afdPDY8MnRQtkIN6ZuAXfuyKcAWtSdIJJIM,2998
8
- depsdev-0.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- depsdev-0.0.3.dist-info/entry_points.txt,sha256=yVCFtXda2xhj-7SmKEw7ynA_8QJrmKi9tORDw2uco9Q,50
10
- depsdev-0.0.3.dist-info/licenses/LICENSE.txt,sha256=jpNC8_qYxlJENCgo7GKooe4rsIx-t_wIWl7ngr03F2k,1131
11
- depsdev-0.0.3.dist-info/RECORD,,