Skip to content

Commit

Permalink
python-poetry#1350 - Improvements to handling of License file(s)
Browse files Browse the repository at this point in the history
- Fixes python-poetry#1350
- Added support for specifying a set of license files as part of the project config.
- Moved the license file config into the package configuration. (`Factory.create_poetry()`) this matches how similar configurations such as “readme” are configured.
- Updated the builders to use the project level license file list instead of searching for their own (previously different) lists of license files.
- Moved the existing auto-discovery behaviours to become the default case during `create_poetry()` if nothing specific was configured.
- Added tests for the new features
- Documented the new configuration field.
- Added the “license-files” field to the poetry-schema.json
- Updated pre-commit config to use the correct black source repo, and set the default language for better consistency with any other pre-commit checks that will be added in the future.
- Fix python-poetry#866 by modifing the base template used by `SdistBuilder`. It now uses `setuptools`, since as documented by @seifertm in issue python-poetry#866 `pip` uses `setuptools` anyway and this clears the way to immediately supporting pypa/setuptools#1767 once this is merged and released from `setuptools`. Until `setuptools` updates to support multiple license files, this will result in a single log line in the debug output from `setuptools` so this is ok to merge before the change to `setuptools` is merged.
  • Loading branch information
Samuel Bishop committed Jan 7, 2020
1 parent 5050362 commit 391f024
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 5 deletions.
9 changes: 9 additions & 0 deletions docs/docs/pyproject.md
Expand Up @@ -40,6 +40,15 @@ The recommended notation for the most common licenses is (alphabetical):
Optional, but it is highly recommended to supply this.
More identifiers are listed at the [SPDX Open Source License Registry](https://www.spdx.org/licenses/).

## license-files

The license file(s) belonging to this package. **optional**

Poetry will automatically include files matching `LICENSE*` and `COPYING*`,
but if you have licenses files that do not fit these patterns,
you can configure `poetry` to include one or more files as the license files.
These paths should be relative to the project root directory, where the `pyproject.toml` lives.

## authors

The authors of the package. **Required**
Expand Down
10 changes: 10 additions & 0 deletions poetry/factory.py
Expand Up @@ -81,6 +81,16 @@ def create_poetry(
if "readme" in local_config:
package.readme = Path(poetry_file.parent) / local_config["readme"]

# Add any manually defined or automatically discovered license files.
if "license-files" in local_config:
for license_file in local_config["license-files"]:
package.license_files.add(Path(poetry_file.parent) / license_file)
else:
for license_file in poetry_file.parent.glob("LICENSE*"):
package.license_files.add(license_file.relative_to(poetry_file.parent))
for license_file in poetry_file.parent.glob("COPYING*"):
package.license_files.add(license_file.relative_to(poetry_file.parent))

if "platform" in local_config:
package.platform = local_config["platform"]

Expand Down
10 changes: 10 additions & 0 deletions poetry/json/schemas/poetry-schema.json
Expand Up @@ -47,6 +47,9 @@
"type": "string",
"description": "License name."
},
"license-files": {
"$ref": "#/definitions/license-files"
},
"authors": {
"$ref": "#/definitions/authors"
},
Expand Down Expand Up @@ -183,6 +186,13 @@
"type": "string"
}
},
"license-files": {
"type": "array",
"description": "A list of license files applicable to this packages. This accepts multiple files for situations involving more complicated licensing situations.",
"items": {
"type": "string"
}
},
"maintainers": {
"type": "array",
"description": "List of maintainers, other than the original author(s), that upkeep the package.",
Expand Down
5 changes: 3 additions & 2 deletions poetry/masonry/builders/builder.py
Expand Up @@ -152,8 +152,9 @@ def find_files_to_add(self, exclude_build=True): # type: (bool) -> list
)
to_add.append(Path("pyproject.toml"))

