Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ help: ## display this message
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)

generate: ## generate environment for tests
cd pyroma/testdata/complete;python setup.py sdist --formats=bztar,gztar,tar,zip
cd pyroma/testdata/complete;$(bin_dir)/python setup.py sdist --formats=bztar,gztar,tar,zip
cp pyroma/testdata/complete/dist/complete-1.0.dev1.* pyroma/testdata/distributions/

tests: devenv generate ## run tests
Expand Down
124 changes: 69 additions & 55 deletions pyroma/ratings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import io
import re
import os
from collections import defaultdict

from docutils.core import publish_parts
from docutils.utils import SystemMessage
Expand All @@ -39,31 +38,6 @@
"Your cheese is so fresh most people think it's a cream: Mascarpone",
]

SHORT_NAME_RE = re.compile(r"\(.*?\)")


def get_code_licenses():
licenses = [each for each in list(CLASSIFIERS) if each.startswith("License")]
code_map = defaultdict(set)
for license in licenses:
short_name = SHORT_NAME_RE.findall(license)
if short_name:
short_name = short_name[0][1:-1]
code_map[short_name].add(license)
if short_name.startswith("GPL"):
code_map["GPL"].add(license)
elif short_name.startswith("LGPL"):
code_map["LGPL"].add(license)
elif "Zope" in license:
code_map["ZPL"].add(license)
elif "MIT License" in license:
code_map["MIT"].add(license)

return code_map


CODE_LICENSES = get_code_licenses()


class BaseTest:
fatal = False
Expand Down Expand Up @@ -349,15 +323,14 @@ def test(self, data):
license = data.get("license")
license_expression = data.get("license-expression")
classifiers = data.get("classifier", [])
licenses = set()
for classifier in classifiers:
parts = [p.strip() for p in classifier.split("::")]
if parts[0] == "License":
# license classifiers exist
licenses.add(classifier)
has_license_classifier = any(c.startswith("License") for c in classifiers)

if not license and not license_expression and not licenses:
self._message = "Your package does neither have a license field nor any license classifiers."
if not license and not license_expression and not has_license_classifier:
self._message = (
"You should specify a license for your package with the 'License-Expression' field. "
"See https://packaging.python.org/en/latest/specifications/core-metadata/#license-expression "
"for more information."
)
return False

if license and license_expression:
Expand All @@ -367,17 +340,16 @@ def test(self, data):
)
return False

if license_expression and licenses:
if license_expression and has_license_classifier:
self._message = (
"Specifying both a License-Expression and license classifiers is ambiguous, deprecated, "
"and may be rejected by package indices."
)
return False

if license in CODE_LICENSES:
if not CODE_LICENSES[license].intersection(licenses):
self._message = f"The license '{license}' specified is not listed in your classifiers."
return False
if has_license_classifier:
self._message = "Using license classifiers is deprecated in favour of the license-expression field."
return False

return True

Expand Down Expand Up @@ -406,13 +378,13 @@ def message(self):


class SDist(BaseTest):
weight = 100

def test(self, data):
if "_has_sdist" not in data:
# We aren't checking on PyPI
self.weight = 0
return None

self.weight = 100
return data["_has_sdist"]

def message(self):
Expand Down Expand Up @@ -476,16 +448,17 @@ def message(self):


class MissingBuildSystem(BaseTest):
# The build system tests give only negative weight, as they are effectively required
# for a working package, so passing them shouldn't give you a better rating,
# but failing them should give you a worse rating.
weight = 0

def test(self, data):
if "_missing_build_system" in data:
# The build system tests give only negative weight, as they are effectively required
# for a working package, so passing them shouldn't give you a better rating,
# but failing them should give you a worse rating.
self.weight = 400
return False

self.weight = 0
return True

def message(self):
return (
"You seem to neither have a setup.py, nor a pyproject.toml, only setup.cfg.\n"
Expand All @@ -495,11 +468,10 @@ def message(self):


class MissingPyProjectToml(BaseTest):
# This may not yet be required, but it will be in the future, so we treat
# give it a negative rating when it fails, but not a positive rating when it succeeds.
weight = 0

def test(self, data):
# This may not yet be required, but it will be in the future, so we treat
# give it a negative rating when it fails, but not a positive rating when it succeeds.
self.weight = 0
if "_missing_build_system" in data or "_missing_pyproject_toml" in data:
self.weight = 100
return False
Expand All @@ -516,12 +488,12 @@ def message(self):


class PyprojectTomlValid(BaseTest):
# The build system tests give only negative weight, as they are effectively required
# for a working package, so passing them shouldn't give you a better rating,
# but failing them should give you a worse rating.
weight = 0

def test(self, data):
# The build system tests give only negative weight, as they are effectively required
# for a working package, so passing them shouldn't give you a better rating,
# but failing them should give you a worse rating.
self.weight = 0

# Only test if we have a path and pyproject.toml exists
if "_path" not in data:
return None
Expand All @@ -547,6 +519,47 @@ def message(self):
)


