diff --git a/README.md b/README.md index 36a41c9..4947add 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Configuration data is stored in a TOML file. | Field | Description | Type | | -------- | -------- | -------- | | pypi_name | package name as stored in PyPI | string | -| python_name | pypi_name prepended with `python-` | string | +| python_name | pypi_name prepended with `python-` and alternative Python version, if `python_alt_version` is defined| string | | archive_name | source tarball name, stripped of version and file extension | string | | version | package version to create spec file for (RPM format) | string | | pypi_version | package version string as in PyPI, '%{version}' if the same as version | string @@ -90,7 +90,8 @@ Configuration data is stored in a TOML file. | source | %{pypi_source} macro with optional arguments (tarball URL can be used instead) | string | | description | long package description | multiline string | | extras | extra subpackages names | list of strings | -| archful | package contains compiled extensions, implies not using `BuildArch: noarch` and adding `BuildRequires: gcc` | bool +| archful | package contains compiled extensions, implies not using `BuildArch: noarch` and adding `BuildRequires: gcc` | bool | +| python_alt_version | specific Python version to create the spec file for, e.g. 3.9, 3.10, 3.12 | string | ### Optional fields diff --git a/pyp2spec/conf2spec.py b/pyp2spec/conf2spec.py index 7e0c310..16358c9 100644 --- a/pyp2spec/conf2spec.py +++ b/pyp2spec/conf2spec.py @@ -133,7 +133,12 @@ def wrap_description(config): break_long_words=False ) -def fill_in_template(config, python_version): + +def python3_pkgversion_or_3(config): + return "%{python3_pkgversion}" if config.get_string("python_alt_version") else "3" + + +def fill_in_template(config): """Return template rendered with data from config file.""" with (files("pyp2spec") / TEMPLATE_FILENAME).open("r", encoding="utf-8") as f: @@ -152,11 +157,12 @@ def fill_in_template(config, python_version): name=config.get_string("pypi_name"), python_name=config.get_string("python_name"), pypi_version=config.get_string("pypi_version"), - python_version=python_version, + python_alt_version=config.get_string("python_alt_version"), source=config.get_string("source"), summary=config.get_string("summary"), test_method=generate_check(config), test_top_level=config.get_bool("test_top_level"), + python3_pkgversion=python3_pkgversion_or_3(config), url=config.get_string("url"), version=config.get_string("version"), ) @@ -164,11 +170,11 @@ def fill_in_template(config, python_version): return result -def save_spec_file(config, output, python_version): +def save_spec_file(config, output): """Save the spec file in the current directory if custom output is not set. Return the saved file name.""" - result = fill_in_template(config, python_version) + result = fill_in_template(config) if output is None: output = config.get_string("python_name") + ".spec" with open(output, "w", encoding="utf-8") as spec_file: @@ -177,10 +183,10 @@ def save_spec_file(config, output, python_version): return output -def create_spec_file(config_file, spec_output=None, python_version=None): +def create_spec_file(config_file, spec_output=None): """Create and save the generate spec file.""" config = ConfigFile(config_file) - return save_spec_file(config, spec_output, python_version) + return save_spec_file(config, spec_output) @click.command() @@ -190,15 +196,9 @@ def create_spec_file(config_file, spec_output=None, python_version=None): "-o", help="Provide custom output for spec file", ) -@click.option( - "--python-version", - "-p", - help="Specify specific python version to build for, e.g 3.11", -) - -def main(config, spec_output, python_version): +def main(config, spec_output): try: - create_spec_file(config, spec_output, python_version) + create_spec_file(config, spec_output) except (Pyp2specError, NotImplementedError) as exc: click.secho(f"Fatal exception occurred: {exc}", fg="red") sys.exit(1) diff --git a/pyp2spec/pyp2conf.py b/pyp2spec/pyp2conf.py index eaca0e1..47c7617 100644 --- a/pyp2spec/pyp2conf.py +++ b/pyp2spec/pyp2conf.py @@ -87,11 +87,30 @@ def _get_package_version_metadata(self): error_str = f"Package `{self.package_name}` or version `{self.version}` was not found on PyPI" return self._get_from_url(pkg_index, error_str) - def python_name(self): + def python_name(self, *, python_alt_version=None): + """Create a component name for the specfile. + + Prepend the name with 'python' (unless it already starts with it). + Add the Python alternative version, if it's defined. + + Valid outcomes: + package name: foo, python_alt_version: 3.11 + -> python3.11-foo + + package name: foo, python_alt_version: None + -> python-foo + + package name: python-foo, python_alt_version: 3.12 + -> python3.12-foo + + package name: python-foo, python_alt_version: None + -> python-foo + """ + + alt_version = "" if python_alt_version is None else python_alt_version if self.pypi_name.startswith("python"): - return self.pypi_name - else: - return f"python-{self.pypi_name}" + return self.pypi_name.replace("python", f"python{alt_version}") + return f"python{alt_version}-{self.pypi_name}" def source(self): """Return valid pypi_source RPM macro. @@ -335,6 +354,7 @@ def create_config_contents( license=None, compliant=False, top_level=False, + python_alt_version=None, ): """Use `package` and provided kwargs to create the whole config contents. Return contents dictionary. @@ -372,6 +392,10 @@ def create_config_contents( if archful := pkg.is_archful(): click.secho("Package is archful - you may need to specify additional build requirements", fg="magenta") + if python_alt_version is not None: + click.secho(f"Assuming build for Python: {python_alt_version}", fg="yellow") + contents["python_alt_version"] = python_alt_version + contents["archful"] = archful contents["description"] = description contents["summary"] = summary @@ -379,7 +403,7 @@ def create_config_contents( contents["pypi_version"] = pkg.pypi_version_or_macro() contents["license"] = license contents["pypi_name"] = pkg.pypi_name - contents["python_name"] = pkg.python_name() + contents["python_name"] = pkg.python_name(python_alt_version=python_alt_version) contents["url"] = pkg.project_url() contents["source"] = pkg.source() contents["archive_name"] = pkg.archive_name() @@ -414,6 +438,7 @@ def create_config(options): license=options["license"], compliant=options["fedora_compliant"], top_level=options["top_level"], + python_alt_version=options["python_alt_version"] ) return save_config(contents, options["config_output"]) @@ -448,6 +473,10 @@ def pypconf_args(func): "--top-level", "-t", is_flag=True, help="Test only top-level modules in %check", ) + @click.option( + "--python-alt-version", "-p", + help="Provide specific Python version to build for, e.g 3.11", + ) @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) diff --git a/pyp2spec/template.spec b/pyp2spec/template.spec index 0a14da8..2208d82 100644 --- a/pyp2spec/template.spec +++ b/pyp2spec/template.spec @@ -1,5 +1,5 @@ -{% if python_version %} -%global python3_pkgversion {{ python_version }} +{% if python_alt_version -%} +%global python3_pkgversion {{ python_alt_version }} {% endif -%} Name: {{python_name}} @@ -15,11 +15,7 @@ Source: {{source}} {% if not archful %} BuildArch: noarch {%- endif %} -{% if python_version %} -BuildRequires: python%{python3_pkgversion}-devel -{% else -%} -BuildRequires: python3-devel -{% endif -%} +BuildRequires: python{{python3_pkgversion}}-devel {% for br in additional_build_requires -%} BuildRequires: {{br}} {% endfor %} @@ -29,25 +25,18 @@ BuildRequires: {{br}} {{description}}} %description %_description -{% if python_version %} -%package -n python%{python3_pkgversion}-{{name}} -{% else %} -%package -n python3-{{name}} -{% endif -%} + +{% if not python_alt_version -%} +%package -n python{{python3_pkgversion}}-{{name}} Summary: %{summary} -{% if python_version %} -%description -n python%{python3_pkgversion}-{{name}} %_description -{% else %} -%description -n python3-{{name}} %_description + +%description -n python{{python3_pkgversion}}-{{name}} %_description {% endif -%} + {% if extras %} # For official Fedora packages, review which extras should be actually packaged # See: https://docs.fedoraproject.org/en-US/packaging-guidelines/Python/#Extras -{%- if python_version %} -%pyproject_extras_subpkg -n python%{python3_pkgversion}-{{name}} {{extras}} -{% else %} -%pyproject_extras_subpkg -n python3-{{name}} {{extras}} -{% endif -%} +%pyproject_extras_subpkg -n python{{python3_pkgversion}}-{{name}} {{extras}} {% endif %} %prep @@ -77,11 +66,8 @@ Summary: %{summary} {% if test_method -%} {{test_method}} {% endif %} -{% if python_version %} -%files -n python%{python3_pkgversion}-{{name}} -f %{pyproject_files} -{% else %} -%files -n python3-{{name}} -f %{pyproject_files} -{% endif -%} + +%files -n python{{python3_pkgversion}}-{{name}} -f %{pyproject_files} {% if doc_files -%} %doc {{doc_files}} {% endif -%} diff --git a/tests/expected_specfiles/python3.9-pello.spec b/tests/expected_specfiles/python3.9-pello.spec new file mode 100644 index 0000000..7c3b791 --- /dev/null +++ b/tests/expected_specfiles/python3.9-pello.spec @@ -0,0 +1,58 @@ +%global python3_pkgversion 3.9 + +Name: python3.9-pello +Version: 1.0.4 +Release: %autorelease +Summary: An example Python Hello World package + +# Check if the automatically generated License and its spelling is correct for Fedora +# https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ +License: MIT-0 +URL: https://github.com/fedora-python/Pello +Source: %{pypi_source Pello} + +BuildArch: noarch +BuildRequires: python%{python3_pkgversion}-devel + + +# Fill in the actual package description to submit package to Fedora +%global _description %{expand: +This is package 'Pello' generated automatically by pyp2spec.} + +%description %_description + + +# For official Fedora packages, review which extras should be actually packaged +# See: https://docs.fedoraproject.org/en-US/packaging-guidelines/Python/#Extras +%pyproject_extras_subpkg -n python%{python3_pkgversion}-pello color + + +%prep +%autosetup -p1 -n Pello-%{version} + + +%generate_buildrequires +# Keep only those extras which you actually want to package or use during tests +%pyproject_buildrequires -x color + + +%build +%pyproject_wheel + + +%install +%pyproject_install +# For official Fedora packages, including files with '*' +auto is not allowed +# Replace it with a list of relevant Python modules/globs and list extra files in %%files +%pyproject_save_files '*' +auto + + +%check +%pyproject_check_import + + +%files -n python%{python3_pkgversion}-pello -f %{pyproject_files} + + +%changelog +%autochangelog \ No newline at end of file diff --git a/tests/fixtures/cassettes/test_pyp2conf.test_automatically_generated_config_with_alt_python_is_valid[Pello-1.0.4-3.9].json b/tests/fixtures/cassettes/test_pyp2conf.test_automatically_generated_config_with_alt_python_is_valid[Pello-1.0.4-3.9].json new file mode 100644 index 0000000..5bb6a21 --- /dev/null +++ b/tests/fixtures/cassettes/test_pyp2conf.test_automatically_generated_config_with_alt_python_is_valid[Pello-1.0.4-3.9].json @@ -0,0 +1 @@ +{"http_interactions": [{"request": {"body": {"encoding": "utf-8", "string": ""}, "headers": {"User-Agent": ["python-requests/2.31.0"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"]}, "method": "GET", "uri": "https://pypi.org/pypi/Pello/1.0.4/json"}, "response": {"body": {"encoding": "utf-8", "base64_string": "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"}, "headers": {"Connection": ["keep-alive"], "Content-Length": ["1940"], "Content-Type": ["application/json"], "Access-Control-Allow-Origin": ["*"], "Access-Control-Allow-Headers": ["Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since"], "Access-Control-Allow-Methods": ["GET"], "Access-Control-Max-Age": ["86400"], "Access-Control-Expose-Headers": ["X-PyPI-Last-Serial"], "X-PyPI-Last-Serial": ["18197067"], "Cache-Control": ["max-age=900, public"], "Content-Security-Policy": ["base-uri 'self'; block-all-mixed-content; connect-src 'self' https://api.github.com/repos/ https://api.github.com/search/issues https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com fastly-insights.com *.fastly-insights.com *.ethicalads.io https://api.pwnedpasswords.com https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/sre/mathmaps/ https://2p66nmmycsj3.statuspage.io; default-src 'none'; font-src 'self' fonts.gstatic.com; form-action 'self' https://checkout.stripe.com; frame-ancestors 'none'; frame-src 'none'; img-src 'self' https://pypi-camo.freetls.fastly.net/ https://*.google-analytics.com https://*.googletagmanager.com *.fastly-insights.com *.ethicalads.io ethicalads.blob.core.windows.net; script-src 'self' https://*.googletagmanager.com https://www.google-analytics.com https://ssl.google-analytics.com *.fastly-insights.com *.ethicalads.io 'sha256-U3hKDidudIaxBDEzwGJApJgPEf2mWk6cfMWghrAa6i0=' https://cdn.jsdelivr.net/npm/mathjax@3.2.2/ 'sha256-1CldwzdEg2k1wTmf7s5RWVd7NMXI/7nxxjJM2C4DqII=' 'sha256-0POaN8stWYQxhzjKS+/eOfbbJ/u4YHO5ZagJvLpMypo='; style-src 'self' fonts.googleapis.com *.ethicalads.io 'sha256-2YHqZokjiizkHi1Zt+6ar0XJ0OeEy/egBnlm+MDMtrM=' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' 'sha256-JLEjeN9e5dGsz5475WyRaoA4eQOdNPxDIeUhclnJDCE=' 'sha256-mQyxHEuwZJqpxCw3SLmc4YOySNKXunyu2Oiz1r3/wAE=' 'sha256-OCf+kv5Asiwp++8PIevKBYSgnNLNUZvxAp4a7wMLuKA=' 'sha256-h5LOiLhk6wiJrGsG5ItM0KimwzWQH/yAcmoJDJL//bY='; worker-src *.fastly-insights.com"], "Referrer-Policy": ["origin-when-cross-origin"], "Content-Encoding": ["gzip"], "ETag": ["\"OGQh87C7R7c65QDkessIGQ\""], "Accept-Ranges": ["bytes"], "Date": ["Fri, 05 Apr 2024 10:52:17 GMT"], "X-Served-By": ["cache-iad-kjyo7100113-IAD, cache-fra-eddf8230042-FRA"], "X-Cache": ["HIT, HIT"], "X-Cache-Hits": ["5, 0"], "X-Timer": ["S1712314338.931780,VS0,VE1"], "Vary": ["Accept-Encoding"], "Strict-Transport-Security": ["max-age=31536000; includeSubDomains; preload"], "X-Frame-Options": ["deny"], "X-XSS-Protection": ["1; mode=block"], "X-Content-Type-Options": ["nosniff"], "X-Permitted-Cross-Domain-Policies": ["none"], "Permissions-Policy": ["publickey-credentials-create=(self),publickey-credentials-get=(self),accelerometer=(),ambient-light-sensor=(),autoplay=(),battery=(),camera=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),identity-credentials-get=(),idle-detection=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),otp-credentials=(),payment=(),picture-in-picture=(),screen-wake-lock=(),serial=(),speaker-selection=(),storage-access=(),usb=(),web-share=(),xr-spatial-tracking=()"]}, "status": {"code": 200, "message": "OK"}, "url": "https://pypi.org/pypi/Pello/1.0.4/json"}, "recorded_at": "2024-04-05T10:52:17"}], "recorded_with": "betamax/0.9.0"} \ No newline at end of file diff --git a/tests/test_conf2spec.py b/tests/test_conf2spec.py index c3eaeaf..f1d5500 100644 --- a/tests/test_conf2spec.py +++ b/tests/test_conf2spec.py @@ -121,7 +121,8 @@ def test_archful_flag_is_loaded(config_dir, conf, expected): "default_python-click.conf", "customized_markdown-it-py.conf", "customized_python-sphinx.conf", - "default_python-numpy.conf" + "default_python-numpy.conf", + "default_python3.9-pello.conf", ] ) def test_default_generated_specfile(file_regression, config_dir, conf): diff --git a/tests/test_configs/default_python3.9-pello.conf b/tests/test_configs/default_python3.9-pello.conf new file mode 100644 index 0000000..9d2e559 --- /dev/null +++ b/tests/test_configs/default_python3.9-pello.conf @@ -0,0 +1,15 @@ +python_alt_version = "3.9" +archful = false +description = "This is package 'Pello' generated automatically by pyp2spec." +summary = "An example Python Hello World package" +version = "1.0.4" +pypi_version = "%{version}" +license = "MIT-0" +pypi_name = "pello" +python_name = "python3.9-pello" +url = "https://github.com/fedora-python/Pello" +source = "%{pypi_source Pello}" +archive_name = "Pello" +extras = [ + "color", +] diff --git a/tests/test_pyp2conf.py b/tests/test_pyp2conf.py index 1ed1b4f..a478bdb 100644 --- a/tests/test_pyp2conf.py +++ b/tests/test_pyp2conf.py @@ -38,6 +38,28 @@ def test_automatically_generated_config_is_valid(betamax_parametrized_session, p assert config == loaded_contents +@pytest.mark.parametrize("package, version, alt_python", + [ + ("Pello", "1.0.4", "3.9"), + ] +) +def test_automatically_generated_config_with_alt_python_is_valid( + betamax_parametrized_session, package, version, alt_python + ): + """Run the config rendering in fully automated mode and compare the results""" + config = create_config_contents( + package=package, + version=version, + python_alt_version=alt_python, + session=betamax_parametrized_session, + ) + + with open(f"tests/test_configs/default_python{alt_python}-{package.lower()}.conf", "rb") as config_file: + loaded_contents = tomllib.load(config_file) + + assert config == loaded_contents + + def test_config_with_customization_is_valid(betamax_session): """Get the upstream metadata and modify some fields to get the custom config file. This also tests the compliance with Fedora Legal data by making @@ -224,6 +246,22 @@ def test_capitalized_underscored_pypi_name_is_normalized(): assert pkg.python_name() == "python-awesome-testpkg" +@pytest.mark.parametrize( + ("pypi_name", "alt_version", "expected"), [ + ("foo", "3.10", "python3.10-foo"), + ("python-foo", "3.9", "python3.9-foo"), + ("python_foo", "3.12", "python3.12-foo"), + ("foo", None, "python-foo"), + ("python-foo", None, "python-foo"), + ("python_foo", None, "python-foo"), + ] +) +def test_python_name(pypi_name, alt_version, expected): + fake_pkg_data = {"info": {"name": pypi_name}} + pkg = PypiPackage(pypi_name, version="1.2.3", package_metadata=fake_pkg_data) + assert pkg.python_name(python_alt_version=alt_version) == expected + + @pytest.mark.parametrize( ("pypi_version", "rpm_version"), [ ("1.1.0", "1.1.0"),