Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

convert '~=' requirements specifier #184

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
65 changes: 58 additions & 7 deletions py2pack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ def fetch(args):

def _canonicalize_setup_data(data):

def _search_requires(req, pat):
for arr in req:
if arr[0] == pat:
return 1
return 0

if data.get('build-system', None):
# PEP 518: 'requires' field is mandatory
data['build_requires'] = py2pack.requires._requirements_sanitize(
Expand All @@ -160,14 +166,13 @@ def _canonicalize_setup_data(data):
if isinstance(setup_requires, str):
setup_requires = setup_requires.splitlines()
# canonicalize to build_requires
data["build_requires"] = ['setuptools', 'wheel'] + \
data["build_requires"] = [['setuptools'], ['wheel']] + \
py2pack.requires._requirements_sanitize(setup_requires)
else:
# no build_requires means most probably legacy setuptools
data["build_requires"] = ['setuptools']
if 'setuptools' in data['build_requires'] and 'wheel' not in data['build_requires']:
data['build_requires'] += ['wheel']

data["build_requires"] = [['setuptools']]
if _search_requires(data['build_requires'], 'setuptools') and not _search_requires(data['build_requires'], 'wheel'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search_requires function is not needed, this can be done with a list comprehension in a more pythonic way:

# Just the list of requires packages, without version information
require_names = [r[0] for r in data['build_requires']]
if 'setuptools' in require_names and 'wheel' not in require_names:

data['build_requires'] += [['wheel']]
install_requires = (
get_pyproject_table(data, "project.dependencies") or
get_pyproject_table(data, "tool.flit.metadata.requires") or
Expand Down Expand Up @@ -336,10 +341,56 @@ def _normalize_license(data):
def _prepare_template_env(template_dir):
# setup jinja2 environment with custom filters
env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir))

def _rpm_format_requires(req, pfx):
req[0] = pfx + req[0]
part1 = " ".join(req[0:3])
part2 = ""
if len(req) > 3:
req[3] = pfx + req[3]
part2 = ", " + " ".join(req[3:])
return part1 + part2

def _parenthesize_version(req):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: This function does complex require formatting, it could be great to have some comment with examples of what it does, input -> output so it will be easier to understand in the future.

ret = req[0].lower()
ver = []
if len(req) > 1:
if req[1] == '<':
req[1] = '<<'
if req[1] == '>':
req[1] = '>>'
ver.append(" (" + " ".join(req[1:3]) + ")")
if len(req) > 3:
if req[4] == '<':
req[4] = '<<'
if req[4] == '>':
req[4] = '>>'
ver.append(req[3].lower() + " (" + " ".join(req[4:6]) + ")")

if ver:
ret += ", ".join(ver)
return ret

def reject_pkg(s, req):
nms = list(map(lambda n: n[0], req))
ret = []
for n in s:
if n[0] not in nms:
ret.append(n)
return ret

env.filters['parenthesize_version'] = \
lambda s: re.sub('([=<>]+)(.+)', r' (\1 \2)', s)
lambda s: _parenthesize_version(s)
env.filters['basename'] = \
lambda s: s[s.rfind('/') + 1:]
env.filters['rpm_format_buildrequires'] = \
lambda s: " ".join(s[0:3])
env.filters['rpm_format_requires'] = \
lambda s, p: _rpm_format_requires(s, p)
env.filters['sort_requires'] = \
lambda s: sorted(s, key=lambda k: k[0].lower())
env.filters['reject_pkg'] = reject_pkg

return env


Expand All @@ -361,7 +412,7 @@ def generate(args):
args.template = file_template_list()[0]
if not args.filename:
args.filename = "python-" + args.name + '.' + args.template.rsplit('.', 1)[1] # take template file ending
print('generating spec file for {0}...'.format(args.name))
print('generating spec file for {0} using {1}...'.format(args.name, args.template))
data = args.fetched_data['info']
durl = newest_download_url(args)
source_url = data['source_url'] = (args.source_url or (durl and durl['url']))
Expand Down
18 changes: 17 additions & 1 deletion py2pack/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,20 @@ def _requirements_sanitize(req_list):
(Requirement(s.split("#", maxsplit=1)[0]) for s in req_list)
if _requirement_filter_by_marker(req)
)
return [" ".join(req) for req in filtered_req_list]
out_list = []
for req in filtered_req_list:
# Convert '~=' operator to something rpm and deb can understand
# abc ~= 1.1.1
# should be converted into something like (for rpm)
#
# Requires: python-abc >= 1.1.1, python-abc < 1.2
# BuildRequires: %{python_module abc >= 1.1.1}
if len(req) > 1 and req[1] == '~=':
req[1] = '>='
v = req[2].split('.')
v.pop()
Comment on lines +115 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version in a ~= requirement could be something without dots (abc ~= 3)? In that case, this can remove the version completely and will crash in the following line. But I'm not sure if that case is something that makes sense.

