pip-fund 0.1.0__tar.gz
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.
- pip_fund-0.1.0/PKG-INFO +140 -0
- pip_fund-0.1.0/README.md +104 -0
- pip_fund-0.1.0/pyproject.toml +98 -0
- pip_fund-0.1.0/setup.cfg +4 -0
- pip_fund-0.1.0/src/pip_fund/__init__.py +11 -0
- pip_fund-0.1.0/src/pip_fund/fund.py +568 -0
- pip_fund-0.1.0/src/pip_fund.egg-info/PKG-INFO +140 -0
- pip_fund-0.1.0/src/pip_fund.egg-info/SOURCES.txt +10 -0
- pip_fund-0.1.0/src/pip_fund.egg-info/dependency_links.txt +1 -0
- pip_fund-0.1.0/src/pip_fund.egg-info/entry_points.txt +2 -0
- pip_fund-0.1.0/src/pip_fund.egg-info/requires.txt +11 -0
- pip_fund-0.1.0/src/pip_fund.egg-info/top_level.txt +1 -0
pip_fund-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pip-fund
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Enumerate funding links for Python packages
|
|
5
|
+
Author-email: Gaurav Sood <gsood07@gmail.com>
|
|
6
|
+
Maintainer-email: Gaurav Sood <gsood07@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/gojiplus/pip-fund
|
|
9
|
+
Project-URL: Repository, https://github.com/gojiplus/pip-fund.git
|
|
10
|
+
Project-URL: Documentation, https://github.com/gojiplus/pip-fund#readme
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/gojiplus/pip-fund/issues
|
|
12
|
+
Project-URL: Funding, https://github.com/sponsors/soodoku
|
|
13
|
+
Keywords: funding,sponsorship,open-source,packaging,pypi,donations,npm-fund
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Software Distribution
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Provides-Extra: github
|
|
28
|
+
Requires-Dist: PyGithub>=1.59; extra == "github"
|
|
29
|
+
Requires-Dist: PyYAML>=6.0; extra == "github"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# pip‑fund
|
|
38
|
+
|
|
39
|
+
`pip‑fund` is a small command‑line tool that scans your Python
|
|
40
|
+
environment (or a list of package names) and reports any funding or
|
|
41
|
+
sponsorship links it can find. It is inspired by Node.js’s
|
|
42
|
+
`npm fund` command and uses Python’s packaging metadata to discover
|
|
43
|
+
funding information. The goal is to make it easy for developers to
|
|
44
|
+
discover how to support the open‑source libraries they depend on.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
* **Normalises labels and URLs:** Funding links are detected by
|
|
49
|
+
examining `Project‑URL` metadata and matching normalised labels
|
|
50
|
+
against a set of known aliases such as `funding`, `sponsor`,
|
|
51
|
+
`donate` and `donation`. Labels containing the words `fund` or
|
|
52
|
+
`sponsor` are also matched. Query strings and fragments are
|
|
53
|
+
stripped from URLs before grouping, so links that differ only by
|
|
54
|
+
tracking parameters are treated as the same.
|
|
55
|
+
* **Groups duplicate links:** When multiple packages share the same
|
|
56
|
+
funding URL, the tool groups them into a single entry and lists
|
|
57
|
+
all associated package names.
|
|
58
|
+
* **Multiple output formats:** Choose human‑readable text (default),
|
|
59
|
+
JSON (`--json`) or Markdown (`--markdown`).
|
|
60
|
+
* **Remote lookups:** Use the `--remote` flag to query the PyPI JSON
|
|
61
|
+
API for funding links of packages that are not installed locally.
|
|
62
|
+
* **Optional GitHub sponsor support:** When installed with the
|
|
63
|
+
`github` extra (`pip install pip‑fund[github]`), the tool can
|
|
64
|
+
attempt to fetch sponsor links from a repository’s
|
|
65
|
+
`.github/FUNDING.yml` file on GitHub if no funding metadata is
|
|
66
|
+
present. This requires a GitHub access token.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
### Basic usage
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install pip-fund
|
|
74
|
+
|
|
75
|
+
# Scan all installed packages for funding information
|
|
76
|
+
pip-fund
|
|
77
|
+
|
|
78
|
+
# Check specific packages (installed or not) and query PyPI if needed
|
|
79
|
+
pip-fund --remote requests flask
|
|
80
|
+
|
|
81
|
+
# Output Markdown instead of plain text
|
|
82
|
+
pip-fund --markdown
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Enabling GitHub sponsor integration
|
|
86
|
+
|
|
87
|
+
To enable GitHub sponsor discovery, install the `github` extra and
|
|
88
|
+
provide a GitHub personal access token via the `GITHUB_TOKEN` environment
|
|
89
|
+
variable. Without a token the GitHub API will operate with very
|
|
90
|
+
limited rate limits.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install pip-fund[github]
|
|
94
|
+
|
|
95
|
+
export GITHUB_TOKEN=ghp_yourtokenhere
|
|
96
|
+
pip-fund --github flask
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
Running `pip-fund` without arguments scans all installed
|
|
102
|
+
distributions in the current Python environment. For each unique
|
|
103
|
+
funding link found, it prints the label, the URL and the names of
|
|
104
|
+
packages that declare it. If no funding information is discovered,
|
|
105
|
+
the tool explains why this might be the case.
|
|
106
|
+
|
|
107
|
+
Use the `--json` or `--markdown` flags to produce machine‑readable
|
|
108
|
+
output. Combine `--remote` with package names to query funding
|
|
109
|
+
information for packages that aren’t installed.
|
|
110
|
+
|
|
111
|
+
## Project structure
|
|
112
|
+
|
|
113
|
+
This project follows the modern Python packaging guidelines:
|
|
114
|
+
|
|
115
|
+
* A `pyproject.toml` file declares the build system (`setuptools`) and
|
|
116
|
+
metadata such as the project name, version and console script
|
|
117
|
+
entry point. The `[project.scripts]` table instructs the build
|
|
118
|
+
backend to generate a `pip-fund` command that invokes the
|
|
119
|
+
`main()` function in `pip_fund/fund.py`【917575989859582†L503-L507】.
|
|
120
|
+
* Source code lives in the `src/pip_fund/` package. The main module
|
|
121
|
+
`fund.py` implements the logic of detecting funding links.
|
|
122
|
+
* Optional dependencies for GitHub sponsor support are defined under
|
|
123
|
+
`[project.optional-dependencies]` in `pyproject.toml`. Installing
|
|
124
|
+
with `pip install pip-fund[github]` adds the `PyGithub` and
|
|
125
|
+
`PyYAML` libraries.
|
|
126
|
+
|
|
127
|
+
## Contributing
|
|
128
|
+
|
|
129
|
+
Contributions are welcome! Feature requests, bug reports and pull
|
|
130
|
+
requests are greatly appreciated. Areas of interest include:
|
|
131
|
+
|
|
132
|
+
* Improving detection of funding links in package metadata
|
|
133
|
+
* Supporting additional donation platforms
|
|
134
|
+
* Enhancing the GitHub sponsor integration
|
|
135
|
+
* Adding interactive prompts or browser integration to open funding
|
|
136
|
+
pages directly from the CLI
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
This project is released under the MIT License.
|
pip_fund-0.1.0/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# pip‑fund
|
|
2
|
+
|
|
3
|
+
`pip‑fund` is a small command‑line tool that scans your Python
|
|
4
|
+
environment (or a list of package names) and reports any funding or
|
|
5
|
+
sponsorship links it can find. It is inspired by Node.js’s
|
|
6
|
+
`npm fund` command and uses Python’s packaging metadata to discover
|
|
7
|
+
funding information. The goal is to make it easy for developers to
|
|
8
|
+
discover how to support the open‑source libraries they depend on.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
* **Normalises labels and URLs:** Funding links are detected by
|
|
13
|
+
examining `Project‑URL` metadata and matching normalised labels
|
|
14
|
+
against a set of known aliases such as `funding`, `sponsor`,
|
|
15
|
+
`donate` and `donation`. Labels containing the words `fund` or
|
|
16
|
+
`sponsor` are also matched. Query strings and fragments are
|
|
17
|
+
stripped from URLs before grouping, so links that differ only by
|
|
18
|
+
tracking parameters are treated as the same.
|
|
19
|
+
* **Groups duplicate links:** When multiple packages share the same
|
|
20
|
+
funding URL, the tool groups them into a single entry and lists
|
|
21
|
+
all associated package names.
|
|
22
|
+
* **Multiple output formats:** Choose human‑readable text (default),
|
|
23
|
+
JSON (`--json`) or Markdown (`--markdown`).
|
|
24
|
+
* **Remote lookups:** Use the `--remote` flag to query the PyPI JSON
|
|
25
|
+
API for funding links of packages that are not installed locally.
|
|
26
|
+
* **Optional GitHub sponsor support:** When installed with the
|
|
27
|
+
`github` extra (`pip install pip‑fund[github]`), the tool can
|
|
28
|
+
attempt to fetch sponsor links from a repository’s
|
|
29
|
+
`.github/FUNDING.yml` file on GitHub if no funding metadata is
|
|
30
|
+
present. This requires a GitHub access token.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Basic usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install pip-fund
|
|
38
|
+
|
|
39
|
+
# Scan all installed packages for funding information
|
|
40
|
+
pip-fund
|
|
41
|
+
|
|
42
|
+
# Check specific packages (installed or not) and query PyPI if needed
|
|
43
|
+
pip-fund --remote requests flask
|
|
44
|
+
|
|
45
|
+
# Output Markdown instead of plain text
|
|
46
|
+
pip-fund --markdown
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Enabling GitHub sponsor integration
|
|
50
|
+
|
|
51
|
+
To enable GitHub sponsor discovery, install the `github` extra and
|
|
52
|
+
provide a GitHub personal access token via the `GITHUB_TOKEN` environment
|
|
53
|
+
variable. Without a token the GitHub API will operate with very
|
|
54
|
+
limited rate limits.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install pip-fund[github]
|
|
58
|
+
|
|
59
|
+
export GITHUB_TOKEN=ghp_yourtokenhere
|
|
60
|
+
pip-fund --github flask
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
Running `pip-fund` without arguments scans all installed
|
|
66
|
+
distributions in the current Python environment. For each unique
|
|
67
|
+
funding link found, it prints the label, the URL and the names of
|
|
68
|
+
packages that declare it. If no funding information is discovered,
|
|
69
|
+
the tool explains why this might be the case.
|
|
70
|
+
|
|
71
|
+
Use the `--json` or `--markdown` flags to produce machine‑readable
|
|
72
|
+
output. Combine `--remote` with package names to query funding
|
|
73
|
+
information for packages that aren’t installed.
|
|
74
|
+
|
|
75
|
+
## Project structure
|
|
76
|
+
|
|
77
|
+
This project follows the modern Python packaging guidelines:
|
|
78
|
+
|
|
79
|
+
* A `pyproject.toml` file declares the build system (`setuptools`) and
|
|
80
|
+
metadata such as the project name, version and console script
|
|
81
|
+
entry point. The `[project.scripts]` table instructs the build
|
|
82
|
+
backend to generate a `pip-fund` command that invokes the
|
|
83
|
+
`main()` function in `pip_fund/fund.py`【917575989859582†L503-L507】.
|
|
84
|
+
* Source code lives in the `src/pip_fund/` package. The main module
|
|
85
|
+
`fund.py` implements the logic of detecting funding links.
|
|
86
|
+
* Optional dependencies for GitHub sponsor support are defined under
|
|
87
|
+
`[project.optional-dependencies]` in `pyproject.toml`. Installing
|
|
88
|
+
with `pip install pip-fund[github]` adds the `PyGithub` and
|
|
89
|
+
`PyYAML` libraries.
|
|
90
|
+
|
|
91
|
+
## Contributing
|
|
92
|
+
|
|
93
|
+
Contributions are welcome! Feature requests, bug reports and pull
|
|
94
|
+
requests are greatly appreciated. Areas of interest include:
|
|
95
|
+
|
|
96
|
+
* Improving detection of funding links in package metadata
|
|
97
|
+
* Supporting additional donation platforms
|
|
98
|
+
* Enhancing the GitHub sponsor integration
|
|
99
|
+
* Adding interactive prompts or browser integration to open funding
|
|
100
|
+
pages directly from the CLI
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
This project is released under the MIT License.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
## pyproject.toml for the pip‑fund package
|
|
2
|
+
## Build system configuration. Setuptools is sufficient for this small
|
|
3
|
+
## command‑line utility. See the Python Packaging User Guide for more
|
|
4
|
+
## details on the `[build-system]` table and specifying build
|
|
5
|
+
## dependencies.
|
|
6
|
+
[build-system]
|
|
7
|
+
requires = ["setuptools>=61.0"]
|
|
8
|
+
build-backend = "setuptools.build_meta"
|
|
9
|
+
|
|
10
|
+
## Project metadata. The name should be unique on PyPI. Update
|
|
11
|
+
## author information and the version as you iterate.
|
|
12
|
+
[project]
|
|
13
|
+
name = "pip-fund"
|
|
14
|
+
version = "0.1.0"
|
|
15
|
+
description = "Enumerate funding links for Python packages"
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">=3.8"
|
|
18
|
+
license = { text = "MIT" }
|
|
19
|
+
authors = [
|
|
20
|
+
{ name = "Gaurav Sood", email = "gsood07@gmail.com" }
|
|
21
|
+
]
|
|
22
|
+
maintainers = [
|
|
23
|
+
{ name = "Gaurav Sood", email = "gsood07@gmail.com" }
|
|
24
|
+
]
|
|
25
|
+
keywords = [
|
|
26
|
+
"funding",
|
|
27
|
+
"sponsorship",
|
|
28
|
+
"open-source",
|
|
29
|
+
"packaging",
|
|
30
|
+
"pypi",
|
|
31
|
+
"donations",
|
|
32
|
+
"npm-fund"
|
|
33
|
+
]
|
|
34
|
+
classifiers = [
|
|
35
|
+
"Development Status :: 4 - Beta",
|
|
36
|
+
"Environment :: Console",
|
|
37
|
+
"Intended Audience :: Developers",
|
|
38
|
+
"License :: OSI Approved :: MIT License",
|
|
39
|
+
"Operating System :: OS Independent",
|
|
40
|
+
"Programming Language :: Python :: 3",
|
|
41
|
+
"Programming Language :: Python :: 3.11",
|
|
42
|
+
"Programming Language :: Python :: 3.12",
|
|
43
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
44
|
+
"Topic :: System :: Software Distribution",
|
|
45
|
+
"Topic :: Utilities"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/gojiplus/pip-fund"
|
|
50
|
+
Repository = "https://github.com/gojiplus/pip-fund.git"
|
|
51
|
+
Documentation = "https://github.com/gojiplus/pip-fund#readme"
|
|
52
|
+
"Bug Tracker" = "https://github.com/gojiplus/pip-fund/issues"
|
|
53
|
+
Funding = "https://github.com/sponsors/soodoku"
|
|
54
|
+
|
|
55
|
+
## Define a console script so that `pip install pip-fund` creates a
|
|
56
|
+
## `pip-fund` command. The value points to the `main` function in
|
|
57
|
+
## `pip_fund/fund.py`.
|
|
58
|
+
[project.scripts]
|
|
59
|
+
pip-fund = "pip_fund.fund:main"
|
|
60
|
+
|
|
61
|
+
## Optional dependencies. Installing with `pip install pip-fund[github]`
|
|
62
|
+
## will pull in PyGithub and PyYAML, enabling GitHub sponsor
|
|
63
|
+
## integration.
|
|
64
|
+
[project.optional-dependencies]
|
|
65
|
+
github = [
|
|
66
|
+
"PyGithub>=1.59",
|
|
67
|
+
"PyYAML>=6.0"
|
|
68
|
+
]
|
|
69
|
+
dev = [
|
|
70
|
+
"pytest>=7.0",
|
|
71
|
+
"pytest-cov>=4.0",
|
|
72
|
+
"black>=23.0",
|
|
73
|
+
"flake8>=6.0",
|
|
74
|
+
"mypy>=1.0"
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
## Tool-specific configuration
|
|
78
|
+
[tool.setuptools]
|
|
79
|
+
package-dir = {"" = "src"}
|
|
80
|
+
|
|
81
|
+
[tool.setuptools.packages.find]
|
|
82
|
+
where = ["src"]
|
|
83
|
+
|
|
84
|
+
[tool.black]
|
|
85
|
+
line-length = 88
|
|
86
|
+
target-version = ["py38"]
|
|
87
|
+
|
|
88
|
+
[tool.mypy]
|
|
89
|
+
python_version = "3.8"
|
|
90
|
+
warn_return_any = true
|
|
91
|
+
warn_unused_configs = true
|
|
92
|
+
disallow_untyped_defs = true
|
|
93
|
+
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
testpaths = ["tests"]
|
|
96
|
+
python_files = ["test_*.py"]
|
|
97
|
+
python_classes = ["Test*"]
|
|
98
|
+
python_functions = ["test_*"]
|
pip_fund-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pip_fund package
|
|
3
|
+
=================
|
|
4
|
+
|
|
5
|
+
This package exposes the ``pip_fund`` command line utility via the
|
|
6
|
+
console script defined in ``pyproject.toml``. Users should import
|
|
7
|
+
functionality from :mod:`pip_fund.fund` rather than this top‑level
|
|
8
|
+
package.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__all__ = ["fund"]
|
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core implementation for the ``pip_fund`` command.
|
|
3
|
+
|
|
4
|
+
This module provides a command‑line interface for discovering funding
|
|
5
|
+
links associated with Python packages. Funding information is
|
|
6
|
+
discovered by inspecting package metadata (``Project‑URL`` entries)
|
|
7
|
+
and, optionally, by querying the PyPI JSON API and GitHub’s
|
|
8
|
+
``.github/FUNDING.yml`` file when the optional ``github`` feature is
|
|
9
|
+
enabled. Duplicate links are normalised and grouped across
|
|
10
|
+
packages so that each unique funding source is printed once with a
|
|
11
|
+
list of packages that declare it.
|
|
12
|
+
|
|
13
|
+
The default output is a simple human‑readable report, but JSON and
|
|
14
|
+
Markdown formats are available via command‑line switches.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
from collections import defaultdict
|
|
24
|
+
from typing import Dict, Iterable, List, Mapping, Optional, Tuple
|
|
25
|
+
from urllib.parse import urlparse, urlunparse
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
# ``importlib.metadata`` is in the standard library for Python 3.8+
|
|
29
|
+
from importlib import metadata as importlib_metadata
|
|
30
|
+
except ImportError: # pragma: no cover
|
|
31
|
+
import importlib_metadata # type: ignore
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import requests # type: ignore[import]
|
|
35
|
+
except ImportError:
|
|
36
|
+
requests = None # type: ignore[assignment]
|
|
37
|
+
|
|
38
|
+
# Optional GitHub dependencies. These are only imported when the
|
|
39
|
+
# ``--github`` flag is provided *and* the extras have been installed.
|
|
40
|
+
try:
|
|
41
|
+
from github import Github # type: ignore[import]
|
|
42
|
+
import yaml # type: ignore[import]
|
|
43
|
+
except ImportError:
|
|
44
|
+
Github = None # type: ignore[assignment]
|
|
45
|
+
yaml = None # type: ignore[assignment]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Normalised labels that count as “funding”. See the Python Packaging
|
|
49
|
+
# User Guide’s “Well-known Project URLs” section for discussion of
|
|
50
|
+
# funding aliases.
|
|
51
|
+
FUNDING_ALIASES: set[str] = {
|
|
52
|
+
"funding",
|
|
53
|
+
"sponsor",
|
|
54
|
+
"donate",
|
|
55
|
+
"donation",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def normalise_label(label: str) -> str:
|
|
60
|
+
"""Normalise a Project‑URL label for comparison.
|
|
61
|
+
|
|
62
|
+
Remove punctuation and whitespace and convert to lowercase. This
|
|
63
|
+
follows the rules described in the core metadata specification and
|
|
64
|
+
PEP 753 for normalising project URL labels.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
label: The label from a Project‑URL entry.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The normalised label.
|
|
71
|
+
"""
|
|
72
|
+
cleaned = re.sub(r"[^A-Za-z0-9]", "", label)
|
|
73
|
+
return cleaned.lower()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def normalise_url(url: str) -> str:
|
|
77
|
+
"""Strip query parameters and fragments from a URL for grouping.
|
|
78
|
+
|
|
79
|
+
Query strings and fragments often contain tracking parameters that
|
|
80
|
+
should not affect whether two URLs refer to the same funding page.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
url: The URL to normalise.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The URL without ``?`` query parameters or ``#`` fragments.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
parsed = urlparse(url)
|
|
90
|
+
cleaned = parsed._replace(query="", fragment="")
|
|
91
|
+
return urlunparse(cleaned)
|
|
92
|
+
except Exception:
|
|
93
|
+
return url
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def extract_funding_urls_from_dist(
|
|
97
|
+
dist: importlib_metadata.Distribution,
|
|
98
|
+
) -> List[Tuple[str, str]]:
|
|
99
|
+
"""Extract funding URLs from a distribution's metadata.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
dist: An ``importlib.metadata`` Distribution object.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A list of ``(label, url)`` pairs representing funding‑related
|
|
106
|
+
Project‑URL entries. Labels are preserved as they appear in
|
|
107
|
+
metadata; if an entry lacks a label, ``"Generic Link"`` is used.
|
|
108
|
+
"""
|
|
109
|
+
funding_entries: List[Tuple[str, str]] = []
|
|
110
|
+
try:
|
|
111
|
+
project_urls = dist.metadata.get_all("Project-URL", []) or []
|
|
112
|
+
except Exception:
|
|
113
|
+
project_urls = []
|
|
114
|
+
for url_entry in project_urls:
|
|
115
|
+
# Format: "Label, URL"
|
|
116
|
+
if "," in url_entry:
|
|
117
|
+
label, url = url_entry.split(",", 1)
|
|
118
|
+
label = label.strip()
|
|
119
|
+
url = url.strip()
|
|
120
|
+
norm = normalise_label(label)
|
|
121
|
+
if (
|
|
122
|
+
norm in FUNDING_ALIASES
|
|
123
|
+
or "fund" in norm
|
|
124
|
+
or "sponsor" in norm
|
|
125
|
+
):
|
|
126
|
+
funding_entries.append((label, url))
|
|
127
|
+
else:
|
|
128
|
+
url = url_entry.strip()
|
|
129
|
+
if re.search(r"fund|sponsor", url, re.IGNORECASE):
|
|
130
|
+
funding_entries.append(("Generic Link", url))
|
|
131
|
+
return funding_entries
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def query_pypi_project_urls(package_name: str) -> Dict[str, str]:
|
|
135
|
+
"""Query the PyPI JSON API for a package's project URLs.
|
|
136
|
+
|
|
137
|
+
When ``--remote`` is specified, this helper uses the public PyPI
|
|
138
|
+
JSON API at ``https://pypi.org/pypi/<package>/json`` to retrieve
|
|
139
|
+
the ``project_urls`` mapping. The API returns a dictionary of
|
|
140
|
+
well‑known URLs, including a possible "Funding" entry.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
package_name: The name of the package on PyPI.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
A mapping of ``{label: url}``. If the package does not exist
|
|
147
|
+
or an error occurs, an empty dict is returned. Requires
|
|
148
|
+
``requests`` to be installed.
|
|
149
|
+
"""
|
|
150
|
+
if requests is None:
|
|
151
|
+
return {}
|
|
152
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
153
|
+
try:
|
|
154
|
+
resp = requests.get(url, timeout=5)
|
|
155
|
+
if resp.status_code == 200:
|
|
156
|
+
data = resp.json()
|
|
157
|
+
project_urls: Mapping[str, str] = (
|
|
158
|
+
data.get("info", {}).get("project_urls", {}) or {}
|
|
159
|
+
)
|
|
160
|
+
return {
|
|
161
|
+
str(k): str(v)
|
|
162
|
+
for k, v in project_urls.items()
|
|
163
|
+
if k and v
|
|
164
|
+
}
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
return {}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def extract_github_repo_from_metadata(
|
|
171
|
+
dist: importlib_metadata.Distribution,
|
|
172
|
+
) -> Optional[str]:
|
|
173
|
+
"""Attempt to find a GitHub repository URL in a distribution's metadata.
|
|
174
|
+
|
|
175
|
+
This helper inspects the ``Home-page`` field and ``Project-URL``
|
|
176
|
+
entries for a link to a GitHub repository. If found, it returns
|
|
177
|
+
the repository in ``owner/repo`` form. Otherwise it returns
|
|
178
|
+
``None``.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
dist: A distribution to inspect.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
A string like ``"octocat/Hello-World"`` or ``None``.
|
|
185
|
+
"""
|
|
186
|
+
# Check the Home-page field
|
|
187
|
+
homepage = dist.metadata.get("Home-page")
|
|
188
|
+
candidates: List[str] = []
|
|
189
|
+
if homepage:
|
|
190
|
+
candidates.append(homepage)
|
|
191
|
+
# Also check Project-URL entries for labels like "Source" or
|
|
192
|
+
# "Homepage" that might include the repository. We don't use
|
|
193
|
+
# funding labels here.
|
|
194
|
+
try:
|
|
195
|
+
project_urls = dist.metadata.get_all("Project-URL", []) or []
|
|
196
|
+
for entry in project_urls:
|
|
197
|
+
if "," in entry:
|
|
198
|
+
label, url = entry.split(",", 1)
|
|
199
|
+
label = label.strip().lower()
|
|
200
|
+
url = url.strip()
|
|
201
|
+
if label in {"source", "homepage", "code", "repository"}:
|
|
202
|
+
candidates.append(url)
|
|
203
|
+
else:
|
|
204
|
+
candidates.append(entry)
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
# Examine candidates for GitHub URLs
|
|
208
|
+
for url in candidates:
|
|
209
|
+
match = re.match(
|
|
210
|
+
r"https?://github\.com/([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)",
|
|
211
|
+
url,
|
|
212
|
+
)
|
|
213
|
+
if match:
|
|
214
|
+
owner, repo = match.groups()
|
|
215
|
+
return f"{owner}/{repo}"
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def fetch_github_funding(repo_path: str, token: Optional[str]) -> List[Tuple[str, str]]:
|
|
220
|
+
"""Fetch funding links from a GitHub repository’s FUNDING.yml.
|
|
221
|
+
|
|
222
|
+
This helper uses the GitHub API via the PyGithub library to fetch
|
|
223
|
+
the contents of ``.github/FUNDING.yml`` for a given repository.
|
|
224
|
+
If successful, it returns a list of ``(platform, url)`` pairs
|
|
225
|
+
representing the configured funding sources. If PyGithub or
|
|
226
|
+
PyYAML is not installed, or the file does not exist, an empty
|
|
227
|
+
list is returned.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
repo_path: A string like ``"owner/repo"`` specifying the
|
|
231
|
+
repository.
|
|
232
|
+
token: Optional GitHub personal access token. Without a
|
|
233
|
+
token, unauthenticated requests may be subject to very
|
|
234
|
+
strict rate limits.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
A list of ``(label, url)`` tuples.
|
|
238
|
+
"""
|
|
239
|
+
if Github is None or yaml is None:
|
|
240
|
+
return []
|
|
241
|
+
try:
|
|
242
|
+
gh = Github(login_or_token=token)
|
|
243
|
+
repo = gh.get_repo(repo_path)
|
|
244
|
+
contents = repo.get_contents(".github/FUNDING.yml")
|
|
245
|
+
data = yaml.safe_load(contents.decoded_content)
|
|
246
|
+
if not isinstance(data, dict):
|
|
247
|
+
return []
|
|
248
|
+
results: List[Tuple[str, str]] = []
|
|
249
|
+
for platform, entries in data.items():
|
|
250
|
+
# Platform names correspond to GitHub sponsors ("github"),
|
|
251
|
+
# Patreon ("patreon"), etc. Entries may be strings or
|
|
252
|
+
# lists of identifiers. Construct a URL for each.
|
|
253
|
+
if not entries:
|
|
254
|
+
continue
|
|
255
|
+
if isinstance(entries, str):
|
|
256
|
+
entries = [entries]
|
|
257
|
+
for entry in entries:
|
|
258
|
+
# Map known platforms to URL patterns. Unknown
|
|
259
|
+
# platforms fall back to None and are skipped.
|
|
260
|
+
if platform == "github":
|
|
261
|
+
url = f"https://github.com/sponsors/{entry}"
|
|
262
|
+
label = "GitHub Sponsors"
|
|
263
|
+
elif platform == "patreon":
|
|
264
|
+
url = f"https://www.patreon.com/{entry}"
|
|
265
|
+
label = "Patreon"
|
|
266
|
+
elif platform == "tidelift":
|
|
267
|
+
url = f"https://tidelift.com/funding/github/{entry}"
|
|
268
|
+
label = "Tidelift"
|
|
269
|
+
elif platform == "custom":
|
|
270
|
+
url = entry
|
|
271
|
+
label = "Custom"
|
|
272
|
+
else:
|
|
273
|
+
# Unknown platform type
|
|
274
|
+
continue
|
|
275
|
+
results.append((label, url))
|
|
276
|
+
return results
|
|
277
|
+
except Exception:
|
|
278
|
+
return []
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def gather_funding_info(
|
|
282
|
+
package_names: Iterable[str],
|
|
283
|
+
use_remote: bool,
|
|
284
|
+
use_github: bool,
|
|
285
|
+
github_token: Optional[str],
|
|
286
|
+
) -> Dict[str, List[Tuple[str, str]]]:
|
|
287
|
+
"""Gather funding information for the given packages.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
package_names: Names of packages to inspect. If empty, all
|
|
291
|
+
installed distributions are scanned.
|
|
292
|
+
use_remote: Whether to query the PyPI JSON API for funding
|
|
293
|
+
information about packages (useful when a package is not
|
|
294
|
+
installed). Requires ``requests``.
|
|
295
|
+
use_github: Whether to attempt to discover funding links via
|
|
296
|
+
GitHub’s ``FUNDING.yml`` file when no funding metadata is
|
|
297
|
+
found locally or remotely. Requires the ``github`` extra
|
|
298
|
+
and a valid access token.
|
|
299
|
+
github_token: Personal access token for the GitHub API.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
A mapping from package name to a list of ``(label, url)`` tuples.
|
|
303
|
+
"""
|
|
304
|
+
results: Dict[str, List[Tuple[str, str]]] = defaultdict(list)
|
|
305
|
+
if package_names:
|
|
306
|
+
# Explicit list of packages
|
|
307
|
+
for pkg in package_names:
|
|
308
|
+
try:
|
|
309
|
+
dist = importlib_metadata.distribution(pkg)
|
|
310
|
+
entries = extract_funding_urls_from_dist(dist)
|
|
311
|
+
results[pkg].extend(entries)
|
|
312
|
+
# Optionally query PyPI for more links even if the
|
|
313
|
+
# package is installed
|
|
314
|
+
if use_remote:
|
|
315
|
+
pypi_urls = query_pypi_project_urls(pkg)
|
|
316
|
+
for lbl, url in pypi_urls.items():
|
|
317
|
+
norm = normalise_label(lbl)
|
|
318
|
+
if (
|
|
319
|
+
norm in FUNDING_ALIASES
|
|
320
|
+
or "fund" in norm
|
|
321
|
+
or "sponsor" in norm
|
|
322
|
+
):
|
|
323
|
+
if (lbl, url) not in results[pkg]:
|
|
324
|
+
results[pkg].append((lbl, url))
|
|
325
|
+
except importlib_metadata.PackageNotFoundError:
|
|
326
|
+
# Not installed: try PyPI
|
|
327
|
+
if use_remote:
|
|
328
|
+
pypi_urls = query_pypi_project_urls(pkg)
|
|
329
|
+
for lbl, url in pypi_urls.items():
|
|
330
|
+
norm = normalise_label(lbl)
|
|
331
|
+
if (
|
|
332
|
+
norm in FUNDING_ALIASES
|
|
333
|
+
or "fund" in norm
|
|
334
|
+
or "sponsor" in norm
|
|
335
|
+
):
|
|
336
|
+
results[pkg].append((lbl, url))
|
|
337
|
+
# If we still have no funding info and GitHub support is
|
|
338
|
+
# enabled, attempt to fetch funding.yml
|
|
339
|
+
if use_github and not results[pkg]:
|
|
340
|
+
try:
|
|
341
|
+
dist = importlib_metadata.distribution(pkg)
|
|
342
|
+
except importlib_metadata.PackageNotFoundError:
|
|
343
|
+
dist = None # type: ignore
|
|
344
|
+
repo_path = None
|
|
345
|
+
if dist:
|
|
346
|
+
repo_path = extract_github_repo_from_metadata(dist)
|
|
347
|
+
if not repo_path and use_remote:
|
|
348
|
+
# Try remote metadata for repository URL
|
|
349
|
+
# using the PyPI JSON API
|
|
350
|
+
pypi_urls = query_pypi_project_urls(pkg)
|
|
351
|
+
for lbl, url in pypi_urls.items():
|
|
352
|
+
# look for GitHub repo in URL
|
|
353
|
+
match = re.match(
|
|
354
|
+
r"https?://github\.com/([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)",
|
|
355
|
+
url,
|
|
356
|
+
)
|
|
357
|
+
if match:
|
|
358
|
+
owner, repo = match.groups()
|
|
359
|
+
repo_path = f"{owner}/{repo}"
|
|
360
|
+
break
|
|
361
|
+
if repo_path:
|
|
362
|
+
entries = fetch_github_funding(repo_path, github_token)
|
|
363
|
+
results[pkg].extend(entries)
|
|
364
|
+
else:
|
|
365
|
+
# Scan all installed distributions
|
|
366
|
+
dists = list(importlib_metadata.distributions())
|
|
367
|
+
for dist in dists:
|
|
368
|
+
name = dist.metadata.get("Name", dist.metadata.get("Summary", "Unknown"))
|
|
369
|
+
entries = extract_funding_urls_from_dist(dist)
|
|
370
|
+
if entries:
|
|
371
|
+
results[name].extend(entries)
|
|
372
|
+
if use_remote and not entries:
|
|
373
|
+
pypi_urls = query_pypi_project_urls(name)
|
|
374
|
+
for lbl, url in pypi_urls.items():
|
|
375
|
+
norm = normalise_label(lbl)
|
|
376
|
+
if (
|
|
377
|
+
norm in FUNDING_ALIASES
|
|
378
|
+
or "fund" in norm
|
|
379
|
+
or "sponsor" in norm
|
|
380
|
+
):
|
|
381
|
+
results[name].append((lbl, url))
|
|
382
|
+
if use_github and not results[name]:
|
|
383
|
+
repo_path = extract_github_repo_from_metadata(dist)
|
|
384
|
+
if repo_path:
|
|
385
|
+
entries = fetch_github_funding(repo_path, github_token)
|
|
386
|
+
results[name].extend(entries)
|
|
387
|
+
return results
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def group_by_url(
|
|
391
|
+
results: Mapping[str, List[Tuple[str, str]]],
|
|
392
|
+
) -> Mapping[Tuple[str, str], List[str]]:
|
|
393
|
+
"""Group funding results by URL.
|
|
394
|
+
|
|
395
|
+
Normalises URLs before grouping so that links differing only in
|
|
396
|
+
query parameters or fragments are treated as the same.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
results: Mapping from package name to funding entries.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
A mapping where each key is a (label, canonical_url) tuple and
|
|
403
|
+
the value is a list of packages that declare that funding
|
|
404
|
+
entry.
|
|
405
|
+
"""
|
|
406
|
+
grouped: Dict[Tuple[str, str], List[str]] = defaultdict(list)
|
|
407
|
+
for pkg, entries in results.items():
|
|
408
|
+
for lbl, url in entries:
|
|
409
|
+
canon = normalise_url(url)
|
|
410
|
+
grouped[(lbl, canon)].append(pkg)
|
|
411
|
+
return grouped
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def format_as_plain(results: Mapping[str, List[Tuple[str, str]]]) -> str:
|
|
415
|
+
"""Format funding results as a human‑readable string.
|
|
416
|
+
|
|
417
|
+
Funding entries are grouped by canonical URL. Each unique entry
|
|
418
|
+
appears once with the list of packages that declare it. If no
|
|
419
|
+
funding information is found, an explanatory message is returned.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
results: Mapping from package to funding entries.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
A string representation of the report.
|
|
426
|
+
"""
|
|
427
|
+
if not results:
|
|
428
|
+
return (
|
|
429
|
+
"No funding links found for any packages.\n"
|
|
430
|
+
"This could mean:\n"
|
|
431
|
+
" - No packages declare funding links in their metadata.\n"
|
|
432
|
+
" - The packages use an older metadata format that doesn't support 'Project-URL'.\n"
|
|
433
|
+
" - The funding links are present but use a different, unrecognised label."
|
|
434
|
+
)
|
|
435
|
+
grouped = group_by_url(results)
|
|
436
|
+
lines: List[str] = []
|
|
437
|
+
lines.append("--- Funding Information Found ---")
|
|
438
|
+
for (lbl, url), packages in sorted(grouped.items(), key=lambda kv: kv[0][1]):
|
|
439
|
+
lines.append(f"{lbl}: {url}")
|
|
440
|
+
lines.append(" Packages: " + ", ".join(sorted(packages)))
|
|
441
|
+
lines.append("".join("-" for _ in range(30)))
|
|
442
|
+
return "\n".join(lines)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def format_as_json(results: Mapping[str, List[Tuple[str, str]]]) -> str:
|
|
446
|
+
"""Format funding results as a JSON string.
|
|
447
|
+
|
|
448
|
+
Funding entries are grouped by canonical URL. The JSON maps each
|
|
449
|
+
unique link to an object containing the label, canonical URL and
|
|
450
|
+
the list of packages that declare it.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
results: Mapping from package to funding entries.
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
A JSON string representation of the grouped report.
|
|
457
|
+
"""
|
|
458
|
+
grouped = group_by_url(results)
|
|
459
|
+
jsonable = {
|
|
460
|
+
f"{lbl}|{url}": {
|
|
461
|
+
"label": lbl,
|
|
462
|
+
"url": url,
|
|
463
|
+
"packages": sorted(packages),
|
|
464
|
+
}
|
|
465
|
+
for (lbl, url), packages in grouped.items()
|
|
466
|
+
}
|
|
467
|
+
return json.dumps(jsonable, indent=2)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def format_as_markdown(results: Mapping[str, List[Tuple[str, str]]]) -> str:
|
|
471
|
+
"""Format funding results as a Markdown document.
|
|
472
|
+
|
|
473
|
+
Funding entries are grouped by canonical URL. Each entry is
|
|
474
|
+
listed once with the packages that declare it. Long lines are
|
|
475
|
+
avoided to ensure readability.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
results: Mapping from package to funding entries.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
A Markdown string representing the report.
|
|
482
|
+
"""
|
|
483
|
+
if not results:
|
|
484
|
+
return "No funding links found."
|
|
485
|
+
grouped = group_by_url(results)
|
|
486
|
+
lines: List[str] = []
|
|
487
|
+
lines.append("# Funding Information\n")
|
|
488
|
+
for (lbl, url), packages in sorted(grouped.items(), key=lambda kv: kv[0][1]):
|
|
489
|
+
lines.append(f"* **{lbl}**: {url}")
|
|
490
|
+
lines.append(f" - Packages: {', '.join(sorted(packages))}\n")
|
|
491
|
+
return "\n".join(lines)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def parse_arguments(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|
495
|
+
"""Parse command‑line arguments.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
argv: Optional list of arguments (for testing). If omitted,
|
|
499
|
+
defaults to ``sys.argv[1:]``.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
An ``argparse.Namespace`` containing parsed options.
|
|
503
|
+
"""
|
|
504
|
+
parser = argparse.ArgumentParser(
|
|
505
|
+
description=(
|
|
506
|
+
"Enumerate funding links for Python packages by reading their metadata "
|
|
507
|
+
"(and optionally PyPI project_urls and GitHub FUNDING.yml).\n"
|
|
508
|
+
"Without arguments the script scans all installed distributions; "
|
|
509
|
+
"otherwise supply package names to inspect them individually."
|
|
510
|
+
)
|
|
511
|
+
)
|
|
512
|
+
parser.add_argument(
|
|
513
|
+
"packages",
|
|
514
|
+
nargs="*",
|
|
515
|
+
help="Names of packages to inspect. If omitted, all installed packages are scanned.",
|
|
516
|
+
)
|
|
517
|
+
parser.add_argument(
|
|
518
|
+
"--json",
|
|
519
|
+
action="store_true",
|
|
520
|
+
help="Output the results as JSON instead of human‑readable text.",
|
|
521
|
+
)
|
|
522
|
+
parser.add_argument(
|
|
523
|
+
"--markdown",
|
|
524
|
+
action="store_true",
|
|
525
|
+
help="Output the results as Markdown instead of human‑readable text.",
|
|
526
|
+
)
|
|
527
|
+
parser.add_argument(
|
|
528
|
+
"--remote",
|
|
529
|
+
action="store_true",
|
|
530
|
+
help="Query the PyPI JSON API for packages (requires network and requests).",
|
|
531
|
+
)
|
|
532
|
+
parser.add_argument(
|
|
533
|
+
"--github",
|
|
534
|
+
action="store_true",
|
|
535
|
+
help=(
|
|
536
|
+
"Attempt to fetch funding links from a repository’s .github/FUNDING.yml via the GitHub API. "
|
|
537
|
+
"Requires the github extra and a personal access token set via the GITHUB_TOKEN environment variable."
|
|
538
|
+
),
|
|
539
|
+
)
|
|
540
|
+
return parser.parse_args(argv)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def main(argv: Optional[List[str]] = None) -> None:
|
|
544
|
+
"""Entry point for the ``pip_fund`` command. Parses arguments and
|
|
545
|
+
prints the formatted funding report.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
argv: Optional list of arguments to parse (for testing). If
|
|
549
|
+
omitted, ``sys.argv[1:]`` is used.
|
|
550
|
+
"""
|
|
551
|
+
args = parse_arguments(argv)
|
|
552
|
+
token = os.environ.get("GITHUB_TOKEN") if args.github else None
|
|
553
|
+
results = gather_funding_info(
|
|
554
|
+
args.packages,
|
|
555
|
+
use_remote=args.remote,
|
|
556
|
+
use_github=args.github,
|
|
557
|
+
github_token=token,
|
|
558
|
+
)
|
|
559
|
+
if args.json:
|
|
560
|
+
print(format_as_json(results))
|
|
561
|
+
elif args.markdown:
|
|
562
|
+
print(format_as_markdown(results))
|
|
563
|
+
else:
|
|
564
|
+
print(format_as_plain(results))
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
if __name__ == "__main__": # pragma: no cover
|
|
568
|
+
main()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pip-fund
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Enumerate funding links for Python packages
|
|
5
|
+
Author-email: Gaurav Sood <gsood07@gmail.com>
|
|
6
|
+
Maintainer-email: Gaurav Sood <gsood07@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/gojiplus/pip-fund
|
|
9
|
+
Project-URL: Repository, https://github.com/gojiplus/pip-fund.git
|
|
10
|
+
Project-URL: Documentation, https://github.com/gojiplus/pip-fund#readme
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/gojiplus/pip-fund/issues
|
|
12
|
+
Project-URL: Funding, https://github.com/sponsors/soodoku
|
|
13
|
+
Keywords: funding,sponsorship,open-source,packaging,pypi,donations,npm-fund
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Software Distribution
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Provides-Extra: github
|
|
28
|
+
Requires-Dist: PyGithub>=1.59; extra == "github"
|
|
29
|
+
Requires-Dist: PyYAML>=6.0; extra == "github"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# pip‑fund
|
|
38
|
+
|
|
39
|
+
`pip‑fund` is a small command‑line tool that scans your Python
|
|
40
|
+
environment (or a list of package names) and reports any funding or
|
|
41
|
+
sponsorship links it can find. It is inspired by Node.js’s
|
|
42
|
+
`npm fund` command and uses Python’s packaging metadata to discover
|
|
43
|
+
funding information. The goal is to make it easy for developers to
|
|
44
|
+
discover how to support the open‑source libraries they depend on.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
* **Normalises labels and URLs:** Funding links are detected by
|
|
49
|
+
examining `Project‑URL` metadata and matching normalised labels
|
|
50
|
+
against a set of known aliases such as `funding`, `sponsor`,
|
|
51
|
+
`donate` and `donation`. Labels containing the words `fund` or
|
|
52
|
+
`sponsor` are also matched. Query strings and fragments are
|
|
53
|
+
stripped from URLs before grouping, so links that differ only by
|
|
54
|
+
tracking parameters are treated as the same.
|
|
55
|
+
* **Groups duplicate links:** When multiple packages share the same
|
|
56
|
+
funding URL, the tool groups them into a single entry and lists
|
|
57
|
+
all associated package names.
|
|
58
|
+
* **Multiple output formats:** Choose human‑readable text (default),
|
|
59
|
+
JSON (`--json`) or Markdown (`--markdown`).
|
|
60
|
+
* **Remote lookups:** Use the `--remote` flag to query the PyPI JSON
|
|
61
|
+
API for funding links of packages that are not installed locally.
|
|
62
|
+
* **Optional GitHub sponsor support:** When installed with the
|
|
63
|
+
`github` extra (`pip install pip‑fund[github]`), the tool can
|
|
64
|
+
attempt to fetch sponsor links from a repository’s
|
|
65
|
+
`.github/FUNDING.yml` file on GitHub if no funding metadata is
|
|
66
|
+
present. This requires a GitHub access token.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
### Basic usage
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install pip-fund
|
|
74
|
+
|
|
75
|
+
# Scan all installed packages for funding information
|
|
76
|
+
pip-fund
|
|
77
|
+
|
|
78
|
+
# Check specific packages (installed or not) and query PyPI if needed
|
|
79
|
+
pip-fund --remote requests flask
|
|
80
|
+
|
|
81
|
+
# Output Markdown instead of plain text
|
|
82
|
+
pip-fund --markdown
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Enabling GitHub sponsor integration
|
|
86
|
+
|
|
87
|
+
To enable GitHub sponsor discovery, install the `github` extra and
|
|
88
|
+
provide a GitHub personal access token via the `GITHUB_TOKEN` environment
|
|
89
|
+
variable. Without a token the GitHub API will operate with very
|
|
90
|
+
limited rate limits.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install pip-fund[github]
|
|
94
|
+
|
|
95
|
+
export GITHUB_TOKEN=ghp_yourtokenhere
|
|
96
|
+
pip-fund --github flask
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
Running `pip-fund` without arguments scans all installed
|
|
102
|
+
distributions in the current Python environment. For each unique
|
|
103
|
+
funding link found, it prints the label, the URL and the names of
|
|
104
|
+
packages that declare it. If no funding information is discovered,
|
|
105
|
+
the tool explains why this might be the case.
|
|
106
|
+
|
|
107
|
+
Use the `--json` or `--markdown` flags to produce machine‑readable
|
|
108
|
+
output. Combine `--remote` with package names to query funding
|
|
109
|
+
information for packages that aren’t installed.
|
|
110
|
+
|
|
111
|
+
## Project structure
|
|
112
|
+
|
|
113
|
+
This project follows the modern Python packaging guidelines:
|
|
114
|
+
|
|
115
|
+
* A `pyproject.toml` file declares the build system (`setuptools`) and
|
|
116
|
+
metadata such as the project name, version and console script
|
|
117
|
+
entry point. The `[project.scripts]` table instructs the build
|
|
118
|
+
backend to generate a `pip-fund` command that invokes the
|
|
119
|
+
`main()` function in `pip_fund/fund.py`【917575989859582†L503-L507】.
|
|
120
|
+
* Source code lives in the `src/pip_fund/` package. The main module
|
|
121
|
+
`fund.py` implements the logic of detecting funding links.
|
|
122
|
+
* Optional dependencies for GitHub sponsor support are defined under
|
|
123
|
+
`[project.optional-dependencies]` in `pyproject.toml`. Installing
|
|
124
|
+
with `pip install pip-fund[github]` adds the `PyGithub` and
|
|
125
|
+
`PyYAML` libraries.
|
|
126
|
+
|
|
127
|
+
## Contributing
|
|
128
|
+
|
|
129
|
+
Contributions are welcome! Feature requests, bug reports and pull
|
|
130
|
+
requests are greatly appreciated. Areas of interest include:
|
|
131
|
+
|
|
132
|
+
* Improving detection of funding links in package metadata
|
|
133
|
+
* Supporting additional donation platforms
|
|
134
|
+
* Enhancing the GitHub sponsor integration
|
|
135
|
+
* Adding interactive prompts or browser integration to open funding
|
|
136
|
+
pages directly from the CLI
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
This project is released under the MIT License.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/pip_fund/__init__.py
|
|
4
|
+
src/pip_fund/fund.py
|
|
5
|
+
src/pip_fund.egg-info/PKG-INFO
|
|
6
|
+
src/pip_fund.egg-info/SOURCES.txt
|
|
7
|
+
src/pip_fund.egg-info/dependency_links.txt
|
|
8
|
+
src/pip_fund.egg-info/entry_points.txt
|
|
9
|
+
src/pip_fund.egg-info/requires.txt
|
|
10
|
+
src/pip_fund.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pip_fund
|