class DeprecatedMetadataFields(BaseTest):
weight = 50

_deprecated = {
"home-page": ("project-url", "1.2"),
"download-url": ("project-url", "1.2"),
"requires": ("requires-dist", "1.2"),
"provides": ("provides-dist", "1.2"),
"obsoletes": ("obsoletes-dist", "1.2"),
"license": ("license-expression", "2.4"),
}

def _version_at_least(self, data, minimum):
metadata_version = data.get("metadata-version")
if not metadata_version:
return True

try:
current = tuple(int(p) for p in str(metadata_version).split("."))
required = tuple(int(p) for p in str(minimum).split("."))
except ValueError:
return True

return current >= required

def test(self, data):
self._warnings = []

for deprecated, (replacement, deprecated_since) in self._deprecated.items():
if not self._version_at_least(data, deprecated_since):
continue

if data.get(deprecated) and not data.get(replacement):
self._warnings.append(f"The metadata field '{deprecated}' is deprecated; use '{replacement}' instead.")

return not self._warnings

def message(self):
return "\n".join(self._warnings)


BUILD_SYSTEM_TESTS = [MissingBuildSystem(), MissingPyProjectToml(), PyprojectTomlValid()]

ALL_TESTS = [
Expand All @@ -572,6 +585,7 @@ def message(self):
ValidREST(),
BusFactor(),
DevStatusClassifier(),
DeprecatedMetadataFields(),
]


Expand Down
21 changes: 21 additions & 0 deletions pyroma/testdata/complete/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License

Copyright (c) 2011 Lennart Regebro

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.
34 changes: 32 additions & 2 deletions pyroma/testdata/complete/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
[tool.black]
line-length = 120
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[project]
name = "complete"
version = "1.0.dev1"
description = "This is a test package for pyroma."
readme = "README.txt"
authors = [{name = "Lennart Regebro", email = "regebro@gmail.com"}]
classifiers = [
"Development Status :: 6 - Mature",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.1",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
]
keywords = [
"pypi",
"quality",
"example",
]
requires-python = ">=2.6"
dependencies = ["zope.event"]
license = "MIT"
license-files = ["LICENSE.txt"]

[project.urls]
repository = "https://github.com/regebro/pyroma"
homepage = "https://github.com/regebro/pyroma"
36 changes: 2 additions & 34 deletions pyroma/testdata/complete/setup.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,4 @@
from setuptools import setup, find_packages
from setuptools import setup

version = "1.0"

with open("README.txt", encoding="UTF-8") as readme:
long_description = readme.read()

setup(
name="complete",
version=version,
description="This is a test package for pyroma.",
long_description=long_description,
python_requires=">=2.6",
classifiers=[
"Development Status :: 6 - Mature",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.1",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"License :: OSI Approved :: MIT License",
],
keywords=["pypi", "quality", "example"],
author="Lennart Regebro",
author_email="regebro@gmail.com",
url="https://github.com/regebro/pyroma",
project_urls={"Source Code": "https://github.com/regebro/pyroma"},
license="MIT",
packages=find_packages(exclude=["ez_setup", "examples", "tests"]),
include_package_data=True,
install_requires=["zope.event"],
tests_require=["six"],
setup_requires=["setuptools"],
zip_safe=True,
)
setup()
Binary file modified pyroma/testdata/distributions/complete-1.0.dev1.tar
Binary file not shown.
Binary file modified pyroma/testdata/distributions/complete-1.0.dev1.tar.bz2
Binary file not shown.
Binary file modified pyroma/testdata/distributions/complete-1.0.dev1.tar.gz
Binary file not shown.
Binary file modified pyroma/testdata/distributions/complete-1.0.dev1.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion pyroma/testdata/jsondata/complete.json

Large diffs are not rendered by default.

Loading
Loading