Skip to content

Commit

Permalink
add Sphinx configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan6419846 authored Apr 7, 2024
1 parent e75b528 commit 9b5a1a0
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 30 deletions.
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
importlib.metadata-inv.txt
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
Empty file added docs/_static/.gitkeep
Empty file.
Empty file added docs/_templates/.gitkeep
Empty file.
6 changes: 6 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
API Reference
=============

.. automodule:: piplicenses_lib
:members:
:exclude-members: NoValueEnum
41 changes: 41 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# Project information
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

from importlib.metadata import version as _version


project = "pip-licenses-lib"
copyright = "2024, raimon49, stefan6419846"
author = "raimon49, stefan6419846"
release = _version("pip-licenses-lib")

# General configuration
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

nitpicky = True


# Options for HTML output
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

master_doc = "index"
html_theme = "furo"
html_static_path = ["_static"]


# Options for cross-referencing.

intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"importlib": ("https://importlib-metadata.readthedocs.io/en/latest/", "importlib.metadata-inv.txt"),
}
77 changes: 77 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Welcome to pip-licenses-lib's documentation!
============================================

Retrieve the software license list of Python packages installed with `pip`.

GitHub: `pip-license-lib <https://github.com/stefan6419846/pip-licenses-lib>`_

.. toctree::
:maxdepth: 1

api

About
-----

This package is a fork of the great `pip-licenses <https://github.com/raimon49/pip-licenses>`_ tool, which provides a CLI with similar functionality. For now, `pip-licenses` itself mostly focuses on the CLI part; while library-based access is possible, some interesting methods for further reuse are nested and therefore hidden inside the corresponding API.

While there have been some attempts to provide similar features in the upstream repository, they are not available inside the official package at the moment, while I needed a short-term solution. Examples:

* In May 2021, a package structure has been introduced by `#88 <https://github.com/raimon49/pip-licenses/pull/88>`_. In August 2023, this is still only available on a ``dev-4.0.0`` branch, while version 4.0.0 has been released in November 2022.
* In October 2020, the PR `#78 <https://github.com/raimon49/pip-licenses/pull/78>`_ for handling multiple license files has been closed to maybe include it in the future, which has not yet happened.

As parsing the license data of packages as provided by the maintainers is at least some first hint regarding the license status, I decided to create this fork with the required modifications and enhancements to suit my current needs.


Differences to pip-licenses
---------------------------

Changes compared to original version:

* Enable support for Python < 3.8 by using the ``importlib_metadata`` backport and dropping support for the new annotations behaviour for now.
* Remove all output/rendering functionality.
* Move all methods to the top level.
* Always return all copyright and notice file matches.
* Always return the system packages as well.
* Include the license names and distribution object inside the result dictionary.
* Do not use abbreviations for naming purposes.
* Rewrite tests to use plain *unittest* functionality.
* Add option to skip retrieving license and notice files for faster version-only checks.


Installation
------------

You can install this package from PyPI:

.. code:: bash
python -m pip install pip-licenses-lib
Alternatively, you can use the package from source directly after installing the required dependencies.


Usage
-----

The main entry point is ``piplicenses_lib.get_packages()``, which will yield a list of package data dictionaries. For more details, see the :doc:`api` documentation itself.


License
-------

This package is subject to the terms of the MIT license.


Disclaimer
----------

