diff --git a/py2pack/__init__.py b/py2pack/__init__.py index 37d39a8..7eaec12 100755 --- a/py2pack/__init__.py +++ b/py2pack/__init__.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# # Copyright (c) 2013, Sascha Peilicke # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +37,22 @@ get_metadata) from email import parser +import tarfile +import zipfile +from packaging.requirements import Requirement +from os.path import basename +from io import StringIO + + +try: + import distro + DEFAULT_TEMPLATE = { + 'fedora': 'fedora.spec', + 'debian': 'opensuse.dsc', + 'mageia': 'mageia.spec' + }.get(distro.id(), 'opensuse.spec') +except ModuleNotFoundError: + DEFAULT_TEMPLATE = 'opensuse.spec' def replace_string(output_string, replaces): @@ -68,8 +81,14 @@ def pypi_json(project, release=None): def pypi_text_file(pkg_info_path): - with open(pkg_info_path, 'r') as pkg_info_file: - pkg_info_lines = parser.Parser().parse(pkg_info_file) + pkg_info_file = open(pkg_info_path, 'r') + text = pypi_text_stream(pkg_info_file) + pkg_info_file.close() + return text + + +def pypi_text_stream(pkg_info_stream): + pkg_info_lines = parser.Parser().parse(pkg_info_stream) pkg_info_dict = {} for key, value in pkg_info_lines.items(): key = key.lower().replace('-', '_') @@ -85,8 +104,14 @@ def pypi_text_file(pkg_info_path): def pypi_json_file(file_path): - with open(file_path, 'r') as json_file: - js = json.load(json_file) + json_file = open(file_path, 'r') + js = pypi_json_stream(json_file) + json_file.close() + return js + + +def pypi_json_stream(json_stream): + js = json.load(json_stream) if 'info' not in js: js = {'info': js} if 'urls' not in js: @@ -94,6 +119,29 @@ def pypi_json_file(file_path): return js +def pypi_archive_file_tar(file_path): + with tarfile.open(file_path, 'r') as archive: + for member in archive.getmembers(): + if os.path.basename(member.name) == 'PKG-INFO': + return pypi_text_stream(StringIO(archive.extractfile(member).read().decode())) + raise KeyError('PKG-INFO not found on archive '+file_path) + + +def pypi_archive_file_zip(file_path): + with zipfile.ZipFile(file_path, 'r') as archive: + for member in archive.namelist(): + if os.path.basename(member) == 'PKG-INFO': + return pypi_text_stream(StringIO(archive.open(member).read().decode())) + raise KeyError('PKG-INFO not found on archive '+file_path) + + +def pypi_archive_file(file_path): + try: + return pypi_archive_file_tar(file_path) + except tarfile.ReadError: + return pypi_archive_file_zip(file_path) + + def _get_template_dirs(): """existing directories where to search for jinja2 templates. The order is important. The first found template from the first found dir wins!""" @@ -111,6 +159,22 @@ def _get_template_dirs(): ]) +def fix_data(data): + extra_from_req = re.compile(r'''\bextra\s+==\s+["']([^"']+)["']''') + extras = [] + data_info = data["info"] + requires_dist = data_info.get("requires_dist", []) or [] + provides_extra = data_info.get("provides_extra", []) or [] + for required_dist in requires_dist: + req = Requirement(required_dist) + if found := extra_from_req.search(str(req.marker)): + extras.append(found.group(1)) + provides_extra = list(sorted(set([*extras, *provides_extra]))) + data_info["requires_dist"] = requires_dist + data_info["provides_extra"] = provides_extra + data_info["classifiers"] = (data_info.get("classifiers", []) or []) + + def list_packages(args=None): """query the "Simple API" of PYPI for all packages and print them.""" print('listing all PyPI packages...') @@ -407,6 +471,12 @@ def generate(args): _normalize_license(data) + for field in ['summary', 'license', 'home_page', 'source_url', 'summary_no_ending_dot']: + if field not in data: + continue + # remove line breaks to avoid multiline rpm spec file + data[field + '_singleline'] = str(data[field]).replace('\n', ' ') + env = _prepare_template_env(_get_template_dirs()) template = env.get_template(args.template) result = template.render(data).encode('utf-8') # render template and encode properly @@ -425,23 +495,28 @@ def fetch_local_data(args): localfile = os.path.join(f'{args.name}.egg-info', 'PKG-INFO') if os.path.isfile(localfile): try: - data = pypi_json_file(localfile) - except json.decoder.JSONDecodeError: - data = pypi_text_file(localfile) + data = pypi_archive_file(localfile) + except Exception: + try: + data = pypi_json_file(localfile) + except json.decoder.JSONDecodeError: + data = pypi_text_file(localfile) args.fetched_data = data args.version = args.fetched_data['info']['version'] - return - fetch_data(args) + fix_data(data) + else: + fetch_data(args) def fetch_data(args): - args.fetched_data = pypi_json(args.name, args.version) - urls = args.fetched_data.get('urls', []) + data = args.fetched_data = pypi_json(args.name, args.version) + urls = data.get('urls', []) if len(urls) == 0: print(f"unable to find a suitable release for {args.name}!") sys.exit(1) else: args.version = args.fetched_data['info']['version'] # return current release number + fix_data(data) def newest_download_url(args): @@ -502,7 +577,7 @@ def main(): parser_generate.add_argument('--source-glob', help='source glob template') parser_generate.add_argument('--local', action='store_true', help='build from local package') parser_generate.add_argument('--localfile', default='', help='path to the local PKG-INFO or json metadata') - parser_generate.add_argument('-t', '--template', choices=file_template_list(), default='opensuse.spec', help='file template') + parser_generate.add_argument('-t', '--template', choices=file_template_list(), default=DEFAULT_TEMPLATE, help='file template') parser_generate.add_argument('-f', '--filename', help='spec filename (optional)') # TODO (toabctl): remove this is a later release parser_generate.add_argument( diff --git a/py2pack/templates/fedora.spec b/py2pack/templates/fedora.spec index 611cbe8..8ce5875 100644 --- a/py2pack/templates/fedora.spec +++ b/py2pack/templates/fedora.spec @@ -3,61 +3,31 @@ Name: python-%{pypi_name} Version: {{ version }} Release: %autorelease -Summary: {{ summary }} +# Fill in the actual package summary to submit package to Fedora +Summary: {{ summary_singleline }} # Check if the automatically generated License and its spelling is correct for Fedora # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ -License: {{ license }} -URL: {{ home_page }} -Source: {{ source_url|replace(version, '%{version}') }} +License: {{ license_singleline }} +URL: {{ home_page_singleline }} +Source: {{ source_url_singleline|replace(version, '%{version}') }} BuildRequires: pyproject-rpm-macros -BuildRequires: python-devel -%if %{undefined python_module} -%define python_module() python3dist(%1) -%endif +BuildRequires: python3-devel -{%- 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 }}} -{%- 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 }}} -{%- 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 }}} -{%- endfor %} -{%- endif %} -%endif -# /SECTION -{%- endif %} {%- if source_url.endswith('.zip') %} BuildRequires: unzip {%- endif %} BuildRequires: fdupes -{%- if install_requires and install_requires is not none %} -{%- for req in install_requires|sort %} -Requires: %{python_module {{ req }}} -{%- endfor %} -{%- endif %} -{%- if extras_require and extras_require is not none %} -{%- for reqlist in extras_require.values() %} -{%- for req in reqlist %} -Suggests: %{python_module {{ req }}} -{%- endfor %} -{%- endfor %} -{%- endif %} {%- if not has_ext_modules %} BuildArch: noarch {%- endif %} +{%- if provides_extra and provides_extra is not none %} +{%- set provides_extra_comma_separated_list = ','.join(provides_extra) %} +{%- set have_provides_extra = 1 %} +{%- endif %} + # Fill in the actual package description to submit package to Fedora %global _description %{expand: {{ description }}} @@ -69,22 +39,27 @@ Summary: %{summary} %description -n %{python_name} %_description +{%- if have_provides_extra %} +# 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_name} {{ provides_extra_comma_separated_list }} +{%- endif %} %prep %autosetup -p1 -n %{pypi_name}-%{version} + +%generate_buildrequires +# Keep only those extras which you actually want to package or use during tests +%pyproject_buildrequires {% if have_provides_extra %}-x {{ provides_extra_comma_separated_list }}{% endif %} + + %build %pyproject_wheel %install %pyproject_install -{%- set scripts_or_console_scripts = ( - (scripts|map('basename')|list if scripts and scripts is not none else []) + - (console_scripts if console_scripts and console_scripts is not none else [])) %} -#{%- for script in scripts_or_console_scripts %} -#%python_clone -a %{buildroot}%{_bindir}/{{ script }} -#{%- endfor %} # 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 @@ -93,6 +68,7 @@ Summary: %{summary} {%- if testsuite or test_suite %} %if %{with test} %check +%pyproject_check_import {%- if has_ext_modules %} %pytest_arch {%- else %} diff --git a/py2pack/templates/mageia.spec b/py2pack/templates/mageia.spec index 5c2140d..52cf170 100644 --- a/py2pack/templates/mageia.spec +++ b/py2pack/templates/mageia.spec @@ -3,11 +3,11 @@ Name: python-%{mod_name} Version: {{ version }} Release: %mkrel 1 -Url: {{ home_page }} -Summary: {{ summary }} -License: {{ license }} +Url: {{ home_page_singleline }} +Summary: {{ summary_singleline }} +License: {{ license_singleline }} Group: Development/Python -Source: {{ source_url|replace(version, '%{version}') }} +Source: {{ source_url_singleline|replace(version, '%{version}') }} BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot BuildRequires: python-devel {%- for req in requires %} diff --git a/py2pack/templates/opensuse-legacy.spec b/py2pack/templates/opensuse-legacy.spec index 349e50d..30b3858 100644 --- a/py2pack/templates/opensuse-legacy.spec +++ b/py2pack/templates/opensuse-legacy.spec @@ -18,10 +18,10 @@ Name: python-{{ name }} Version: {{ version }} Release: 0 -Summary: {{ summary_no_ending_dot|default(summary, true) }} -License: {{ license }} -URL: {{ home_page }} -Source: {{ source_url|replace(version, '%{version}') }} +Summary: {{ summary_no_ending_dot_singleline|default(summary, true) }} +License: {{ license_singleline }} +URL: {{ home_page_singleline }} +Source: {{ source_url_singleline|replace(version, '%{version}') }} BuildRequires: python-setuptools {%- if install_requires and install_requires is not none %} {%- for req in install_requires|sort %} diff --git a/py2pack/templates/opensuse.spec b/py2pack/templates/opensuse.spec index 0d7286a..62d953f 100644 --- a/py2pack/templates/opensuse.spec +++ b/py2pack/templates/opensuse.spec @@ -19,10 +19,10 @@ Name: python-{{ name }} Version: {{ version }} Release: 0 -Summary: {{ summary_no_ending_dot|default(summary, true) }} -License: {{ license }} -URL: {{ home_page }} -Source: {{ source_url|replace(version, '%{version}') }} +Summary: {{ summary_no_ending_dot_singleline|default(summary, true) }} +License: {{ license_singleline }} +URL: {{ home_page_singleline }} +Source: {{ source_url_singleline|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']) %} diff --git a/python-setuptools-scm.conf b/python-setuptools-scm.conf new file mode 100644 index 0000000..bd9ec8d --- /dev/null +++ b/python-setuptools-scm.conf @@ -0,0 +1,15 @@ +license = "MIT" +archful = false +summary = "the blessed package to manage your versions by scm tags" +pypi_version = "8.1.0" +pypi_name = "setuptools-scm" +python_name = "python-setuptools-scm" +url = "https://pypi.org/project/setuptools-scm/" +source = "PyPI" +archive_name = "setuptools_scm-8.1.0.tar.gz" +extras = [ + "docs", + "rich", + "test", +] +license_files_present = true diff --git a/python-setuptools-scm.spec b/python-setuptools-scm.spec new file mode 100644 index 0000000..08a2d51 --- /dev/null +++ b/python-setuptools-scm.spec @@ -0,0 +1,60 @@ +Name: python-setuptools-scm +Version: 8.1.0 +Release: %autorelease +# Fill in the actual package summary to submit package to Fedora +Summary: the blessed package to manage your versions by scm tags + +# Check if the automatically generated License and its spelling is correct for Fedora +# https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ +License: MIT +URL: https://pypi.org/project/setuptools-scm/ +Source: %{pypi_source setuptools_scm} + +BuildArch: noarch +BuildRequires: python3-devel + + +# Fill in the actual package description to submit package to Fedora +%global _description %{expand: +This is package 'setuptools-scm' generated automatically by pyp2spec.} + +%description %_description + +%package -n python3-setuptools-scm +Summary: %{summary} + +%description -n python3-setuptools-scm %_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 python3-setuptools-scm docs,rich,test + + +%prep +%autosetup -p1 -n setuptools_scm-%{version} + + +%generate_buildrequires +# Keep only those extras which you actually want to package or use during tests +%pyproject_buildrequires -x docs,rich,test + + +%build +%pyproject_wheel + + +%install +%pyproject_install +# Add top-level Python module names here as arguments, you can use globs +%pyproject_save_files -l ... + + +%check +%pyproject_check_import + + +%files -n python3-setuptools-scm -f %{pyproject_files} + + +%changelog +%autochangelog \ No newline at end of file diff --git a/test/examples/poetry-fedora-augmented.spec b/test/examples/poetry-fedora-augmented.spec new file mode 100644 index 0000000..064f295 --- /dev/null +++ b/test/examples/poetry-fedora-augmented.spec @@ -0,0 +1,174 @@ +%define pypi_name poetry +%define python_name python3-%{pypi_name} +Name: python-%{pypi_name} +Version: 1.5.1 +Release: %autorelease +# Fill in the actual package summary to submit package to Fedora +Summary: Python dependency management and packaging made easy. + +# Check if the automatically generated License and its spelling is correct for Fedora +# https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ +License: MIT +URL: None +Source: https://files.pythonhosted.org/packages/source/p/poetry/poetry-%{version}.tar.gz + +BuildRequires: pyproject-rpm-macros +BuildRequires: python3-devel +BuildRequires: fdupes +BuildArch: noarch + +# Fill in the actual package description to submit package to Fedora +%global _description %{expand: +# Poetry: Python packaging and dependency management made easy + +[![Stable Version](https://img.shields.io/pypi/v/poetry?label=stable)][PyPI Releases] +[![Pre-release Version](https://img.shields.io/github/v/release/python-poetry/poetry?label=pre-release&include_prereleases&sort=semver)][PyPI Releases] +[![Python Versions](https://img.shields.io/pypi/pyversions/poetry)][PyPI] +[![Download Stats](https://img.shields.io/pypi/dm/poetry)](https://pypistats.org/packages/poetry) +[![Discord](https://img.shields.io/discord/487711540787675139?logo=discord)][Discord] + +Poetry helps you declare, manage and install dependencies of Python projects, +ensuring you have the right stack everywhere. + +![Poetry Install](https://raw.githubusercontent.com/python-poetry/poetry/master/assets/install.gif) + +Poetry replaces `setup.py`, `requirements.txt`, `setup.cfg`, `MANIFEST.in` and `Pipfile` with a simple `pyproject.toml` +based project format. + +```toml +[tool.poetry] +name = "my-package" +version = "0.1.0" +description = "The description of the package" + +license = "MIT" + +authors = [ + "Sébastien Eustace " +] + +repository = "https://github.com/python-poetry/poetry" +homepage = "https://python-poetry.org" + +# README file(s) are used as the package description +readme = ["README.md", "LICENSE"] + +# Keywords (translated to tags on the package index) +keywords = ["packaging", "poetry"] + +[tool.poetry.dependencies] +# Compatible Python versions +python = ">=3.8" +# Standard dependency with semver constraints +aiohttp = "^3.8.1" +# Dependency with extras +requests = { version = "^2.28", extras = ["security"] } +# Version-specific dependencies with prereleases allowed +tomli = { version = "^2.0.1", python = "<3.11", allow-prereleases = true } +# Git dependencies +cleo = { git = "https://github.com/python-poetry/cleo.git", branch = "master" } +# Optional dependencies (installed by extras) +pendulum = { version = "^2.1.2", optional = true } + +# Dependency groups are supported for organizing your dependencies +[tool.poetry.group.dev.dependencies] +pytest = "^7.1.2" +pytest-cov = "^3.0" + +# ...and can be installed only when explicitly requested +[tool.poetry.group.docs] +optional = true +[tool.poetry.group.docs.dependencies] +Sphinx = "^5.1.1" + +# Python-style entrypoints and scripts are easily expressed +[tool.poetry.scripts] +my-script = "my_package:main" +``` + +## Installation + +Poetry supports multiple installation methods, including a simple script found at [install.python-poetry.org]. For full +installation instructions, including advanced usage of the script, alternate install methods, and CI best practices, see +the full [installation documentation]. + +## Documentation + +[Documentation] for the current version of Poetry (as well as the development branch and recently out of support +versions) is available from the [official website]. + +## Contribute + +Poetry is a large, complex project always in need of contributors. For those new to the project, a list of +[suggested issues] to work on in Poetry and poetry-core is available. The full [contributing documentation] also +provides helpful guidance. + +## Resources + +* [Releases][PyPI Releases] +* [Official Website] +* [Documentation] +* [Issue Tracker] +* [Discord] + + [PyPI]: https://pypi.org/project/poetry/ + [PyPI Releases]: https://pypi.org/project/poetry/#history + [Official Website]: https://python-poetry.org + [Documentation]: https://python-poetry.org/docs/ + [Issue Tracker]: https://github.com/python-poetry/poetry/issues + [Suggested Issues]: https://github.com/python-poetry/poetry/contribute + [Contributing Documentation]: https://python-poetry.org/docs/contributing + [Discord]: https://discord.com/invite/awxPgve + [install.python-poetry.org]: https://install.python-poetry.org + [Installation Documentation]: https://python-poetry.org/docs/#installation + +## Related Projects + +* [poetry-core](https://github.com/python-poetry/poetry-core): PEP 517 build-system for Poetry projects, and +dependency-free core functionality of the Poetry frontend +* [poetry-plugin-export](https://github.com/python-poetry/poetry-plugin-export): Export Poetry projects/lock files to +foreign formats like requirements.txt +* [poetry-plugin-bundle](https://github.com/python-poetry/poetry-plugin-bundle): Install Poetry projects/lock files to +external formats like virtual environments +* [install.python-poetry.org](https://github.com/python-poetry/install.python-poetry.org): The official Poetry +installation script +* [website](https://github.com/python-poetry/website): The official Poetry website and blog + +} + +%description %_description + +%package -n %{python_name} +Summary: %{summary} + +%description -n %{python_name} %_description + +%prep +%autosetup -p1 -n %{pypi_name}-%{version} + + +%generate_buildrequires +# Keep only those extras which you actually want to package or use during tests +%pyproject_buildrequires + + +%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 +%if %{with test} +%check +%pyproject_check_import +%pytest +%endif + +%files -n %{python_name} -f %{pyproject_files} + +%changelog +%autochangelog + diff --git a/test/examples/poetry-mageia.spec b/test/examples/poetry-mageia.spec new file mode 100644 index 0000000..8307c0e --- /dev/null +++ b/test/examples/poetry-mageia.spec @@ -0,0 +1,32 @@ +%define mod_name poetry + +Name: python-%{mod_name} +Version: 1.5.1 +Release: %mkrel 1 +Url: https://python-poetry.org/ +Summary: Python dependency management and packaging made easy. +License: MIT +Group: Development/Python +Source: https://files.pythonhosted.org/packages/source/p/poetry/poetry-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot +BuildRequires: python-devel + +%description +Python dependency management and packaging made easy. + + +%prep +%setup -q -n %{mod_name}-%{version} + +%build +%{__python} setup.py build + +%install +%{__python} setup.py install --prefix=%{_prefix} --root=%{buildroot} + +%clean +rm -rf %{buildroot} + +%files -f +%defattr(-,root,root) +%{python_sitelib}/* diff --git a/test/examples/poetry-opensuse-augmented.spec b/test/examples/poetry-opensuse-augmented.spec index 8f12f0b..09dd022 100644 --- a/test/examples/poetry-opensuse-augmented.spec +++ b/test/examples/poetry-opensuse-augmented.spec @@ -1,7 +1,7 @@ # # spec file for package python-poetry # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -21,7 +21,7 @@ Version: 1.5.1 Release: 0 Summary: Python dependency management and packaging made easy License: MIT -URL: https://python-poetry.org/ +URL: None Source: https://files.pythonhosted.org/packages/source/p/poetry/poetry-%{version}.tar.gz BuildRequires: python-rpm-macros BuildRequires: %{python_module pip} diff --git a/test/examples/poetry-opensuse-legacy.spec b/test/examples/poetry-opensuse-legacy.spec new file mode 100644 index 0000000..377b78a --- /dev/null +++ b/test/examples/poetry-opensuse-legacy.spec @@ -0,0 +1,159 @@ +# +# spec file for package python-poetry +# +# Copyright (c) 2025 SUSE LINUX GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via https://bugs.opensuse.org/ + + +Name: python-poetry +Version: 1.5.1 +Release: 0 +Summary: Python dependency management and packaging made easy +License: MIT +URL: https://python-poetry.org/ +Source: https://files.pythonhosted.org/packages/source/p/poetry/poetry-%{version}.tar.gz +BuildRequires: python-setuptools +BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildArch: noarch + +%description +# Poetry: Python packaging and dependency management made easy + +[![Stable Version](https://img.shields.io/pypi/v/poetry?label=stable)][PyPI Releases] +[![Pre-release Version](https://img.shields.io/github/v/release/python-poetry/poetry?label=pre-release&include_prereleases&sort=semver)][PyPI Releases] +[![Python Versions](https://img.shields.io/pypi/pyversions/poetry)][PyPI] +[![Download Stats](https://img.shields.io/pypi/dm/poetry)](https://pypistats.org/packages/poetry) +[![Discord](https://img.shields.io/discord/487711540787675139?logo=discord)][Discord] + +Poetry helps you declare, manage and install dependencies of Python projects, +ensuring you have the right stack everywhere. + +![Poetry Install](https://raw.githubusercontent.com/python-poetry/poetry/master/assets/install.gif) + +Poetry replaces `setup.py`, `requirements.txt`, `setup.cfg`, `MANIFEST.in` and `Pipfile` with a simple `pyproject.toml` +based project format. + +```toml +[tool.poetry] +name = "my-package" +version = "0.1.0" +description = "The description of the package" + +license = "MIT" + +authors = [ + "Sébastien Eustace " +] + +repository = "https://github.com/python-poetry/poetry" +homepage = "https://python-poetry.org" + +# README file(s) are used as the package description +readme = ["README.md", "LICENSE"] + +# Keywords (translated to tags on the package index) +keywords = ["packaging", "poetry"] + +[tool.poetry.dependencies] +# Compatible Python versions +python = ">=3.8" +# Standard dependency with semver constraints +aiohttp = "^3.8.1" +# Dependency with extras +requests = { version = "^2.28", extras = ["security"] } +# Version-specific dependencies with prereleases allowed +tomli = { version = "^2.0.1", python = "<3.11", allow-prereleases = true } +# Git dependencies +cleo = { git = "https://github.com/python-poetry/cleo.git", branch = "master" } +# Optional dependencies (installed by extras) +pendulum = { version = "^2.1.2", optional = true } + +# Dependency groups are supported for organizing your dependencies +[tool.poetry.group.dev.dependencies] +pytest = "^7.1.2" +pytest-cov = "^3.0" + +# ...and can be installed only when explicitly requested +[tool.poetry.group.docs] +optional = true +[tool.poetry.group.docs.dependencies] +Sphinx = "^5.1.1" + +# Python-style entrypoints and scripts are easily expressed +[tool.poetry.scripts] +my-script = "my_package:main" +``` + +## Installation + +Poetry supports multiple installation methods, including a simple script found at [install.python-poetry.org]. For full +installation instructions, including advanced usage of the script, alternate install methods, and CI best practices, see +the full [installation documentation]. + +## Documentation + +[Documentation] for the current version of Poetry (as well as the development branch and recently out of support +versions) is available from the [official website]. + +## Contribute + +Poetry is a large, complex project always in need of contributors. For those new to the project, a list of +[suggested issues] to work on in Poetry and poetry-core is available. The full [contributing documentation] also +provides helpful guidance. + +## Resources + +* [Releases][PyPI Releases] +* [Official Website] +* [Documentation] +* [Issue Tracker] +* [Discord] + + [PyPI]: https://pypi.org/project/poetry/ + [PyPI Releases]: https://pypi.org/project/poetry/#history + [Official Website]: https://python-poetry.org + [Documentation]: https://python-poetry.org/docs/ + [Issue Tracker]: https://github.com/python-poetry/poetry/issues + [Suggested Issues]: https://github.com/python-poetry/poetry/contribute + [Contributing Documentation]: https://python-poetry.org/docs/contributing + [Discord]: https://discord.com/invite/awxPgve + [install.python-poetry.org]: https://install.python-poetry.org + [Installation Documentation]: https://python-poetry.org/docs/#installation + +## Related Projects + +* [poetry-core](https://github.com/python-poetry/poetry-core): PEP 517 build-system for Poetry projects, and +dependency-free core functionality of the Poetry frontend +* [poetry-plugin-export](https://github.com/python-poetry/poetry-plugin-export): Export Poetry projects/lock files to +foreign formats like requirements.txt +* [poetry-plugin-bundle](https://github.com/python-poetry/poetry-plugin-bundle): Install Poetry projects/lock files to +external formats like virtual environments +* [install.python-poetry.org](https://github.com/python-poetry/install.python-poetry.org): The official Poetry +installation script +* [website](https://github.com/python-poetry/website): The official Poetry website and blog + + +%prep +%autosetup -p1 -n poetry-%{version} + +%build +python setup.py build + +%install +python setup.py install --prefix=%{_prefix} --root=%{buildroot} + +%files +%defattr(-,root,root,-) +%{python_sitelib}/* + +%changelog diff --git a/test/examples/poetry-opensuse.dsc b/test/examples/poetry-opensuse.dsc new file mode 100644 index 0000000..68e4287 --- /dev/null +++ b/test/examples/poetry-opensuse.dsc @@ -0,0 +1,8 @@ +Format: 1.0 +Source: poetry +Version: 1.5.1 +Binary: python-poetry +Maintainer: __USER__ +Architecture: any +Standards-Version: 3.7.1 +Build-Depends: debhelper (>= 4.0.0), python-dev diff --git a/test/examples/poetry-opensuse.spec b/test/examples/poetry-opensuse.spec new file mode 100644 index 0000000..ba28973 --- /dev/null +++ b/test/examples/poetry-opensuse.spec @@ -0,0 +1,163 @@ +# +# spec file for package python-poetry +# +# Copyright (c) 2025 SUSE LLC +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via https://bugs.opensuse.org/ +# + + +Name: python-poetry +Version: 1.5.1 +Release: 0 +Summary: Python dependency management and packaging made easy +License: MIT +URL: https://python-poetry.org/ +Source: https://files.pythonhosted.org/packages/source/p/poetry/poetry-%{version}.tar.gz +BuildRequires: python-rpm-macros +BuildRequires: %{python_module pip} +BuildRequires: fdupes +BuildArch: noarch +%python_subpackages + +%description +# Poetry: Python packaging and dependency management made easy + +[![Stable Version](https://img.shields.io/pypi/v/poetry?label=stable)][PyPI Releases] +[![Pre-release Version](https://img.shields.io/github/v/release/python-poetry/poetry?label=pre-release&include_prereleases&sort=semver)][PyPI Releases] +[![Python Versions](https://img.shields.io/pypi/pyversions/poetry)][PyPI] +[![Download Stats](https://img.shields.io/pypi/dm/poetry)](https://pypistats.org/packages/poetry) +[![Discord](https://img.shields.io/discord/487711540787675139?logo=discord)][Discord] + +Poetry helps you declare, manage and install dependencies of Python projects, +ensuring you have the right stack everywhere. + +![Poetry Install](https://raw.githubusercontent.com/python-poetry/poetry/master/assets/install.gif) + +Poetry replaces `setup.py`, `requirements.txt`, `setup.cfg`, `MANIFEST.in` and `Pipfile` with a simple `pyproject.toml` +based project format. + +```toml +[tool.poetry] +name = "my-package" +version = "0.1.0" +description = "The description of the package" + +license = "MIT" + +authors = [ + "Sébastien Eustace " +] + +repository = "https://github.com/python-poetry/poetry" +homepage = "https://python-poetry.org" + +# README file(s) are used as the package description +readme = ["README.md", "LICENSE"] + +# Keywords (translated to tags on the package index) +keywords = ["packaging", "poetry"] + +[tool.poetry.dependencies] +# Compatible Python versions +python = ">=3.8" +# Standard dependency with semver constraints +aiohttp = "^3.8.1" +# Dependency with extras +requests = { version = "^2.28", extras = ["security"] } +# Version-specific dependencies with prereleases allowed +tomli = { version = "^2.0.1", python = "<3.11", allow-prereleases = true } +# Git dependencies +cleo = { git = "https://github.com/python-poetry/cleo.git", branch = "master" } +# Optional dependencies (installed by extras) +pendulum = { version = "^2.1.2", optional = true } + +# Dependency groups are supported for organizing your dependencies +[tool.poetry.group.dev.dependencies] +pytest = "^7.1.2" +pytest-cov = "^3.0" + +# ...and can be installed only when explicitly requested +[tool.poetry.group.docs] +optional = true +[tool.poetry.group.docs.dependencies] +Sphinx = "^5.1.1" + +# Python-style entrypoints and scripts are easily expressed +[tool.poetry.scripts] +my-script = "my_package:main" +``` + +## Installation + +Poetry supports multiple installation methods, including a simple script found at [install.python-poetry.org]. For full +installation instructions, including advanced usage of the script, alternate install methods, and CI best practices, see +the full [installation documentation]. + +## Documentation + +[Documentation] for the current version of Poetry (as well as the development branch and recently out of support +versions) is available from the [official website]. + +## Contribute + +Poetry is a large, complex project always in need of contributors. For those new to the project, a list of +[suggested issues] to work on in Poetry and poetry-core is available. The full [contributing documentation] also +provides helpful guidance. + +## Resources + +* [Releases][PyPI Releases] +* [Official Website] +* [Documentation] +* [Issue Tracker] +* [Discord] + + [PyPI]: https://pypi.org/project/poetry/ + [PyPI Releases]: https://pypi.org/project/poetry/#history + [Official Website]: https://python-poetry.org + [Documentation]: https://python-poetry.org/docs/ + [Issue Tracker]: https://github.com/python-poetry/poetry/issues + [Suggested Issues]: https://github.com/python-poetry/poetry/contribute + [Contributing Documentation]: https://python-poetry.org/docs/contributing + [Discord]: https://discord.com/invite/awxPgve + [install.python-poetry.org]: https://install.python-poetry.org + [Installation Documentation]: https://python-poetry.org/docs/#installation + +## Related Projects + +* [poetry-core](https://github.com/python-poetry/poetry-core): PEP 517 build-system for Poetry projects, and +dependency-free core functionality of the Poetry frontend +* [poetry-plugin-export](https://github.com/python-poetry/poetry-plugin-export): Export Poetry projects/lock files to +foreign formats like requirements.txt +* [poetry-plugin-bundle](https://github.com/python-poetry/poetry-plugin-bundle): Install Poetry projects/lock files to +external formats like virtual environments +* [install.python-poetry.org](https://github.com/python-poetry/install.python-poetry.org): The official Poetry +installation script +* [website](https://github.com/python-poetry/website): The official Poetry website and blog + + +%prep +%autosetup -p1 -n poetry-%{version} + +%build +%pyproject_wheel + +%install +%pyproject_install +%python_expand %fdupes %{buildroot}%{$python_sitelib} + +%files %{python_files} +%{python_sitelib}/poetry +%{python_sitelib}/poetry-%{version}.dist-info + +%changelog diff --git a/test/examples/py2pack-fedora-augmented.spec b/test/examples/py2pack-fedora-augmented.spec index edec47d..b3cfad1 100644 --- a/test/examples/py2pack-fedora-augmented.spec +++ b/test/examples/py2pack-fedora-augmented.spec @@ -3,6 +3,7 @@ Name: python-%{pypi_name} Version: 0.8.5 Release: %autorelease +# Fill in the actual package summary to submit package to Fedora Summary: Generate distribution packages from PyPI # Check if the automatically generated License and its spelling is correct for Fedora @@ -12,27 +13,8 @@ URL: http://github.com/openSUSE/py2pack Source: https://files.pythonhosted.org/packages/source/p/py2pack/py2pack-%{version}.tar.gz BuildRequires: pyproject-rpm-macros -BuildRequires: python-devel -%if %{undefined python_module} -%define python_module() python3dist(%1) -%endif -BuildRequires: %{python_module pbr >= 1.8} -BuildRequires: %{python_module pip} -BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module wheel} -# SECTION test requirements -%if %{with test} -BuildRequires: %{python_module Jinja2} -BuildRequires: %{python_module metaextract} -BuildRequires: %{python_module six} -%endif -# /SECTION +BuildRequires: python3-devel BuildRequires: fdupes -Requires: %{python_module Jinja2} -Requires: %{python_module metaextract} -Requires: %{python_module setuptools} -Requires: %{python_module six} -Suggests: %{python_module typing} BuildArch: noarch # Fill in the actual package description to submit package to Fedora @@ -46,24 +28,27 @@ Summary: %{summary} %description -n %{python_name} %_description - %prep %autosetup -p1 -n %{pypi_name}-%{version} + +%generate_buildrequires +# Keep only those extras which you actually want to package or use during tests +%pyproject_buildrequires + + %build %pyproject_wheel %install %pyproject_install -# -#%python_clone -a %{buildroot}%{_bindir}/py2pack -# # 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 %if %{with test} %check +%pyproject_check_import %pytest %endif diff --git a/test/examples/py2pack-opensuse-augmented.spec b/test/examples/py2pack-opensuse-augmented.spec index e876c16..0f28129 100644 --- a/test/examples/py2pack-opensuse-augmented.spec +++ b/test/examples/py2pack-opensuse-augmented.spec @@ -1,7 +1,7 @@ # # spec file for package python-py2pack # -# Copyright (c) __YEAR__ SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed diff --git a/test/examples/py2pack-opensuse-legacy.spec b/test/examples/py2pack-opensuse-legacy.spec index 1db7df9..f2253e6 100644 --- a/test/examples/py2pack-opensuse-legacy.spec +++ b/test/examples/py2pack-opensuse-legacy.spec @@ -1,7 +1,7 @@ # # spec file for package python-py2pack # -# Copyright (c) __YEAR__ SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2025 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed diff --git a/test/examples/py2pack-opensuse.spec b/test/examples/py2pack-opensuse.spec index e40af6b..06bf0d8 100644 --- a/test/examples/py2pack-opensuse.spec +++ b/test/examples/py2pack-opensuse.spec @@ -1,7 +1,7 @@ # # spec file for package python-py2pack # -# Copyright (c) __YEAR__ SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed diff --git a/test/examples/sampleproject-fedora-augmented.spec b/test/examples/sampleproject-fedora-augmented.spec index 5ccce9d..6ce7535 100644 --- a/test/examples/sampleproject-fedora-augmented.spec +++ b/test/examples/sampleproject-fedora-augmented.spec @@ -3,50 +3,18 @@ Name: python-%{pypi_name} Version: 3.0.0 Release: %autorelease +# Fill in the actual package summary to submit package to Fedora Summary: A sample Python project # Check if the automatically generated License and its spelling is correct for Fedora # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ -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) +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: https://github.com/pypa/sampleproject Source: https://files.pythonhosted.org/packages/source/s/sampleproject/sampleproject-%{version}.tar.gz BuildRequires: pyproject-rpm-macros -BuildRequires: python-devel -%if %{undefined python_module} -%define python_module() python3dist(%1) -%endif -BuildRequires: %{python_module pip} -BuildRequires: %{python_module setuptools >= 43.0.0} -BuildRequires: %{python_module wheel} -# SECTION test requirements -%if %{with test} -BuildRequires: %{python_module peppercorn} -BuildRequires: %{python_module coverage} -%endif -# /SECTION +BuildRequires: python3-devel BuildRequires: fdupes -Requires: %{python_module peppercorn} -Suggests: %{python_module check-manifest} BuildArch: noarch # Fill in the actual package description to submit package to Fedora @@ -59,20 +27,25 @@ A sample Python project} Summary: %{summary} %description -n %{python_name} %_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_name} dev,test %prep %autosetup -p1 -n %{pypi_name}-%{version} + +%generate_buildrequires +# Keep only those extras which you actually want to package or use during tests +%pyproject_buildrequires -x dev,test + + %build %pyproject_wheel %install %pyproject_install -# -#%python_clone -a %{buildroot}%{_bindir}/sample -# # 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 diff --git a/test/examples/sampleproject-opensuse-augmented.spec b/test/examples/sampleproject-opensuse-augmented.spec index 1b54385..8362adb 100644 --- a/test/examples/sampleproject-opensuse-augmented.spec +++ b/test/examples/sampleproject-opensuse-augmented.spec @@ -1,7 +1,7 @@ # # spec file for package python-sampleproject # -# Copyright (c) __YEAR__ SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -20,26 +20,7 @@ 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) +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: https://github.com/pypa/sampleproject Source: https://files.pythonhosted.org/packages/source/s/sampleproject/sampleproject-%{version}.tar.gz BuildRequires: python-rpm-macros diff --git a/test/examples/sampleproject-opensuse-legacy.spec b/test/examples/sampleproject-opensuse-legacy.spec index 407452b..8579beb 100644 --- a/test/examples/sampleproject-opensuse-legacy.spec +++ b/test/examples/sampleproject-opensuse-legacy.spec @@ -1,7 +1,7 @@ # # spec file for package python-sampleproject # -# Copyright (c) __YEAR__ SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2025 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed diff --git a/test/examples/sampleproject-opensuse.spec b/test/examples/sampleproject-opensuse.spec index 84fa2cb..5f5bb6c 100644 --- a/test/examples/sampleproject-opensuse.spec +++ b/test/examples/sampleproject-opensuse.spec @@ -1,7 +1,7 @@ # # spec file for package python-sampleproject # -# Copyright (c) __YEAR__ SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed diff --git a/test/test_template.py b/test/test_template.py index 3d3d487..e78c1d6 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -67,11 +67,11 @@ def test_template(tmpdir, template, fetch_tarball, project, version): args.name = project args.version = version reference = os.path.join(compare_dir, f'{args.name}-{filename}') + no_ref = False if project == 'poetry' and sys.version_info < (3, 11): pytest.xfail("Different requirements for python < 3.11") - if not os.path.exists(reference): - pytest.xfail("No reference template available") + no_ref = True with tmpdir.as_cwd(): if fetch_tarball: py2pack.fetch(args) @@ -81,8 +81,15 @@ def test_template(tmpdir, template, fetch_tarball, project, version): py2pack.generate(args) with open(filename) as filehandle: written_spec = filehandle.read() - with open(reference) as filehandle: - required = filehandle.read() - required = required.replace('__USER__', username, 1) - required = required.replace('__YEAR__', str(datetime.date.today().year), 1) - assert written_spec == required + if no_ref: + required = written_spec.replace(username, '__USER__', 1) + required = required.replace(str(datetime.date.today().year), '__YEAR__', 1) + with open(reference, 'w') as filehandle: + filehandle.write(written_spec) + pytest.xfail("No reference template available") + else: + with open(reference) as filehandle: + required = filehandle.read() + required = required.replace('__USER__', username, 1) + required = required.replace('__YEAR__', str(datetime.date.today().year), 1) + assert written_spec == required