v[-1] = str(int(v[-1]) + 1)
req += [req[0], '<', ".".join(v)]
out_list.append(req)

return out_list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding this code correctly, the return now is a list of lists, so we should update the documentation of this method to match the new way of producing the output.

27 changes: 9 additions & 18 deletions py2pack/templates/fedora.spec
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,15 @@ Summary: {{ summary }}
License: {{ license }}
URL: {{ home_page }}
Source: {{ source_url|replace(version, '%{version}') }}

BuildRequires: pyproject-rpm-macros
BuildRequires: python-devel
%if %{undefined python_module}
%define python_module() python3dist(%1)
%endif

{%- set build_requires_plus_pip = ((build_requires if build_requires and build_requires is not none else []) +
['pip']) %}
{%- for req in build_requires_plus_pip |sort %}
BuildRequires: %{python_module {{ req }}}
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: python-devel {%- if requires_python %} {{ requires_python }} {% endif %}
{%- for req in requires %}
BuildRequires: {{ req|rpm_format_requires("python-") }}
Requires: {{ req|rpm_format_requires }}
{%- endfor %}
{%- if (install_requires and install_requires is not none) or (tests_require and tests_require is not none) %}
# SECTION test requirements
%if %{with test}
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|reject("in",build_requires)|sort %}
BuildRequires: %{python_module {{ req }}}
{%- for req in install_requires %}
BuildRequires: {{ req|rpm_format_requires("python-") }}
Requires: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endif %}
{%- if tests_require and tests_require is not none %}
Expand All @@ -50,7 +41,7 @@ Requires: %{python_module {{ req }}}
{%- if extras_require and extras_require is not none %}
{%- for reqlist in extras_require.values() %}
{%- for req in reqlist %}
Suggests: %{python_module {{ req }}}
Suggests: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endfor %}
{%- endif %}
Expand Down
12 changes: 7 additions & 5 deletions py2pack/templates/mageia.spec
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ Source: {{ source_url|replace(version, '%{version}') }}
BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot
BuildRequires: python-devel
{%- for req in requires %}
BuildRequires: python-{{ req|lower }}
Requires: pyhton-{{ req|lower }}
BuildRequires: {{ req|rpm_format_requires("python-")|lower }}
Requires: {{ req|rpm_format_requires("python-")|lower }}
{%- endfor %}
{%- for req in install_requires %}
BuildRequires: python-{{ req|lower }}
Requires: python-{{ req|lower }}
BuildRequires: {{ req|rpm_format_requires("python-")|lower }}
Requires: {{ req|rpm_format_requires("python-")|lower }}
{%- endfor %}
{%- if extras_require %}
{%- for reqlist in extras_require.values() %}
{%- for req in reqlist %}
Suggests: python-{{ req|lower }}
Suggests: {{ req|rpm_format_requires("python-")|lower }}
{%- endfor %}
{%- endfor %}
{%- endif %}
Expand Down Expand Up @@ -51,8 +51,10 @@ rm -rf %{buildroot}
{%- if doc_files %}
%doc {{ doc_files|join(" ") }}
{%- endif %}
{%- if scripts %}
{%- for script in scripts %}
%{_bindir}/{{ script }}
{%- endfor %}
{%- endif %}
%{python_sitelib}/*

8 changes: 4 additions & 4 deletions py2pack/templates/opensuse-legacy.spec
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ Source: {{ source_url|replace(version, '%{version}') }}
BuildRequires: python-setuptools
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|sort %}
BuildRequires: python-{{ req }}
BuildRequires: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endif %}
{%- if tests_require and tests_require is not none %}
# test requirements
{%- for req in tests_require|sort %}
BuildRequires: python-{{ req }}
BuildRequires: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endif %}
{%- if source_url.endswith('.zip') %}
BuildRequires: unzip
{%- endif %}
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|sort %}
Requires: python-{{ req }}
Requires: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endif %}
{%- if extras_require and extras_require is not none %}
{%- for reqlist in extras_require.values() %}
{%- for req in reqlist %}
Suggests: python-{{ req }}
Suggests: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endfor %}
{%- endif %}
Expand Down
6 changes: 3 additions & 3 deletions py2pack/templates/opensuse.dsc
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ Binary: python-{{ name|lower }}
Maintainer: {{ user_name }}
Architecture: any
Standards-Version: 3.7.1
Build-Depends: debhelper (>= 4.0.0), python-dev{% for req in requires %}, python-{{ req|lower|parenthesize_version }}{% endfor %}{% for req in install_requires %}, python-{{ req|lower|parenthesize_version }}{% endfor %}
Build-Depends: debhelper (>= 4.0.0), python-dev{% for req in requires %}, python-{{ req|parenthesize_version }}{% endfor %}{% for req in install_requires|reject_pkg(build_requires) %}, python-{{ req|parenthesize_version }}{% endfor %}{% for req in build_requires %}, python-{{ req|parenthesize_version }}{% endfor %}{% for req in tests_require|reject_pkg(build_requires) %}, python-{{ req|parenthesize_version }}{% endfor %}
{%- if requires or install_requires %}
Depends: {% for req in requires %}python-{{ req|lower|parenthesize_version }}{{ ', ' if not loop.last or install_requires }}{% endfor %}{% for req in install_requires %}python-{{ req|lower|parenthesize_version }}{{ ', ' if not loop.last }}{% endfor %}
Depends: {% for req in requires %}python-{{ req|parenthesize_version }}{{ ', ' if not loop.last or install_requires }}{% endfor %}{% for req in install_requires %}python-{{ req|parenthesize_version }}{{ ', ' if not loop.last }}{% endfor %}
{%- endif %}
{%- if extras_require %}
Suggests: {% for reqlist in extras_require.values() %}{% for req in reqlist %}python-{{ req|lower|parenthesize_version }}{{ ', ' if not loop.last }}{%- endfor %}{{ ', ' if not loop.last }}{%- endfor %}
Suggests: {% for reqlist in extras_require.values() %}{% for req in reqlist %}python-{{ req|parenthesize_version }}{{ ', ' if not loop.last }}{%- endfor %}{{ ', ' if not loop.last }}{%- endfor %}
{%- endif %}

20 changes: 10 additions & 10 deletions py2pack/templates/opensuse.spec
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ URL: {{ home_page }}
Source: {{ source_url|replace(version, '%{version}') }}
BuildRequires: python-rpm-macros
{%- set build_requires_plus_pip = ((build_requires if build_requires and build_requires is not none else []) +
['pip']) %}
{%- for req in build_requires_plus_pip |sort %}
BuildRequires: %{python_module {{ req }}}
[['pip']]) %}
{%- for req in build_requires_plus_pip|sort_requires %}
BuildRequires: %{python_module {{ req|rpm_format_buildrequires }}}
{%- endfor %}
{%- if (install_requires and install_requires is not none) or (tests_require and tests_require is not none) %}
# SECTION test requirements
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|reject("in",build_requires)|sort %}
BuildRequires: %{python_module {{ req }}}
{%- for req in install_requires|reject_pkg(build_requires)|sort_requires %}
BuildRequires: %{python_module {{ req|rpm_format_buildrequires }}}
{%- endfor %}
{%- endif %}
{%- if tests_require and tests_require is not none %}
{%- for req in tests_require|sort|reject("in",build_requires|sort) %}
BuildRequires: %{python_module {{ req }}}
{%- for req in tests_require|reject_pkg(build_requires)|sort_requires %}
BuildRequires: %{python_module {{ req|rpm_format_buildrequires }}}
{%- endfor %}
{%- endif %}
# /SECTION
Expand All @@ -48,14 +48,14 @@ BuildRequires: unzip
{%- endif %}
BuildRequires: fdupes
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|sort %}
Requires: python-{{ req }}
{%- for req in install_requires|sort_requires %}
Requires: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endif %}
{%- if extras_require and extras_require is not none %}
{%- for reqlist in extras_require.values() %}
{%- for req in reqlist %}
Suggests: python-{{ req }}
Suggests: {{ req|rpm_format_requires("python-") }}
{%- endfor %}
{%- endfor %}
{%- endif %}
Expand Down
9 changes: 5 additions & 4 deletions py2pack/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@ def get_setuptools_scripts(data):
Returns:
list of script names
"""
entry_points = data.get('entry_points', None)
if isinstance(entry_points, str):
eps = EntryPoints(EntryPoints._from_text(entry_points))
elif isinstance(entry_points, dict):
if "entry_points" not in data or not data['entry_points']:
return []
if isinstance(data["entry_points"], str):
eps = EntryPoints(EntryPoints._from_text(data["entry_points"]))
elif isinstance(data["entry_points"], dict):
Comment on lines +132 to +136
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change break the following code, because entry_points is not defined and it's used below.

And I think this change is not needed, it's doing the same. If data['entry_points'] is None the if isinstance(data["entry_points"], str): and elif isinstance(data["entry_points"], dict): will be false so it'll go to the else clause and will return []

eps = EntryPoints([EntryPoint(*map(str.strip, entry.split("=", 1)), groupname)
for groupname, group in entry_points.items()
for entry in group
Expand Down
75 changes: 75 additions & 0 deletions test/examples/sampleproject-fedora.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#
# spec file for package python-sampleproject
#
# Copyright (c) __YEAR__ __USER__.
#

Name: python-sampleproject
Version: 3.0.0
Release: 0
Summary: A sample Python project
License: Copyright (c) 2016 The Python Packaging Authority (PyPA) 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. (FIXME:No SPDX)
URL:
Source: https://files.pythonhosted.org/packages/source/s/sampleproject/sampleproject-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: python-devel >=3.7

%description
# A sample Python project

![Python Logo](https://www.python.org/static/community_logos/python-logo.png "Sample inline image")

A sample project that exists as an aid to the [Python Packaging User
Guide][packaging guide]'s [Tutorial on Packaging and Distributing
Projects][distribution tutorial].

This project does not aim to cover best practices for Python project
development as a whole. For example, it does not provide guidance or tool
recommendations for version control, documentation, or testing.

[The source for this project is available here][src].

The metadata for a Python project is defined in the `pyproject.toml` file,
an example of which is included in this project. You should edit this file
accordingly to adapt this sample project to your needs.

----

This is the README file for the project.

The file should use UTF-8 encoding and can be written using
[reStructuredText][rst] or [markdown][md use] with the appropriate [key set][md
use]. It will be used to generate the project webpage on PyPI and will be
displayed as the project homepage on common code-hosting services, and should be
written for that purpose.

Typical contents for this file would include an overview of the project, basic
usage examples, etc. Generally, including the project changelog in here is not a
good idea, although a simple “What's New” section for the most recent version
may be appropriate.

[packaging guide]: https://packaging.python.org
[distribution tutorial]: https://packaging.python.org/tutorials/packaging-projects/
[src]: https://github.com/pypa/sampleproject
[rst]: http://docutils.sourceforge.net/rst.html
[md]: https://tools.ietf.org/html/rfc7764#section-3.5 "CommonMark variant"
[md use]: https://packaging.python.org/specifications/core-metadata/#description-content-type-optional


%prep
%setup -q -n sampleproject-%{version}

%build
python setup.py build

%install
python setup.py install --prefix=%{_prefix} --root=%{buildroot}

%clean
rm -rf %{buildroot}

%files
%defattr(-,root,root,-)
%{python_sitelib}/*

%changelog
Loading
Loading