All results are generated automatically from the data supplied by the corresponding package maintainers and provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. No generated content should be considered or used as legal advice. Consult an Attorney for any legal advice.


Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
35 changes: 35 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd
109 changes: 109 additions & 0 deletions docs/prepare_importlib_metadata_inv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Make `importlib.metadata` compatible for intersphinx mappings.
Issue: The official Python docs refer to the external `importlib_metadata` package
and thus this is leading to undefined names.
Upstream implementation: https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/intersphinx.py
"""

# This file is loosely based upon the upstream implementation of the intersphinx extension.
# Original source: https://github.com/sphinx-doc/sphinx/blob/c7c02002e58befc64e4df7db0263994db5204f12/sphinx/ext/intersphinx.py
# The file follows the same copyright.
#
# Copyright (c) 2007-2024 by the Sphinx team (see AUTHORS file).
# Copyright (c) 2024 stefan6419846
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import functools
import zlib
from typing import Generator, IO, List

import requests
from sphinx.util.inventory import InventoryFileReader


def load_original(url: str) -> IO:
response = requests.get(url, stream=True)
response.raise_for_status()
response.raw.url = response.url
# decode content-body based on the header.
# ref: https://github.com/psf/requests/issues/2155
response.raw.read = functools.partial(response.raw.read, decode_content=True)
return response.raw


def load_lines(url: str) -> Generator[str, None, None]:
data = load_original(url)
reader = InventoryFileReader(data)
yield data.readline().decode() # Inventory version.
yield data.readline().decode() # Project name.
yield data.readline().decode() # Project version.
yield data.readline().decode() # Compression indicator.
for line in reader.read_compressed_lines():
yield line


def replace_names(lines: List[str], source: str, target: str) -> None:
for index, line in enumerate(lines):
if line.startswith("# Project: "):
lines[index] = "# Project: " + target
if line.startswith(source):
name = line.split(" ", maxsplit=1)
new_line = target + line[len(source):]
if " api.html#$ " in line:
# Self-references (`$`) do not work anymore due to changing the name.
# Fix this by adding an explicit reference with the original name.
new_line = new_line.replace(" api.html#$ ", f" api.html#{name[0]} ")
lines[index] = new_line


def dump(filename: str, lines: List[str]):
with open(filename, 'wb') as f:
# header
f.write((
f'# Sphinx inventory version 2\n'
f'{lines[1].rstrip()}\n'
f'# Version: custom\n'
f'# The remainder of this file is compressed using zlib.\n'
).encode())

# body
compressor = zlib.compressobj(9)
for line in lines[4:]: # Skip header lines.
# Ensure we add a trailing newline!
f.write(compressor.compress(f'{line}\n'.encode()))
f.write(compressor.flush())


def main() -> None:
lines = list(load_lines("https://importlib-metadata.readthedocs.io/en/latest/objects.inv"))
replace_names(lines, "importlib_metadata", "importlib.metadata")
dump(filename="importlib.metadata-inv.txt", lines=lines)


if __name__ == "__main__":
main()
72 changes: 43 additions & 29 deletions piplicenses_lib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
"""
pip-licenses-lib
MIT License
Copyright (c) 2018 raimon
Copyright (c) 2023 stefan6419846
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
# pip-licenses-lib
#
# MIT License
#
# Copyright (c) 2018 raimon
# Copyright (c) 2023 stefan6419846
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# TODO: Enable and change type hints accordingly after dropping support for Python < 3.8.
# from __future__ import annotations

Expand Down Expand Up @@ -99,8 +98,8 @@ def normalize_package_name(package_name: str) -> str:
See here: https://peps.python.org/pep-0503/#normalized-names
:param package_name: Package name it is extracted from the package metadata
or specified in the CLI.
:param package_name: Package name as it is extracted from the package metadata
or specified as parameter somewhere.
:return: Normalized package name.
"""
return PATTERN_PACKAGE_NAME_DELIMITER.sub("-", package_name).lower()
Expand Down Expand Up @@ -152,7 +151,7 @@ def get_package_included_files(
package_files = package.files or ()
pattern = re.compile(file_names_regex)
matched_relative_paths = filter(
lambda file: pattern.match(file.name), package_files
lambda entry: pattern.match(entry.name), package_files
)
for relative_path in matched_relative_paths:
absolute_path = Path(package.locate_file(relative_path))
Expand Down Expand Up @@ -315,6 +314,21 @@ class FromArg(NoValueEnum):
"""

META = auto()
"""
Retrieve from metadata field only.
"""

CLASSIFIER = auto()
"""
Retrieve from classifiers only.
"""

MIXED = auto()
"""
Prefer classifiers over the metadata field.
"""

ALL = auto()
"""
Retrieve from all. Currently not implemented.
"""
Loading

0 comments on commit 9b5a1a0

Please sign in to comment.