# If a license file exists, add it
for license_file in self._path.glob("LICENSE*"):
# If any license files exist, add them.
for license_filename in self._package.license_files:
license_file = self._path / license_filename
self._io.write_line(
" - Adding: <comment>{}</comment>".format(
license_file.relative_to(self._path)
Expand Down
5 changes: 5 additions & 0 deletions poetry/masonry/builders/sdist.py
Expand Up @@ -139,6 +139,11 @@ def build_setup(self): # type: () -> bytes
else:
pass

license_files = list(map(str, self._package.license_files))
if license_files:
before.append("license_files = \\\n{}\n".format(pformat(license_files)))
extra.append("'license_files': license_files,")

if package_dir:
before.append("package_dir = \\\n{}\n".format(pformat(package_dir)))
extra.append("'package_dir': package_dir,")
Expand Down
8 changes: 5 additions & 3 deletions poetry/masonry/builders/wheel.py
Expand Up @@ -183,9 +183,11 @@ def _write_metadata(self, wheel):
with self._write_to_zip(wheel, self.dist_info + "/entry_points.txt") as f:
self._write_entry_points(f)

for base in ("COPYING", "LICENSE"):
for path in sorted(self._path.glob(base + "*")):
self._add_file(wheel, path, "%s/%s" % (self.dist_info, path.name))
for relative_license_path in self._package.license_files:
license_path = self._path / relative_license_path
self._add_file(
wheel, license_path, "%s/%s" % (self.dist_info, license_path.name)
)

with self._write_to_zip(wheel, self.dist_info + "/WHEEL") as f:
self._write_wheel_file(f)
Expand Down
1 change: 1 addition & 0 deletions poetry/packages/package.py
Expand Up @@ -58,6 +58,7 @@ def __init__(self, name, version, pretty_version=None):
self.documentation_url = None
self.keywords = []
self._license = None
self.license_files = set()
self.readme = None

self.source_name = ""
Expand Down
@@ -0,0 +1,2 @@
Module 1
========
@@ -0,0 +1,18 @@
[tool.poetry]
name = "the-package"
version = "4.5.6"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license-files = [
"the-first-license.txt",
"the-second-license.txt"
]


readme = "README.rst"


[tool.poetry.dependencies]
python = "3.6"
@@ -0,0 +1,20 @@
Copyright (c) 2018 Sébastien Eustace

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.
@@ -0,0 +1,20 @@
Copyright (c) 2018 Sébastien Eustace

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.
@@ -0,0 +1,3 @@
"""Example module"""

__version__ = "0.1"
@@ -0,0 +1,2 @@
Module 1
========
@@ -0,0 +1,16 @@
[tool.poetry]
name = "the-package"
version = "4.5.6"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license-files = [
"the-license.txt"
]

readme = "README.rst"


[tool.poetry.dependencies]
python = "3.6"
@@ -0,0 +1,20 @@
Copyright (c) 2018 Sébastien Eustace

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.
@@ -0,0 +1,3 @@
"""Example module"""

__version__ = "0.1"
20 changes: 20 additions & 0 deletions tests/masonry/builders/fixtures/multiple-licenses/COPYING.txt
@@ -0,0 +1,20 @@
Copyright (c) 2018 Sébastien Eustace

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.
20 changes: 20 additions & 0 deletions tests/masonry/builders/fixtures/multiple-licenses/LICENSE.txt
@@ -0,0 +1,20 @@
Copyright (c) 2018 Sébastien Eustace

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.
2 changes: 2 additions & 0 deletions tests/masonry/builders/fixtures/multiple-licenses/README.rst
@@ -0,0 +1,2 @@
Module 1
========
13 changes: 13 additions & 0 deletions tests/masonry/builders/fixtures/multiple-licenses/pyproject.toml
@@ -0,0 +1,13 @@
[tool.poetry]
name = "the-package"
version = "4.5.6"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]

readme = "README.rst"


[tool.poetry.dependencies]
python = "3.6"
@@ -0,0 +1,3 @@
"""Example module"""

__version__ = "4.5.6"
73 changes: 73 additions & 0 deletions tests/masonry/builders/test_complete.py
Expand Up @@ -395,6 +395,79 @@ def test_package_src():
zip.close()


def test_multiple_licenses():
module_path = fixtures_dir / "multiple-licenses"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()

sdist = module_path / "dist" / "the-package-4.5.6.tar.gz"
wheel = module_path / "dist" / "the_package-4.5.6-py3-none-any.whl"

assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
names = tar.getnames()
assert len(names) == len(set(names))
assert "the-package-4.5.6/LICENSE.txt" in names
assert "the-package-4.5.6/COPYING.txt" in names

assert wheel.exists()
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
assert len(names) == len(set(names))
assert "the_package-4.5.6.dist-info/LICENSE.txt" in names
assert "the_package-4.5.6.dist-info/COPYING.txt" in names


def test_explicit_license_config_single_license():
module_path = fixtures_dir / "explicit-license-config-single-license"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()

sdist = module_path / "dist" / "the-package-4.5.6.tar.gz"
wheel = module_path / "dist" / "the_package-4.5.6-py3-none-any.whl"

assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
names = tar.getnames()
assert len(names) == len(set(names))
assert "the-package-4.5.6/the-license.txt" in names

assert wheel.exists()
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
assert len(names) == len(set(names))
assert "the_package-4.5.6.dist-info/the-license.txt" in names


def test_explicit_license_config_multiple_licenses():
module_path = fixtures_dir / "explicit-license-config-multiple-licenses"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()

sdist = module_path / "dist" / "the-package-4.5.6.tar.gz"
wheel = module_path / "dist" / "the_package-4.5.6-py3-none-any.whl"

assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
names = tar.getnames()
assert len(names) == len(set(names))
assert "the-package-4.5.6/the-first-license.txt" in names
assert "the-package-4.5.6/the-second-license.txt" in names

assert wheel.exists()
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
assert len(names) == len(set(names))
assert "the_package-4.5.6.dist-info/the-first-license.txt" in names
assert "the_package-4.5.6.dist-info/the-second-license.txt" in names


def test_package_with_include(mocker):
module_path = fixtures_dir / "with-include"

Expand Down

0 comments on commit 391f024

Please sign in to comment.