From 375b69a4784394060ccf1b7b0e286c4c90922a05 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:05:56 +0000 Subject: [PATCH 01/39] Enable --only-deps with FLIT_ALLOW_INVALID --- .devcontainer/Dockerfile | 12 + .devcontainer/devcontainer.json | 43 +++ .vscode/settings.json | 35 +++ flit/__init__.py | 217 ++++++++----- flit/config.py | 10 +- flit/install.py | 307 ++++++++++++------- flit_core/flit_core/common.py | 201 ++++++------ flit_core/flit_core/config.py | 523 +++++++++++++++++--------------- test/pyproject.toml | 46 +++ tests/samples/only-deps.toml | 10 + tests/test_install.py | 414 +++++++++++++++---------- 11 files changed, 1118 insertions(+), 700 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .vscode/settings.json create mode 100644 test/pyproject.toml create mode 100644 tests/samples/only-deps.toml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..5bf67bed --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/devcontainers/python:3 + +RUN python -m pip install --upgrade pip \ + && python -m pip install pytest pytest-cov \ + && python -m pip install 'flit>=3.8.0' + +ENV FLIT_ROOT_INSTALL=1 + +COPY pyproject.toml README.rst ./ +RUN mkdir -p flit \ + && python -m flit install --only-deps --deps develop \ + && rm -r pyproject.toml README.rst flit diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..a7c38927 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,43 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/python-3-miniconda +{ + "name": "Python Environment", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "customizations": { + "vscode": { + "extensions": [ + "editorconfig.editorconfig", + "github.vscode-pull-request-github", + "ms-azuretools.vscode-docker", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.pylint", + "ms-python.flake8", + "ms-python.black-formatter", + "ms-vsliveshare.vsliveshare", + "ryanluker.vscode-coverage-gutters", + "bungcip.better-toml", + "GitHub.copilot" + ], + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.formatting.provider": "black", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "black-formatter.path": [ + "/usr/local/py-utils/bin/black" + ], + "pylint.path": [ + "/usr/local/py-utils/bin/pylint" + ], + "flake8.path": [ + "/usr/local/py-utils/bin/flake8" + ] + } + } + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..29c13fb1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,35 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "files.trimTrailingWhitespace": true, + "files.autoSave": "onFocusChange", + "git.autofetch": true, + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.formatting.provider": "black", + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "pylint.args": [ + "--rcfile=pyproject.toml" + ], + "pylint.path": [ + "/usr/local/py-utils/bin/pylint" + ], + "black-formatter.args": [ + "--config=pyproject.toml" + ], + "black-formatter.path": [ + "/usr/local/py-utils/bin/black" + ], + "flake8.args": [ + "--config=.flake8" + ], + "flake8.path": [ + "/usr/local/py-utils/bin/flake8" + ] +} \ No newline at end of file diff --git a/flit/__init__.py b/flit/__init__.py index 9d881446..7abd7d1a 100644 --- a/flit/__init__.py +++ b/flit/__init__.py @@ -12,12 +12,13 @@ from .config import ConfigError from .log import enable_colourful_output -__version__ = '3.8.0' +__version__ = "3.8.0" log = logging.getLogger(__name__) -class PythonNotFoundError(FileNotFoundError): pass +class PythonNotFoundError(FileNotFoundError): + pass def find_python_executable(python: Optional[str] = None) -> str: @@ -33,7 +34,9 @@ def find_python_executable(python: Optional[str] = None) -> str: # see https://github.com/pypa/flit/pull/300 and https://bugs.python.org/issue38905 resolved_python = shutil.which(python) if resolved_python is None: - raise PythonNotFoundError("Unable to resolve Python executable {!r}".format(python)) + raise PythonNotFoundError( + "Unable to resolve Python executable {!r}".format(python) + ) try: return subprocess.check_output( [resolved_python, "-c", "import sys; print(sys.executable)"], @@ -48,125 +51,167 @@ def find_python_executable(python: Optional[str] = None) -> str: def add_shared_install_options(parser: argparse.ArgumentParser): - parser.add_argument('--user', action='store_true', default=None, - help="Do a user-local install (default if site.ENABLE_USER_SITE is True)" + parser.add_argument( + "--user", + action="store_true", + default=None, + help="Do a user-local install (default if site.ENABLE_USER_SITE is True)", ) - parser.add_argument('--env', action='store_false', dest='user', - help="Install into sys.prefix (default if site.ENABLE_USER_SITE is False, i.e. in virtualenvs)" + parser.add_argument( + "--env", + action="store_false", + dest="user", + help="Install into sys.prefix (default if site.ENABLE_USER_SITE is False, i.e. in virtualenvs)", ) - parser.add_argument('--python', - help="Target Python executable, if different from the one running flit" + parser.add_argument( + "--python", + help="Target Python executable, if different from the one running flit", ) - parser.add_argument('--deps', choices=['all', 'production', 'develop', 'none'], default='all', - help="Which set of dependencies to install. If --deps=develop, the extras dev, doc, and test are installed" + parser.add_argument( + "--deps", + choices=["all", "production", "develop", "none"], + default="all", + help="Which set of dependencies to install. If --deps=develop, the extras dev, doc, and test are installed", ) - parser.add_argument('--only-deps', action='store_true', - help="Install only dependencies of this package, and not the package itself" + parser.add_argument( + "--only-deps", + action="store_true", + help="Install only dependencies of this package, and not the package itself", ) - parser.add_argument('--extras', default=(), type=lambda l: l.split(',') if l else (), + parser.add_argument( + "--extras", + default=(), + type=lambda l: l.split(",") if l else (), help="Install the dependencies of these (comma separated) extras additionally to the ones implied by --deps. " - "--extras=all can be useful in combination with --deps=production, --deps=none precludes using --extras" + "--extras=all can be useful in combination with --deps=production, --deps=none precludes using --extras", ) def add_shared_build_options(parser: argparse.ArgumentParser): - parser.add_argument('--format', action='append', - help="Select a format to publish. Options: 'wheel', 'sdist'" + parser.add_argument( + "--format", + action="append", + help="Select a format to publish. Options: 'wheel', 'sdist'", ) setup_py_grp = parser.add_mutually_exclusive_group() - setup_py_grp.add_argument('--setup-py', action='store_true', - help=("Generate a setup.py file in the sdist. " - "The sdist will work with older tools that predate PEP 517. " - ) + setup_py_grp.add_argument( + "--setup-py", + action="store_true", + help=( + "Generate a setup.py file in the sdist. " + "The sdist will work with older tools that predate PEP 517. " + ), ) - setup_py_grp.add_argument('--no-setup-py', action='store_true', - help=("Don't generate a setup.py file in the sdist. This is the default. " - "The sdist will only work with tools that support PEP 517, " - "but the wheel will still be usable by any compatible tool." - ) + setup_py_grp.add_argument( + "--no-setup-py", + action="store_true", + help=( + "Don't generate a setup.py file in the sdist. This is the default. " + "The sdist will only work with tools that support PEP 517, " + "but the wheel will still be usable by any compatible tool." + ), ) vcs_grp = parser.add_mutually_exclusive_group() - vcs_grp.add_argument('--use-vcs', action='store_true', - help=("Choose which files to include in the sdist using git or hg. " - "This is a convenient way to include all checked-in files, like " - "tests and doc source files, in your sdist, but requires that git " - "or hg is available on the command line. This is currently the " - "default, but it will change in a future version. " - ) + vcs_grp.add_argument( + "--use-vcs", + action="store_true", + help=( + "Choose which files to include in the sdist using git or hg. " + "This is a convenient way to include all checked-in files, like " + "tests and doc source files, in your sdist, but requires that git " + "or hg is available on the command line. This is currently the " + "default, but it will change in a future version. " + ), ) - vcs_grp.add_argument('--no-use-vcs', action='store_true', - help=("Select the files to include in the sdist without using git or hg. " - "This should include all essential files to install and use your " - "package; see the documentation for precisely what is included. " - "This will become the default in a future version." - ) + vcs_grp.add_argument( + "--no-use-vcs", + action="store_true", + help=( + "Select the files to include in the sdist without using git or hg. " + "This should include all essential files to install and use your " + "package; see the documentation for precisely what is included. " + "This will become the default in a future version." + ), ) def main(argv=None): ap = argparse.ArgumentParser() - ap.add_argument('-f', '--ini-file', type=pathlib.Path, default='pyproject.toml') - ap.add_argument('-V', '--version', action='version', version='Flit '+__version__) + ap.add_argument("-f", "--ini-file", type=pathlib.Path, default="pyproject.toml") + ap.add_argument("-V", "--version", action="version", version="Flit " + __version__) # --repository now belongs on 'flit publish' - it's still here for # compatibility with scripts passing it before the subcommand. - ap.add_argument('--repository', dest='deprecated_repository', help=argparse.SUPPRESS) - ap.add_argument('--debug', action='store_true', help=argparse.SUPPRESS) - ap.add_argument('--logo', action='store_true', help=argparse.SUPPRESS) - subparsers = ap.add_subparsers(title='subcommands', dest='subcmd') + ap.add_argument( + "--repository", dest="deprecated_repository", help=argparse.SUPPRESS + ) + ap.add_argument("--debug", action="store_true", help=argparse.SUPPRESS) + ap.add_argument("--logo", action="store_true", help=argparse.SUPPRESS) + subparsers = ap.add_subparsers(title="subcommands", dest="subcmd") # flit build -------------------------------------------- - parser_build = subparsers.add_parser('build', + parser_build = subparsers.add_parser( + "build", help="Build wheel and sdist", ) add_shared_build_options(parser_build) # flit publish -------------------------------------------- - parser_publish = subparsers.add_parser('publish', + parser_publish = subparsers.add_parser( + "publish", help="Upload wheel and sdist", ) add_shared_build_options(parser_publish) - parser_publish.add_argument('--pypirc', - help="The .pypirc config file to be used. DEFAULT = \"~/.pypirc\"" + parser_publish.add_argument( + "--pypirc", help='The .pypirc config file to be used. DEFAULT = "~/.pypirc"' ) - parser_publish.add_argument('--repository', - help="Name of the repository to upload to (must be in the specified .pypirc file)" + parser_publish.add_argument( + "--repository", + help="Name of the repository to upload to (must be in the specified .pypirc file)", ) # flit install -------------------------------------------- - parser_install = subparsers.add_parser('install', + parser_install = subparsers.add_parser( + "install", help="Install the package", ) - parser_install.add_argument('-s', '--symlink', action='store_true', - help="Symlink the module/package into site packages instead of copying it" + parser_install.add_argument( + "-s", + "--symlink", + action="store_true", + help="Symlink the module/package into site packages instead of copying it", ) - parser_install.add_argument('--pth-file', action='store_true', - help="Add .pth file for the module/package to site packages instead of copying it" + parser_install.add_argument( + "--pth-file", + action="store_true", + help="Add .pth file for the module/package to site packages instead of copying it", ) add_shared_install_options(parser_install) # flit init -------------------------------------------- - parser_init = subparsers.add_parser('init', - help="Prepare pyproject.toml for a new package" + parser_init = subparsers.add_parser( + "init", help="Prepare pyproject.toml for a new package" ) args = ap.parse_args(argv) - if args.ini_file.suffix == '.ini': - sys.exit("flit.ini format is no longer supported. You can use " - "'python3 -m flit.tomlify' to convert it to pyproject.toml") + if args.ini_file.suffix == ".ini": + sys.exit( + "flit.ini format is no longer supported. You can use " + "'python3 -m flit.tomlify' to convert it to pyproject.toml" + ) - if args.subcmd not in {'init'} and not args.ini_file.is_file(): - sys.exit('Config file {} does not exist'.format(args.ini_file)) + if args.subcmd not in {"init"} and not args.ini_file.is_file(): + sys.exit("Config file {} does not exist".format(args.ini_file)) enable_colourful_output(logging.DEBUG if args.debug else logging.INFO) @@ -174,6 +219,7 @@ def main(argv=None): if args.logo: from .logo import clogo + print(clogo.format(version=__version__)) sys.exit(0) @@ -185,23 +231,38 @@ def gen_setup_py(): def sdist_use_vcs(): return not args.no_use_vcs - if args.subcmd == 'build': + if args.subcmd == "build": from .build import main + try: - main(args.ini_file, formats=set(args.format or []), - gen_setup_py=gen_setup_py(), use_vcs=sdist_use_vcs()) - except(common.NoDocstringError, common.VCSError, common.NoVersionError) as e: + main( + args.ini_file, + formats=set(args.format or []), + gen_setup_py=gen_setup_py(), + use_vcs=sdist_use_vcs(), + ) + except (common.NoDocstringError, common.VCSError, common.NoVersionError) as e: sys.exit(e.args[0]) - elif args.subcmd == 'publish': + elif args.subcmd == "publish": if args.deprecated_repository: - log.warning("Passing --repository before the 'upload' subcommand is deprecated: pass it after") + log.warning( + "Passing --repository before the 'upload' subcommand is deprecated: pass it after" + ) repository = args.repository or args.deprecated_repository from .upload import main - main(args.ini_file, repository, args.pypirc, formats=set(args.format or []), - gen_setup_py=gen_setup_py(), use_vcs=sdist_use_vcs()) - elif args.subcmd == 'install': + main( + args.ini_file, + repository, + args.pypirc, + formats=set(args.format or []), + gen_setup_py=gen_setup_py(), + use_vcs=sdist_use_vcs(), + ) + + elif args.subcmd == "install": from .install import Installer + try: python = find_python_executable(args.python) installer = Installer.from_ini_path( @@ -211,17 +272,23 @@ def sdist_use_vcs(): symlink=args.symlink, deps=args.deps, extras=args.extras, - pth=args.pth_file + pth=args.pth_file, ) if args.only_deps: installer.install_requirements() else: installer.install() - except (ConfigError, PythonNotFoundError, common.NoDocstringError, common.NoVersionError) as e: + except ( + ConfigError, + PythonNotFoundError, + common.NoDocstringError, + common.NoVersionError, + ) as e: sys.exit(e.args[0]) - elif args.subcmd == 'init': + elif args.subcmd == "init": from .init import TerminalIniter + TerminalIniter().initialise() else: ap.print_help() diff --git a/flit/config.py b/flit/config.py index 9186e0ac..85026a9e 100644 --- a/flit/config.py +++ b/flit/config.py @@ -6,13 +6,15 @@ def read_flit_config(path): - """Read and check the `pyproject.toml` or `flit.ini` file with data about the package. - """ + """Read and check the `pyproject.toml` or `flit.ini` file with data about the package.""" + res = _read_flit_config_core(path) if validate_config(res): - if os.environ.get('FLIT_ALLOW_INVALID'): - log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") + if os.environ.get("FLIT_ALLOW_INVALID"): + log.warning( + "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." + ) else: raise ConfigError("Invalid config values (see log)") return res diff --git a/flit/install.py b/flit/install.py index 3ea9a4bf..b1e97e73 100644 --- a/flit/install.py +++ b/flit/install.py @@ -21,48 +21,51 @@ log = logging.getLogger(__name__) + def _requires_dist_to_pip_requirement(requires_dist): """Parse "Foo (v); python_version == '2.x'" from Requires-Dist Returns pip-style appropriate for requirements.txt. """ - env_mark = '' - if ';' in requires_dist: - name_version, env_mark = requires_dist.split(';', 1) + env_mark = "" + if ";" in requires_dist: + name_version, env_mark = requires_dist.split(";", 1) else: name_version = requires_dist - if '(' in name_version: + if "(" in name_version: # turn 'name (X)' and 'name ('): - version = '==' + version + version = version.replace(")", "").strip() + if not any(c in version for c in "=<>"): + version = "==" + version name_version = name + version # re-add environment marker - return ' ;'.join([name_version, env_mark]) + return " ;".join([name_version, env_mark]) + def test_writable_dir(path): """Check if a directory is writable. Uses os.access() on POSIX, tries creating files on Windows. """ - if os.name == 'posix': + if os.name == "posix": return os.access(path, os.W_OK) return _test_writable_dir_win(path) + def _test_writable_dir_win(path): # os.access doesn't work on Windows: http://bugs.python.org/issue2528 # and we can't use tempfile: http://bugs.python.org/issue22107 - basename = 'accesstest_deleteme_fishfingers_custard_' - alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789' + basename = "accesstest_deleteme_fishfingers_custard_" + alphabet = "abcdefghijklmnopqrstuvwxyz0123456789" for i in range(10): - name = basename + ''.join(random.choice(alphabet) for _ in range(6)) + name = basename + "".join(random.choice(alphabet) for _ in range(6)) file = osp.join(path, name) try: - with open(file, mode='xb'): + with open(file, mode="xb"): pass except FileExistsError: continue @@ -76,22 +79,38 @@ def _test_writable_dir_win(path): return True # This should never be reached - msg = ('Unexpected condition testing for writable directory {!r}. ' - 'Please open an issue on flit to debug why this occurred.') # pragma: no cover + msg = ( + "Unexpected condition testing for writable directory {!r}. " + "Please open an issue on flit to debug why this occurred." + ) # pragma: no cover raise EnvironmentError(msg.format(path)) # pragma: no cover + class RootInstallError(Exception): def __str__(self): - return ("Installing packages as root is not recommended. " - "To allow this, set FLIT_ROOT_INSTALL=1 and try again.") + return ( + "Installing packages as root is not recommended. " + "To allow this, set FLIT_ROOT_INSTALL=1 and try again." + ) + class DependencyError(Exception): def __str__(self): - return 'To install dependencies for extras, you cannot set deps=none.' + return "To install dependencies for extras, you cannot set deps=none." + class Installer(object): - def __init__(self, directory, ini_info, user=None, python=sys.executable, - symlink=False, deps='all', extras=(), pth=False): + def __init__( + self, + directory, + ini_info, + user=None, + python=sys.executable, + symlink=False, + deps="all", + extras=(), + pth=False, + ): self.directory = directory self.ini_info = ini_info self.python = python @@ -99,50 +118,77 @@ def __init__(self, directory, ini_info, user=None, python=sys.executable, self.pth = pth self.deps = deps self.extras = extras - if deps != 'none' and os.environ.get('FLIT_NO_NETWORK', ''): - self.deps = 'none' - log.warning('Not installing dependencies, because FLIT_NO_NETWORK is set') - if deps == 'none' and extras: + if deps != "none" and os.environ.get("FLIT_NO_NETWORK", ""): + self.deps = "none" + log.warning("Not installing dependencies, because FLIT_NO_NETWORK is set") + if deps == "none" and extras: raise DependencyError() - self.module = common.Module(self.ini_info.module, directory) + try: + self.module = common.Module(self.ini_info.module, directory) + except AttributeError as exec: + if os.environ.get("FLIT_ALLOW_INVALID"): + log.warning( + "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." + ) + else: + raise ValueError() from exec - if (hasattr(os, 'getuid') and (os.getuid() == 0) and - (not os.environ.get('FLIT_ROOT_INSTALL'))): + if ( + hasattr(os, "getuid") + and (os.getuid() == 0) + and (not os.environ.get("FLIT_ROOT_INSTALL")) + ): raise RootInstallError if user is None: self.user = self._auto_user(python) else: self.user = user - log.debug('User install? %s', self.user) + log.debug("User install? %s", self.user) self.installed_files = [] @classmethod - def from_ini_path(cls, ini_path, user=None, python=sys.executable, - symlink=False, deps='all', extras=(), pth=False): + def from_ini_path( + cls, + ini_path, + user=None, + python=sys.executable, + symlink=False, + deps="all", + extras=(), + pth=False, + ): ini_info = read_flit_config(ini_path) - return cls(ini_path.parent, ini_info, user=user, python=python, - symlink=symlink, deps=deps, extras=extras, pth=pth) + return cls( + ini_path.parent, + ini_info, + user=user, + python=python, + symlink=symlink, + deps=deps, + extras=extras, + pth=pth, + ) def _run_python(self, code=None, file=None, extra_args=()): if code and file: - raise ValueError('Specify code or file, not both') + raise ValueError("Specify code or file, not both") if not (code or file): - raise ValueError('Specify code or file') + raise ValueError("Specify code or file") if code: - args = [self.python, '-c', code] + args = [self.python, "-c", code] else: args = [self.python, file] args.extend(extra_args) env = os.environ.copy() - env['PYTHONIOENCODING'] = 'utf-8' + env["PYTHONIOENCODING"] = "utf-8" # On Windows, shell needs to be True to pick up our local PATH # when finding the Python command. - shell = (os.name == 'nt') - return check_output(args, shell=shell, env=env).decode('utf-8') + shell = os.name == "nt" + return check_output(args, shell=shell, env=env).decode("utf-8") def _auto_user(self, python): """Default guess for whether to do user-level install. @@ -151,47 +197,53 @@ def _auto_user(self, python): """ if python == sys.executable: user_site = site.ENABLE_USER_SITE - lib_dir = sysconfig.get_path('purelib') + lib_dir = sysconfig.get_path("purelib") else: - out = self._run_python(code= - ("import sysconfig, site; " - "print(site.ENABLE_USER_SITE); " - "print(sysconfig.get_path('purelib'))")) - user_site, lib_dir = out.split('\n', 1) - user_site = (user_site.strip() == 'True') + out = self._run_python( + code=( + "import sysconfig, site; " + "print(site.ENABLE_USER_SITE); " + "print(sysconfig.get_path('purelib'))" + ) + ) + user_site, lib_dir = out.split("\n", 1) + user_site = user_site.strip() == "True" lib_dir = lib_dir.strip() if not user_site: # No user site packages - probably a virtualenv - log.debug('User site packages not available - env install') + log.debug("User site packages not available - env install") return False - log.debug('Checking access to %s', lib_dir) + log.debug("Checking access to %s", lib_dir) return not test_writable_dir(lib_dir) def install_scripts(self, script_defs, scripts_dir): for name, ep in script_defs.items(): module, func = common.parse_entry_point(ep) - import_name = func.split('.')[0] + import_name = func.split(".")[0] script_file = pathlib.Path(scripts_dir) / name - log.info('Writing script to %s', script_file) - with script_file.open('w', encoding='utf-8') as f: - f.write(common.script_template.format( - interpreter=self.python, - module=module, - import_name=import_name, - func=func - )) + log.info("Writing script to %s", script_file) + with script_file.open("w", encoding="utf-8") as f: + f.write( + common.script_template.format( + interpreter=self.python, + module=module, + import_name=import_name, + func=func, + ) + ) script_file.chmod(0o755) self.installed_files.append(script_file) - if sys.platform == 'win32': - cmd_file = script_file.with_suffix('.cmd') + if sys.platform == "win32": + cmd_file = script_file.with_suffix(".cmd") cmd = '@echo off\r\n"{python}" "%~dp0\\{script}" %*\r\n'.format( - python=self.python, script=name) + python=self.python, script=name + ) log.debug("Writing script wrapper to %s", cmd_file) - with cmd_file.open('w') as f: + with cmd_file.open("w") as f: f.write(cmd) self.installed_files.append(cmd_file) @@ -214,15 +266,15 @@ def _record_installed_directory(self, path): def _extras_to_install(self): extras_to_install = set(self.extras) - if self.deps == 'all' or 'all' in extras_to_install: + if self.deps == "all" or "all" in extras_to_install: extras_to_install |= set(self.ini_info.reqs_by_extra.keys()) # We don’t remove 'all' from the set because there might be an extra called “all”. - elif self.deps == 'develop': - extras_to_install |= {'dev', 'doc', 'test'} + elif self.deps == "develop": + extras_to_install |= {"dev", "doc", "test"} - if self.deps != 'none': + if self.deps != "none": # '.none' is an internal token for normal requirements - extras_to_install.add('.none') + extras_to_install.add(".none") log.info("Extras to install for deps %r: %s", self.deps, extras_to_install) return extras_to_install @@ -234,7 +286,7 @@ def install_requirements(self): # construct the full list of requirements, including dev requirements requirements = [] - if self.deps == 'none': + if self.deps == "none": return for extra in self._extras_to_install(): @@ -245,19 +297,18 @@ def install_requirements(self): return requirements = [ - _requires_dist_to_pip_requirement(req_d) - for req_d in requirements + _requires_dist_to_pip_requirement(req_d) for req_d in requirements ] # install the requirements with pip - cmd = [self.python, '-m', 'pip', 'install'] + cmd = [self.python, "-m", "pip", "install"] if self.user: - cmd.append('--user') - with tempfile.NamedTemporaryFile(mode='w', - suffix='requirements.txt', - delete=False) as tf: - tf.file.write('\n'.join(requirements)) - cmd.extend(['-r', tf.name]) + cmd.append("--user") + with tempfile.NamedTemporaryFile( + mode="w", suffix="requirements.txt", delete=False + ) as tf: + tf.file.write("\n".join(requirements)) + cmd.extend(["-r", tf.name]) log.info("Installing requirements") try: check_call(cmd) @@ -274,12 +325,12 @@ def install_reqs_my_python_if_needed(self): try: common.get_info_from_module(self.module, self.ini_info.dynamic_metadata) except ImportError: - if self.deps == 'none': + if self.deps == "none": raise # We were asked not to install deps, so bail out. log.warning("Installing requirements to Flit's env to import module.") user = self.user if (self.python == sys.executable) else None - i2 = Installer(self.directory, self.ini_info, user=user, deps='production') + i2 = Installer(self.directory, self.ini_info, user=user, deps="production") i2.install_requirements() def _get_dirs(self, user): @@ -287,19 +338,19 @@ def _get_dirs(self, user): return get_dirs(user=user) else: import json - path = osp.join(osp.dirname(__file__), '_get_dirs.py') - args = ['--user'] if user else [] + + path = osp.join(osp.dirname(__file__), "_get_dirs.py") + args = ["--user"] if user else [] return json.loads(self._run_python(file=path, extra_args=args)) def install_directly(self): - """Install a module/package into site-packages, and create its scripts. - """ + """Install a module/package into site-packages, and create its scripts.""" dirs = self._get_dirs(user=self.user) - os.makedirs(dirs['purelib'], exist_ok=True) - os.makedirs(dirs['scripts'], exist_ok=True) + os.makedirs(dirs["purelib"], exist_ok=True) + os.makedirs(dirs["scripts"], exist_ok=True) module_rel_path = self.module.path.relative_to(self.module.source_dir) - dst = osp.join(dirs['purelib'], module_rel_path) + dst = osp.join(dirs["purelib"], module_rel_path) if osp.lexists(dst): if osp.isdir(dst) and not osp.islink(dst): shutil.rmtree(dst) @@ -326,9 +377,9 @@ def install_directly(self): elif self.pth: # .pth points to the the folder containing the module (which is # added to sys.path) - pth_file = pathlib.Path(dirs['purelib'], self.module.name + '.pth') + pth_file = pathlib.Path(dirs["purelib"], self.module.name + ".pth") log.info("Adding .pth file %s for %s", pth_file, self.module.source_dir) - pth_file.write_text(str(self.module.source_dir.resolve()), 'utf-8') + pth_file.write_text(str(self.module.source_dir.resolve()), "utf-8") self.installed_files.append(pth_file) elif self.module.is_package: log.info("Copying directory %s -> %s", src, dst) @@ -340,12 +391,12 @@ def install_directly(self): shutil.copy2(src, dst) self.installed_files.append(dst) - scripts = self.ini_info.entrypoints.get('console_scripts', {}) - self.install_scripts(scripts, dirs['scripts']) + scripts = self.ini_info.entrypoints.get("console_scripts", {}) + self.install_scripts(scripts, dirs["scripts"]) - self.install_data_dir(dirs['data']) + self.install_data_dir(dirs["data"]) - self.write_dist_info(dirs['purelib']) + self.write_dist_info(dirs["purelib"]) def install_with_pip(self): """Let pip install the project directory @@ -358,64 +409,69 @@ def install_with_pip(self): """ self.install_reqs_my_python_if_needed() extras = self._extras_to_install() - extras.discard('.none') - req_with_extras = '{}[{}]'.format(self.directory, ','.join(extras)) \ - if extras else str(self.directory) - cmd = [self.python, '-m', 'pip', 'install', req_with_extras] + extras.discard(".none") + req_with_extras = ( + "{}[{}]".format(self.directory, ",".join(extras)) + if extras + else str(self.directory) + ) + cmd = [self.python, "-m", "pip", "install", req_with_extras] if self.user: - cmd.append('--user') - if self.deps == 'none': - cmd.append('--no-deps') - shell = (os.name == 'nt') + cmd.append("--user") + if self.deps == "none": + cmd.append("--no-deps") + shell = os.name == "nt" check_call(cmd, shell=shell) def write_dist_info(self, site_pkgs): """Write dist-info folder, according to PEP 376""" metadata = common.make_metadata(self.module, self.ini_info) dist_info = pathlib.Path(site_pkgs) / common.dist_info_name( - metadata.name, metadata.version) + metadata.name, metadata.version + ) try: dist_info.mkdir() except FileExistsError: shutil.rmtree(str(dist_info)) dist_info.mkdir() - with (dist_info / 'METADATA').open('w', encoding='utf-8') as f: + with (dist_info / "METADATA").open("w", encoding="utf-8") as f: metadata.write_metadata_file(f) - self.installed_files.append(dist_info / 'METADATA') + self.installed_files.append(dist_info / "METADATA") - with (dist_info / 'INSTALLER').open('w', encoding='utf-8') as f: - f.write('flit') - self.installed_files.append(dist_info / 'INSTALLER') + with (dist_info / "INSTALLER").open("w", encoding="utf-8") as f: + f.write("flit") + self.installed_files.append(dist_info / "INSTALLER") # We only handle explicitly requested installations - with (dist_info / 'REQUESTED').open('wb'): pass - self.installed_files.append(dist_info / 'REQUESTED') + with (dist_info / "REQUESTED").open("wb"): + pass + self.installed_files.append(dist_info / "REQUESTED") if self.ini_info.entrypoints: - with (dist_info / 'entry_points.txt').open('w') as f: + with (dist_info / "entry_points.txt").open("w") as f: common.write_entry_points(self.ini_info.entrypoints, f) - self.installed_files.append(dist_info / 'entry_points.txt') + self.installed_files.append(dist_info / "entry_points.txt") - with (dist_info / 'direct_url.json').open('w', encoding='utf-8') as f: + with (dist_info / "direct_url.json").open("w", encoding="utf-8") as f: json.dump( { "url": self.directory.resolve().as_uri(), - "dir_info": {"editable": bool(self.symlink or self.pth)} + "dir_info": {"editable": bool(self.symlink or self.pth)}, }, - f + f, ) - self.installed_files.append(dist_info / 'direct_url.json') + self.installed_files.append(dist_info / "direct_url.json") # newline='' because the csv module does its own newline translation - with (dist_info / 'RECORD').open('w', encoding='utf-8', newline='') as f: + with (dist_info / "RECORD").open("w", encoding="utf-8", newline="") as f: cf = csv.writer(f) for path in sorted(self.installed_files, key=str): path = pathlib.Path(path) - if path.is_symlink() or path.suffix in {'.pyc', '.pyo'}: - hash, size = '', '' + if path.is_symlink() or path.suffix in {".pyc", ".pyo"}: + hash, size = "", "" else: - hash = 'sha256=' + common.hash_file(str(path)) + hash = "sha256=" + common.hash_file(str(path)) size = path.stat().st_size try: path = path.relative_to(site_pkgs) @@ -423,10 +479,25 @@ def write_dist_info(self, site_pkgs): pass cf.writerow((str(path), hash, size)) - cf.writerow(((dist_info / 'RECORD').relative_to(site_pkgs), '', '')) + cf.writerow(((dist_info / "RECORD").relative_to(site_pkgs), "", "")) def install(self): if self.symlink or self.pth: self.install_directly() else: self.install_with_pip() + + +class DependencyInstaller(Installer): + def __init__( + self, + directory, + ini_info, + user=None, + python=sys.executable, + symlink=False, + deps="all", + extras=(), + pth=False, + ): + super().__init__(directory, ini_info, user, python, symlink, deps, extras, pth) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 68d91bb9..494244d2 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -12,9 +12,10 @@ from .versionno import normalise_version + class Module(object): - """This represents the module/package that we are going to distribute - """ + """This represents the module/package that we are going to distribute""" + in_namespace_package = False namespace_package_name = None @@ -22,52 +23,58 @@ def __init__(self, name, directory=Path()): self.name = name # It must exist either as a .py file or a directory, but not both - name_as_path = name.replace('.', os.sep) + name_as_path = name.replace(".", os.sep) pkg_dir = directory / name_as_path - py_file = directory / (name_as_path+'.py') - src_pkg_dir = directory / 'src' / name_as_path - src_py_file = directory / 'src' / (name_as_path+'.py') + py_file = directory / (name_as_path + ".py") + src_pkg_dir = directory / "src" / name_as_path + src_py_file = directory / "src" / (name_as_path + ".py") existing = set() if pkg_dir.is_dir(): self.path = pkg_dir self.is_package = True - self.prefix = '' + self.prefix = "" existing.add(pkg_dir) if py_file.is_file(): self.path = py_file self.is_package = False - self.prefix = '' + self.prefix = "" existing.add(py_file) if src_pkg_dir.is_dir(): self.path = src_pkg_dir self.is_package = True - self.prefix = 'src' + self.prefix = "src" existing.add(src_pkg_dir) if src_py_file.is_file(): self.path = src_py_file self.is_package = False - self.prefix = 'src' + self.prefix = "src" existing.add(src_py_file) if len(existing) > 1: raise ValueError( - "Multiple files or folders could be module {}: {}" - .format(name, ", ".join([str(p) for p in sorted(existing)])) + "Multiple files or folders could be module {}: {}".format( + name, ", ".join([str(p) for p in sorted(existing)]) + ) ) elif not existing: - raise ValueError("No file/folder found for module {}".format(name)) + if os.environ.get("FLIT_ALLOW_INVALID"): + log.warning( + "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." + ) + else: + raise ValueError("No file/folder found for module {}".format(name)) self.source_dir = directory / self.prefix - if '.' in name: - self.namespace_package_name = name.rpartition('.')[0] + if "." in name: + self.namespace_package_name = name.rpartition(".")[0] self.in_namespace_package = True @property def file(self): if self.is_package: - return self.path / '__init__.py' + return self.path / "__init__.py" else: return self.path @@ -77,9 +84,10 @@ def iter_files(self): Yields absolute paths - caller may want to make them relative. Excludes any __pycache__ and *.pyc files. """ + def _include(path): name = os.path.basename(path) - if (name == '__pycache__') or name.endswith('.pyc'): + if (name == "__pycache__") or name.endswith(".pyc"): return False return True @@ -96,10 +104,22 @@ def _include(path): else: yield str(self.path) -class ProblemInModule(ValueError): pass -class NoDocstringError(ProblemInModule): pass -class NoVersionError(ProblemInModule): pass -class InvalidVersion(ProblemInModule): pass + +class ProblemInModule(ValueError): + pass + + +class NoDocstringError(ProblemInModule): + pass + + +class NoVersionError(ProblemInModule): + pass + + +class InvalidVersion(ProblemInModule): + pass + class VCSError(Exception): def __init__(self, msg, directory): @@ -107,7 +127,7 @@ def __init__(self, msg, directory): self.directory = directory def __str__(self): - return self.msg + ' ({})'.format(self.directory) + return self.msg + " ({})".format(self.directory) @contextmanager @@ -122,25 +142,25 @@ def _module_load_ctx(): finally: logging.root.handlers = logging_handlers + def get_docstring_and_version_via_ast(target): """ Return a tuple like (docstring, version) for the given module, extracted by parsing its AST. """ # read as bytes to enable custom encodings - with target.file.open('rb') as f: + with target.file.open("rb") as f: node = ast.parse(f.read()) for child in node.body: # Only use the version from the given module if it's a simple # string assignment to __version__ is_version_str = ( - isinstance(child, ast.Assign) - and any( - isinstance(target, ast.Name) - and target.id == "__version__" - for target in child.targets - ) - and isinstance(child.value, ast.Str) + isinstance(child, ast.Assign) + and any( + isinstance(target, ast.Name) and target.id == "__version__" + for target in child.targets + ) + and isinstance(child.value, ast.Str) ) if is_version_str: version = child.value.s @@ -167,7 +187,8 @@ def get_docstring_and_version_via_import(target): log.debug("Loading module %s", target.file) from importlib.util import spec_from_file_location, module_from_spec - mod_name = 'flit_core.dummy.import%d' % _import_i + + mod_name = "flit_core.dummy.import%d" % _import_i spec = spec_from_file_location(mod_name, target.file) with _module_load_ctx(): m = module_from_spec(spec) @@ -181,20 +202,19 @@ def get_docstring_and_version_via_import(target): finally: sys.modules.pop(mod_name, None) - docstring = m.__dict__.get('__doc__', None) - version = m.__dict__.get('__version__', None) + docstring = m.__dict__.get("__doc__", None) + version = m.__dict__.get("__version__", None) return docstring, version -def get_info_from_module(target, for_fields=('version', 'description')): - """Load the module/package, get its docstring and __version__ - """ +def get_info_from_module(target, for_fields=("version", "description")): + """Load the module/package, get its docstring and __version__""" if not for_fields: return {} # What core metadata calls Summary, PEP 621 calls description - want_summary = 'description' in for_fields - want_version = 'version' in for_fields + want_summary = "description" in for_fields + want_version = "version" in for_fields log.debug("Loading module %s", target.file) @@ -211,16 +231,17 @@ def get_info_from_module(target, for_fields=('version', 'description')): if want_summary: if (not docstring) or not docstring.strip(): raise NoDocstringError( - 'Flit cannot package module without docstring, or empty docstring. ' - 'Please add a docstring to your module ({}).'.format(target.file) + "Flit cannot package module without docstring, or empty docstring. " + "Please add a docstring to your module ({}).".format(target.file) ) - res['summary'] = docstring.lstrip().splitlines()[0] + res["summary"] = docstring.lstrip().splitlines()[0] if want_version: - res['version'] = check_version(version) + res["version"] = check_version(version) return res + def check_version(version): """ Check whether a given version string match PEP 440, and do normalisation. @@ -233,11 +254,14 @@ def check_version(version): Returns the version in canonical PEP 440 format. """ if not version: - raise NoVersionError('Cannot package module without a version string. ' - 'Please define a `__version__ = "x.y.z"` in your module.') + raise NoVersionError( + "Cannot package module without a version string. " + 'Please define a `__version__ = "x.y.z"` in your module.' + ) if not isinstance(version, str): - raise InvalidVersion('__version__ must be a string, not {}.' - .format(type(version))) + raise InvalidVersion( + "__version__ must be a string, not {}.".format(type(version)) + ) # Import here to avoid circular import version = normalise_version(version) @@ -256,42 +280,46 @@ def check_version(version): sys.exit({func}()) """ + def parse_entry_point(ep): """Check and parse a 'package.module:func' style entry point specification. Returns (modulename, funcname) """ - if ':' not in ep: + if ":" not in ep: raise ValueError("Invalid entry point (no ':'): %r" % ep) - mod, func = ep.split(':') + mod, func = ep.split(":") - for piece in func.split('.'): + for piece in func.split("."): if not piece.isidentifier(): raise ValueError("Invalid entry point: %r is not an identifier" % piece) - for piece in mod.split('.'): + for piece in mod.split("."): if not piece.isidentifier(): raise ValueError("Invalid entry point: %r is not a module path" % piece) return mod, func + def write_entry_points(d, fp): """Write entry_points.txt from a two-level dict Sorts on keys to ensure results are reproducible. """ for group_name in sorted(d): - fp.write(u'[{}]\n'.format(group_name)) + fp.write("[{}]\n".format(group_name)) group = d[group_name] for name in sorted(group): val = group[name] - fp.write(u'{}={}\n'.format(name, val)) - fp.write(u'\n') + fp.write("{}={}\n".format(name, val)) + fp.write("\n") -def hash_file(path, algorithm='sha256'): - with open(path, 'rb') as f: + +def hash_file(path, algorithm="sha256"): + with open(path, "rb") as f: h = hashlib.new(algorithm, f.read()) return h.hexdigest() + def normalize_file_permissions(st_mode): """Normalize the permission bits in the st_mode field from stat to 644/755 @@ -305,8 +333,8 @@ def normalize_file_permissions(st_mode): new_mode |= 0o111 # Executable: 644 -> 755 return new_mode -class Metadata(object): +class Metadata(object): summary = None home_page = None author = None @@ -337,39 +365,39 @@ class Metadata(object): def __init__(self, data): data = data.copy() - self.name = data.pop('name') - self.version = data.pop('version') + self.name = data.pop("name") + self.version = data.pop("version") for k, v in data.items(): assert hasattr(self, k), "data does not have attribute '{}'".format(k) setattr(self, k, v) def _normalise_name(self, n): - return n.lower().replace('-', '_') + return n.lower().replace("-", "_") def write_metadata_file(self, fp): """Write out metadata in the email headers format""" fields = [ - 'Metadata-Version', - 'Name', - 'Version', + "Metadata-Version", + "Name", + "Version", ] optional_fields = [ - 'Summary', - 'Home-page', - 'License', - 'Keywords', - 'Author', - 'Author-email', - 'Maintainer', - 'Maintainer-email', - 'Requires-Python', - 'Description-Content-Type', + "Summary", + "Home-page", + "License", + "Keywords", + "Author", + "Author-email", + "Maintainer", + "Maintainer-email", + "Requires-Python", + "Description-Content-Type", ] for field in fields: value = getattr(self, self._normalise_name(field)) - fp.write(u"{}: {}\n".format(field, value)) + fp.write("{}: {}\n".format(field, value)) for field in optional_fields: value = getattr(self, self._normalise_name(field)) @@ -378,23 +406,23 @@ def write_metadata_file(self, fp): # The spec has multiline examples for Author, Maintainer & # License (& Description, but we put that in the body) # Indent following lines with 8 spaces: - value = '\n '.join(value.splitlines()) - fp.write(u"{}: {}\n".format(field, value)) + value = "\n ".join(value.splitlines()) + fp.write("{}: {}\n".format(field, value)) for clsfr in self.classifiers: - fp.write(u'Classifier: {}\n'.format(clsfr)) + fp.write("Classifier: {}\n".format(clsfr)) for req in self.requires_dist: - fp.write(u'Requires-Dist: {}\n'.format(req)) + fp.write("Requires-Dist: {}\n".format(req)) for url in self.project_urls: - fp.write(u'Project-URL: {}\n'.format(url)) + fp.write("Project-URL: {}\n".format(url)) for extra in self.provides_extra: - fp.write(u'Provides-Extra: {}\n'.format(extra)) + fp.write("Provides-Extra: {}\n".format(extra)) if self.description is not None: - fp.write(u'\n' + self.description + u'\n') + fp.write("\n" + self.description + "\n") @property def supports_py2(self): @@ -406,13 +434,12 @@ def supports_py2(self): def make_metadata(module, ini_info): - md_dict = {'name': module.name, 'provides': [module.name]} + md_dict = {"name": module.name, "provides": [module.name]} md_dict.update(get_info_from_module(module, ini_info.dynamic_metadata)) md_dict.update(ini_info.metadata) return Metadata(md_dict) - def normalize_dist_name(name: str, version: str) -> str: """Normalizes a name and a PEP 440 version @@ -421,15 +448,15 @@ def normalize_dist_name(name: str, version: str) -> str: See https://packaging.python.org/specifications/binary-distribution-format/#escaping-and-unicode """ - normalized_name = re.sub(r'[-_.]+', '_', name, flags=re.UNICODE).lower() + normalized_name = re.sub(r"[-_.]+", "_", name, flags=re.UNICODE).lower() assert check_version(version) == version - assert '-' not in version, 'Normalized versions can’t have dashes' - return '{}-{}'.format(normalized_name, version) + assert "-" not in version, "Normalized versions can’t have dashes" + return "{}-{}".format(normalized_name, version) def dist_info_name(distribution, version): """Get the correct name of the .dist-info folder""" - return normalize_dist_name(distribution, version) + '.dist-info' + return normalize_dist_name(distribution, version) + ".dist-info" def walk_data_dir(data_directory): @@ -446,4 +473,4 @@ def walk_data_dir(data_directory): full_path = os.path.join(dirpath, file) yield full_path - dirs[:] = [d for d in sorted(dirs) if d != '__pycache__'] + dirs[:] = [d for d in sorted(dirs) if d != "__pycache__"] diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 12929561..1d2a4555 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -25,137 +25,142 @@ class ConfigError(ValueError): pass -metadata_list_fields = { - 'classifiers', - 'requires', - 'dev-requires' -} + +metadata_list_fields = {"classifiers", "requires", "dev-requires"} metadata_allowed_fields = { - 'module', - 'author', - 'author-email', - 'maintainer', - 'maintainer-email', - 'home-page', - 'license', - 'keywords', - 'requires-python', - 'dist-name', - 'description-file', - 'requires-extra', + "module", + "author", + "author-email", + "maintainer", + "maintainer-email", + "home-page", + "license", + "keywords", + "requires-python", + "dist-name", + "description-file", + "requires-extra", } | metadata_list_fields metadata_required_fields = { - 'module', - 'author', + "module", + "author", } pep621_allowed_fields = { - 'name', - 'version', - 'description', - 'readme', - 'requires-python', - 'license', - 'authors', - 'maintainers', - 'keywords', - 'classifiers', - 'urls', - 'scripts', - 'gui-scripts', - 'entry-points', - 'dependencies', - 'optional-dependencies', - 'dynamic', + "name", + "version", + "description", + "readme", + "requires-python", + "license", + "authors", + "maintainers", + "keywords", + "classifiers", + "urls", + "scripts", + "gui-scripts", + "entry-points", + "dependencies", + "optional-dependencies", + "dynamic", } def read_flit_config(path): - """Read and check the `pyproject.toml` file with data about the package. - """ - d = tomllib.loads(path.read_text('utf-8')) + """Read and check the `pyproject.toml` file with data about the package.""" + d = tomllib.loads(path.read_text("utf-8")) return prep_toml_config(d, path) class EntryPointsConflict(ConfigError): def __str__(self): - return ('Please specify console_scripts entry points, or [scripts] in ' - 'flit config, not both.') + return ( + "Please specify console_scripts entry points, or [scripts] in " + "flit config, not both." + ) + def prep_toml_config(d, path): """Validate config loaded from pyproject.toml and prepare common metadata - + Returns a LoadedConfig object. """ - dtool = d.get('tool', {}).get('flit', {}) + dtool = d.get("tool", {}).get("flit", {}) - if 'project' in d: + if "project" in d: # Metadata in [project] table (PEP 621) - if 'metadata' in dtool: + if "metadata" in dtool: raise ConfigError( "Use [project] table for metadata or [tool.flit.metadata], not both." ) - if ('scripts' in dtool) or ('entrypoints' in dtool): + if ("scripts" in dtool) or ("entrypoints" in dtool): raise ConfigError( "Don't mix [project] metadata with [tool.flit.scripts] or " "[tool.flit.entrypoints]. Use [project.scripts]," "[project.gui-scripts] or [project.entry-points] as replacements." ) - loaded_cfg = read_pep621_metadata(d['project'], path) + loaded_cfg = read_pep621_metadata(d["project"], path) - module_tbl = dtool.get('module', {}) - if 'name' in module_tbl: - loaded_cfg.module = module_tbl['name'] - elif 'metadata' in dtool: + module_tbl = dtool.get("module", {}) + if "name" in module_tbl: + loaded_cfg.module = module_tbl["name"] + elif "metadata" in dtool: # Metadata in [tool.flit.metadata] (pre PEP 621 format) - if 'module' in dtool: + if "module" in dtool: raise ConfigError( "Use [tool.flit.module] table with new-style [project] metadata, " "not [tool.flit.metadata]" ) - loaded_cfg = _prep_metadata(dtool['metadata'], path) - loaded_cfg.dynamic_metadata = ['version', 'description'] + loaded_cfg = _prep_metadata(dtool["metadata"], path) + loaded_cfg.dynamic_metadata = ["version", "description"] - if 'entrypoints' in dtool: - loaded_cfg.entrypoints = flatten_entrypoints(dtool['entrypoints']) + if "entrypoints" in dtool: + loaded_cfg.entrypoints = flatten_entrypoints(dtool["entrypoints"]) - if 'scripts' in dtool: - loaded_cfg.add_scripts(dict(dtool['scripts'])) + if "scripts" in dtool: + loaded_cfg.add_scripts(dict(dtool["scripts"])) else: raise ConfigError( "Neither [project] nor [tool.flit.metadata] found in pyproject.toml" ) unknown_sections = set(dtool) - { - 'metadata', 'module', 'scripts', 'entrypoints', 'sdist', 'external-data' + "metadata", + "module", + "scripts", + "entrypoints", + "sdist", + "external-data", } - unknown_sections = [s for s in unknown_sections if not s.lower().startswith('x-')] + unknown_sections = [s for s in unknown_sections if not s.lower().startswith("x-")] if unknown_sections: - raise ConfigError('Unexpected tables in pyproject.toml: ' + ', '.join( - '[tool.flit.{}]'.format(s) for s in unknown_sections - )) + raise ConfigError( + "Unexpected tables in pyproject.toml: " + + ", ".join("[tool.flit.{}]".format(s) for s in unknown_sections) + ) - if 'sdist' in dtool: - unknown_keys = set(dtool['sdist']) - {'include', 'exclude'} + if "sdist" in dtool: + unknown_keys = set(dtool["sdist"]) - {"include", "exclude"} if unknown_keys: raise ConfigError( "Unknown keys in [tool.flit.sdist]:" + ", ".join(unknown_keys) ) loaded_cfg.sdist_include_patterns = _check_glob_patterns( - dtool['sdist'].get('include', []), 'include' + dtool["sdist"].get("include", []), "include" ) exclude = [ "**/__pycache__", "**.pyc", - ] + dtool['sdist'].get('exclude', []) - loaded_cfg.sdist_exclude_patterns = _check_glob_patterns( - exclude, 'exclude' - ) + ] + dtool[ + "sdist" + ].get("exclude", []) + loaded_cfg.sdist_exclude_patterns = _check_glob_patterns(exclude, "exclude") - data_dir = dtool.get('external-data', {}).get('directory', None) + data_dir = dtool.get("external-data", {}).get("directory", None) if data_dir is not None: toml_key = "tool.flit.external-data.directory" if not isinstance(data_dir, str): @@ -164,11 +169,11 @@ def prep_toml_config(d, path): normp = osp.normpath(data_dir) if osp.isabs(normp): raise ConfigError(f"{toml_key} cannot be an absolute path") - if normp.startswith('..' + os.sep): + if normp.startswith(".." + os.sep): raise ConfigError( f"{toml_key} cannot point outside the directory containing pyproject.toml" ) - if normp == '.': + if normp == ".": raise ConfigError( f"{toml_key} cannot refer to the directory containing pyproject.toml" ) @@ -178,6 +183,7 @@ def prep_toml_config(d, path): return loaded_cfg + def flatten_entrypoints(ep): """Flatten nested entrypoints dicts. @@ -194,11 +200,12 @@ def flatten_entrypoints(ep): flit allows you to use the former. This flattens the nested dictionaries from loading pyproject.toml. """ + def _flatten(d, prefix): d1 = {} for k, v in d.items(): if isinstance(v, dict): - for flattened in _flatten(v, prefix+'.'+k): + for flattened in _flatten(v, prefix + "." + k): yield flattened else: d1[k] = v @@ -226,20 +233,20 @@ def _check_glob_patterns(pats, clude): for p in pats: if bad_chars.search(p): raise ConfigError( - '{} pattern {!r} contains bad characters (<>:\"\\ or control characters)' - .format(clude, p) + '{} pattern {!r} contains bad characters (<>:"\\ or control characters)'.format( + clude, p + ) ) normp = osp.normpath(p) if osp.isabs(normp): + raise ConfigError("{} pattern {!r} is an absolute path".format(clude, p)) + if normp.startswith(".." + os.sep): raise ConfigError( - '{} pattern {!r} is an absolute path'.format(clude, p) - ) - if normp.startswith('..' + os.sep): - raise ConfigError( - '{} pattern {!r} points out of the directory containing pyproject.toml' - .format(clude, p) + "{} pattern {!r} points out of the directory containing pyproject.toml".format( + clude, p + ) ) normed.append(normp) @@ -260,15 +267,16 @@ def __init__(self): def add_scripts(self, scripts_dict): if scripts_dict: - if 'console_scripts' in self.entrypoints: + if "console_scripts" in self.entrypoints: raise EntryPointsConflict else: - self.entrypoints['console_scripts'] = scripts_dict + self.entrypoints["console_scripts"] = scripts_dict + readme_ext_to_content_type = { - '.rst': 'text/x-rst', - '.md': 'text/markdown', - '.txt': 'text/plain', + ".rst": "text/x-rst", + ".md": "text/markdown", + ".txt": "text/plain", } @@ -278,13 +286,11 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): desc_path = proj_dir / rel_path try: - with desc_path.open('r', encoding='utf-8') as f: + with desc_path.open("r", encoding="utf-8") as f: raw_desc = f.read() except IOError as e: if e.errno == errno.ENOENT: - raise ConfigError( - "Description file {} does not exist".format(desc_path) - ) + raise ConfigError("Description file {} does not exist".format(desc_path)) raise if guess_mimetype: @@ -293,8 +299,9 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): mimetype = readme_ext_to_content_type[ext] except KeyError: log.warning("Unknown extension %r for description file.", ext) - log.warning(" Recognised extensions: %s", - " ".join(readme_ext_to_content_type)) + log.warning( + " Recognised extensions: %s", " ".join(readme_ext_to_content_type) + ) mimetype = None else: mimetype = None @@ -304,110 +311,128 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): def _prep_metadata(md_sect, path): """Process & verify the metadata from a config file - + - Pull out the module name we're packaging. - Read description-file and check that it's valid rst - Convert dashes in key names to underscores - (e.g. home-page in config -> home_page in metadata) + (e.g. home-page in config -> home_page in metadata) """ if not set(md_sect).issuperset(metadata_required_fields): missing = metadata_required_fields - set(md_sect) - raise ConfigError("Required fields missing: " + '\n'.join(missing)) + raise ConfigError("Required fields missing: " + "\n".join(missing)) res = LoadedConfig() - res.module = md_sect.get('module') + res.module = md_sect.get("module") if not all([m.isidentifier() for m in res.module.split(".")]): raise ConfigError("Module name %r is not a valid identifier" % res.module) md_dict = res.metadata # Description file - if 'description-file' in md_sect: - desc_path = md_sect.get('description-file') - res.referenced_files.append(desc_path) - desc_content, mimetype = description_from_file(desc_path, path.parent) - md_dict['description'] = desc_content - md_dict['description_content_type'] = mimetype - - if 'urls' in md_sect: - project_urls = md_dict['project_urls'] = [] - for label, url in sorted(md_sect.pop('urls').items()): + if "description-file" in md_sect: + try: + desc_path = md_sect.get("description-file") + res.referenced_files.append(desc_path) + desc_content, mimetype = description_from_file(desc_path, path.parent) + md_dict["description"] = desc_content + md_dict["description_content_type"] = mimetype + except ConfigError as ex: + if os.environ.get("FLIT_ALLOW_INVALID"): + log.warning( + "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." + ) + else: + raise ConfigError("Invalid config values (see log)") from ex + + if "urls" in md_sect: + project_urls = md_dict["project_urls"] = [] + for label, url in sorted(md_sect.pop("urls").items()): project_urls.append("{}, {}".format(label, url)) for key, value in md_sect.items(): - if key in {'description-file', 'module'}: + if key in {"description-file", "module"}: continue if key not in metadata_allowed_fields: - closest = difflib.get_close_matches(key, metadata_allowed_fields, - n=1, cutoff=0.7) + closest = difflib.get_close_matches( + key, metadata_allowed_fields, n=1, cutoff=0.7 + ) msg = "Unrecognised metadata key: {!r}".format(key) if closest: msg += " (did you mean {!r}?)".format(closest[0]) raise ConfigError(msg) - k2 = key.replace('-', '_') + k2 = key.replace("-", "_") md_dict[k2] = value if key in metadata_list_fields: if not isinstance(value, list): - raise ConfigError('Expected a list for {} field, found {!r}' - .format(key, value)) + raise ConfigError( + "Expected a list for {} field, found {!r}".format(key, value) + ) if not all(isinstance(a, str) for a in value): - raise ConfigError('Expected a list of strings for {} field' - .format(key)) - elif key == 'requires-extra': + raise ConfigError("Expected a list of strings for {} field".format(key)) + elif key == "requires-extra": if not isinstance(value, dict): - raise ConfigError('Expected a dict for requires-extra field, found {!r}' - .format(value)) + raise ConfigError( + "Expected a dict for requires-extra field, found {!r}".format(value) + ) if not all(isinstance(e, list) for e in value.values()): - raise ConfigError('Expected a dict of lists for requires-extra field') + raise ConfigError("Expected a dict of lists for requires-extra field") for e, reqs in value.items(): if not all(isinstance(a, str) for a in reqs): - raise ConfigError('Expected a string list for requires-extra. (extra {})' - .format(e)) + raise ConfigError( + "Expected a string list for requires-extra. (extra {})".format( + e + ) + ) else: if not isinstance(value, str): - raise ConfigError('Expected a string for {} field, found {!r}' - .format(key, value)) + raise ConfigError( + "Expected a string for {} field, found {!r}".format(key, value) + ) # What we call requires in the ini file is technically requires_dist in # the metadata. - if 'requires' in md_dict: - md_dict['requires_dist'] = md_dict.pop('requires') + if "requires" in md_dict: + md_dict["requires_dist"] = md_dict.pop("requires") # And what we call dist-name is name in the metadata - if 'dist_name' in md_dict: - md_dict['name'] = md_dict.pop('dist_name') + if "dist_name" in md_dict: + md_dict["name"] = md_dict.pop("dist_name") # Move dev-requires into requires-extra - reqs_noextra = md_dict.pop('requires_dist', []) - res.reqs_by_extra = md_dict.pop('requires_extra', {}) - dev_requires = md_dict.pop('dev_requires', None) + reqs_noextra = md_dict.pop("requires_dist", []) + res.reqs_by_extra = md_dict.pop("requires_extra", {}) + dev_requires = md_dict.pop("dev_requires", None) if dev_requires is not None: - if 'dev' in res.reqs_by_extra: + if "dev" in res.reqs_by_extra: raise ConfigError( - 'dev-requires occurs together with its replacement requires-extra.dev.') + "dev-requires occurs together with its replacement requires-extra.dev." + ) else: log.warning( - '"dev-requires = ..." is obsolete. Use "requires-extra = {"dev" = ...}" instead.') - res.reqs_by_extra['dev'] = dev_requires + '"dev-requires = ..." is obsolete. Use "requires-extra = {"dev" = ...}" instead.' + ) + res.reqs_by_extra["dev"] = dev_requires # Add requires-extra requirements into requires_dist - md_dict['requires_dist'] = \ - reqs_noextra + list(_expand_requires_extra(res.reqs_by_extra)) + md_dict["requires_dist"] = reqs_noextra + list( + _expand_requires_extra(res.reqs_by_extra) + ) - md_dict['provides_extra'] = sorted(res.reqs_by_extra.keys()) + md_dict["provides_extra"] = sorted(res.reqs_by_extra.keys()) # For internal use, record the main requirements as a '.none' extra. - res.reqs_by_extra['.none'] = reqs_noextra + res.reqs_by_extra[".none"] = reqs_noextra return res + def _expand_requires_extra(re): for extra, reqs in sorted(re.items()): for req in reqs: - if ';' in req: - name, envmark = req.split(';', 1) + if ";" in req: + name, envmark = req.split(";", 1) yield '{} ; extra == "{}" and ({})'.format(name, extra, envmark) else: yield '{} ; extra == "{}"'.format(req, extra) @@ -419,49 +444,49 @@ def _check_type(d, field_name, cls): "{} field should be {}, not {}".format(field_name, cls, type(d[field_name])) ) + def _check_list_of_str(d, field_name): if not isinstance(d[field_name], list) or not all( isinstance(e, str) for e in d[field_name] ): - raise ConfigError( - "{} field should be a list of strings".format(field_name) - ) + raise ConfigError("{} field should be a list of strings".format(field_name)) + def read_pep621_metadata(proj, path) -> LoadedConfig: lc = LoadedConfig() md_dict = lc.metadata - if 'name' not in proj: - raise ConfigError('name must be specified in [project] table') - _check_type(proj, 'name', str) - md_dict['name'] = proj['name'] - lc.module = md_dict['name'].replace('-', '_') + if "name" not in proj: + raise ConfigError("name must be specified in [project] table") + _check_type(proj, "name", str) + md_dict["name"] = proj["name"] + lc.module = md_dict["name"].replace("-", "_") unexpected_keys = proj.keys() - pep621_allowed_fields if unexpected_keys: - log.warning("Unexpected names under [project]: %s", ', '.join(unexpected_keys)) - - if 'version' in proj: - _check_type(proj, 'version', str) - md_dict['version'] = normalise_version(proj['version']) - if 'description' in proj: - _check_type(proj, 'description', str) - md_dict['summary'] = proj['description'] - if 'readme' in proj: - readme = proj['readme'] + log.warning("Unexpected names under [project]: %s", ", ".join(unexpected_keys)) + + if "version" in proj: + _check_type(proj, "version", str) + md_dict["version"] = normalise_version(proj["version"]) + if "description" in proj: + _check_type(proj, "description", str) + md_dict["summary"] = proj["description"] + if "readme" in proj: + readme = proj["readme"] if isinstance(readme, str): lc.referenced_files.append(readme) desc_content, mimetype = description_from_file(readme, path.parent) elif isinstance(readme, dict): - unrec_keys = set(readme.keys()) - {'text', 'file', 'content-type'} + unrec_keys = set(readme.keys()) - {"text", "file", "content-type"} if unrec_keys: raise ConfigError( "Unrecognised keys in [project.readme]: {}".format(unrec_keys) ) - if 'content-type' in readme: - mimetype = readme['content-type'] - mtype_base = mimetype.split(';')[0].strip() # e.g. text/x-rst + if "content-type" in readme: + mimetype = readme["content-type"] + mtype_base = mimetype.split(";")[0].strip() # e.g. text/x-rst if mtype_base not in readme_ext_to_content_type.values(): raise ConfigError( "Unrecognised readme content-type: {!r}".format(mtype_base) @@ -471,36 +496,34 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: raise ConfigError( "content-type field required in [project.readme] table" ) - if 'file' in readme: - if 'text' in readme: + if "file" in readme: + if "text" in readme: raise ConfigError( "[project.readme] should specify file or text, not both" ) - lc.referenced_files.append(readme['file']) + lc.referenced_files.append(readme["file"]) desc_content, _ = description_from_file( - readme['file'], path.parent, guess_mimetype=False + readme["file"], path.parent, guess_mimetype=False ) - elif 'text' in readme: - desc_content = readme['text'] + elif "text" in readme: + desc_content = readme["text"] else: raise ConfigError( "file or text field required in [project.readme] table" ) else: - raise ConfigError( - "project.readme should be a string or a table" - ) + raise ConfigError("project.readme should be a string or a table") - md_dict['description'] = desc_content - md_dict['description_content_type'] = mimetype + md_dict["description"] = desc_content + md_dict["description_content_type"] = mimetype - if 'requires-python' in proj: - md_dict['requires_python'] = proj['requires-python'] + if "requires-python" in proj: + md_dict["requires_python"] = proj["requires-python"] - if 'license' in proj: - _check_type(proj, 'license', dict) - license_tbl = proj['license'] - unrec_keys = set(license_tbl.keys()) - {'text', 'file'} + if "license" in proj: + _check_type(proj, "license", dict) + license_tbl = proj["license"] + unrec_keys = set(license_tbl.keys()) - {"text", "file"} if unrec_keys: raise ConfigError( "Unrecognised keys in [project.license]: {}".format(unrec_keys) @@ -510,44 +533,42 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: # The 'License' field in packaging metadata is a brief description of # a license, not the full text or a file path. PEP 639 will improve on # how licenses are recorded. - if 'file' in license_tbl: - if 'text' in license_tbl: + if "file" in license_tbl: + if "text" in license_tbl: raise ConfigError( "[project.license] should specify file or text, not both" ) - lc.referenced_files.append(license_tbl['file']) - elif 'text' in license_tbl: + lc.referenced_files.append(license_tbl["file"]) + elif "text" in license_tbl: pass else: - raise ConfigError( - "file or text field required in [project.license] table" - ) + raise ConfigError("file or text field required in [project.license] table") - if 'authors' in proj: - _check_type(proj, 'authors', list) - md_dict.update(pep621_people(proj['authors'])) + if "authors" in proj: + _check_type(proj, "authors", list) + md_dict.update(pep621_people(proj["authors"])) - if 'maintainers' in proj: - _check_type(proj, 'maintainers', list) - md_dict.update(pep621_people(proj['maintainers'], group_name='maintainer')) + if "maintainers" in proj: + _check_type(proj, "maintainers", list) + md_dict.update(pep621_people(proj["maintainers"], group_name="maintainer")) - if 'keywords' in proj: - _check_list_of_str(proj, 'keywords') - md_dict['keywords'] = ",".join(proj['keywords']) + if "keywords" in proj: + _check_list_of_str(proj, "keywords") + md_dict["keywords"] = ",".join(proj["keywords"]) - if 'classifiers' in proj: - _check_list_of_str(proj, 'classifiers') - md_dict['classifiers'] = proj['classifiers'] + if "classifiers" in proj: + _check_list_of_str(proj, "classifiers") + md_dict["classifiers"] = proj["classifiers"] - if 'urls' in proj: - _check_type(proj, 'urls', dict) - project_urls = md_dict['project_urls'] = [] - for label, url in sorted(proj['urls'].items()): + if "urls" in proj: + _check_type(proj, "urls", dict) + project_urls = md_dict["project_urls"] = [] + for label, url in sorted(proj["urls"].items()): project_urls.append("{}, {}".format(label, url)) - if 'entry-points' in proj: - _check_type(proj, 'entry-points', dict) - for grp in proj['entry-points'].values(): + if "entry-points" in proj: + _check_type(proj, "entry-points", dict) + for grp in proj["entry-points"].values(): if not isinstance(grp, dict): raise ConfigError( "projects.entry-points should only contain sub-tables" @@ -556,62 +577,57 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: raise ConfigError( "[projects.entry-points.*] tables should have string values" ) - if set(proj['entry-points'].keys()) & {'console_scripts', 'gui_scripts'}: + if set(proj["entry-points"].keys()) & {"console_scripts", "gui_scripts"}: raise ConfigError( "Scripts should be specified in [project.scripts] or " "[project.gui-scripts], not under [project.entry-points]" ) - lc.entrypoints = proj['entry-points'] - - if 'scripts' in proj: - _check_type(proj, 'scripts', dict) - if not all(isinstance(k, str) for k in proj['scripts'].values()): - raise ConfigError( - "[projects.scripts] table should have string values" - ) - lc.entrypoints['console_scripts'] = proj['scripts'] - - if 'gui-scripts' in proj: - _check_type(proj, 'gui-scripts', dict) - if not all(isinstance(k, str) for k in proj['gui-scripts'].values()): - raise ConfigError( - "[projects.gui-scripts] table should have string values" - ) - lc.entrypoints['gui_scripts'] = proj['gui-scripts'] - - if 'dependencies' in proj: - _check_list_of_str(proj, 'dependencies') - reqs_noextra = proj['dependencies'] + lc.entrypoints = proj["entry-points"] + + if "scripts" in proj: + _check_type(proj, "scripts", dict) + if not all(isinstance(k, str) for k in proj["scripts"].values()): + raise ConfigError("[projects.scripts] table should have string values") + lc.entrypoints["console_scripts"] = proj["scripts"] + + if "gui-scripts" in proj: + _check_type(proj, "gui-scripts", dict) + if not all(isinstance(k, str) for k in proj["gui-scripts"].values()): + raise ConfigError("[projects.gui-scripts] table should have string values") + lc.entrypoints["gui_scripts"] = proj["gui-scripts"] + + if "dependencies" in proj: + _check_list_of_str(proj, "dependencies") + reqs_noextra = proj["dependencies"] else: reqs_noextra = [] - if 'optional-dependencies' in proj: - _check_type(proj, 'optional-dependencies', dict) - optdeps = proj['optional-dependencies'] + if "optional-dependencies" in proj: + _check_type(proj, "optional-dependencies", dict) + optdeps = proj["optional-dependencies"] if not all(isinstance(e, list) for e in optdeps.values()): - raise ConfigError( - 'Expected a dict of lists in optional-dependencies field' - ) + raise ConfigError("Expected a dict of lists in optional-dependencies field") for e, reqs in optdeps.items(): if not all(isinstance(a, str) for a in reqs): raise ConfigError( - 'Expected a string list for optional-dependencies ({})'.format(e) + "Expected a string list for optional-dependencies ({})".format(e) ) lc.reqs_by_extra = optdeps.copy() - md_dict['provides_extra'] = sorted(lc.reqs_by_extra.keys()) + md_dict["provides_extra"] = sorted(lc.reqs_by_extra.keys()) - md_dict['requires_dist'] = \ - reqs_noextra + list(_expand_requires_extra(lc.reqs_by_extra)) + md_dict["requires_dist"] = reqs_noextra + list( + _expand_requires_extra(lc.reqs_by_extra) + ) # For internal use, record the main requirements as a '.none' extra. if reqs_noextra: - lc.reqs_by_extra['.none'] = reqs_noextra + lc.reqs_by_extra[".none"] = reqs_noextra - if 'dynamic' in proj: - _check_list_of_str(proj, 'dynamic') - dynamic = set(proj['dynamic']) - unrec_dynamic = dynamic - {'version', 'description'} + if "dynamic" in proj: + _check_list_of_str(proj, "dynamic") + dynamic = set(proj["dynamic"]) + unrec_dynamic = dynamic - {"version", "description"} if unrec_dynamic: raise ConfigError( "flit only supports dynamic metadata for 'version' & 'description'" @@ -622,39 +638,40 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: ) lc.dynamic_metadata = dynamic - if ('version' not in proj) and ('version' not in lc.dynamic_metadata): + if ("version" not in proj) and ("version" not in lc.dynamic_metadata): raise ConfigError( "version must be specified under [project] or listed as a dynamic field" ) - if ('description' not in proj) and ('description' not in lc.dynamic_metadata): + if ("description" not in proj) and ("description" not in lc.dynamic_metadata): raise ConfigError( "description must be specified under [project] or listed as a dynamic field" ) return lc -def pep621_people(people, group_name='author') -> dict: + +def pep621_people(people, group_name="author") -> dict: """Convert authors/maintainers from PEP 621 to core metadata fields""" names, emails = [], [] for person in people: if not isinstance(person, dict): raise ConfigError("{} info must be list of dicts".format(group_name)) - unrec_keys = set(person.keys()) - {'name', 'email'} + unrec_keys = set(person.keys()) - {"name", "email"} if unrec_keys: raise ConfigError( "Unrecognised keys in {} info: {}".format(group_name, unrec_keys) ) - if 'email' in person: - email = person['email'] - if 'name' in person: - email = str(Address(person['name'], addr_spec=email)) + if "email" in person: + email = person["email"] + if "name" in person: + email = str(Address(person["name"], addr_spec=email)) emails.append(email) - elif 'name' in person: - names.append(person['name']) + elif "name" in person: + names.append(person["name"]) res = {} if names: res[group_name] = ", ".join(names) if emails: - res[group_name + '_email'] = ", ".join(emails) + res[group_name + "_email"] = ", ".join(emails) return res diff --git a/test/pyproject.toml b/test/pyproject.toml new file mode 100644 index 00000000..caeefcd1 --- /dev/null +++ b/test/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["flit_core >=3.8.0,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "flit" +authors = [ + {name = "Thomas Kluyver", email = "thomas@kluyver.me.uk"}, +] +dependencies = [ + "flit_core >=3.8.0", + "requests", + "docutils", + "tomli-w", +] +requires-python = ">=3.6" +readme = "README.rst" +license = {file = "LICENSE"} +classifiers = ["Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dynamic = ['version', 'description'] + +[project.optional-dependencies] +test = [ + "testpath", + "responses", + "pytest>=2.7.3", + "pytest-cov", + "tomli", +] +doc = [ + "sphinx", + "sphinxcontrib_github_alt", + "pygments-github-lexers", # TOML highlighting +] + +[project.urls] +Documentation = "https://flit.pypa.io" +Source = "https://github.com/pypa/flit" +Changelog = "https://flit.pypa.io/en/stable/history.html" + +[project.scripts] +flit = "flit:main" diff --git a/tests/samples/only-deps.toml b/tests/samples/only-deps.toml new file mode 100644 index 00000000..f39955bf --- /dev/null +++ b/tests/samples/only-deps.toml @@ -0,0 +1,10 @@ +[build-system] +requires = ["flit"] + +[tool.flit.metadata] +module = "nomodule" +author = "Sir Robin" +author-email = "robin@camelot.uk" +home-page = "http://github.com/sirrobin/module1" +description-file = "NO_README.rst" +requires = ["requests"] diff --git a/tests/test_install.py b/tests/test_install.py index b4e9068e..2efaeb7c 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -8,26 +8,33 @@ import pytest from testpath import ( - assert_isfile, assert_isdir, assert_islink, assert_not_path_exists, MockCommand + assert_isfile, + assert_isdir, + assert_islink, + assert_not_path_exists, + MockCommand, ) from flit import install from flit.install import Installer, _requires_dist_to_pip_requirement, DependencyError import flit_core.tests -samples_dir = pathlib.Path(__file__).parent / 'samples' -core_samples_dir = pathlib.Path(flit_core.tests.__file__).parent / 'samples' +samples_dir = pathlib.Path(__file__).parent / "samples" +core_samples_dir = pathlib.Path(flit_core.tests.__file__).parent / "samples" + class InstallTests(TestCase): def setUp(self): td = tempfile.TemporaryDirectory() self.addCleanup(td.cleanup) - self.get_dirs_patch = patch('flit.install.get_dirs', - return_value={ - 'scripts': os.path.join(td.name, 'scripts'), - 'purelib': os.path.join(td.name, 'site-packages'), - 'data': os.path.join(td.name, 'data'), - }) + self.get_dirs_patch = patch( + "flit.install.get_dirs", + return_value={ + "scripts": os.path.join(td.name, "scripts"), + "purelib": os.path.join(td.name, "site-packages"), + "data": os.path.join(td.name, "data"), + }, + ) self.get_dirs_patch.start() self.tmpdir = pathlib.Path(td.name) @@ -37,251 +44,309 @@ def tearDown(self): def _assert_direct_url(self, directory, package, version, expected_editable): direct_url_file = ( self.tmpdir - / 'site-packages' - / '{}-{}.dist-info'.format(package, version) - / 'direct_url.json' + / "site-packages" + / "{}-{}.dist-info".format(package, version) + / "direct_url.json" ) assert_isfile(direct_url_file) with direct_url_file.open() as f: direct_url = json.load(f) - assert direct_url['url'].startswith('file:///') - assert direct_url['url'] == directory.as_uri() - assert direct_url['dir_info'].get('editable') is expected_editable + assert direct_url["url"].startswith("file:///") + assert direct_url["url"] == directory.as_uri() + assert direct_url["dir_info"].get("editable") is expected_editable def test_install_module(self): - Installer.from_ini_path(samples_dir / 'module1_toml' / 'pyproject.toml').install_directly() - assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') - assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') + Installer.from_ini_path( + samples_dir / "module1_toml" / "pyproject.toml" + ).install_directly() + assert_isfile(self.tmpdir / "site-packages" / "module1.py") + assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") self._assert_direct_url( - samples_dir / 'module1_toml', 'module1', '0.1', expected_editable=False + samples_dir / "module1_toml", "module1", "0.1", expected_editable=False ) def test_install_module_pep621(self): Installer.from_ini_path( - core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', + core_samples_dir / "pep621_nodynamic" / "pyproject.toml", ).install_directly() - assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') - assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info') + assert_isfile(self.tmpdir / "site-packages" / "module1.py") + assert_isdir(self.tmpdir / "site-packages" / "module1-0.3.dist-info") self._assert_direct_url( - core_samples_dir / 'pep621_nodynamic', 'module1', '0.3', - expected_editable=False + core_samples_dir / "pep621_nodynamic", + "module1", + "0.3", + expected_editable=False, ) def test_install_package(self): oldcwd = os.getcwd() - os.chdir(str(samples_dir / 'package1')) + os.chdir(str(samples_dir / "package1")) try: - Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly() + Installer.from_ini_path(pathlib.Path("pyproject.toml")).install_directly() finally: os.chdir(oldcwd) - assert_isdir(self.tmpdir / 'site-packages' / 'package1') - assert_isdir(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info') - assert_isfile(self.tmpdir / 'scripts' / 'pkg_script') - with (self.tmpdir / 'scripts' / 'pkg_script').open() as f: + assert_isdir(self.tmpdir / "site-packages" / "package1") + assert_isdir(self.tmpdir / "site-packages" / "package1-0.1.dist-info") + assert_isfile(self.tmpdir / "scripts" / "pkg_script") + with (self.tmpdir / "scripts" / "pkg_script").open() as f: assert f.readline().strip() == "#!" + sys.executable self._assert_direct_url( - samples_dir / 'package1', 'package1', '0.1', expected_editable=False + samples_dir / "package1", "package1", "0.1", expected_editable=False ) def test_install_module_in_src(self): oldcwd = os.getcwd() - os.chdir(samples_dir / 'packageinsrc') + os.chdir(samples_dir / "packageinsrc") try: - Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly() + Installer.from_ini_path(pathlib.Path("pyproject.toml")).install_directly() finally: os.chdir(oldcwd) - assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') - assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') + assert_isfile(self.tmpdir / "site-packages" / "module1.py") + assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") def test_install_ns_package_native(self): - Installer.from_ini_path(samples_dir / 'ns1-pkg' / 'pyproject.toml').install_directly() - assert_isdir(self.tmpdir / 'site-packages' / 'ns1') - assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'pkg' / '__init__.py') - assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py') - assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info') + Installer.from_ini_path( + samples_dir / "ns1-pkg" / "pyproject.toml" + ).install_directly() + assert_isdir(self.tmpdir / "site-packages" / "ns1") + assert_isfile(self.tmpdir / "site-packages" / "ns1" / "pkg" / "__init__.py") + assert_not_path_exists(self.tmpdir / "site-packages" / "ns1" / "__init__.py") + assert_isdir(self.tmpdir / "site-packages" / "ns1_pkg-0.1.dist-info") def test_install_ns_package_module_native(self): - Installer.from_ini_path(samples_dir / 'ns1-pkg-mod' / 'pyproject.toml').install_directly() - assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'module.py') - assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py') + Installer.from_ini_path( + samples_dir / "ns1-pkg-mod" / "pyproject.toml" + ).install_directly() + assert_isfile(self.tmpdir / "site-packages" / "ns1" / "module.py") + assert_not_path_exists(self.tmpdir / "site-packages" / "ns1" / "__init__.py") def test_install_ns_package_native_symlink(self): - if os.name == 'nt': - raise SkipTest('symlink') + if os.name == "nt": + raise SkipTest("symlink") Installer.from_ini_path( - samples_dir / 'ns1-pkg' / 'pyproject.toml', symlink=True + samples_dir / "ns1-pkg" / "pyproject.toml", symlink=True ).install_directly() Installer.from_ini_path( - samples_dir / 'ns1-pkg2' / 'pyproject.toml', symlink=True + samples_dir / "ns1-pkg2" / "pyproject.toml", symlink=True ).install_directly() Installer.from_ini_path( - samples_dir / 'ns1-pkg-mod' / 'pyproject.toml', symlink=True + samples_dir / "ns1-pkg-mod" / "pyproject.toml", symlink=True ).install_directly() - assert_isdir(self.tmpdir / 'site-packages' / 'ns1') - assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg') - assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg', - to=str(samples_dir / 'ns1-pkg' / 'ns1' / 'pkg')) - assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info') + assert_isdir(self.tmpdir / "site-packages" / "ns1") + assert_isdir(self.tmpdir / "site-packages" / "ns1" / "pkg") + assert_islink( + self.tmpdir / "site-packages" / "ns1" / "pkg", + to=str(samples_dir / "ns1-pkg" / "ns1" / "pkg"), + ) + assert_isdir(self.tmpdir / "site-packages" / "ns1_pkg-0.1.dist-info") - assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2') - assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2', - to=str(samples_dir / 'ns1-pkg2' / 'ns1' / 'pkg2')) - assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg2-0.1.dist-info') + assert_isdir(self.tmpdir / "site-packages" / "ns1" / "pkg2") + assert_islink( + self.tmpdir / "site-packages" / "ns1" / "pkg2", + to=str(samples_dir / "ns1-pkg2" / "ns1" / "pkg2"), + ) + assert_isdir(self.tmpdir / "site-packages" / "ns1_pkg2-0.1.dist-info") - assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'module.py', - to=samples_dir / 'ns1-pkg-mod' / 'ns1' / 'module.py') - assert_isdir(self.tmpdir / 'site-packages' / 'ns1_module-0.1.dist-info') + assert_islink( + self.tmpdir / "site-packages" / "ns1" / "module.py", + to=samples_dir / "ns1-pkg-mod" / "ns1" / "module.py", + ) + assert_isdir(self.tmpdir / "site-packages" / "ns1_module-0.1.dist-info") def test_install_ns_package_pth_file(self): Installer.from_ini_path( - samples_dir / 'ns1-pkg' / 'pyproject.toml', pth=True + samples_dir / "ns1-pkg" / "pyproject.toml", pth=True ).install_directly() - pth_file = self.tmpdir / 'site-packages' / 'ns1.pkg.pth' + pth_file = self.tmpdir / "site-packages" / "ns1.pkg.pth" assert_isfile(pth_file) - assert pth_file.read_text('utf-8').strip() == str(samples_dir / 'ns1-pkg') + assert pth_file.read_text("utf-8").strip() == str(samples_dir / "ns1-pkg") def test_symlink_package(self): - if os.name == 'nt': + if os.name == "nt": raise SkipTest("symlink") - Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', symlink=True).install() - assert_islink(self.tmpdir / 'site-packages' / 'package1', - to=samples_dir / 'package1' / 'package1') - assert_isfile(self.tmpdir / 'scripts' / 'pkg_script') - with (self.tmpdir / 'scripts' / 'pkg_script').open() as f: + Installer.from_ini_path( + samples_dir / "package1" / "pyproject.toml", symlink=True + ).install() + assert_islink( + self.tmpdir / "site-packages" / "package1", + to=samples_dir / "package1" / "package1", + ) + assert_isfile(self.tmpdir / "scripts" / "pkg_script") + with (self.tmpdir / "scripts" / "pkg_script").open() as f: assert f.readline().strip() == "#!" + sys.executable self._assert_direct_url( - samples_dir / 'package1', 'package1', '0.1', expected_editable=True + samples_dir / "package1", "package1", "0.1", expected_editable=True ) def test_symlink_module_pep621(self): - if os.name == 'nt': + if os.name == "nt": raise SkipTest("symlink") Installer.from_ini_path( - core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', symlink=True + core_samples_dir / "pep621_nodynamic" / "pyproject.toml", symlink=True ).install_directly() - assert_islink(self.tmpdir / 'site-packages' / 'module1.py', - to=core_samples_dir / 'pep621_nodynamic' / 'module1.py') - assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info') + assert_islink( + self.tmpdir / "site-packages" / "module1.py", + to=core_samples_dir / "pep621_nodynamic" / "module1.py", + ) + assert_isdir(self.tmpdir / "site-packages" / "module1-0.3.dist-info") self._assert_direct_url( - core_samples_dir / 'pep621_nodynamic', 'module1', '0.3', - expected_editable=True + core_samples_dir / "pep621_nodynamic", + "module1", + "0.3", + expected_editable=True, ) def test_symlink_module_in_src(self): - if os.name == 'nt': + if os.name == "nt": raise SkipTest("symlink") oldcwd = os.getcwd() - os.chdir(samples_dir / 'packageinsrc') + os.chdir(samples_dir / "packageinsrc") try: Installer.from_ini_path( - pathlib.Path('pyproject.toml'), symlink=True + pathlib.Path("pyproject.toml"), symlink=True ).install_directly() finally: os.chdir(oldcwd) - assert_islink(self.tmpdir / 'site-packages' / 'module1.py', - to=(samples_dir / 'packageinsrc' / 'src' / 'module1.py')) - assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') + assert_islink( + self.tmpdir / "site-packages" / "module1.py", + to=(samples_dir / "packageinsrc" / "src" / "module1.py"), + ) + assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") def test_pth_package(self): - Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', pth=True).install() - assert_isfile(self.tmpdir / 'site-packages' / 'package1.pth') - with open(str(self.tmpdir / 'site-packages' / 'package1.pth')) as f: - assert f.read() == str(samples_dir / 'package1') - assert_isfile(self.tmpdir / 'scripts' / 'pkg_script') + Installer.from_ini_path( + samples_dir / "package1" / "pyproject.toml", pth=True + ).install() + assert_isfile(self.tmpdir / "site-packages" / "package1.pth") + with open(str(self.tmpdir / "site-packages" / "package1.pth")) as f: + assert f.read() == str(samples_dir / "package1") + assert_isfile(self.tmpdir / "scripts" / "pkg_script") self._assert_direct_url( - samples_dir / 'package1', 'package1', '0.1', expected_editable=True + samples_dir / "package1", "package1", "0.1", expected_editable=True ) def test_pth_module_in_src(self): oldcwd = os.getcwd() - os.chdir(samples_dir / 'packageinsrc') + os.chdir(samples_dir / "packageinsrc") try: Installer.from_ini_path( - pathlib.Path('pyproject.toml'), pth=True + pathlib.Path("pyproject.toml"), pth=True ).install_directly() finally: os.chdir(oldcwd) - pth_path = self.tmpdir / 'site-packages' / 'module1.pth' + pth_path = self.tmpdir / "site-packages" / "module1.pth" assert_isfile(pth_path) - assert pth_path.read_text('utf-8').strip() == str( - samples_dir / 'packageinsrc' / 'src' + assert pth_path.read_text("utf-8").strip() == str( + samples_dir / "packageinsrc" / "src" ) - assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') + assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") def test_dist_name(self): - Installer.from_ini_path(samples_dir / 'altdistname' / 'pyproject.toml').install_directly() - assert_isdir(self.tmpdir / 'site-packages' / 'package1') - assert_isdir(self.tmpdir / 'site-packages' / 'package_dist1-0.1.dist-info') + Installer.from_ini_path( + samples_dir / "altdistname" / "pyproject.toml" + ).install_directly() + assert_isdir(self.tmpdir / "site-packages" / "package1") + assert_isdir(self.tmpdir / "site-packages" / "package_dist1-0.1.dist-info") def test_entry_points(self): - Installer.from_ini_path(samples_dir / 'entrypoints_valid' / 'pyproject.toml').install_directly() - assert_isfile(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info' / 'entry_points.txt') + Installer.from_ini_path( + samples_dir / "entrypoints_valid" / "pyproject.toml" + ).install_directly() + assert_isfile( + self.tmpdir + / "site-packages" + / "package1-0.1.dist-info" + / "entry_points.txt" + ) def test_pip_install(self): - ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python', - user=False) + ins = Installer.from_ini_path( + samples_dir / "package1" / "pyproject.toml", + python="mock_python", + user=False, + ) - with MockCommand('mock_python') as mock_py: + with MockCommand("mock_python") as mock_py: ins.install() calls = mock_py.get_calls() assert len(calls) == 1 - cmd = calls[0]['argv'] - assert cmd[1:4] == ['-m', 'pip', 'install'] - assert cmd[4].endswith('package1') + cmd = calls[0]["argv"] + assert cmd[1:4] == ["-m", "pip", "install"] + assert cmd[4].endswith("package1") def test_symlink_other_python(self): - if os.name == 'nt': - raise SkipTest('symlink') - (self.tmpdir / 'site-packages2').mkdir() - (self.tmpdir / 'scripts2').mkdir() + if os.name == "nt": + raise SkipTest("symlink") + (self.tmpdir / "site-packages2").mkdir() + (self.tmpdir / "scripts2").mkdir() # Called by Installer._auto_user() : - script1 = ("#!{python}\n" - "import sysconfig\n" - "print(True)\n" # site.ENABLE_USER_SITE - "print({purelib!r})" # sysconfig.get_path('purelib') - ).format(python=sys.executable, - purelib=str(self.tmpdir / 'site-packages2')) + script1 = ( + "#!{python}\n" + "import sysconfig\n" + "print(True)\n" # site.ENABLE_USER_SITE + "print({purelib!r})" # sysconfig.get_path('purelib') + ).format(python=sys.executable, purelib=str(self.tmpdir / "site-packages2")) # Called by Installer._get_dirs() : - script2 = ("#!{python}\n" - "import json, sys\n" - "json.dump({{'purelib': {purelib!r}, 'scripts': {scripts!r}, 'data': {data!r} }}, " - "sys.stdout)" - ).format(python=sys.executable, - purelib=str(self.tmpdir / 'site-packages2'), - scripts=str(self.tmpdir / 'scripts2'), - data=str(self.tmpdir / 'data'), - ) - - with MockCommand('mock_python', content=script1): - ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python', - symlink=True) - with MockCommand('mock_python', content=script2): + script2 = ( + "#!{python}\n" + "import json, sys\n" + "json.dump({{'purelib': {purelib!r}, 'scripts': {scripts!r}, 'data': {data!r} }}, " + "sys.stdout)" + ).format( + python=sys.executable, + purelib=str(self.tmpdir / "site-packages2"), + scripts=str(self.tmpdir / "scripts2"), + data=str(self.tmpdir / "data"), + ) + + with MockCommand("mock_python", content=script1): + ins = Installer.from_ini_path( + samples_dir / "package1" / "pyproject.toml", + python="mock_python", + symlink=True, + ) + with MockCommand("mock_python", content=script2): ins.install() - assert_islink(self.tmpdir / 'site-packages2' / 'package1', - to=samples_dir / 'package1' / 'package1') - assert_isfile(self.tmpdir / 'scripts2' / 'pkg_script') - with (self.tmpdir / 'scripts2' / 'pkg_script').open() as f: + assert_islink( + self.tmpdir / "site-packages2" / "package1", + to=samples_dir / "package1" / "package1", + ) + assert_isfile(self.tmpdir / "scripts2" / "pkg_script") + with (self.tmpdir / "scripts2" / "pkg_script").open() as f: assert f.readline().strip() == "#!mock_python" def test_install_requires(self): - ins = Installer.from_ini_path(samples_dir / 'requires-requests.toml', - user=False, python='mock_python') + ins = Installer.from_ini_path( + samples_dir / "requires-requests.toml", user=False, python="mock_python" + ) - with MockCommand('mock_python') as mockpy: + with MockCommand("mock_python") as mockpy: ins.install_requirements() calls = mockpy.get_calls() assert len(calls) == 1 - assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r'] + assert calls[0]["argv"][1:5] == ["-m", "pip", "install", "-r"] + + def test_install_only_deps(self): + os.environ.setdefault("FLIT_ALLOW_INVALID", "1") + ins = Installer.from_ini_path( + samples_dir / "only-deps.toml", user=False, python="mock_python" + ) + + with MockCommand("mock_python") as mockpy: + ins.install_requirements() + calls = mockpy.get_calls() + assert len(calls) == 1 + assert calls[0]["argv"][1:5] == ["-m", "pip", "install", "-r"] def test_install_reqs_my_python_if_needed_pep621(self): ins = Installer.from_ini_path( - core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', - deps='none', + core_samples_dir / "pep621_nodynamic" / "pyproject.toml", + deps="none", ) # This shouldn't try to get version & docstring from the module @@ -289,42 +354,61 @@ def test_install_reqs_my_python_if_needed_pep621(self): def test_extras_error(self): with pytest.raises(DependencyError): - Installer.from_ini_path(samples_dir / 'requires-requests.toml', - user=False, deps='none', extras='dev') + Installer.from_ini_path( + samples_dir / "requires-requests.toml", + user=False, + deps="none", + extras="dev", + ) def test_install_data_dir(self): Installer.from_ini_path( - core_samples_dir / 'with_data_dir' / 'pyproject.toml', + core_samples_dir / "with_data_dir" / "pyproject.toml", ).install_directly() - assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') - assert_isfile(self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1') + assert_isfile(self.tmpdir / "site-packages" / "module1.py") + assert_isfile(self.tmpdir / "data" / "share" / "man" / "man1" / "foo.1") def test_symlink_data_dir(self): - if os.name == 'nt': + if os.name == "nt": raise SkipTest("symlink") Installer.from_ini_path( - core_samples_dir / 'with_data_dir' / 'pyproject.toml', symlink=True + core_samples_dir / "with_data_dir" / "pyproject.toml", symlink=True ).install_directly() - assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') + assert_isfile(self.tmpdir / "site-packages" / "module1.py") assert_islink( - self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1', - to=core_samples_dir / 'with_data_dir' / 'data' / 'share' / 'man' / 'man1' / 'foo.1' + self.tmpdir / "data" / "share" / "man" / "man1" / "foo.1", + to=core_samples_dir + / "with_data_dir" + / "data" + / "share" + / "man" + / "man1" + / "foo.1", ) -@pytest.mark.parametrize(('deps', 'extras', 'installed'), [ - ('none', [], set()), - ('develop', [], {'pytest ;', 'toml ;'}), - ('production', [], {'toml ;'}), - ('all', [], {'toml ;', 'pytest ;', 'requests ;'}), -]) + +@pytest.mark.parametrize( + ("deps", "extras", "installed"), + [ + ("none", [], set()), + ("develop", [], {"pytest ;", "toml ;"}), + ("production", [], {"toml ;"}), + ("all", [], {"toml ;", "pytest ;", "requests ;"}), + ], +) def test_install_requires_extra(deps, extras, installed): it = InstallTests() try: it.setUp() - ins = Installer.from_ini_path(samples_dir / 'extras' / 'pyproject.toml', python='mock_python', - user=False, deps=deps, extras=extras) + ins = Installer.from_ini_path( + samples_dir / "extras" / "pyproject.toml", + python="mock_python", + user=False, + deps=deps, + extras=extras, + ) - cmd = MockCommand('mock_python') + cmd = MockCommand("mock_python") get_reqs = ( "#!{python}\n" "import sys\n" @@ -337,16 +421,20 @@ def test_install_requires_extra(deps, extras, installed): ins.install_requirements() with open(mock_py.recording_file) as f: str_deps = f.read() - deps = str_deps.split('\n') if str_deps else [] + deps = str_deps.split("\n") if str_deps else [] assert set(deps) == installed finally: it.tearDown() + def test_requires_dist_to_pip_requirement(): rd = 'pathlib2 (>=2.3); python_version == "2.7"' - assert _requires_dist_to_pip_requirement(rd) == \ - 'pathlib2>=2.3 ; python_version == "2.7"' + assert ( + _requires_dist_to_pip_requirement(rd) + == 'pathlib2>=2.3 ; python_version == "2.7"' + ) + def test_test_writable_dir_win(): with tempfile.TemporaryDirectory() as td: @@ -354,7 +442,7 @@ def test_test_writable_dir_win(): # Ironically, I don't know how to make a non-writable dir on Windows, # so although the functionality is for Windows, the test is for Posix - if os.name != 'posix': + if os.name != "posix": return # Remove write permissions from the directory From 0abd80208ed7511ce433db03dad01cb2a7ca1cf8 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:18:03 +0000 Subject: [PATCH 02/39] undo black --- .devcontainer/devcontainer.json | 4 - .vscode/settings.json | 11 +- flit/__init__.py | 217 +++++++++++--------------------- 3 files changed, 76 insertions(+), 156 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a7c38927..f8070194 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,6 @@ "ms-python.vscode-pylance", "ms-python.pylint", "ms-python.flake8", - "ms-python.black-formatter", "ms-vsliveshare.vsliveshare", "ryanluker.vscode-coverage-gutters", "bungcip.better-toml", @@ -28,9 +27,6 @@ "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, - "black-formatter.path": [ - "/usr/local/py-utils/bin/black" - ], "pylint.path": [ "/usr/local/py-utils/bin/pylint" ], diff --git a/.vscode/settings.json b/.vscode/settings.json index 29c13fb1..357e4545 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,11 +7,8 @@ "[jsonc]": { "editor.defaultFormatter": "vscode.json-language-features" }, - "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.formatting.provider": "black", + "python.formatting.provider": "autopep8", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "pylint.args": [ @@ -20,12 +17,6 @@ "pylint.path": [ "/usr/local/py-utils/bin/pylint" ], - "black-formatter.args": [ - "--config=pyproject.toml" - ], - "black-formatter.path": [ - "/usr/local/py-utils/bin/black" - ], "flake8.args": [ "--config=.flake8" ], diff --git a/flit/__init__.py b/flit/__init__.py index 7abd7d1a..9d881446 100644 --- a/flit/__init__.py +++ b/flit/__init__.py @@ -12,13 +12,12 @@ from .config import ConfigError from .log import enable_colourful_output -__version__ = "3.8.0" +__version__ = '3.8.0' log = logging.getLogger(__name__) -class PythonNotFoundError(FileNotFoundError): - pass +class PythonNotFoundError(FileNotFoundError): pass def find_python_executable(python: Optional[str] = None) -> str: @@ -34,9 +33,7 @@ def find_python_executable(python: Optional[str] = None) -> str: # see https://github.com/pypa/flit/pull/300 and https://bugs.python.org/issue38905 resolved_python = shutil.which(python) if resolved_python is None: - raise PythonNotFoundError( - "Unable to resolve Python executable {!r}".format(python) - ) + raise PythonNotFoundError("Unable to resolve Python executable {!r}".format(python)) try: return subprocess.check_output( [resolved_python, "-c", "import sys; print(sys.executable)"], @@ -51,167 +48,125 @@ def find_python_executable(python: Optional[str] = None) -> str: def add_shared_install_options(parser: argparse.ArgumentParser): - parser.add_argument( - "--user", - action="store_true", - default=None, - help="Do a user-local install (default if site.ENABLE_USER_SITE is True)", + parser.add_argument('--user', action='store_true', default=None, + help="Do a user-local install (default if site.ENABLE_USER_SITE is True)" ) - parser.add_argument( - "--env", - action="store_false", - dest="user", - help="Install into sys.prefix (default if site.ENABLE_USER_SITE is False, i.e. in virtualenvs)", + parser.add_argument('--env', action='store_false', dest='user', + help="Install into sys.prefix (default if site.ENABLE_USER_SITE is False, i.e. in virtualenvs)" ) - parser.add_argument( - "--python", - help="Target Python executable, if different from the one running flit", + parser.add_argument('--python', + help="Target Python executable, if different from the one running flit" ) - parser.add_argument( - "--deps", - choices=["all", "production", "develop", "none"], - default="all", - help="Which set of dependencies to install. If --deps=develop, the extras dev, doc, and test are installed", + parser.add_argument('--deps', choices=['all', 'production', 'develop', 'none'], default='all', + help="Which set of dependencies to install. If --deps=develop, the extras dev, doc, and test are installed" ) - parser.add_argument( - "--only-deps", - action="store_true", - help="Install only dependencies of this package, and not the package itself", + parser.add_argument('--only-deps', action='store_true', + help="Install only dependencies of this package, and not the package itself" ) - parser.add_argument( - "--extras", - default=(), - type=lambda l: l.split(",") if l else (), + parser.add_argument('--extras', default=(), type=lambda l: l.split(',') if l else (), help="Install the dependencies of these (comma separated) extras additionally to the ones implied by --deps. " - "--extras=all can be useful in combination with --deps=production, --deps=none precludes using --extras", + "--extras=all can be useful in combination with --deps=production, --deps=none precludes using --extras" ) def add_shared_build_options(parser: argparse.ArgumentParser): - parser.add_argument( - "--format", - action="append", - help="Select a format to publish. Options: 'wheel', 'sdist'", + parser.add_argument('--format', action='append', + help="Select a format to publish. Options: 'wheel', 'sdist'" ) setup_py_grp = parser.add_mutually_exclusive_group() - setup_py_grp.add_argument( - "--setup-py", - action="store_true", - help=( - "Generate a setup.py file in the sdist. " - "The sdist will work with older tools that predate PEP 517. " - ), + setup_py_grp.add_argument('--setup-py', action='store_true', + help=("Generate a setup.py file in the sdist. " + "The sdist will work with older tools that predate PEP 517. " + ) ) - setup_py_grp.add_argument( - "--no-setup-py", - action="store_true", - help=( - "Don't generate a setup.py file in the sdist. This is the default. " - "The sdist will only work with tools that support PEP 517, " - "but the wheel will still be usable by any compatible tool." - ), + setup_py_grp.add_argument('--no-setup-py', action='store_true', + help=("Don't generate a setup.py file in the sdist. This is the default. " + "The sdist will only work with tools that support PEP 517, " + "but the wheel will still be usable by any compatible tool." + ) ) vcs_grp = parser.add_mutually_exclusive_group() - vcs_grp.add_argument( - "--use-vcs", - action="store_true", - help=( - "Choose which files to include in the sdist using git or hg. " - "This is a convenient way to include all checked-in files, like " - "tests and doc source files, in your sdist, but requires that git " - "or hg is available on the command line. This is currently the " - "default, but it will change in a future version. " - ), + vcs_grp.add_argument('--use-vcs', action='store_true', + help=("Choose which files to include in the sdist using git or hg. " + "This is a convenient way to include all checked-in files, like " + "tests and doc source files, in your sdist, but requires that git " + "or hg is available on the command line. This is currently the " + "default, but it will change in a future version. " + ) ) - vcs_grp.add_argument( - "--no-use-vcs", - action="store_true", - help=( - "Select the files to include in the sdist without using git or hg. " - "This should include all essential files to install and use your " - "package; see the documentation for precisely what is included. " - "This will become the default in a future version." - ), + vcs_grp.add_argument('--no-use-vcs', action='store_true', + help=("Select the files to include in the sdist without using git or hg. " + "This should include all essential files to install and use your " + "package; see the documentation for precisely what is included. " + "This will become the default in a future version." + ) ) def main(argv=None): ap = argparse.ArgumentParser() - ap.add_argument("-f", "--ini-file", type=pathlib.Path, default="pyproject.toml") - ap.add_argument("-V", "--version", action="version", version="Flit " + __version__) + ap.add_argument('-f', '--ini-file', type=pathlib.Path, default='pyproject.toml') + ap.add_argument('-V', '--version', action='version', version='Flit '+__version__) # --repository now belongs on 'flit publish' - it's still here for # compatibility with scripts passing it before the subcommand. - ap.add_argument( - "--repository", dest="deprecated_repository", help=argparse.SUPPRESS - ) - ap.add_argument("--debug", action="store_true", help=argparse.SUPPRESS) - ap.add_argument("--logo", action="store_true", help=argparse.SUPPRESS) - subparsers = ap.add_subparsers(title="subcommands", dest="subcmd") + ap.add_argument('--repository', dest='deprecated_repository', help=argparse.SUPPRESS) + ap.add_argument('--debug', action='store_true', help=argparse.SUPPRESS) + ap.add_argument('--logo', action='store_true', help=argparse.SUPPRESS) + subparsers = ap.add_subparsers(title='subcommands', dest='subcmd') # flit build -------------------------------------------- - parser_build = subparsers.add_parser( - "build", + parser_build = subparsers.add_parser('build', help="Build wheel and sdist", ) add_shared_build_options(parser_build) # flit publish -------------------------------------------- - parser_publish = subparsers.add_parser( - "publish", + parser_publish = subparsers.add_parser('publish', help="Upload wheel and sdist", ) add_shared_build_options(parser_publish) - parser_publish.add_argument( - "--pypirc", help='The .pypirc config file to be used. DEFAULT = "~/.pypirc"' + parser_publish.add_argument('--pypirc', + help="The .pypirc config file to be used. DEFAULT = \"~/.pypirc\"" ) - parser_publish.add_argument( - "--repository", - help="Name of the repository to upload to (must be in the specified .pypirc file)", + parser_publish.add_argument('--repository', + help="Name of the repository to upload to (must be in the specified .pypirc file)" ) # flit install -------------------------------------------- - parser_install = subparsers.add_parser( - "install", + parser_install = subparsers.add_parser('install', help="Install the package", ) - parser_install.add_argument( - "-s", - "--symlink", - action="store_true", - help="Symlink the module/package into site packages instead of copying it", + parser_install.add_argument('-s', '--symlink', action='store_true', + help="Symlink the module/package into site packages instead of copying it" ) - parser_install.add_argument( - "--pth-file", - action="store_true", - help="Add .pth file for the module/package to site packages instead of copying it", + parser_install.add_argument('--pth-file', action='store_true', + help="Add .pth file for the module/package to site packages instead of copying it" ) add_shared_install_options(parser_install) # flit init -------------------------------------------- - parser_init = subparsers.add_parser( - "init", help="Prepare pyproject.toml for a new package" + parser_init = subparsers.add_parser('init', + help="Prepare pyproject.toml for a new package" ) args = ap.parse_args(argv) - if args.ini_file.suffix == ".ini": - sys.exit( - "flit.ini format is no longer supported. You can use " - "'python3 -m flit.tomlify' to convert it to pyproject.toml" - ) + if args.ini_file.suffix == '.ini': + sys.exit("flit.ini format is no longer supported. You can use " + "'python3 -m flit.tomlify' to convert it to pyproject.toml") - if args.subcmd not in {"init"} and not args.ini_file.is_file(): - sys.exit("Config file {} does not exist".format(args.ini_file)) + if args.subcmd not in {'init'} and not args.ini_file.is_file(): + sys.exit('Config file {} does not exist'.format(args.ini_file)) enable_colourful_output(logging.DEBUG if args.debug else logging.INFO) @@ -219,7 +174,6 @@ def main(argv=None): if args.logo: from .logo import clogo - print(clogo.format(version=__version__)) sys.exit(0) @@ -231,38 +185,23 @@ def gen_setup_py(): def sdist_use_vcs(): return not args.no_use_vcs - if args.subcmd == "build": + if args.subcmd == 'build': from .build import main - try: - main( - args.ini_file, - formats=set(args.format or []), - gen_setup_py=gen_setup_py(), - use_vcs=sdist_use_vcs(), - ) - except (common.NoDocstringError, common.VCSError, common.NoVersionError) as e: + main(args.ini_file, formats=set(args.format or []), + gen_setup_py=gen_setup_py(), use_vcs=sdist_use_vcs()) + except(common.NoDocstringError, common.VCSError, common.NoVersionError) as e: sys.exit(e.args[0]) - elif args.subcmd == "publish": + elif args.subcmd == 'publish': if args.deprecated_repository: - log.warning( - "Passing --repository before the 'upload' subcommand is deprecated: pass it after" - ) + log.warning("Passing --repository before the 'upload' subcommand is deprecated: pass it after") repository = args.repository or args.deprecated_repository from .upload import main + main(args.ini_file, repository, args.pypirc, formats=set(args.format or []), + gen_setup_py=gen_setup_py(), use_vcs=sdist_use_vcs()) - main( - args.ini_file, - repository, - args.pypirc, - formats=set(args.format or []), - gen_setup_py=gen_setup_py(), - use_vcs=sdist_use_vcs(), - ) - - elif args.subcmd == "install": + elif args.subcmd == 'install': from .install import Installer - try: python = find_python_executable(args.python) installer = Installer.from_ini_path( @@ -272,23 +211,17 @@ def sdist_use_vcs(): symlink=args.symlink, deps=args.deps, extras=args.extras, - pth=args.pth_file, + pth=args.pth_file ) if args.only_deps: installer.install_requirements() else: installer.install() - except ( - ConfigError, - PythonNotFoundError, - common.NoDocstringError, - common.NoVersionError, - ) as e: + except (ConfigError, PythonNotFoundError, common.NoDocstringError, common.NoVersionError) as e: sys.exit(e.args[0]) - elif args.subcmd == "init": + elif args.subcmd == 'init': from .init import TerminalIniter - TerminalIniter().initialise() else: ap.print_help() From 7fa6cc0e2de35179b3187ab33fe14d068ab541de Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:20:00 +0000 Subject: [PATCH 03/39] undo black --- flit/config.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/flit/config.py b/flit/config.py index 85026a9e..9186e0ac 100644 --- a/flit/config.py +++ b/flit/config.py @@ -6,15 +6,13 @@ def read_flit_config(path): - """Read and check the `pyproject.toml` or `flit.ini` file with data about the package.""" - + """Read and check the `pyproject.toml` or `flit.ini` file with data about the package. + """ res = _read_flit_config_core(path) if validate_config(res): - if os.environ.get("FLIT_ALLOW_INVALID"): - log.warning( - "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." - ) + if os.environ.get('FLIT_ALLOW_INVALID'): + log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") else: raise ConfigError("Invalid config values (see log)") return res From 09255a73a15c75c6a366711b019f36b0898c6ff0 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:25:22 +0000 Subject: [PATCH 04/39] fix black --- flit_core/flit_core/config.py | 505 +++++++++++++++++----------------- 1 file changed, 247 insertions(+), 258 deletions(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 1d2a4555..f508d767 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -25,142 +25,137 @@ class ConfigError(ValueError): pass - -metadata_list_fields = {"classifiers", "requires", "dev-requires"} +metadata_list_fields = { + 'classifiers', + 'requires', + 'dev-requires' +} metadata_allowed_fields = { - "module", - "author", - "author-email", - "maintainer", - "maintainer-email", - "home-page", - "license", - "keywords", - "requires-python", - "dist-name", - "description-file", - "requires-extra", + 'module', + 'author', + 'author-email', + 'maintainer', + 'maintainer-email', + 'home-page', + 'license', + 'keywords', + 'requires-python', + 'dist-name', + 'description-file', + 'requires-extra', } | metadata_list_fields metadata_required_fields = { - "module", - "author", + 'module', + 'author', } pep621_allowed_fields = { - "name", - "version", - "description", - "readme", - "requires-python", - "license", - "authors", - "maintainers", - "keywords", - "classifiers", - "urls", - "scripts", - "gui-scripts", - "entry-points", - "dependencies", - "optional-dependencies", - "dynamic", + 'name', + 'version', + 'description', + 'readme', + 'requires-python', + 'license', + 'authors', + 'maintainers', + 'keywords', + 'classifiers', + 'urls', + 'scripts', + 'gui-scripts', + 'entry-points', + 'dependencies', + 'optional-dependencies', + 'dynamic', } def read_flit_config(path): - """Read and check the `pyproject.toml` file with data about the package.""" - d = tomllib.loads(path.read_text("utf-8")) + """Read and check the `pyproject.toml` file with data about the package. + """ + d = tomllib.loads(path.read_text('utf-8')) return prep_toml_config(d, path) class EntryPointsConflict(ConfigError): def __str__(self): - return ( - "Please specify console_scripts entry points, or [scripts] in " - "flit config, not both." - ) - + return ('Please specify console_scripts entry points, or [scripts] in ' + 'flit config, not both.') def prep_toml_config(d, path): """Validate config loaded from pyproject.toml and prepare common metadata Returns a LoadedConfig object. """ - dtool = d.get("tool", {}).get("flit", {}) + dtool = d.get('tool', {}).get('flit', {}) - if "project" in d: + if 'project' in d: # Metadata in [project] table (PEP 621) - if "metadata" in dtool: + if 'metadata' in dtool: raise ConfigError( "Use [project] table for metadata or [tool.flit.metadata], not both." ) - if ("scripts" in dtool) or ("entrypoints" in dtool): + if ('scripts' in dtool) or ('entrypoints' in dtool): raise ConfigError( "Don't mix [project] metadata with [tool.flit.scripts] or " "[tool.flit.entrypoints]. Use [project.scripts]," "[project.gui-scripts] or [project.entry-points] as replacements." ) - loaded_cfg = read_pep621_metadata(d["project"], path) + loaded_cfg = read_pep621_metadata(d['project'], path) - module_tbl = dtool.get("module", {}) - if "name" in module_tbl: - loaded_cfg.module = module_tbl["name"] - elif "metadata" in dtool: + module_tbl = dtool.get('module', {}) + if 'name' in module_tbl: + loaded_cfg.module = module_tbl['name'] + elif 'metadata' in dtool: # Metadata in [tool.flit.metadata] (pre PEP 621 format) - if "module" in dtool: + if 'module' in dtool: raise ConfigError( "Use [tool.flit.module] table with new-style [project] metadata, " "not [tool.flit.metadata]" ) - loaded_cfg = _prep_metadata(dtool["metadata"], path) - loaded_cfg.dynamic_metadata = ["version", "description"] + loaded_cfg = _prep_metadata(dtool['metadata'], path) + loaded_cfg.dynamic_metadata = ['version', 'description'] - if "entrypoints" in dtool: - loaded_cfg.entrypoints = flatten_entrypoints(dtool["entrypoints"]) + if 'entrypoints' in dtool: + loaded_cfg.entrypoints = flatten_entrypoints(dtool['entrypoints']) - if "scripts" in dtool: - loaded_cfg.add_scripts(dict(dtool["scripts"])) + if 'scripts' in dtool: + loaded_cfg.add_scripts(dict(dtool['scripts'])) else: raise ConfigError( "Neither [project] nor [tool.flit.metadata] found in pyproject.toml" ) unknown_sections = set(dtool) - { - "metadata", - "module", - "scripts", - "entrypoints", - "sdist", - "external-data", + 'metadata', 'module', 'scripts', 'entrypoints', 'sdist', 'external-data' } - unknown_sections = [s for s in unknown_sections if not s.lower().startswith("x-")] + unknown_sections = [s for s in unknown_sections if not s.lower().startswith('x-')] if unknown_sections: - raise ConfigError( - "Unexpected tables in pyproject.toml: " - + ", ".join("[tool.flit.{}]".format(s) for s in unknown_sections) - ) + raise ConfigError('Unexpected tables in pyproject.toml: ' + ', '.join( + '[tool.flit.{}]'.format(s) for s in unknown_sections + )) - if "sdist" in dtool: - unknown_keys = set(dtool["sdist"]) - {"include", "exclude"} + if 'sdist' in dtool: + unknown_keys = set(dtool['sdist']) - {'include', 'exclude'} if unknown_keys: raise ConfigError( "Unknown keys in [tool.flit.sdist]:" + ", ".join(unknown_keys) ) loaded_cfg.sdist_include_patterns = _check_glob_patterns( - dtool["sdist"].get("include", []), "include" + dtool['sdist'].get('include', []), 'include' ) exclude = [ "**/__pycache__", "**.pyc", - ] + dtool[ - "sdist" - ].get("exclude", []) - loaded_cfg.sdist_exclude_patterns = _check_glob_patterns(exclude, "exclude") + ] + dtool['sdist'].get('exclude', []) + loaded_cfg.sdist_exclude_patterns = _check_glob_patterns( + exclude, 'exclude' + ) - data_dir = dtool.get("external-data", {}).get("directory", None) + data_dir = dtool.get('external-data', {}).get('directory', None) if data_dir is not None: toml_key = "tool.flit.external-data.directory" if not isinstance(data_dir, str): @@ -169,11 +164,11 @@ def prep_toml_config(d, path): normp = osp.normpath(data_dir) if osp.isabs(normp): raise ConfigError(f"{toml_key} cannot be an absolute path") - if normp.startswith(".." + os.sep): + if normp.startswith('..' + os.sep): raise ConfigError( f"{toml_key} cannot point outside the directory containing pyproject.toml" ) - if normp == ".": + if normp == '.': raise ConfigError( f"{toml_key} cannot refer to the directory containing pyproject.toml" ) @@ -183,7 +178,6 @@ def prep_toml_config(d, path): return loaded_cfg - def flatten_entrypoints(ep): """Flatten nested entrypoints dicts. @@ -200,12 +194,11 @@ def flatten_entrypoints(ep): flit allows you to use the former. This flattens the nested dictionaries from loading pyproject.toml. """ - def _flatten(d, prefix): d1 = {} for k, v in d.items(): if isinstance(v, dict): - for flattened in _flatten(v, prefix + "." + k): + for flattened in _flatten(v, prefix+'.'+k): yield flattened else: d1[k] = v @@ -233,20 +226,20 @@ def _check_glob_patterns(pats, clude): for p in pats: if bad_chars.search(p): raise ConfigError( - '{} pattern {!r} contains bad characters (<>:"\\ or control characters)'.format( - clude, p - ) + '{} pattern {!r} contains bad characters (<>:\"\\ or control characters)' + .format(clude, p) ) normp = osp.normpath(p) if osp.isabs(normp): - raise ConfigError("{} pattern {!r} is an absolute path".format(clude, p)) - if normp.startswith(".." + os.sep): raise ConfigError( - "{} pattern {!r} points out of the directory containing pyproject.toml".format( - clude, p - ) + '{} pattern {!r} is an absolute path'.format(clude, p) + ) + if normp.startswith('..' + os.sep): + raise ConfigError( + '{} pattern {!r} points out of the directory containing pyproject.toml' + .format(clude, p) ) normed.append(normp) @@ -267,16 +260,15 @@ def __init__(self): def add_scripts(self, scripts_dict): if scripts_dict: - if "console_scripts" in self.entrypoints: + if 'console_scripts' in self.entrypoints: raise EntryPointsConflict else: - self.entrypoints["console_scripts"] = scripts_dict - + self.entrypoints['console_scripts'] = scripts_dict readme_ext_to_content_type = { - ".rst": "text/x-rst", - ".md": "text/markdown", - ".txt": "text/plain", + '.rst': 'text/x-rst', + '.md': 'text/markdown', + '.txt': 'text/plain', } @@ -286,11 +278,13 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): desc_path = proj_dir / rel_path try: - with desc_path.open("r", encoding="utf-8") as f: + with desc_path.open('r', encoding='utf-8') as f: raw_desc = f.read() except IOError as e: if e.errno == errno.ENOENT: - raise ConfigError("Description file {} does not exist".format(desc_path)) + raise ConfigError( + "Description file {} does not exist".format(desc_path) + ) raise if guess_mimetype: @@ -299,9 +293,8 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): mimetype = readme_ext_to_content_type[ext] except KeyError: log.warning("Unknown extension %r for description file.", ext) - log.warning( - " Recognised extensions: %s", " ".join(readme_ext_to_content_type) - ) + log.warning(" Recognised extensions: %s", + " ".join(readme_ext_to_content_type)) mimetype = None else: mimetype = None @@ -319,11 +312,11 @@ def _prep_metadata(md_sect, path): """ if not set(md_sect).issuperset(metadata_required_fields): missing = metadata_required_fields - set(md_sect) - raise ConfigError("Required fields missing: " + "\n".join(missing)) + raise ConfigError("Required fields missing: " + '\n'.join(missing)) res = LoadedConfig() - res.module = md_sect.get("module") + res.module = md_sect.get('module') if not all([m.isidentifier() for m in res.module.split(".")]): raise ConfigError("Module name %r is not a valid identifier" % res.module) @@ -338,101 +331,89 @@ def _prep_metadata(md_sect, path): md_dict["description"] = desc_content md_dict["description_content_type"] = mimetype except ConfigError as ex: - if os.environ.get("FLIT_ALLOW_INVALID"): - log.warning( - "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." - ) - else: + if not os.environ.get("FLIT_ALLOW_INVALID"): raise ConfigError("Invalid config values (see log)") from ex + log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Skipping missing description-file. Uploads may still fail.") - if "urls" in md_sect: - project_urls = md_dict["project_urls"] = [] - for label, url in sorted(md_sect.pop("urls").items()): + + if 'urls' in md_sect: + project_urls = md_dict['project_urls'] = [] + for label, url in sorted(md_sect.pop('urls').items()): project_urls.append("{}, {}".format(label, url)) for key, value in md_sect.items(): - if key in {"description-file", "module"}: + if key in {'description-file', 'module'}: continue if key not in metadata_allowed_fields: - closest = difflib.get_close_matches( - key, metadata_allowed_fields, n=1, cutoff=0.7 - ) + closest = difflib.get_close_matches(key, metadata_allowed_fields, + n=1, cutoff=0.7) msg = "Unrecognised metadata key: {!r}".format(key) if closest: msg += " (did you mean {!r}?)".format(closest[0]) raise ConfigError(msg) - k2 = key.replace("-", "_") + k2 = key.replace('-', '_') md_dict[k2] = value if key in metadata_list_fields: if not isinstance(value, list): - raise ConfigError( - "Expected a list for {} field, found {!r}".format(key, value) - ) + raise ConfigError('Expected a list for {} field, found {!r}' + .format(key, value)) if not all(isinstance(a, str) for a in value): - raise ConfigError("Expected a list of strings for {} field".format(key)) - elif key == "requires-extra": + raise ConfigError('Expected a list of strings for {} field' + .format(key)) + elif key == 'requires-extra': if not isinstance(value, dict): - raise ConfigError( - "Expected a dict for requires-extra field, found {!r}".format(value) - ) + raise ConfigError('Expected a dict for requires-extra field, found {!r}' + .format(value)) if not all(isinstance(e, list) for e in value.values()): - raise ConfigError("Expected a dict of lists for requires-extra field") + raise ConfigError('Expected a dict of lists for requires-extra field') for e, reqs in value.items(): if not all(isinstance(a, str) for a in reqs): - raise ConfigError( - "Expected a string list for requires-extra. (extra {})".format( - e - ) - ) + raise ConfigError('Expected a string list for requires-extra. (extra {})' + .format(e)) else: if not isinstance(value, str): - raise ConfigError( - "Expected a string for {} field, found {!r}".format(key, value) - ) + raise ConfigError('Expected a string for {} field, found {!r}' + .format(key, value)) # What we call requires in the ini file is technically requires_dist in # the metadata. - if "requires" in md_dict: - md_dict["requires_dist"] = md_dict.pop("requires") + if 'requires' in md_dict: + md_dict['requires_dist'] = md_dict.pop('requires') # And what we call dist-name is name in the metadata - if "dist_name" in md_dict: - md_dict["name"] = md_dict.pop("dist_name") + if 'dist_name' in md_dict: + md_dict['name'] = md_dict.pop('dist_name') # Move dev-requires into requires-extra - reqs_noextra = md_dict.pop("requires_dist", []) - res.reqs_by_extra = md_dict.pop("requires_extra", {}) - dev_requires = md_dict.pop("dev_requires", None) + reqs_noextra = md_dict.pop('requires_dist', []) + res.reqs_by_extra = md_dict.pop('requires_extra', {}) + dev_requires = md_dict.pop('dev_requires', None) if dev_requires is not None: - if "dev" in res.reqs_by_extra: + if 'dev' in res.reqs_by_extra: raise ConfigError( - "dev-requires occurs together with its replacement requires-extra.dev." - ) + 'dev-requires occurs together with its replacement requires-extra.dev.') else: log.warning( - '"dev-requires = ..." is obsolete. Use "requires-extra = {"dev" = ...}" instead.' - ) - res.reqs_by_extra["dev"] = dev_requires + '"dev-requires = ..." is obsolete. Use "requires-extra = {"dev" = ...}" instead.') + res.reqs_by_extra['dev'] = dev_requires # Add requires-extra requirements into requires_dist - md_dict["requires_dist"] = reqs_noextra + list( - _expand_requires_extra(res.reqs_by_extra) - ) + md_dict['requires_dist'] = \ + reqs_noextra + list(_expand_requires_extra(res.reqs_by_extra)) - md_dict["provides_extra"] = sorted(res.reqs_by_extra.keys()) + md_dict['provides_extra'] = sorted(res.reqs_by_extra.keys()) # For internal use, record the main requirements as a '.none' extra. - res.reqs_by_extra[".none"] = reqs_noextra + res.reqs_by_extra['.none'] = reqs_noextra return res - def _expand_requires_extra(re): for extra, reqs in sorted(re.items()): for req in reqs: - if ";" in req: - name, envmark = req.split(";", 1) + if ';' in req: + name, envmark = req.split(';', 1) yield '{} ; extra == "{}" and ({})'.format(name, extra, envmark) else: yield '{} ; extra == "{}"'.format(req, extra) @@ -444,49 +425,49 @@ def _check_type(d, field_name, cls): "{} field should be {}, not {}".format(field_name, cls, type(d[field_name])) ) - def _check_list_of_str(d, field_name): if not isinstance(d[field_name], list) or not all( isinstance(e, str) for e in d[field_name] ): - raise ConfigError("{} field should be a list of strings".format(field_name)) - + raise ConfigError( + "{} field should be a list of strings".format(field_name) + ) def read_pep621_metadata(proj, path) -> LoadedConfig: lc = LoadedConfig() md_dict = lc.metadata - if "name" not in proj: - raise ConfigError("name must be specified in [project] table") - _check_type(proj, "name", str) - md_dict["name"] = proj["name"] - lc.module = md_dict["name"].replace("-", "_") + if 'name' not in proj: + raise ConfigError('name must be specified in [project] table') + _check_type(proj, 'name', str) + md_dict['name'] = proj['name'] + lc.module = md_dict['name'].replace('-', '_') unexpected_keys = proj.keys() - pep621_allowed_fields if unexpected_keys: - log.warning("Unexpected names under [project]: %s", ", ".join(unexpected_keys)) - - if "version" in proj: - _check_type(proj, "version", str) - md_dict["version"] = normalise_version(proj["version"]) - if "description" in proj: - _check_type(proj, "description", str) - md_dict["summary"] = proj["description"] - if "readme" in proj: - readme = proj["readme"] + log.warning("Unexpected names under [project]: %s", ', '.join(unexpected_keys)) + + if 'version' in proj: + _check_type(proj, 'version', str) + md_dict['version'] = normalise_version(proj['version']) + if 'description' in proj: + _check_type(proj, 'description', str) + md_dict['summary'] = proj['description'] + if 'readme' in proj: + readme = proj['readme'] if isinstance(readme, str): lc.referenced_files.append(readme) desc_content, mimetype = description_from_file(readme, path.parent) elif isinstance(readme, dict): - unrec_keys = set(readme.keys()) - {"text", "file", "content-type"} + unrec_keys = set(readme.keys()) - {'text', 'file', 'content-type'} if unrec_keys: raise ConfigError( "Unrecognised keys in [project.readme]: {}".format(unrec_keys) ) - if "content-type" in readme: - mimetype = readme["content-type"] - mtype_base = mimetype.split(";")[0].strip() # e.g. text/x-rst + if 'content-type' in readme: + mimetype = readme['content-type'] + mtype_base = mimetype.split(';')[0].strip() # e.g. text/x-rst if mtype_base not in readme_ext_to_content_type.values(): raise ConfigError( "Unrecognised readme content-type: {!r}".format(mtype_base) @@ -496,34 +477,36 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: raise ConfigError( "content-type field required in [project.readme] table" ) - if "file" in readme: - if "text" in readme: + if 'file' in readme: + if 'text' in readme: raise ConfigError( "[project.readme] should specify file or text, not both" ) - lc.referenced_files.append(readme["file"]) + lc.referenced_files.append(readme['file']) desc_content, _ = description_from_file( - readme["file"], path.parent, guess_mimetype=False + readme['file'], path.parent, guess_mimetype=False ) - elif "text" in readme: - desc_content = readme["text"] + elif 'text' in readme: + desc_content = readme['text'] else: raise ConfigError( "file or text field required in [project.readme] table" ) else: - raise ConfigError("project.readme should be a string or a table") + raise ConfigError( + "project.readme should be a string or a table" + ) - md_dict["description"] = desc_content - md_dict["description_content_type"] = mimetype + md_dict['description'] = desc_content + md_dict['description_content_type'] = mimetype - if "requires-python" in proj: - md_dict["requires_python"] = proj["requires-python"] + if 'requires-python' in proj: + md_dict['requires_python'] = proj['requires-python'] - if "license" in proj: - _check_type(proj, "license", dict) - license_tbl = proj["license"] - unrec_keys = set(license_tbl.keys()) - {"text", "file"} + if 'license' in proj: + _check_type(proj, 'license', dict) + license_tbl = proj['license'] + unrec_keys = set(license_tbl.keys()) - {'text', 'file'} if unrec_keys: raise ConfigError( "Unrecognised keys in [project.license]: {}".format(unrec_keys) @@ -533,42 +516,44 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: # The 'License' field in packaging metadata is a brief description of # a license, not the full text or a file path. PEP 639 will improve on # how licenses are recorded. - if "file" in license_tbl: - if "text" in license_tbl: + if 'file' in license_tbl: + if 'text' in license_tbl: raise ConfigError( "[project.license] should specify file or text, not both" ) - lc.referenced_files.append(license_tbl["file"]) - elif "text" in license_tbl: + lc.referenced_files.append(license_tbl['file']) + elif 'text' in license_tbl: pass else: - raise ConfigError("file or text field required in [project.license] table") + raise ConfigError( + "file or text field required in [project.license] table" + ) - if "authors" in proj: - _check_type(proj, "authors", list) - md_dict.update(pep621_people(proj["authors"])) + if 'authors' in proj: + _check_type(proj, 'authors', list) + md_dict.update(pep621_people(proj['authors'])) - if "maintainers" in proj: - _check_type(proj, "maintainers", list) - md_dict.update(pep621_people(proj["maintainers"], group_name="maintainer")) + if 'maintainers' in proj: + _check_type(proj, 'maintainers', list) + md_dict.update(pep621_people(proj['maintainers'], group_name='maintainer')) - if "keywords" in proj: - _check_list_of_str(proj, "keywords") - md_dict["keywords"] = ",".join(proj["keywords"]) + if 'keywords' in proj: + _check_list_of_str(proj, 'keywords') + md_dict['keywords'] = ",".join(proj['keywords']) - if "classifiers" in proj: - _check_list_of_str(proj, "classifiers") - md_dict["classifiers"] = proj["classifiers"] + if 'classifiers' in proj: + _check_list_of_str(proj, 'classifiers') + md_dict['classifiers'] = proj['classifiers'] - if "urls" in proj: - _check_type(proj, "urls", dict) - project_urls = md_dict["project_urls"] = [] - for label, url in sorted(proj["urls"].items()): + if 'urls' in proj: + _check_type(proj, 'urls', dict) + project_urls = md_dict['project_urls'] = [] + for label, url in sorted(proj['urls'].items()): project_urls.append("{}, {}".format(label, url)) - if "entry-points" in proj: - _check_type(proj, "entry-points", dict) - for grp in proj["entry-points"].values(): + if 'entry-points' in proj: + _check_type(proj, 'entry-points', dict) + for grp in proj['entry-points'].values(): if not isinstance(grp, dict): raise ConfigError( "projects.entry-points should only contain sub-tables" @@ -577,57 +562,62 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: raise ConfigError( "[projects.entry-points.*] tables should have string values" ) - if set(proj["entry-points"].keys()) & {"console_scripts", "gui_scripts"}: + if set(proj['entry-points'].keys()) & {'console_scripts', 'gui_scripts'}: raise ConfigError( "Scripts should be specified in [project.scripts] or " "[project.gui-scripts], not under [project.entry-points]" ) - lc.entrypoints = proj["entry-points"] - - if "scripts" in proj: - _check_type(proj, "scripts", dict) - if not all(isinstance(k, str) for k in proj["scripts"].values()): - raise ConfigError("[projects.scripts] table should have string values") - lc.entrypoints["console_scripts"] = proj["scripts"] - - if "gui-scripts" in proj: - _check_type(proj, "gui-scripts", dict) - if not all(isinstance(k, str) for k in proj["gui-scripts"].values()): - raise ConfigError("[projects.gui-scripts] table should have string values") - lc.entrypoints["gui_scripts"] = proj["gui-scripts"] - - if "dependencies" in proj: - _check_list_of_str(proj, "dependencies") - reqs_noextra = proj["dependencies"] + lc.entrypoints = proj['entry-points'] + + if 'scripts' in proj: + _check_type(proj, 'scripts', dict) + if not all(isinstance(k, str) for k in proj['scripts'].values()): + raise ConfigError( + "[projects.scripts] table should have string values" + ) + lc.entrypoints['console_scripts'] = proj['scripts'] + + if 'gui-scripts' in proj: + _check_type(proj, 'gui-scripts', dict) + if not all(isinstance(k, str) for k in proj['gui-scripts'].values()): + raise ConfigError( + "[projects.gui-scripts] table should have string values" + ) + lc.entrypoints['gui_scripts'] = proj['gui-scripts'] + + if 'dependencies' in proj: + _check_list_of_str(proj, 'dependencies') + reqs_noextra = proj['dependencies'] else: reqs_noextra = [] - if "optional-dependencies" in proj: - _check_type(proj, "optional-dependencies", dict) - optdeps = proj["optional-dependencies"] + if 'optional-dependencies' in proj: + _check_type(proj, 'optional-dependencies', dict) + optdeps = proj['optional-dependencies'] if not all(isinstance(e, list) for e in optdeps.values()): - raise ConfigError("Expected a dict of lists in optional-dependencies field") + raise ConfigError( + 'Expected a dict of lists in optional-dependencies field' + ) for e, reqs in optdeps.items(): if not all(isinstance(a, str) for a in reqs): raise ConfigError( - "Expected a string list for optional-dependencies ({})".format(e) + 'Expected a string list for optional-dependencies ({})'.format(e) ) lc.reqs_by_extra = optdeps.copy() - md_dict["provides_extra"] = sorted(lc.reqs_by_extra.keys()) + md_dict['provides_extra'] = sorted(lc.reqs_by_extra.keys()) - md_dict["requires_dist"] = reqs_noextra + list( - _expand_requires_extra(lc.reqs_by_extra) - ) + md_dict['requires_dist'] = \ + reqs_noextra + list(_expand_requires_extra(lc.reqs_by_extra)) # For internal use, record the main requirements as a '.none' extra. if reqs_noextra: - lc.reqs_by_extra[".none"] = reqs_noextra + lc.reqs_by_extra['.none'] = reqs_noextra - if "dynamic" in proj: - _check_list_of_str(proj, "dynamic") - dynamic = set(proj["dynamic"]) - unrec_dynamic = dynamic - {"version", "description"} + if 'dynamic' in proj: + _check_list_of_str(proj, 'dynamic') + dynamic = set(proj['dynamic']) + unrec_dynamic = dynamic - {'version', 'description'} if unrec_dynamic: raise ConfigError( "flit only supports dynamic metadata for 'version' & 'description'" @@ -638,40 +628,39 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: ) lc.dynamic_metadata = dynamic - if ("version" not in proj) and ("version" not in lc.dynamic_metadata): + if ('version' not in proj) and ('version' not in lc.dynamic_metadata): raise ConfigError( "version must be specified under [project] or listed as a dynamic field" ) - if ("description" not in proj) and ("description" not in lc.dynamic_metadata): + if ('description' not in proj) and ('description' not in lc.dynamic_metadata): raise ConfigError( "description must be specified under [project] or listed as a dynamic field" ) return lc - -def pep621_people(people, group_name="author") -> dict: +def pep621_people(people, group_name='author') -> dict: """Convert authors/maintainers from PEP 621 to core metadata fields""" names, emails = [], [] for person in people: if not isinstance(person, dict): raise ConfigError("{} info must be list of dicts".format(group_name)) - unrec_keys = set(person.keys()) - {"name", "email"} + unrec_keys = set(person.keys()) - {'name', 'email'} if unrec_keys: raise ConfigError( "Unrecognised keys in {} info: {}".format(group_name, unrec_keys) ) - if "email" in person: - email = person["email"] - if "name" in person: - email = str(Address(person["name"], addr_spec=email)) + if 'email' in person: + email = person['email'] + if 'name' in person: + email = str(Address(person['name'], addr_spec=email)) emails.append(email) - elif "name" in person: - names.append(person["name"]) + elif 'name' in person: + names.append(person['name']) res = {} if names: res[group_name] = ", ".join(names) if emails: - res[group_name + "_email"] = ", ".join(emails) - return res + res[group_name + '_email'] = ", ".join(emails) + return res \ No newline at end of file From f4efdead95e6ff918620b6aa23d39d17d2b9fc50 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:26:09 +0000 Subject: [PATCH 05/39] fix black --- flit_core/flit_core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index f508d767..9e7dd1bf 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -663,4 +663,4 @@ def pep621_people(people, group_name='author') -> dict: res[group_name] = ", ".join(names) if emails: res[group_name + '_email'] = ", ".join(emails) - return res \ No newline at end of file + return res From b2c311cab8bd860b4f8bfb41de76e807bfd90837 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 16:26:24 -0400 Subject: [PATCH 06/39] Delete pyproject.toml --- test/pyproject.toml | 46 --------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 test/pyproject.toml diff --git a/test/pyproject.toml b/test/pyproject.toml deleted file mode 100644 index caeefcd1..00000000 --- a/test/pyproject.toml +++ /dev/null @@ -1,46 +0,0 @@ -[build-system] -requires = ["flit_core >=3.8.0,<4"] -build-backend = "flit_core.buildapi" - -[project] -name = "flit" -authors = [ - {name = "Thomas Kluyver", email = "thomas@kluyver.me.uk"}, -] -dependencies = [ - "flit_core >=3.8.0", - "requests", - "docutils", - "tomli-w", -] -requires-python = ">=3.6" -readme = "README.rst" -license = {file = "LICENSE"} -classifiers = ["Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 3", - "Topic :: Software Development :: Libraries :: Python Modules", -] -dynamic = ['version', 'description'] - -[project.optional-dependencies] -test = [ - "testpath", - "responses", - "pytest>=2.7.3", - "pytest-cov", - "tomli", -] -doc = [ - "sphinx", - "sphinxcontrib_github_alt", - "pygments-github-lexers", # TOML highlighting -] - -[project.urls] -Documentation = "https://flit.pypa.io" -Source = "https://github.com/pypa/flit" -Changelog = "https://flit.pypa.io/en/stable/history.html" - -[project.scripts] -flit = "flit:main" From b958dfb844e21c27f9791c9dd6a48e95d8fc2a1f Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:29:01 +0000 Subject: [PATCH 07/39] fix black --- flit_core/flit_core/common.py | 201 +++++++++++++++------------------- 1 file changed, 88 insertions(+), 113 deletions(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 494244d2..bcd79407 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -12,10 +12,9 @@ from .versionno import normalise_version - class Module(object): - """This represents the module/package that we are going to distribute""" - + """This represents the module/package that we are going to distribute + """ in_namespace_package = False namespace_package_name = None @@ -23,58 +22,54 @@ def __init__(self, name, directory=Path()): self.name = name # It must exist either as a .py file or a directory, but not both - name_as_path = name.replace(".", os.sep) + name_as_path = name.replace('.', os.sep) pkg_dir = directory / name_as_path - py_file = directory / (name_as_path + ".py") - src_pkg_dir = directory / "src" / name_as_path - src_py_file = directory / "src" / (name_as_path + ".py") + py_file = directory / (name_as_path+'.py') + src_pkg_dir = directory / 'src' / name_as_path + src_py_file = directory / 'src' / (name_as_path+'.py') existing = set() if pkg_dir.is_dir(): self.path = pkg_dir self.is_package = True - self.prefix = "" + self.prefix = '' existing.add(pkg_dir) if py_file.is_file(): self.path = py_file self.is_package = False - self.prefix = "" + self.prefix = '' existing.add(py_file) if src_pkg_dir.is_dir(): self.path = src_pkg_dir self.is_package = True - self.prefix = "src" + self.prefix = 'src' existing.add(src_pkg_dir) if src_py_file.is_file(): self.path = src_py_file self.is_package = False - self.prefix = "src" + self.prefix = 'src' existing.add(src_py_file) if len(existing) > 1: raise ValueError( - "Multiple files or folders could be module {}: {}".format( - name, ", ".join([str(p) for p in sorted(existing)]) - ) + "Multiple files or folders could be module {}: {}" + .format(name, ", ".join([str(p) for p in sorted(existing)])) ) elif not existing: - if os.environ.get("FLIT_ALLOW_INVALID"): - log.warning( - "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." - ) - else: + if not os.environ.get("FLIT_ALLOW_INVALID"): raise ValueError("No file/folder found for module {}".format(name)) + log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") self.source_dir = directory / self.prefix - if "." in name: - self.namespace_package_name = name.rpartition(".")[0] + if '.' in name: + self.namespace_package_name = name.rpartition('.')[0] self.in_namespace_package = True @property def file(self): if self.is_package: - return self.path / "__init__.py" + return self.path / '__init__.py' else: return self.path @@ -84,10 +79,9 @@ def iter_files(self): Yields absolute paths - caller may want to make them relative. Excludes any __pycache__ and *.pyc files. """ - def _include(path): name = os.path.basename(path) - if (name == "__pycache__") or name.endswith(".pyc"): + if (name == '__pycache__') or name.endswith('.pyc'): return False return True @@ -104,22 +98,10 @@ def _include(path): else: yield str(self.path) - -class ProblemInModule(ValueError): - pass - - -class NoDocstringError(ProblemInModule): - pass - - -class NoVersionError(ProblemInModule): - pass - - -class InvalidVersion(ProblemInModule): - pass - +class ProblemInModule(ValueError): pass +class NoDocstringError(ProblemInModule): pass +class NoVersionError(ProblemInModule): pass +class InvalidVersion(ProblemInModule): pass class VCSError(Exception): def __init__(self, msg, directory): @@ -127,7 +109,7 @@ def __init__(self, msg, directory): self.directory = directory def __str__(self): - return self.msg + " ({})".format(self.directory) + return self.msg + ' ({})'.format(self.directory) @contextmanager @@ -142,25 +124,25 @@ def _module_load_ctx(): finally: logging.root.handlers = logging_handlers - def get_docstring_and_version_via_ast(target): """ Return a tuple like (docstring, version) for the given module, extracted by parsing its AST. """ # read as bytes to enable custom encodings - with target.file.open("rb") as f: + with target.file.open('rb') as f: node = ast.parse(f.read()) for child in node.body: # Only use the version from the given module if it's a simple # string assignment to __version__ is_version_str = ( - isinstance(child, ast.Assign) - and any( - isinstance(target, ast.Name) and target.id == "__version__" - for target in child.targets - ) - and isinstance(child.value, ast.Str) + isinstance(child, ast.Assign) + and any( + isinstance(target, ast.Name) + and target.id == "__version__" + for target in child.targets + ) + and isinstance(child.value, ast.Str) ) if is_version_str: version = child.value.s @@ -187,8 +169,7 @@ def get_docstring_and_version_via_import(target): log.debug("Loading module %s", target.file) from importlib.util import spec_from_file_location, module_from_spec - - mod_name = "flit_core.dummy.import%d" % _import_i + mod_name = 'flit_core.dummy.import%d' % _import_i spec = spec_from_file_location(mod_name, target.file) with _module_load_ctx(): m = module_from_spec(spec) @@ -202,19 +183,20 @@ def get_docstring_and_version_via_import(target): finally: sys.modules.pop(mod_name, None) - docstring = m.__dict__.get("__doc__", None) - version = m.__dict__.get("__version__", None) + docstring = m.__dict__.get('__doc__', None) + version = m.__dict__.get('__version__', None) return docstring, version -def get_info_from_module(target, for_fields=("version", "description")): - """Load the module/package, get its docstring and __version__""" +def get_info_from_module(target, for_fields=('version', 'description')): + """Load the module/package, get its docstring and __version__ + """ if not for_fields: return {} # What core metadata calls Summary, PEP 621 calls description - want_summary = "description" in for_fields - want_version = "version" in for_fields + want_summary = 'description' in for_fields + want_version = 'version' in for_fields log.debug("Loading module %s", target.file) @@ -231,17 +213,16 @@ def get_info_from_module(target, for_fields=("version", "description")): if want_summary: if (not docstring) or not docstring.strip(): raise NoDocstringError( - "Flit cannot package module without docstring, or empty docstring. " - "Please add a docstring to your module ({}).".format(target.file) + 'Flit cannot package module without docstring, or empty docstring. ' + 'Please add a docstring to your module ({}).'.format(target.file) ) - res["summary"] = docstring.lstrip().splitlines()[0] + res['summary'] = docstring.lstrip().splitlines()[0] if want_version: - res["version"] = check_version(version) + res['version'] = check_version(version) return res - def check_version(version): """ Check whether a given version string match PEP 440, and do normalisation. @@ -254,14 +235,11 @@ def check_version(version): Returns the version in canonical PEP 440 format. """ if not version: - raise NoVersionError( - "Cannot package module without a version string. " - 'Please define a `__version__ = "x.y.z"` in your module.' - ) + raise NoVersionError('Cannot package module without a version string. ' + 'Please define a `__version__ = "x.y.z"` in your module.') if not isinstance(version, str): - raise InvalidVersion( - "__version__ must be a string, not {}.".format(type(version)) - ) + raise InvalidVersion('__version__ must be a string, not {}.' + .format(type(version))) # Import here to avoid circular import version = normalise_version(version) @@ -280,46 +258,42 @@ def check_version(version): sys.exit({func}()) """ - def parse_entry_point(ep): """Check and parse a 'package.module:func' style entry point specification. Returns (modulename, funcname) """ - if ":" not in ep: + if ':' not in ep: raise ValueError("Invalid entry point (no ':'): %r" % ep) - mod, func = ep.split(":") + mod, func = ep.split(':') - for piece in func.split("."): + for piece in func.split('.'): if not piece.isidentifier(): raise ValueError("Invalid entry point: %r is not an identifier" % piece) - for piece in mod.split("."): + for piece in mod.split('.'): if not piece.isidentifier(): raise ValueError("Invalid entry point: %r is not a module path" % piece) return mod, func - def write_entry_points(d, fp): """Write entry_points.txt from a two-level dict Sorts on keys to ensure results are reproducible. """ for group_name in sorted(d): - fp.write("[{}]\n".format(group_name)) + fp.write(u'[{}]\n'.format(group_name)) group = d[group_name] for name in sorted(group): val = group[name] - fp.write("{}={}\n".format(name, val)) - fp.write("\n") + fp.write(u'{}={}\n'.format(name, val)) + fp.write(u'\n') - -def hash_file(path, algorithm="sha256"): - with open(path, "rb") as f: +def hash_file(path, algorithm='sha256'): + with open(path, 'rb') as f: h = hashlib.new(algorithm, f.read()) return h.hexdigest() - def normalize_file_permissions(st_mode): """Normalize the permission bits in the st_mode field from stat to 644/755 @@ -333,8 +307,8 @@ def normalize_file_permissions(st_mode): new_mode |= 0o111 # Executable: 644 -> 755 return new_mode - class Metadata(object): + summary = None home_page = None author = None @@ -365,39 +339,39 @@ class Metadata(object): def __init__(self, data): data = data.copy() - self.name = data.pop("name") - self.version = data.pop("version") + self.name = data.pop('name') + self.version = data.pop('version') for k, v in data.items(): assert hasattr(self, k), "data does not have attribute '{}'".format(k) setattr(self, k, v) def _normalise_name(self, n): - return n.lower().replace("-", "_") + return n.lower().replace('-', '_') def write_metadata_file(self, fp): """Write out metadata in the email headers format""" fields = [ - "Metadata-Version", - "Name", - "Version", + 'Metadata-Version', + 'Name', + 'Version', ] optional_fields = [ - "Summary", - "Home-page", - "License", - "Keywords", - "Author", - "Author-email", - "Maintainer", - "Maintainer-email", - "Requires-Python", - "Description-Content-Type", + 'Summary', + 'Home-page', + 'License', + 'Keywords', + 'Author', + 'Author-email', + 'Maintainer', + 'Maintainer-email', + 'Requires-Python', + 'Description-Content-Type', ] for field in fields: value = getattr(self, self._normalise_name(field)) - fp.write("{}: {}\n".format(field, value)) + fp.write(u"{}: {}\n".format(field, value)) for field in optional_fields: value = getattr(self, self._normalise_name(field)) @@ -406,23 +380,23 @@ def write_metadata_file(self, fp): # The spec has multiline examples for Author, Maintainer & # License (& Description, but we put that in the body) # Indent following lines with 8 spaces: - value = "\n ".join(value.splitlines()) - fp.write("{}: {}\n".format(field, value)) + value = '\n '.join(value.splitlines()) + fp.write(u"{}: {}\n".format(field, value)) for clsfr in self.classifiers: - fp.write("Classifier: {}\n".format(clsfr)) + fp.write(u'Classifier: {}\n'.format(clsfr)) for req in self.requires_dist: - fp.write("Requires-Dist: {}\n".format(req)) + fp.write(u'Requires-Dist: {}\n'.format(req)) for url in self.project_urls: - fp.write("Project-URL: {}\n".format(url)) + fp.write(u'Project-URL: {}\n'.format(url)) for extra in self.provides_extra: - fp.write("Provides-Extra: {}\n".format(extra)) + fp.write(u'Provides-Extra: {}\n'.format(extra)) if self.description is not None: - fp.write("\n" + self.description + "\n") + fp.write(u'\n' + self.description + u'\n') @property def supports_py2(self): @@ -434,12 +408,13 @@ def supports_py2(self): def make_metadata(module, ini_info): - md_dict = {"name": module.name, "provides": [module.name]} + md_dict = {'name': module.name, 'provides': [module.name]} md_dict.update(get_info_from_module(module, ini_info.dynamic_metadata)) md_dict.update(ini_info.metadata) return Metadata(md_dict) + def normalize_dist_name(name: str, version: str) -> str: """Normalizes a name and a PEP 440 version @@ -448,15 +423,15 @@ def normalize_dist_name(name: str, version: str) -> str: See https://packaging.python.org/specifications/binary-distribution-format/#escaping-and-unicode """ - normalized_name = re.sub(r"[-_.]+", "_", name, flags=re.UNICODE).lower() + normalized_name = re.sub(r'[-_.]+', '_', name, flags=re.UNICODE).lower() assert check_version(version) == version - assert "-" not in version, "Normalized versions can’t have dashes" - return "{}-{}".format(normalized_name, version) + assert '-' not in version, 'Normalized versions can’t have dashes' + return '{}-{}'.format(normalized_name, version) def dist_info_name(distribution, version): """Get the correct name of the .dist-info folder""" - return normalize_dist_name(distribution, version) + ".dist-info" + return normalize_dist_name(distribution, version) + '.dist-info' def walk_data_dir(data_directory): @@ -473,4 +448,4 @@ def walk_data_dir(data_directory): full_path = os.path.join(dirpath, file) yield full_path - dirs[:] = [d for d in sorted(dirs) if d != "__pycache__"] + dirs[:] = [d for d in sorted(dirs) if d != '__pycache__'] \ No newline at end of file From 8025d097cdc4891fbe73677561af0983cd415c75 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:29:30 +0000 Subject: [PATCH 08/39] fix black --- flit_core/flit_core/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index bcd79407..0f5d7d0d 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -448,4 +448,4 @@ def walk_data_dir(data_directory): full_path = os.path.join(dirpath, file) yield full_path - dirs[:] = [d for d in sorted(dirs) if d != '__pycache__'] \ No newline at end of file + dirs[:] = [d for d in sorted(dirs) if d != '__pycache__'] From 2a3dbe93c9270e84a92474404138eef22e77551f Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:33:47 +0000 Subject: [PATCH 09/39] fix black --- tests/test_install.py | 403 +++++++++++++++++------------------------- 1 file changed, 164 insertions(+), 239 deletions(-) diff --git a/tests/test_install.py b/tests/test_install.py index 2efaeb7c..f59eff84 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -8,33 +8,26 @@ import pytest from testpath import ( - assert_isfile, - assert_isdir, - assert_islink, - assert_not_path_exists, - MockCommand, + assert_isfile, assert_isdir, assert_islink, assert_not_path_exists, MockCommand ) from flit import install from flit.install import Installer, _requires_dist_to_pip_requirement, DependencyError import flit_core.tests -samples_dir = pathlib.Path(__file__).parent / "samples" -core_samples_dir = pathlib.Path(flit_core.tests.__file__).parent / "samples" - +samples_dir = pathlib.Path(__file__).parent / 'samples' +core_samples_dir = pathlib.Path(flit_core.tests.__file__).parent / 'samples' class InstallTests(TestCase): def setUp(self): td = tempfile.TemporaryDirectory() self.addCleanup(td.cleanup) - self.get_dirs_patch = patch( - "flit.install.get_dirs", - return_value={ - "scripts": os.path.join(td.name, "scripts"), - "purelib": os.path.join(td.name, "site-packages"), - "data": os.path.join(td.name, "data"), - }, - ) + self.get_dirs_patch = patch('flit.install.get_dirs', + return_value={ + 'scripts': os.path.join(td.name, 'scripts'), + 'purelib': os.path.join(td.name, 'site-packages'), + 'data': os.path.join(td.name, 'data'), + }) self.get_dirs_patch.start() self.tmpdir = pathlib.Path(td.name) @@ -44,294 +37,249 @@ def tearDown(self): def _assert_direct_url(self, directory, package, version, expected_editable): direct_url_file = ( self.tmpdir - / "site-packages" - / "{}-{}.dist-info".format(package, version) - / "direct_url.json" + / 'site-packages' + / '{}-{}.dist-info'.format(package, version) + / 'direct_url.json' ) assert_isfile(direct_url_file) with direct_url_file.open() as f: direct_url = json.load(f) - assert direct_url["url"].startswith("file:///") - assert direct_url["url"] == directory.as_uri() - assert direct_url["dir_info"].get("editable") is expected_editable + assert direct_url['url'].startswith('file:///') + assert direct_url['url'] == directory.as_uri() + assert direct_url['dir_info'].get('editable') is expected_editable def test_install_module(self): - Installer.from_ini_path( - samples_dir / "module1_toml" / "pyproject.toml" - ).install_directly() - assert_isfile(self.tmpdir / "site-packages" / "module1.py") - assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") + Installer.from_ini_path(samples_dir / 'module1_toml' / 'pyproject.toml').install_directly() + assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') + assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') self._assert_direct_url( - samples_dir / "module1_toml", "module1", "0.1", expected_editable=False + samples_dir / 'module1_toml', 'module1', '0.1', expected_editable=False ) def test_install_module_pep621(self): Installer.from_ini_path( - core_samples_dir / "pep621_nodynamic" / "pyproject.toml", + core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', ).install_directly() - assert_isfile(self.tmpdir / "site-packages" / "module1.py") - assert_isdir(self.tmpdir / "site-packages" / "module1-0.3.dist-info") + assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') + assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info') self._assert_direct_url( - core_samples_dir / "pep621_nodynamic", - "module1", - "0.3", - expected_editable=False, + core_samples_dir / 'pep621_nodynamic', 'module1', '0.3', + expected_editable=False ) def test_install_package(self): oldcwd = os.getcwd() - os.chdir(str(samples_dir / "package1")) + os.chdir(str(samples_dir / 'package1')) try: - Installer.from_ini_path(pathlib.Path("pyproject.toml")).install_directly() + Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly() finally: os.chdir(oldcwd) - assert_isdir(self.tmpdir / "site-packages" / "package1") - assert_isdir(self.tmpdir / "site-packages" / "package1-0.1.dist-info") - assert_isfile(self.tmpdir / "scripts" / "pkg_script") - with (self.tmpdir / "scripts" / "pkg_script").open() as f: + assert_isdir(self.tmpdir / 'site-packages' / 'package1') + assert_isdir(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info') + assert_isfile(self.tmpdir / 'scripts' / 'pkg_script') + with (self.tmpdir / 'scripts' / 'pkg_script').open() as f: assert f.readline().strip() == "#!" + sys.executable self._assert_direct_url( - samples_dir / "package1", "package1", "0.1", expected_editable=False + samples_dir / 'package1', 'package1', '0.1', expected_editable=False ) def test_install_module_in_src(self): oldcwd = os.getcwd() - os.chdir(samples_dir / "packageinsrc") + os.chdir(samples_dir / 'packageinsrc') try: - Installer.from_ini_path(pathlib.Path("pyproject.toml")).install_directly() + Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly() finally: os.chdir(oldcwd) - assert_isfile(self.tmpdir / "site-packages" / "module1.py") - assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") + assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') + assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') def test_install_ns_package_native(self): - Installer.from_ini_path( - samples_dir / "ns1-pkg" / "pyproject.toml" - ).install_directly() - assert_isdir(self.tmpdir / "site-packages" / "ns1") - assert_isfile(self.tmpdir / "site-packages" / "ns1" / "pkg" / "__init__.py") - assert_not_path_exists(self.tmpdir / "site-packages" / "ns1" / "__init__.py") - assert_isdir(self.tmpdir / "site-packages" / "ns1_pkg-0.1.dist-info") + Installer.from_ini_path(samples_dir / 'ns1-pkg' / 'pyproject.toml').install_directly() + assert_isdir(self.tmpdir / 'site-packages' / 'ns1') + assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'pkg' / '__init__.py') + assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py') + assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info') def test_install_ns_package_module_native(self): - Installer.from_ini_path( - samples_dir / "ns1-pkg-mod" / "pyproject.toml" - ).install_directly() - assert_isfile(self.tmpdir / "site-packages" / "ns1" / "module.py") - assert_not_path_exists(self.tmpdir / "site-packages" / "ns1" / "__init__.py") + Installer.from_ini_path(samples_dir / 'ns1-pkg-mod' / 'pyproject.toml').install_directly() + assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'module.py') + assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py') def test_install_ns_package_native_symlink(self): - if os.name == "nt": - raise SkipTest("symlink") + if os.name == 'nt': + raise SkipTest('symlink') Installer.from_ini_path( - samples_dir / "ns1-pkg" / "pyproject.toml", symlink=True + samples_dir / 'ns1-pkg' / 'pyproject.toml', symlink=True ).install_directly() Installer.from_ini_path( - samples_dir / "ns1-pkg2" / "pyproject.toml", symlink=True + samples_dir / 'ns1-pkg2' / 'pyproject.toml', symlink=True ).install_directly() Installer.from_ini_path( - samples_dir / "ns1-pkg-mod" / "pyproject.toml", symlink=True + samples_dir / 'ns1-pkg-mod' / 'pyproject.toml', symlink=True ).install_directly() - assert_isdir(self.tmpdir / "site-packages" / "ns1") - assert_isdir(self.tmpdir / "site-packages" / "ns1" / "pkg") - assert_islink( - self.tmpdir / "site-packages" / "ns1" / "pkg", - to=str(samples_dir / "ns1-pkg" / "ns1" / "pkg"), - ) - assert_isdir(self.tmpdir / "site-packages" / "ns1_pkg-0.1.dist-info") + assert_isdir(self.tmpdir / 'site-packages' / 'ns1') + assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg') + assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg', + to=str(samples_dir / 'ns1-pkg' / 'ns1' / 'pkg')) + assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info') - assert_isdir(self.tmpdir / "site-packages" / "ns1" / "pkg2") - assert_islink( - self.tmpdir / "site-packages" / "ns1" / "pkg2", - to=str(samples_dir / "ns1-pkg2" / "ns1" / "pkg2"), - ) - assert_isdir(self.tmpdir / "site-packages" / "ns1_pkg2-0.1.dist-info") + assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2') + assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2', + to=str(samples_dir / 'ns1-pkg2' / 'ns1' / 'pkg2')) + assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg2-0.1.dist-info') - assert_islink( - self.tmpdir / "site-packages" / "ns1" / "module.py", - to=samples_dir / "ns1-pkg-mod" / "ns1" / "module.py", - ) - assert_isdir(self.tmpdir / "site-packages" / "ns1_module-0.1.dist-info") + assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'module.py', + to=samples_dir / 'ns1-pkg-mod' / 'ns1' / 'module.py') + assert_isdir(self.tmpdir / 'site-packages' / 'ns1_module-0.1.dist-info') def test_install_ns_package_pth_file(self): Installer.from_ini_path( - samples_dir / "ns1-pkg" / "pyproject.toml", pth=True + samples_dir / 'ns1-pkg' / 'pyproject.toml', pth=True ).install_directly() - pth_file = self.tmpdir / "site-packages" / "ns1.pkg.pth" + pth_file = self.tmpdir / 'site-packages' / 'ns1.pkg.pth' assert_isfile(pth_file) - assert pth_file.read_text("utf-8").strip() == str(samples_dir / "ns1-pkg") + assert pth_file.read_text('utf-8').strip() == str(samples_dir / 'ns1-pkg') def test_symlink_package(self): - if os.name == "nt": + if os.name == 'nt': raise SkipTest("symlink") - Installer.from_ini_path( - samples_dir / "package1" / "pyproject.toml", symlink=True - ).install() - assert_islink( - self.tmpdir / "site-packages" / "package1", - to=samples_dir / "package1" / "package1", - ) - assert_isfile(self.tmpdir / "scripts" / "pkg_script") - with (self.tmpdir / "scripts" / "pkg_script").open() as f: + Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', symlink=True).install() + assert_islink(self.tmpdir / 'site-packages' / 'package1', + to=samples_dir / 'package1' / 'package1') + assert_isfile(self.tmpdir / 'scripts' / 'pkg_script') + with (self.tmpdir / 'scripts' / 'pkg_script').open() as f: assert f.readline().strip() == "#!" + sys.executable self._assert_direct_url( - samples_dir / "package1", "package1", "0.1", expected_editable=True + samples_dir / 'package1', 'package1', '0.1', expected_editable=True ) def test_symlink_module_pep621(self): - if os.name == "nt": + if os.name == 'nt': raise SkipTest("symlink") Installer.from_ini_path( - core_samples_dir / "pep621_nodynamic" / "pyproject.toml", symlink=True + core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', symlink=True ).install_directly() - assert_islink( - self.tmpdir / "site-packages" / "module1.py", - to=core_samples_dir / "pep621_nodynamic" / "module1.py", - ) - assert_isdir(self.tmpdir / "site-packages" / "module1-0.3.dist-info") + assert_islink(self.tmpdir / 'site-packages' / 'module1.py', + to=core_samples_dir / 'pep621_nodynamic' / 'module1.py') + assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info') self._assert_direct_url( - core_samples_dir / "pep621_nodynamic", - "module1", - "0.3", - expected_editable=True, + core_samples_dir / 'pep621_nodynamic', 'module1', '0.3', + expected_editable=True ) def test_symlink_module_in_src(self): - if os.name == "nt": + if os.name == 'nt': raise SkipTest("symlink") oldcwd = os.getcwd() - os.chdir(samples_dir / "packageinsrc") + os.chdir(samples_dir / 'packageinsrc') try: Installer.from_ini_path( - pathlib.Path("pyproject.toml"), symlink=True + pathlib.Path('pyproject.toml'), symlink=True ).install_directly() finally: os.chdir(oldcwd) - assert_islink( - self.tmpdir / "site-packages" / "module1.py", - to=(samples_dir / "packageinsrc" / "src" / "module1.py"), - ) - assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") + assert_islink(self.tmpdir / 'site-packages' / 'module1.py', + to=(samples_dir / 'packageinsrc' / 'src' / 'module1.py')) + assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') def test_pth_package(self): - Installer.from_ini_path( - samples_dir / "package1" / "pyproject.toml", pth=True - ).install() - assert_isfile(self.tmpdir / "site-packages" / "package1.pth") - with open(str(self.tmpdir / "site-packages" / "package1.pth")) as f: - assert f.read() == str(samples_dir / "package1") - assert_isfile(self.tmpdir / "scripts" / "pkg_script") + Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', pth=True).install() + assert_isfile(self.tmpdir / 'site-packages' / 'package1.pth') + with open(str(self.tmpdir / 'site-packages' / 'package1.pth')) as f: + assert f.read() == str(samples_dir / 'package1') + assert_isfile(self.tmpdir / 'scripts' / 'pkg_script') self._assert_direct_url( - samples_dir / "package1", "package1", "0.1", expected_editable=True + samples_dir / 'package1', 'package1', '0.1', expected_editable=True ) def test_pth_module_in_src(self): oldcwd = os.getcwd() - os.chdir(samples_dir / "packageinsrc") + os.chdir(samples_dir / 'packageinsrc') try: Installer.from_ini_path( - pathlib.Path("pyproject.toml"), pth=True + pathlib.Path('pyproject.toml'), pth=True ).install_directly() finally: os.chdir(oldcwd) - pth_path = self.tmpdir / "site-packages" / "module1.pth" + pth_path = self.tmpdir / 'site-packages' / 'module1.pth' assert_isfile(pth_path) - assert pth_path.read_text("utf-8").strip() == str( - samples_dir / "packageinsrc" / "src" + assert pth_path.read_text('utf-8').strip() == str( + samples_dir / 'packageinsrc' / 'src' ) - assert_isdir(self.tmpdir / "site-packages" / "module1-0.1.dist-info") + assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info') def test_dist_name(self): - Installer.from_ini_path( - samples_dir / "altdistname" / "pyproject.toml" - ).install_directly() - assert_isdir(self.tmpdir / "site-packages" / "package1") - assert_isdir(self.tmpdir / "site-packages" / "package_dist1-0.1.dist-info") + Installer.from_ini_path(samples_dir / 'altdistname' / 'pyproject.toml').install_directly() + assert_isdir(self.tmpdir / 'site-packages' / 'package1') + assert_isdir(self.tmpdir / 'site-packages' / 'package_dist1-0.1.dist-info') def test_entry_points(self): - Installer.from_ini_path( - samples_dir / "entrypoints_valid" / "pyproject.toml" - ).install_directly() - assert_isfile( - self.tmpdir - / "site-packages" - / "package1-0.1.dist-info" - / "entry_points.txt" - ) + Installer.from_ini_path(samples_dir / 'entrypoints_valid' / 'pyproject.toml').install_directly() + assert_isfile(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info' / 'entry_points.txt') def test_pip_install(self): - ins = Installer.from_ini_path( - samples_dir / "package1" / "pyproject.toml", - python="mock_python", - user=False, - ) + ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python', + user=False) - with MockCommand("mock_python") as mock_py: + with MockCommand('mock_python') as mock_py: ins.install() calls = mock_py.get_calls() assert len(calls) == 1 - cmd = calls[0]["argv"] - assert cmd[1:4] == ["-m", "pip", "install"] - assert cmd[4].endswith("package1") + cmd = calls[0]['argv'] + assert cmd[1:4] == ['-m', 'pip', 'install'] + assert cmd[4].endswith('package1') def test_symlink_other_python(self): - if os.name == "nt": - raise SkipTest("symlink") - (self.tmpdir / "site-packages2").mkdir() - (self.tmpdir / "scripts2").mkdir() + if os.name == 'nt': + raise SkipTest('symlink') + (self.tmpdir / 'site-packages2').mkdir() + (self.tmpdir / 'scripts2').mkdir() # Called by Installer._auto_user() : - script1 = ( - "#!{python}\n" - "import sysconfig\n" - "print(True)\n" # site.ENABLE_USER_SITE - "print({purelib!r})" # sysconfig.get_path('purelib') - ).format(python=sys.executable, purelib=str(self.tmpdir / "site-packages2")) + script1 = ("#!{python}\n" + "import sysconfig\n" + "print(True)\n" # site.ENABLE_USER_SITE + "print({purelib!r})" # sysconfig.get_path('purelib') + ).format(python=sys.executable, + purelib=str(self.tmpdir / 'site-packages2')) # Called by Installer._get_dirs() : - script2 = ( - "#!{python}\n" - "import json, sys\n" - "json.dump({{'purelib': {purelib!r}, 'scripts': {scripts!r}, 'data': {data!r} }}, " - "sys.stdout)" - ).format( - python=sys.executable, - purelib=str(self.tmpdir / "site-packages2"), - scripts=str(self.tmpdir / "scripts2"), - data=str(self.tmpdir / "data"), - ) - - with MockCommand("mock_python", content=script1): - ins = Installer.from_ini_path( - samples_dir / "package1" / "pyproject.toml", - python="mock_python", - symlink=True, - ) - with MockCommand("mock_python", content=script2): + script2 = ("#!{python}\n" + "import json, sys\n" + "json.dump({{'purelib': {purelib!r}, 'scripts': {scripts!r}, 'data': {data!r} }}, " + "sys.stdout)" + ).format(python=sys.executable, + purelib=str(self.tmpdir / 'site-packages2'), + scripts=str(self.tmpdir / 'scripts2'), + data=str(self.tmpdir / 'data'), + ) + + with MockCommand('mock_python', content=script1): + ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python', + symlink=True) + with MockCommand('mock_python', content=script2): ins.install() - assert_islink( - self.tmpdir / "site-packages2" / "package1", - to=samples_dir / "package1" / "package1", - ) - assert_isfile(self.tmpdir / "scripts2" / "pkg_script") - with (self.tmpdir / "scripts2" / "pkg_script").open() as f: + assert_islink(self.tmpdir / 'site-packages2' / 'package1', + to=samples_dir / 'package1' / 'package1') + assert_isfile(self.tmpdir / 'scripts2' / 'pkg_script') + with (self.tmpdir / 'scripts2' / 'pkg_script').open() as f: assert f.readline().strip() == "#!mock_python" def test_install_requires(self): - ins = Installer.from_ini_path( - samples_dir / "requires-requests.toml", user=False, python="mock_python" - ) + ins = Installer.from_ini_path(samples_dir / 'requires-requests.toml', + user=False, python='mock_python') - with MockCommand("mock_python") as mockpy: + with MockCommand('mock_python') as mockpy: ins.install_requirements() calls = mockpy.get_calls() assert len(calls) == 1 - assert calls[0]["argv"][1:5] == ["-m", "pip", "install", "-r"] + assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r'] def test_install_only_deps(self): + """Test if we can install using --only-deps with the pyproject.toml, and without the README or module folder""" os.environ.setdefault("FLIT_ALLOW_INVALID", "1") ins = Installer.from_ini_path( samples_dir / "only-deps.toml", user=False, python="mock_python" @@ -345,8 +293,8 @@ def test_install_only_deps(self): def test_install_reqs_my_python_if_needed_pep621(self): ins = Installer.from_ini_path( - core_samples_dir / "pep621_nodynamic" / "pyproject.toml", - deps="none", + core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', + deps='none', ) # This shouldn't try to get version & docstring from the module @@ -354,61 +302,42 @@ def test_install_reqs_my_python_if_needed_pep621(self): def test_extras_error(self): with pytest.raises(DependencyError): - Installer.from_ini_path( - samples_dir / "requires-requests.toml", - user=False, - deps="none", - extras="dev", - ) + Installer.from_ini_path(samples_dir / 'requires-requests.toml', + user=False, deps='none', extras='dev') def test_install_data_dir(self): Installer.from_ini_path( - core_samples_dir / "with_data_dir" / "pyproject.toml", + core_samples_dir / 'with_data_dir' / 'pyproject.toml', ).install_directly() - assert_isfile(self.tmpdir / "site-packages" / "module1.py") - assert_isfile(self.tmpdir / "data" / "share" / "man" / "man1" / "foo.1") + assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') + assert_isfile(self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1') def test_symlink_data_dir(self): - if os.name == "nt": + if os.name == 'nt': raise SkipTest("symlink") Installer.from_ini_path( - core_samples_dir / "with_data_dir" / "pyproject.toml", symlink=True + core_samples_dir / 'with_data_dir' / 'pyproject.toml', symlink=True ).install_directly() - assert_isfile(self.tmpdir / "site-packages" / "module1.py") + assert_isfile(self.tmpdir / 'site-packages' / 'module1.py') assert_islink( - self.tmpdir / "data" / "share" / "man" / "man1" / "foo.1", - to=core_samples_dir - / "with_data_dir" - / "data" - / "share" - / "man" - / "man1" - / "foo.1", + self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1', + to=core_samples_dir / 'with_data_dir' / 'data' / 'share' / 'man' / 'man1' / 'foo.1' ) - -@pytest.mark.parametrize( - ("deps", "extras", "installed"), - [ - ("none", [], set()), - ("develop", [], {"pytest ;", "toml ;"}), - ("production", [], {"toml ;"}), - ("all", [], {"toml ;", "pytest ;", "requests ;"}), - ], -) +@pytest.mark.parametrize(('deps', 'extras', 'installed'), [ + ('none', [], set()), + ('develop', [], {'pytest ;', 'toml ;'}), + ('production', [], {'toml ;'}), + ('all', [], {'toml ;', 'pytest ;', 'requests ;'}), +]) def test_install_requires_extra(deps, extras, installed): it = InstallTests() try: it.setUp() - ins = Installer.from_ini_path( - samples_dir / "extras" / "pyproject.toml", - python="mock_python", - user=False, - deps=deps, - extras=extras, - ) + ins = Installer.from_ini_path(samples_dir / 'extras' / 'pyproject.toml', python='mock_python', + user=False, deps=deps, extras=extras) - cmd = MockCommand("mock_python") + cmd = MockCommand('mock_python') get_reqs = ( "#!{python}\n" "import sys\n" @@ -421,20 +350,16 @@ def test_install_requires_extra(deps, extras, installed): ins.install_requirements() with open(mock_py.recording_file) as f: str_deps = f.read() - deps = str_deps.split("\n") if str_deps else [] + deps = str_deps.split('\n') if str_deps else [] assert set(deps) == installed finally: it.tearDown() - def test_requires_dist_to_pip_requirement(): rd = 'pathlib2 (>=2.3); python_version == "2.7"' - assert ( - _requires_dist_to_pip_requirement(rd) - == 'pathlib2>=2.3 ; python_version == "2.7"' - ) - + assert _requires_dist_to_pip_requirement(rd) == \ + 'pathlib2>=2.3 ; python_version == "2.7"' def test_test_writable_dir_win(): with tempfile.TemporaryDirectory() as td: @@ -442,7 +367,7 @@ def test_test_writable_dir_win(): # Ironically, I don't know how to make a non-writable dir on Windows, # so although the functionality is for Windows, the test is for Posix - if os.name != "posix": + if os.name != 'posix': return # Remove write permissions from the directory From 3d8820bf882799932ee27e62a01df610c5344ead Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:37:29 +0000 Subject: [PATCH 10/39] fix black --- .devcontainer/devcontainer.json | 8 -------- .vscode/settings.json | 6 ------ tests/test_install.py | 1 + 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f8070194..fd40e657 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,6 @@ "ms-azuretools.vscode-docker", "ms-python.python", "ms-python.vscode-pylance", - "ms-python.pylint", "ms-python.flake8", "ms-vsliveshare.vsliveshare", "ryanluker.vscode-coverage-gutters", @@ -23,13 +22,6 @@ ], "settings": { "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.formatting.provider": "black", - "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, - "pylint.path": [ - "/usr/local/py-utils/bin/pylint" - ], "flake8.path": [ "/usr/local/py-utils/bin/flake8" ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 357e4545..375989b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,12 +11,6 @@ "python.formatting.provider": "autopep8", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "pylint.args": [ - "--rcfile=pyproject.toml" - ], - "pylint.path": [ - "/usr/local/py-utils/bin/pylint" - ], "flake8.args": [ "--config=.flake8" ], diff --git a/tests/test_install.py b/tests/test_install.py index f59eff84..48961614 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -290,6 +290,7 @@ def test_install_only_deps(self): calls = mockpy.get_calls() assert len(calls) == 1 assert calls[0]["argv"][1:5] == ["-m", "pip", "install", "-r"] + del os.environ['FLIT_ALLOW_INVALID'] def test_install_reqs_my_python_if_needed_pep621(self): ins = Installer.from_ini_path( From 10156bf3651b776f034c2ea1b3f5583fa17e45cd Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:43:39 +0000 Subject: [PATCH 11/39] fix error message --- flit_core/flit_core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 9e7dd1bf..88fea117 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -332,7 +332,7 @@ def _prep_metadata(md_sect, path): md_dict["description_content_type"] = mimetype except ConfigError as ex: if not os.environ.get("FLIT_ALLOW_INVALID"): - raise ConfigError("Invalid config values (see log)") from ex + raise ConfigError("Description file .* does not exist") from ex log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Skipping missing description-file. Uploads may still fail.") From 903b32e95e1b762f5427f1f61e130fd50e9437a8 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:46:47 +0000 Subject: [PATCH 12/39] unblack --- flit/install.py | 311 ++++++++++++++-------------------- flit_core/flit_core/config.py | 2 +- 2 files changed, 124 insertions(+), 189 deletions(-) diff --git a/flit/install.py b/flit/install.py index b1e97e73..cef8d3d0 100644 --- a/flit/install.py +++ b/flit/install.py @@ -21,51 +21,48 @@ log = logging.getLogger(__name__) - def _requires_dist_to_pip_requirement(requires_dist): """Parse "Foo (v); python_version == '2.x'" from Requires-Dist Returns pip-style appropriate for requirements.txt. """ - env_mark = "" - if ";" in requires_dist: - name_version, env_mark = requires_dist.split(";", 1) + env_mark = '' + if ';' in requires_dist: + name_version, env_mark = requires_dist.split(';', 1) else: name_version = requires_dist - if "(" in name_version: + if '(' in name_version: # turn 'name (X)' and 'name ("): - version = "==" + version + version = version.replace(')', '').strip() + if not any(c in version for c in '=<>'): + version = '==' + version name_version = name + version # re-add environment marker - return " ;".join([name_version, env_mark]) - + return ' ;'.join([name_version, env_mark]) def test_writable_dir(path): """Check if a directory is writable. Uses os.access() on POSIX, tries creating files on Windows. """ - if os.name == "posix": + if os.name == 'posix': return os.access(path, os.W_OK) return _test_writable_dir_win(path) - def _test_writable_dir_win(path): # os.access doesn't work on Windows: http://bugs.python.org/issue2528 # and we can't use tempfile: http://bugs.python.org/issue22107 - basename = "accesstest_deleteme_fishfingers_custard_" - alphabet = "abcdefghijklmnopqrstuvwxyz0123456789" + basename = 'accesstest_deleteme_fishfingers_custard_' + alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789' for i in range(10): - name = basename + "".join(random.choice(alphabet) for _ in range(6)) + name = basename + ''.join(random.choice(alphabet) for _ in range(6)) file = osp.join(path, name) try: - with open(file, mode="xb"): + with open(file, mode='xb'): pass except FileExistsError: continue @@ -79,38 +76,22 @@ def _test_writable_dir_win(path): return True # This should never be reached - msg = ( - "Unexpected condition testing for writable directory {!r}. " - "Please open an issue on flit to debug why this occurred." - ) # pragma: no cover + msg = ('Unexpected condition testing for writable directory {!r}. ' + 'Please open an issue on flit to debug why this occurred.') # pragma: no cover raise EnvironmentError(msg.format(path)) # pragma: no cover - class RootInstallError(Exception): def __str__(self): - return ( - "Installing packages as root is not recommended. " - "To allow this, set FLIT_ROOT_INSTALL=1 and try again." - ) - + return ("Installing packages as root is not recommended. " + "To allow this, set FLIT_ROOT_INSTALL=1 and try again.") class DependencyError(Exception): def __str__(self): - return "To install dependencies for extras, you cannot set deps=none." - + return 'To install dependencies for extras, you cannot set deps=none.' class Installer(object): - def __init__( - self, - directory, - ini_info, - user=None, - python=sys.executable, - symlink=False, - deps="all", - extras=(), - pth=False, - ): + def __init__(self, directory, ini_info, user=None, python=sys.executable, + symlink=False, deps='all', extras=(), pth=False): self.directory = directory self.ini_info = ini_info self.python = python @@ -118,77 +99,56 @@ def __init__( self.pth = pth self.deps = deps self.extras = extras - if deps != "none" and os.environ.get("FLIT_NO_NETWORK", ""): - self.deps = "none" - log.warning("Not installing dependencies, because FLIT_NO_NETWORK is set") - if deps == "none" and extras: + if deps != 'none' and os.environ.get('FLIT_NO_NETWORK', ''): + self.deps = 'none' + log.warning('Not installing dependencies, because FLIT_NO_NETWORK is set') + if deps == 'none' and extras: raise DependencyError() + self.module = common.Module(self.ini_info.module, directory) try: self.module = common.Module(self.ini_info.module, directory) - except AttributeError as exec: - if os.environ.get("FLIT_ALLOW_INVALID"): - log.warning( - "Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail." - ) - else: - raise ValueError() from exec + except AttributeError as error: + if not os.environ.get('FLIT_ALLOW_INVALID'): + raise AttributeError() from error + log.warning('Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.') - if ( - hasattr(os, "getuid") - and (os.getuid() == 0) - and (not os.environ.get("FLIT_ROOT_INSTALL")) - ): + if (hasattr(os, 'getuid') and (os.getuid() == 0) and + (not os.environ.get('FLIT_ROOT_INSTALL'))): raise RootInstallError if user is None: self.user = self._auto_user(python) else: self.user = user - log.debug("User install? %s", self.user) + log.debug('User install? %s', self.user) self.installed_files = [] @classmethod - def from_ini_path( - cls, - ini_path, - user=None, - python=sys.executable, - symlink=False, - deps="all", - extras=(), - pth=False, - ): + def from_ini_path(cls, ini_path, user=None, python=sys.executable, + symlink=False, deps='all', extras=(), pth=False): ini_info = read_flit_config(ini_path) - return cls( - ini_path.parent, - ini_info, - user=user, - python=python, - symlink=symlink, - deps=deps, - extras=extras, - pth=pth, - ) + return cls(ini_path.parent, ini_info, user=user, python=python, + symlink=symlink, deps=deps, extras=extras, pth=pth) def _run_python(self, code=None, file=None, extra_args=()): if code and file: - raise ValueError("Specify code or file, not both") + raise ValueError('Specify code or file, not both') if not (code or file): - raise ValueError("Specify code or file") + raise ValueError('Specify code or file') if code: - args = [self.python, "-c", code] + args = [self.python, '-c', code] else: args = [self.python, file] args.extend(extra_args) env = os.environ.copy() - env["PYTHONIOENCODING"] = "utf-8" + env['PYTHONIOENCODING'] = 'utf-8' # On Windows, shell needs to be True to pick up our local PATH # when finding the Python command. - shell = os.name == "nt" - return check_output(args, shell=shell, env=env).decode("utf-8") + shell = (os.name == 'nt') + return check_output(args, shell=shell, env=env).decode('utf-8') def _auto_user(self, python): """Default guess for whether to do user-level install. @@ -197,53 +157,47 @@ def _auto_user(self, python): """ if python == sys.executable: user_site = site.ENABLE_USER_SITE - lib_dir = sysconfig.get_path("purelib") + lib_dir = sysconfig.get_path('purelib') else: - out = self._run_python( - code=( - "import sysconfig, site; " - "print(site.ENABLE_USER_SITE); " - "print(sysconfig.get_path('purelib'))" - ) - ) - user_site, lib_dir = out.split("\n", 1) - user_site = user_site.strip() == "True" + out = self._run_python(code= + ("import sysconfig, site; " + "print(site.ENABLE_USER_SITE); " + "print(sysconfig.get_path('purelib'))")) + user_site, lib_dir = out.split('\n', 1) + user_site = (user_site.strip() == 'True') lib_dir = lib_dir.strip() if not user_site: # No user site packages - probably a virtualenv - log.debug("User site packages not available - env install") + log.debug('User site packages not available - env install') return False - log.debug("Checking access to %s", lib_dir) + log.debug('Checking access to %s', lib_dir) return not test_writable_dir(lib_dir) def install_scripts(self, script_defs, scripts_dir): for name, ep in script_defs.items(): module, func = common.parse_entry_point(ep) - import_name = func.split(".")[0] + import_name = func.split('.')[0] script_file = pathlib.Path(scripts_dir) / name - log.info("Writing script to %s", script_file) - with script_file.open("w", encoding="utf-8") as f: - f.write( - common.script_template.format( - interpreter=self.python, - module=module, - import_name=import_name, - func=func, - ) - ) + log.info('Writing script to %s', script_file) + with script_file.open('w', encoding='utf-8') as f: + f.write(common.script_template.format( + interpreter=self.python, + module=module, + import_name=import_name, + func=func + )) script_file.chmod(0o755) self.installed_files.append(script_file) - if sys.platform == "win32": - cmd_file = script_file.with_suffix(".cmd") + if sys.platform == 'win32': + cmd_file = script_file.with_suffix('.cmd') cmd = '@echo off\r\n"{python}" "%~dp0\\{script}" %*\r\n'.format( - python=self.python, script=name - ) + python=self.python, script=name) log.debug("Writing script wrapper to %s", cmd_file) - with cmd_file.open("w") as f: + with cmd_file.open('w') as f: f.write(cmd) self.installed_files.append(cmd_file) @@ -266,15 +220,15 @@ def _record_installed_directory(self, path): def _extras_to_install(self): extras_to_install = set(self.extras) - if self.deps == "all" or "all" in extras_to_install: + if self.deps == 'all' or 'all' in extras_to_install: extras_to_install |= set(self.ini_info.reqs_by_extra.keys()) # We don’t remove 'all' from the set because there might be an extra called “all”. - elif self.deps == "develop": - extras_to_install |= {"dev", "doc", "test"} + elif self.deps == 'develop': + extras_to_install |= {'dev', 'doc', 'test'} - if self.deps != "none": + if self.deps != 'none': # '.none' is an internal token for normal requirements - extras_to_install.add(".none") + extras_to_install.add('.none') log.info("Extras to install for deps %r: %s", self.deps, extras_to_install) return extras_to_install @@ -286,7 +240,7 @@ def install_requirements(self): # construct the full list of requirements, including dev requirements requirements = [] - if self.deps == "none": + if self.deps == 'none': return for extra in self._extras_to_install(): @@ -297,18 +251,19 @@ def install_requirements(self): return requirements = [ - _requires_dist_to_pip_requirement(req_d) for req_d in requirements + _requires_dist_to_pip_requirement(req_d) + for req_d in requirements ] # install the requirements with pip - cmd = [self.python, "-m", "pip", "install"] + cmd = [self.python, '-m', 'pip', 'install'] if self.user: - cmd.append("--user") - with tempfile.NamedTemporaryFile( - mode="w", suffix="requirements.txt", delete=False - ) as tf: - tf.file.write("\n".join(requirements)) - cmd.extend(["-r", tf.name]) + cmd.append('--user') + with tempfile.NamedTemporaryFile(mode='w', + suffix='requirements.txt', + delete=False) as tf: + tf.file.write('\n'.join(requirements)) + cmd.extend(['-r', tf.name]) log.info("Installing requirements") try: check_call(cmd) @@ -325,12 +280,12 @@ def install_reqs_my_python_if_needed(self): try: common.get_info_from_module(self.module, self.ini_info.dynamic_metadata) except ImportError: - if self.deps == "none": + if self.deps == 'none': raise # We were asked not to install deps, so bail out. log.warning("Installing requirements to Flit's env to import module.") user = self.user if (self.python == sys.executable) else None - i2 = Installer(self.directory, self.ini_info, user=user, deps="production") + i2 = Installer(self.directory, self.ini_info, user=user, deps='production') i2.install_requirements() def _get_dirs(self, user): @@ -338,19 +293,19 @@ def _get_dirs(self, user): return get_dirs(user=user) else: import json - - path = osp.join(osp.dirname(__file__), "_get_dirs.py") - args = ["--user"] if user else [] + path = osp.join(osp.dirname(__file__), '_get_dirs.py') + args = ['--user'] if user else [] return json.loads(self._run_python(file=path, extra_args=args)) def install_directly(self): - """Install a module/package into site-packages, and create its scripts.""" + """Install a module/package into site-packages, and create its scripts. + """ dirs = self._get_dirs(user=self.user) - os.makedirs(dirs["purelib"], exist_ok=True) - os.makedirs(dirs["scripts"], exist_ok=True) + os.makedirs(dirs['purelib'], exist_ok=True) + os.makedirs(dirs['scripts'], exist_ok=True) module_rel_path = self.module.path.relative_to(self.module.source_dir) - dst = osp.join(dirs["purelib"], module_rel_path) + dst = osp.join(dirs['purelib'], module_rel_path) if osp.lexists(dst): if osp.isdir(dst) and not osp.islink(dst): shutil.rmtree(dst) @@ -377,9 +332,9 @@ def install_directly(self): elif self.pth: # .pth points to the the folder containing the module (which is # added to sys.path) - pth_file = pathlib.Path(dirs["purelib"], self.module.name + ".pth") + pth_file = pathlib.Path(dirs['purelib'], self.module.name + '.pth') log.info("Adding .pth file %s for %s", pth_file, self.module.source_dir) - pth_file.write_text(str(self.module.source_dir.resolve()), "utf-8") + pth_file.write_text(str(self.module.source_dir.resolve()), 'utf-8') self.installed_files.append(pth_file) elif self.module.is_package: log.info("Copying directory %s -> %s", src, dst) @@ -391,12 +346,12 @@ def install_directly(self): shutil.copy2(src, dst) self.installed_files.append(dst) - scripts = self.ini_info.entrypoints.get("console_scripts", {}) - self.install_scripts(scripts, dirs["scripts"]) + scripts = self.ini_info.entrypoints.get('console_scripts', {}) + self.install_scripts(scripts, dirs['scripts']) - self.install_data_dir(dirs["data"]) + self.install_data_dir(dirs['data']) - self.write_dist_info(dirs["purelib"]) + self.write_dist_info(dirs['purelib']) def install_with_pip(self): """Let pip install the project directory @@ -409,69 +364,64 @@ def install_with_pip(self): """ self.install_reqs_my_python_if_needed() extras = self._extras_to_install() - extras.discard(".none") - req_with_extras = ( - "{}[{}]".format(self.directory, ",".join(extras)) - if extras - else str(self.directory) - ) - cmd = [self.python, "-m", "pip", "install", req_with_extras] + extras.discard('.none') + req_with_extras = '{}[{}]'.format(self.directory, ','.join(extras)) \ + if extras else str(self.directory) + cmd = [self.python, '-m', 'pip', 'install', req_with_extras] if self.user: - cmd.append("--user") - if self.deps == "none": - cmd.append("--no-deps") - shell = os.name == "nt" + cmd.append('--user') + if self.deps == 'none': + cmd.append('--no-deps') + shell = (os.name == 'nt') check_call(cmd, shell=shell) def write_dist_info(self, site_pkgs): """Write dist-info folder, according to PEP 376""" metadata = common.make_metadata(self.module, self.ini_info) dist_info = pathlib.Path(site_pkgs) / common.dist_info_name( - metadata.name, metadata.version - ) + metadata.name, metadata.version) try: dist_info.mkdir() except FileExistsError: shutil.rmtree(str(dist_info)) dist_info.mkdir() - with (dist_info / "METADATA").open("w", encoding="utf-8") as f: + with (dist_info / 'METADATA').open('w', encoding='utf-8') as f: metadata.write_metadata_file(f) - self.installed_files.append(dist_info / "METADATA") + self.installed_files.append(dist_info / 'METADATA') - with (dist_info / "INSTALLER").open("w", encoding="utf-8") as f: - f.write("flit") - self.installed_files.append(dist_info / "INSTALLER") + with (dist_info / 'INSTALLER').open('w', encoding='utf-8') as f: + f.write('flit') + self.installed_files.append(dist_info / 'INSTALLER') # We only handle explicitly requested installations - with (dist_info / "REQUESTED").open("wb"): - pass - self.installed_files.append(dist_info / "REQUESTED") + with (dist_info / 'REQUESTED').open('wb'): pass + self.installed_files.append(dist_info / 'REQUESTED') if self.ini_info.entrypoints: - with (dist_info / "entry_points.txt").open("w") as f: + with (dist_info / 'entry_points.txt').open('w') as f: common.write_entry_points(self.ini_info.entrypoints, f) - self.installed_files.append(dist_info / "entry_points.txt") + self.installed_files.append(dist_info / 'entry_points.txt') - with (dist_info / "direct_url.json").open("w", encoding="utf-8") as f: + with (dist_info / 'direct_url.json').open('w', encoding='utf-8') as f: json.dump( { "url": self.directory.resolve().as_uri(), - "dir_info": {"editable": bool(self.symlink or self.pth)}, + "dir_info": {"editable": bool(self.symlink or self.pth)} }, - f, + f ) - self.installed_files.append(dist_info / "direct_url.json") + self.installed_files.append(dist_info / 'direct_url.json') # newline='' because the csv module does its own newline translation - with (dist_info / "RECORD").open("w", encoding="utf-8", newline="") as f: + with (dist_info / 'RECORD').open('w', encoding='utf-8', newline='') as f: cf = csv.writer(f) for path in sorted(self.installed_files, key=str): path = pathlib.Path(path) - if path.is_symlink() or path.suffix in {".pyc", ".pyo"}: - hash, size = "", "" + if path.is_symlink() or path.suffix in {'.pyc', '.pyo'}: + hash, size = '', '' else: - hash = "sha256=" + common.hash_file(str(path)) + hash = 'sha256=' + common.hash_file(str(path)) size = path.stat().st_size try: path = path.relative_to(site_pkgs) @@ -479,25 +429,10 @@ def write_dist_info(self, site_pkgs): pass cf.writerow((str(path), hash, size)) - cf.writerow(((dist_info / "RECORD").relative_to(site_pkgs), "", "")) + cf.writerow(((dist_info / 'RECORD').relative_to(site_pkgs), '', '')) def install(self): if self.symlink or self.pth: self.install_directly() else: - self.install_with_pip() - - -class DependencyInstaller(Installer): - def __init__( - self, - directory, - ini_info, - user=None, - python=sys.executable, - symlink=False, - deps="all", - extras=(), - pth=False, - ): - super().__init__(directory, ini_info, user, python, symlink, deps, extras, pth) + self.install_with_pip() \ No newline at end of file diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 88fea117..9e7dd1bf 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -332,7 +332,7 @@ def _prep_metadata(md_sect, path): md_dict["description_content_type"] = mimetype except ConfigError as ex: if not os.environ.get("FLIT_ALLOW_INVALID"): - raise ConfigError("Description file .* does not exist") from ex + raise ConfigError("Invalid config values (see log)") from ex log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Skipping missing description-file. Uploads may still fail.") From e91b4d7e126e95c66a1efa589c1cd326623c7bc2 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:54:08 +0000 Subject: [PATCH 13/39] fix tests --- flit_core/flit_core/common.py | 4 ++-- flit_core/flit_core/config.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 0f5d7d0d..9a847a1a 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -59,8 +59,8 @@ def __init__(self, name, directory=Path()): if not os.environ.get("FLIT_ALLOW_INVALID"): raise ValueError("No file/folder found for module {}".format(name)) log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") - - self.source_dir = directory / self.prefix + else: + self.source_dir = directory / self.prefix if '.' in name: self.namespace_package_name = name.rpartition('.')[0] diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 9e7dd1bf..d16d890c 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -331,9 +331,9 @@ def _prep_metadata(md_sect, path): md_dict["description"] = desc_content md_dict["description_content_type"] = mimetype except ConfigError as ex: - if not os.environ.get("FLIT_ALLOW_INVALID"): - raise ConfigError("Invalid config values (see log)") from ex - log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Skipping missing description-file. Uploads may still fail.") + if not os.environ.get('FLIT_ALLOW_INVALID'): + raise ConfigError('Description file .* does not exist') from ex + log.warning('Allowing invalid data (FLIT_ALLOW_INVALID set). Skipping missing description-file. Uploads may still fail.') if 'urls' in md_sect: From 9aabad5f64a28758615ac31d386397cdf78d5f2b Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:17:36 -0400 Subject: [PATCH 14/39] Update test_install.py --- tests/test_install.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_install.py b/tests/test_install.py index 48961614..42a98158 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -292,6 +292,16 @@ def test_install_only_deps(self): assert calls[0]["argv"][1:5] == ["-m", "pip", "install", "-r"] del os.environ['FLIT_ALLOW_INVALID'] + def test_install_only_deps_fail(self): + with pytest.raises(ConfigError, match=r"Description file .* does not exist"): + Installer.from_ini_path( + samples_dir / 'missing-description-file.toml', user=False, python='mock_python' + ) + with pytest.raises(ValueError, match=r"No file/folder found for module nomodule"): + Installer.from_ini_path( + samples_dir / "missing-module.toml", user=False, python="mock_python" + ) + def test_install_reqs_my_python_if_needed_pep621(self): ins = Installer.from_ini_path( core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', From 7694731d90f2da3465e6b754f2a0a96fb9b23a32 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:18:10 -0400 Subject: [PATCH 15/39] Create --- tests/samples/missing-module.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/samples/missing-module.toml diff --git a/tests/samples/missing-module.toml b/tests/samples/missing-module.toml new file mode 100644 index 00000000..05e89575 --- /dev/null +++ b/tests/samples/missing-module.toml @@ -0,0 +1,10 @@ +[build-system] +requires = ["flit"] + +[tool.flit.metadata] +module = "nomodule" +author = "Sir Robin" +author-email = "robin@camelot.uk" +home-page = "http://github.com/sirrobin/module1" +description-file = "EG_README.rst" +requires = ["requests"] From e90caf4a05e202cbb91dd69fa6d3436aa7707597 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:18:55 -0400 Subject: [PATCH 16/39] Update install.py --- flit/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flit/install.py b/flit/install.py index cef8d3d0..63ce664b 100644 --- a/flit/install.py +++ b/flit/install.py @@ -435,4 +435,4 @@ def install(self): if self.symlink or self.pth: self.install_directly() else: - self.install_with_pip() \ No newline at end of file + self.install_with_pip() From c1c3b50468c4fe2e3ba4a711ba19bf37d8ed3ccc Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:21:12 -0400 Subject: [PATCH 17/39] Update test_install.py --- tests/test_install.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_install.py b/tests/test_install.py index 42a98158..575758cd 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -13,6 +13,7 @@ from flit import install from flit.install import Installer, _requires_dist_to_pip_requirement, DependencyError +from flit_core.config import ConfigError import flit_core.tests samples_dir = pathlib.Path(__file__).parent / 'samples' From 757d67547713663420260eee35ca7f1ca5f420e7 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:28:36 -0400 Subject: [PATCH 18/39] Update install.py --- flit/install.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/flit/install.py b/flit/install.py index 63ce664b..3ea9a4bf 100644 --- a/flit/install.py +++ b/flit/install.py @@ -106,12 +106,6 @@ def __init__(self, directory, ini_info, user=None, python=sys.executable, raise DependencyError() self.module = common.Module(self.ini_info.module, directory) - try: - self.module = common.Module(self.ini_info.module, directory) - except AttributeError as error: - if not os.environ.get('FLIT_ALLOW_INVALID'): - raise AttributeError() from error - log.warning('Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.') if (hasattr(os, 'getuid') and (os.getuid() == 0) and (not os.environ.get('FLIT_ROOT_INSTALL'))): From 4064a536a22b5ed9e957a191159e7cb685cb2de5 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:30:40 -0400 Subject: [PATCH 19/39] Update config.py --- flit_core/flit_core/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index d16d890c..1024a58b 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -86,7 +86,7 @@ def __str__(self): def prep_toml_config(d, path): """Validate config loaded from pyproject.toml and prepare common metadata - + Returns a LoadedConfig object. """ dtool = d.get('tool', {}).get('flit', {}) @@ -304,11 +304,11 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): def _prep_metadata(md_sect, path): """Process & verify the metadata from a config file - + - Pull out the module name we're packaging. - Read description-file and check that it's valid rst - Convert dashes in key names to underscores - (e.g. home-page in config -> home_page in metadata) + (e.g. home-page in config -> home_page in metadata) """ if not set(md_sect).issuperset(metadata_required_fields): missing = metadata_required_fields - set(md_sect) From 9b5ae822277dbd750634cf294d7b1c4815f0b9d1 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:32:47 -0400 Subject: [PATCH 20/39] Delete Dockerfile --- .devcontainer/Dockerfile | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 5bf67bed..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/python:3 - -RUN python -m pip install --upgrade pip \ - && python -m pip install pytest pytest-cov \ - && python -m pip install 'flit>=3.8.0' - -ENV FLIT_ROOT_INSTALL=1 - -COPY pyproject.toml README.rst ./ -RUN mkdir -p flit \ - && python -m flit install --only-deps --deps develop \ - && rm -r pyproject.toml README.rst flit From 3ce790ee47fa8254b9e9aad954f93c21b638467a Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:32:53 -0400 Subject: [PATCH 21/39] Delete devcontainer.json --- .devcontainer/devcontainer.json | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index fd40e657..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,31 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/python-3-miniconda -{ - "name": "Python Environment", - "build": { - "dockerfile": "Dockerfile", - "context": ".." - }, - "customizations": { - "vscode": { - "extensions": [ - "editorconfig.editorconfig", - "github.vscode-pull-request-github", - "ms-azuretools.vscode-docker", - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.flake8", - "ms-vsliveshare.vsliveshare", - "ryanluker.vscode-coverage-gutters", - "bungcip.better-toml", - "GitHub.copilot" - ], - "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python", - "flake8.path": [ - "/usr/local/py-utils/bin/flake8" - ] - } - } - } -} \ No newline at end of file From e1b2f718fb95473c103f855d4f3be45eeb825f90 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:32:59 -0400 Subject: [PATCH 22/39] Delete settings.json --- .vscode/settings.json | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 375989b2..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "editor.formatOnSave": true, - "editor.formatOnPaste": true, - "files.trimTrailingWhitespace": true, - "files.autoSave": "onFocusChange", - "git.autofetch": true, - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.formatting.provider": "autopep8", - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "flake8.args": [ - "--config=.flake8" - ], - "flake8.path": [ - "/usr/local/py-utils/bin/flake8" - ] -} \ No newline at end of file From 68acc55e1e8a4580f4dd87212e8f1e9f4472b11d Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:36:03 -0400 Subject: [PATCH 23/39] Update missing-description-file.toml --- tests/samples/missing-description-file.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/samples/missing-description-file.toml b/tests/samples/missing-description-file.toml index 00fae72f..fe924d48 100644 --- a/tests/samples/missing-description-file.toml +++ b/tests/samples/missing-description-file.toml @@ -7,3 +7,4 @@ author = "Sir Robin" author-email = "robin@camelot.uk" home-page = "http://github.com/sirrobin/missingdescriptionfile" description-file = "definitely-missing.rst" +requires = ["requests"] From 8f1192f40d6483ea7363a2a62f3f30b654236641 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:36:12 -0400 Subject: [PATCH 24/39] Delete only-deps.toml --- tests/samples/only-deps.toml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tests/samples/only-deps.toml diff --git a/tests/samples/only-deps.toml b/tests/samples/only-deps.toml deleted file mode 100644 index f39955bf..00000000 --- a/tests/samples/only-deps.toml +++ /dev/null @@ -1,10 +0,0 @@ -[build-system] -requires = ["flit"] - -[tool.flit.metadata] -module = "nomodule" -author = "Sir Robin" -author-email = "robin@camelot.uk" -home-page = "http://github.com/sirrobin/module1" -description-file = "NO_README.rst" -requires = ["requests"] From d0828d763fafaed2fcb5b2f6b30fa45262763a18 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 17:37:48 -0400 Subject: [PATCH 25/39] Update test_install.py --- tests/test_install.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_install.py b/tests/test_install.py index 575758cd..4156e5dc 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -281,16 +281,16 @@ def test_install_requires(self): def test_install_only_deps(self): """Test if we can install using --only-deps with the pyproject.toml, and without the README or module folder""" - os.environ.setdefault("FLIT_ALLOW_INVALID", "1") + os.environ.setdefault('FLIT_ALLOW_INVALID', '1') ins = Installer.from_ini_path( - samples_dir / "only-deps.toml", user=False, python="mock_python" + samples_dir / 'missing-description-file.toml', user=False, python='mock_python' ) - with MockCommand("mock_python") as mockpy: + with MockCommand('mock_python') as mockpy: ins.install_requirements() calls = mockpy.get_calls() assert len(calls) == 1 - assert calls[0]["argv"][1:5] == ["-m", "pip", "install", "-r"] + assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r'] del os.environ['FLIT_ALLOW_INVALID'] def test_install_only_deps_fail(self): @@ -300,7 +300,7 @@ def test_install_only_deps_fail(self): ) with pytest.raises(ValueError, match=r"No file/folder found for module nomodule"): Installer.from_ini_path( - samples_dir / "missing-module.toml", user=False, python="mock_python" + samples_dir / 'missing-module.toml', user=False, python='mock_python' ) def test_install_reqs_my_python_if_needed_pep621(self): From 9f94a37637da8fad99bfb6460fe51d93862d7006 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Mon, 13 Mar 2023 00:05:57 +0000 Subject: [PATCH 26/39] simplify error catch --- flit_core/flit_core/config.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 1024a58b..195b6dcc 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -281,6 +281,8 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): with desc_path.open('r', encoding='utf-8') as f: raw_desc = f.read() except IOError as e: + if os.environ.get('FLIT_ALLOW_INVALID'): + return None, None if e.errno == errno.ENOENT: raise ConfigError( "Description file {} does not exist".format(desc_path) @@ -324,17 +326,11 @@ def _prep_metadata(md_sect, path): # Description file if "description-file" in md_sect: - try: - desc_path = md_sect.get("description-file") - res.referenced_files.append(desc_path) - desc_content, mimetype = description_from_file(desc_path, path.parent) - md_dict["description"] = desc_content - md_dict["description_content_type"] = mimetype - except ConfigError as ex: - if not os.environ.get('FLIT_ALLOW_INVALID'): - raise ConfigError('Description file .* does not exist') from ex - log.warning('Allowing invalid data (FLIT_ALLOW_INVALID set). Skipping missing description-file. Uploads may still fail.') - + desc_path = md_sect.get("description-file") + res.referenced_files.append(desc_path) + desc_content, mimetype = description_from_file(desc_path, path.parent) + md_dict["description"] = desc_content + md_dict["description_content_type"] = mimetype if 'urls' in md_sect: project_urls = md_dict['project_urls'] = [] From 5e5ed224794cc4ac05fe73a8cafe86a9dcbef1af Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Mon, 13 Mar 2023 00:06:49 +0000 Subject: [PATCH 27/39] fix quotes --- flit_core/flit_core/config.py | 158 +++++++++++++++++----------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 195b6dcc..0ef420a1 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -73,8 +73,8 @@ class ConfigError(ValueError): def read_flit_config(path): - """Read and check the `pyproject.toml` file with data about the package. - """ + '''Read and check the `pyproject.toml` file with data about the package. + ''' d = tomllib.loads(path.read_text('utf-8')) return prep_toml_config(d, path) @@ -85,23 +85,23 @@ def __str__(self): 'flit config, not both.') def prep_toml_config(d, path): - """Validate config loaded from pyproject.toml and prepare common metadata - + '''Validate config loaded from pyproject.toml and prepare common metadata + Returns a LoadedConfig object. - """ + ''' dtool = d.get('tool', {}).get('flit', {}) if 'project' in d: # Metadata in [project] table (PEP 621) if 'metadata' in dtool: raise ConfigError( - "Use [project] table for metadata or [tool.flit.metadata], not both." + 'Use [project] table for metadata or [tool.flit.metadata], not both.' ) if ('scripts' in dtool) or ('entrypoints' in dtool): raise ConfigError( - "Don't mix [project] metadata with [tool.flit.scripts] or " - "[tool.flit.entrypoints]. Use [project.scripts]," - "[project.gui-scripts] or [project.entry-points] as replacements." + 'Don't mix [project] metadata with [tool.flit.scripts] or ' + '[tool.flit.entrypoints]. Use [project.scripts],' + '[project.gui-scripts] or [project.entry-points] as replacements.' ) loaded_cfg = read_pep621_metadata(d['project'], path) @@ -112,8 +112,8 @@ def prep_toml_config(d, path): # Metadata in [tool.flit.metadata] (pre PEP 621 format) if 'module' in dtool: raise ConfigError( - "Use [tool.flit.module] table with new-style [project] metadata, " - "not [tool.flit.metadata]" + 'Use [tool.flit.module] table with new-style [project] metadata, ' + 'not [tool.flit.metadata]' ) loaded_cfg = _prep_metadata(dtool['metadata'], path) loaded_cfg.dynamic_metadata = ['version', 'description'] @@ -125,7 +125,7 @@ def prep_toml_config(d, path): loaded_cfg.add_scripts(dict(dtool['scripts'])) else: raise ConfigError( - "Neither [project] nor [tool.flit.metadata] found in pyproject.toml" + 'Neither [project] nor [tool.flit.metadata] found in pyproject.toml' ) unknown_sections = set(dtool) - { @@ -141,15 +141,15 @@ def prep_toml_config(d, path): unknown_keys = set(dtool['sdist']) - {'include', 'exclude'} if unknown_keys: raise ConfigError( - "Unknown keys in [tool.flit.sdist]:" + ", ".join(unknown_keys) + 'Unknown keys in [tool.flit.sdist]:' + ', '.join(unknown_keys) ) loaded_cfg.sdist_include_patterns = _check_glob_patterns( dtool['sdist'].get('include', []), 'include' ) exclude = [ - "**/__pycache__", - "**.pyc", + '**/__pycache__', + '**.pyc', ] + dtool['sdist'].get('exclude', []) loaded_cfg.sdist_exclude_patterns = _check_glob_patterns( exclude, 'exclude' @@ -157,29 +157,29 @@ def prep_toml_config(d, path): data_dir = dtool.get('external-data', {}).get('directory', None) if data_dir is not None: - toml_key = "tool.flit.external-data.directory" + toml_key = 'tool.flit.external-data.directory' if not isinstance(data_dir, str): - raise ConfigError(f"{toml_key} must be a string") + raise ConfigError(f'{toml_key} must be a string') normp = osp.normpath(data_dir) if osp.isabs(normp): - raise ConfigError(f"{toml_key} cannot be an absolute path") + raise ConfigError(f'{toml_key} cannot be an absolute path') if normp.startswith('..' + os.sep): raise ConfigError( - f"{toml_key} cannot point outside the directory containing pyproject.toml" + f'{toml_key} cannot point outside the directory containing pyproject.toml' ) if normp == '.': raise ConfigError( - f"{toml_key} cannot refer to the directory containing pyproject.toml" + f'{toml_key} cannot refer to the directory containing pyproject.toml' ) loaded_cfg.data_directory = path.parent / data_dir if not loaded_cfg.data_directory.is_dir(): - raise ConfigError(f"{toml_key} must refer to a directory") + raise ConfigError(f'{toml_key} must refer to a directory') return loaded_cfg def flatten_entrypoints(ep): - """Flatten nested entrypoints dicts. + '''Flatten nested entrypoints dicts. Entry points group names can include dots. But dots in TOML make nested dictionaries: @@ -188,12 +188,12 @@ def flatten_entrypoints(ep): The proper way to avoid this is: - [entrypoints."a.b"] # {'entrypoints': {'a.b': {}}} + [entrypoints.'a.b'] # {'entrypoints': {'a.b': {}}} But since there isn't a need for arbitrarily nested mappings in entrypoints, flit allows you to use the former. This flattens the nested dictionaries from loading pyproject.toml. - """ + ''' def _flatten(d, prefix): d1 = {} for k, v in d.items(): @@ -213,20 +213,20 @@ def _flatten(d, prefix): def _check_glob_patterns(pats, clude): - """Check and normalise glob patterns for sdist include/exclude""" + '''Check and normalise glob patterns for sdist include/exclude''' if not isinstance(pats, list): - raise ConfigError("sdist {} patterns must be a list".format(clude)) + raise ConfigError('sdist {} patterns must be a list'.format(clude)) # Windows filenames can't contain these (nor * or ?, but they are part of # glob patterns) - https://stackoverflow.com/a/31976060/434217 - bad_chars = re.compile(r'[\000-\037<>:"\\]') + bad_chars = re.compile(r'[\000-\037<>:'\\]') normed = [] for p in pats: if bad_chars.search(p): raise ConfigError( - '{} pattern {!r} contains bad characters (<>:\"\\ or control characters)' + '{} pattern {!r} contains bad characters (<>:\'\\ or control characters)' .format(clude, p) ) @@ -274,7 +274,7 @@ def add_scripts(self, scripts_dict): def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): if osp.isabs(rel_path): - raise ConfigError("Readme path must be relative") + raise ConfigError('Readme path must be relative') desc_path = proj_dir / rel_path try: @@ -285,7 +285,7 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): return None, None if e.errno == errno.ENOENT: raise ConfigError( - "Description file {} does not exist".format(desc_path) + 'Description file {} does not exist'.format(desc_path) ) raise @@ -294,9 +294,9 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): try: mimetype = readme_ext_to_content_type[ext] except KeyError: - log.warning("Unknown extension %r for description file.", ext) - log.warning(" Recognised extensions: %s", - " ".join(readme_ext_to_content_type)) + log.warning('Unknown extension %r for description file.', ext) + log.warning(' Recognised extensions: %s', + ' '.join(readme_ext_to_content_type)) mimetype = None else: mimetype = None @@ -305,37 +305,37 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): def _prep_metadata(md_sect, path): - """Process & verify the metadata from a config file - + '''Process & verify the metadata from a config file + - Pull out the module name we're packaging. - Read description-file and check that it's valid rst - Convert dashes in key names to underscores - (e.g. home-page in config -> home_page in metadata) - """ + (e.g. home-page in config -> home_page in metadata) + ''' if not set(md_sect).issuperset(metadata_required_fields): missing = metadata_required_fields - set(md_sect) - raise ConfigError("Required fields missing: " + '\n'.join(missing)) + raise ConfigError('Required fields missing: ' + '\n'.join(missing)) res = LoadedConfig() res.module = md_sect.get('module') - if not all([m.isidentifier() for m in res.module.split(".")]): - raise ConfigError("Module name %r is not a valid identifier" % res.module) + if not all([m.isidentifier() for m in res.module.split('.')]): + raise ConfigError('Module name %r is not a valid identifier' % res.module) md_dict = res.metadata # Description file - if "description-file" in md_sect: - desc_path = md_sect.get("description-file") + if 'description-file' in md_sect: + desc_path = md_sect.get('description-file') res.referenced_files.append(desc_path) desc_content, mimetype = description_from_file(desc_path, path.parent) - md_dict["description"] = desc_content - md_dict["description_content_type"] = mimetype + md_dict['description'] = desc_content + md_dict['description_content_type'] = mimetype if 'urls' in md_sect: project_urls = md_dict['project_urls'] = [] for label, url in sorted(md_sect.pop('urls').items()): - project_urls.append("{}, {}".format(label, url)) + project_urls.append('{}, {}'.format(label, url)) for key, value in md_sect.items(): if key in {'description-file', 'module'}: @@ -343,9 +343,9 @@ def _prep_metadata(md_sect, path): if key not in metadata_allowed_fields: closest = difflib.get_close_matches(key, metadata_allowed_fields, n=1, cutoff=0.7) - msg = "Unrecognised metadata key: {!r}".format(key) + msg = 'Unrecognised metadata key: {!r}'.format(key) if closest: - msg += " (did you mean {!r}?)".format(closest[0]) + msg += ' (did you mean {!r}?)'.format(closest[0]) raise ConfigError(msg) k2 = key.replace('-', '_') @@ -391,7 +391,7 @@ def _prep_metadata(md_sect, path): 'dev-requires occurs together with its replacement requires-extra.dev.') else: log.warning( - '"dev-requires = ..." is obsolete. Use "requires-extra = {"dev" = ...}" instead.') + ''dev-requires = ...' is obsolete. Use 'requires-extra = {'dev' = ...}' instead.') res.reqs_by_extra['dev'] = dev_requires # Add requires-extra requirements into requires_dist @@ -410,15 +410,15 @@ def _expand_requires_extra(re): for req in reqs: if ';' in req: name, envmark = req.split(';', 1) - yield '{} ; extra == "{}" and ({})'.format(name, extra, envmark) + yield '{} ; extra == '{}' and ({})'.format(name, extra, envmark) else: - yield '{} ; extra == "{}"'.format(req, extra) + yield '{} ; extra == '{}''.format(req, extra) def _check_type(d, field_name, cls): if not isinstance(d[field_name], cls): raise ConfigError( - "{} field should be {}, not {}".format(field_name, cls, type(d[field_name])) + '{} field should be {}, not {}'.format(field_name, cls, type(d[field_name])) ) def _check_list_of_str(d, field_name): @@ -426,7 +426,7 @@ def _check_list_of_str(d, field_name): isinstance(e, str) for e in d[field_name] ): raise ConfigError( - "{} field should be a list of strings".format(field_name) + '{} field should be a list of strings'.format(field_name) ) def read_pep621_metadata(proj, path) -> LoadedConfig: @@ -441,7 +441,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unexpected_keys = proj.keys() - pep621_allowed_fields if unexpected_keys: - log.warning("Unexpected names under [project]: %s", ', '.join(unexpected_keys)) + log.warning('Unexpected names under [project]: %s', ', '.join(unexpected_keys)) if 'version' in proj: _check_type(proj, 'version', str) @@ -459,24 +459,24 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unrec_keys = set(readme.keys()) - {'text', 'file', 'content-type'} if unrec_keys: raise ConfigError( - "Unrecognised keys in [project.readme]: {}".format(unrec_keys) + 'Unrecognised keys in [project.readme]: {}'.format(unrec_keys) ) if 'content-type' in readme: mimetype = readme['content-type'] mtype_base = mimetype.split(';')[0].strip() # e.g. text/x-rst if mtype_base not in readme_ext_to_content_type.values(): raise ConfigError( - "Unrecognised readme content-type: {!r}".format(mtype_base) + 'Unrecognised readme content-type: {!r}'.format(mtype_base) ) # TODO: validate content-type parameters (charset, md variant)? else: raise ConfigError( - "content-type field required in [project.readme] table" + 'content-type field required in [project.readme] table' ) if 'file' in readme: if 'text' in readme: raise ConfigError( - "[project.readme] should specify file or text, not both" + '[project.readme] should specify file or text, not both' ) lc.referenced_files.append(readme['file']) desc_content, _ = description_from_file( @@ -486,11 +486,11 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: desc_content = readme['text'] else: raise ConfigError( - "file or text field required in [project.readme] table" + 'file or text field required in [project.readme] table' ) else: raise ConfigError( - "project.readme should be a string or a table" + 'project.readme should be a string or a table' ) md_dict['description'] = desc_content @@ -505,7 +505,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unrec_keys = set(license_tbl.keys()) - {'text', 'file'} if unrec_keys: raise ConfigError( - "Unrecognised keys in [project.license]: {}".format(unrec_keys) + 'Unrecognised keys in [project.license]: {}'.format(unrec_keys) ) # TODO: Do something with license info. @@ -515,14 +515,14 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: if 'file' in license_tbl: if 'text' in license_tbl: raise ConfigError( - "[project.license] should specify file or text, not both" + '[project.license] should specify file or text, not both' ) lc.referenced_files.append(license_tbl['file']) elif 'text' in license_tbl: pass else: raise ConfigError( - "file or text field required in [project.license] table" + 'file or text field required in [project.license] table' ) if 'authors' in proj: @@ -535,7 +535,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: if 'keywords' in proj: _check_list_of_str(proj, 'keywords') - md_dict['keywords'] = ",".join(proj['keywords']) + md_dict['keywords'] = ','.join(proj['keywords']) if 'classifiers' in proj: _check_list_of_str(proj, 'classifiers') @@ -545,23 +545,23 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: _check_type(proj, 'urls', dict) project_urls = md_dict['project_urls'] = [] for label, url in sorted(proj['urls'].items()): - project_urls.append("{}, {}".format(label, url)) + project_urls.append('{}, {}'.format(label, url)) if 'entry-points' in proj: _check_type(proj, 'entry-points', dict) for grp in proj['entry-points'].values(): if not isinstance(grp, dict): raise ConfigError( - "projects.entry-points should only contain sub-tables" + 'projects.entry-points should only contain sub-tables' ) if not all(isinstance(k, str) for k in grp.values()): raise ConfigError( - "[projects.entry-points.*] tables should have string values" + '[projects.entry-points.*] tables should have string values' ) if set(proj['entry-points'].keys()) & {'console_scripts', 'gui_scripts'}: raise ConfigError( - "Scripts should be specified in [project.scripts] or " - "[project.gui-scripts], not under [project.entry-points]" + 'Scripts should be specified in [project.scripts] or ' + '[project.gui-scripts], not under [project.entry-points]' ) lc.entrypoints = proj['entry-points'] @@ -569,7 +569,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: _check_type(proj, 'scripts', dict) if not all(isinstance(k, str) for k in proj['scripts'].values()): raise ConfigError( - "[projects.scripts] table should have string values" + '[projects.scripts] table should have string values' ) lc.entrypoints['console_scripts'] = proj['scripts'] @@ -577,7 +577,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: _check_type(proj, 'gui-scripts', dict) if not all(isinstance(k, str) for k in proj['gui-scripts'].values()): raise ConfigError( - "[projects.gui-scripts] table should have string values" + '[projects.gui-scripts] table should have string values' ) lc.entrypoints['gui_scripts'] = proj['gui-scripts'] @@ -616,35 +616,35 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unrec_dynamic = dynamic - {'version', 'description'} if unrec_dynamic: raise ConfigError( - "flit only supports dynamic metadata for 'version' & 'description'" + 'flit only supports dynamic metadata for 'version' & 'description'' ) if dynamic.intersection(proj): raise ConfigError( - "keys listed in project.dynamic must not be in [project] table" + 'keys listed in project.dynamic must not be in [project] table' ) lc.dynamic_metadata = dynamic if ('version' not in proj) and ('version' not in lc.dynamic_metadata): raise ConfigError( - "version must be specified under [project] or listed as a dynamic field" + 'version must be specified under [project] or listed as a dynamic field' ) if ('description' not in proj) and ('description' not in lc.dynamic_metadata): raise ConfigError( - "description must be specified under [project] or listed as a dynamic field" + 'description must be specified under [project] or listed as a dynamic field' ) return lc def pep621_people(people, group_name='author') -> dict: - """Convert authors/maintainers from PEP 621 to core metadata fields""" + '''Convert authors/maintainers from PEP 621 to core metadata fields''' names, emails = [], [] for person in people: if not isinstance(person, dict): - raise ConfigError("{} info must be list of dicts".format(group_name)) + raise ConfigError('{} info must be list of dicts'.format(group_name)) unrec_keys = set(person.keys()) - {'name', 'email'} if unrec_keys: raise ConfigError( - "Unrecognised keys in {} info: {}".format(group_name, unrec_keys) + 'Unrecognised keys in {} info: {}'.format(group_name, unrec_keys) ) if 'email' in person: email = person['email'] @@ -656,7 +656,7 @@ def pep621_people(people, group_name='author') -> dict: res = {} if names: - res[group_name] = ", ".join(names) + res[group_name] = ', '.join(names) if emails: - res[group_name + '_email'] = ", ".join(emails) + res[group_name + '_email'] = ', '.join(emails) return res From c313a69128284c2261f746b5330ce08e486cf8d7 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Mon, 13 Mar 2023 00:07:55 +0000 Subject: [PATCH 28/39] fix quotes --- flit_core/flit_core/config.py | 144 +++++++++++++++++----------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 0ef420a1..ab98b199 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -73,8 +73,8 @@ class ConfigError(ValueError): def read_flit_config(path): - '''Read and check the `pyproject.toml` file with data about the package. - ''' + """Read and check the `pyproject.toml` file with data about the package. + """ d = tomllib.loads(path.read_text('utf-8')) return prep_toml_config(d, path) @@ -85,23 +85,23 @@ def __str__(self): 'flit config, not both.') def prep_toml_config(d, path): - '''Validate config loaded from pyproject.toml and prepare common metadata + """Validate config loaded from pyproject.toml and prepare common metadata Returns a LoadedConfig object. - ''' + """ dtool = d.get('tool', {}).get('flit', {}) if 'project' in d: # Metadata in [project] table (PEP 621) if 'metadata' in dtool: raise ConfigError( - 'Use [project] table for metadata or [tool.flit.metadata], not both.' + "Use [project] table for metadata or [tool.flit.metadata], not both." ) if ('scripts' in dtool) or ('entrypoints' in dtool): raise ConfigError( - 'Don't mix [project] metadata with [tool.flit.scripts] or ' - '[tool.flit.entrypoints]. Use [project.scripts],' - '[project.gui-scripts] or [project.entry-points] as replacements.' + "Don't mix [project] metadata with [tool.flit.scripts] or " + "[tool.flit.entrypoints]. Use [project.scripts]," + "[project.gui-scripts] or [project.entry-points] as replacements." ) loaded_cfg = read_pep621_metadata(d['project'], path) @@ -112,8 +112,8 @@ def prep_toml_config(d, path): # Metadata in [tool.flit.metadata] (pre PEP 621 format) if 'module' in dtool: raise ConfigError( - 'Use [tool.flit.module] table with new-style [project] metadata, ' - 'not [tool.flit.metadata]' + "Use [tool.flit.module] table with new-style [project] metadata, " + "not [tool.flit.metadata]" ) loaded_cfg = _prep_metadata(dtool['metadata'], path) loaded_cfg.dynamic_metadata = ['version', 'description'] @@ -125,7 +125,7 @@ def prep_toml_config(d, path): loaded_cfg.add_scripts(dict(dtool['scripts'])) else: raise ConfigError( - 'Neither [project] nor [tool.flit.metadata] found in pyproject.toml' + "Neither [project] nor [tool.flit.metadata] found in pyproject.toml" ) unknown_sections = set(dtool) - { @@ -141,15 +141,15 @@ def prep_toml_config(d, path): unknown_keys = set(dtool['sdist']) - {'include', 'exclude'} if unknown_keys: raise ConfigError( - 'Unknown keys in [tool.flit.sdist]:' + ', '.join(unknown_keys) + "Unknown keys in [tool.flit.sdist]:" + ", ".join(unknown_keys) ) loaded_cfg.sdist_include_patterns = _check_glob_patterns( dtool['sdist'].get('include', []), 'include' ) exclude = [ - '**/__pycache__', - '**.pyc', + "**/__pycache__", + "**.pyc", ] + dtool['sdist'].get('exclude', []) loaded_cfg.sdist_exclude_patterns = _check_glob_patterns( exclude, 'exclude' @@ -157,29 +157,29 @@ def prep_toml_config(d, path): data_dir = dtool.get('external-data', {}).get('directory', None) if data_dir is not None: - toml_key = 'tool.flit.external-data.directory' + toml_key = "tool.flit.external-data.directory" if not isinstance(data_dir, str): - raise ConfigError(f'{toml_key} must be a string') + raise ConfigError(f"{toml_key} must be a string") normp = osp.normpath(data_dir) if osp.isabs(normp): - raise ConfigError(f'{toml_key} cannot be an absolute path') + raise ConfigError(f"{toml_key} cannot be an absolute path") if normp.startswith('..' + os.sep): raise ConfigError( - f'{toml_key} cannot point outside the directory containing pyproject.toml' + f"{toml_key} cannot point outside the directory containing pyproject.toml" ) if normp == '.': raise ConfigError( - f'{toml_key} cannot refer to the directory containing pyproject.toml' + f"{toml_key} cannot refer to the directory containing pyproject.toml" ) loaded_cfg.data_directory = path.parent / data_dir if not loaded_cfg.data_directory.is_dir(): - raise ConfigError(f'{toml_key} must refer to a directory') + raise ConfigError(f"{toml_key} must refer to a directory") return loaded_cfg def flatten_entrypoints(ep): - '''Flatten nested entrypoints dicts. + """Flatten nested entrypoints dicts. Entry points group names can include dots. But dots in TOML make nested dictionaries: @@ -188,12 +188,12 @@ def flatten_entrypoints(ep): The proper way to avoid this is: - [entrypoints.'a.b'] # {'entrypoints': {'a.b': {}}} + [entrypoints."a.b"] # {'entrypoints': {'a.b': {}}} But since there isn't a need for arbitrarily nested mappings in entrypoints, flit allows you to use the former. This flattens the nested dictionaries from loading pyproject.toml. - ''' + """ def _flatten(d, prefix): d1 = {} for k, v in d.items(): @@ -213,20 +213,20 @@ def _flatten(d, prefix): def _check_glob_patterns(pats, clude): - '''Check and normalise glob patterns for sdist include/exclude''' + """Check and normalise glob patterns for sdist include/exclude""" if not isinstance(pats, list): - raise ConfigError('sdist {} patterns must be a list'.format(clude)) + raise ConfigError("sdist {} patterns must be a list".format(clude)) # Windows filenames can't contain these (nor * or ?, but they are part of # glob patterns) - https://stackoverflow.com/a/31976060/434217 - bad_chars = re.compile(r'[\000-\037<>:'\\]') + bad_chars = re.compile(r'[\000-\037<>:"\\]') normed = [] for p in pats: if bad_chars.search(p): raise ConfigError( - '{} pattern {!r} contains bad characters (<>:\'\\ or control characters)' + '{} pattern {!r} contains bad characters (<>:\"\\ or control characters)' .format(clude, p) ) @@ -274,7 +274,7 @@ def add_scripts(self, scripts_dict): def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): if osp.isabs(rel_path): - raise ConfigError('Readme path must be relative') + raise ConfigError("Readme path must be relative") desc_path = proj_dir / rel_path try: @@ -285,7 +285,7 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): return None, None if e.errno == errno.ENOENT: raise ConfigError( - 'Description file {} does not exist'.format(desc_path) + "Description file {} does not exist".format(desc_path) ) raise @@ -294,9 +294,9 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): try: mimetype = readme_ext_to_content_type[ext] except KeyError: - log.warning('Unknown extension %r for description file.', ext) - log.warning(' Recognised extensions: %s', - ' '.join(readme_ext_to_content_type)) + log.warning("Unknown extension %r for description file.", ext) + log.warning(" Recognised extensions: %s", + " ".join(readme_ext_to_content_type)) mimetype = None else: mimetype = None @@ -305,22 +305,22 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): def _prep_metadata(md_sect, path): - '''Process & verify the metadata from a config file + """Process & verify the metadata from a config file - Pull out the module name we're packaging. - Read description-file and check that it's valid rst - Convert dashes in key names to underscores (e.g. home-page in config -> home_page in metadata) - ''' + """ if not set(md_sect).issuperset(metadata_required_fields): missing = metadata_required_fields - set(md_sect) - raise ConfigError('Required fields missing: ' + '\n'.join(missing)) + raise ConfigError("Required fields missing: " + '\n'.join(missing)) res = LoadedConfig() res.module = md_sect.get('module') - if not all([m.isidentifier() for m in res.module.split('.')]): - raise ConfigError('Module name %r is not a valid identifier' % res.module) + if not all([m.isidentifier() for m in res.module.split(".")]): + raise ConfigError("Module name %r is not a valid identifier" % res.module) md_dict = res.metadata @@ -335,7 +335,7 @@ def _prep_metadata(md_sect, path): if 'urls' in md_sect: project_urls = md_dict['project_urls'] = [] for label, url in sorted(md_sect.pop('urls').items()): - project_urls.append('{}, {}'.format(label, url)) + project_urls.append("{}, {}".format(label, url)) for key, value in md_sect.items(): if key in {'description-file', 'module'}: @@ -343,9 +343,9 @@ def _prep_metadata(md_sect, path): if key not in metadata_allowed_fields: closest = difflib.get_close_matches(key, metadata_allowed_fields, n=1, cutoff=0.7) - msg = 'Unrecognised metadata key: {!r}'.format(key) + msg = "Unrecognised metadata key: {!r}".format(key) if closest: - msg += ' (did you mean {!r}?)'.format(closest[0]) + msg += " (did you mean {!r}?)".format(closest[0]) raise ConfigError(msg) k2 = key.replace('-', '_') @@ -391,7 +391,7 @@ def _prep_metadata(md_sect, path): 'dev-requires occurs together with its replacement requires-extra.dev.') else: log.warning( - ''dev-requires = ...' is obsolete. Use 'requires-extra = {'dev' = ...}' instead.') + '"dev-requires = ..." is obsolete. Use "requires-extra = {"dev" = ...}" instead.') res.reqs_by_extra['dev'] = dev_requires # Add requires-extra requirements into requires_dist @@ -410,15 +410,15 @@ def _expand_requires_extra(re): for req in reqs: if ';' in req: name, envmark = req.split(';', 1) - yield '{} ; extra == '{}' and ({})'.format(name, extra, envmark) + yield '{} ; extra == "{}" and ({})'.format(name, extra, envmark) else: - yield '{} ; extra == '{}''.format(req, extra) + yield '{} ; extra == "{}"'.format(req, extra) def _check_type(d, field_name, cls): if not isinstance(d[field_name], cls): raise ConfigError( - '{} field should be {}, not {}'.format(field_name, cls, type(d[field_name])) + "{} field should be {}, not {}".format(field_name, cls, type(d[field_name])) ) def _check_list_of_str(d, field_name): @@ -426,7 +426,7 @@ def _check_list_of_str(d, field_name): isinstance(e, str) for e in d[field_name] ): raise ConfigError( - '{} field should be a list of strings'.format(field_name) + "{} field should be a list of strings".format(field_name) ) def read_pep621_metadata(proj, path) -> LoadedConfig: @@ -441,7 +441,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unexpected_keys = proj.keys() - pep621_allowed_fields if unexpected_keys: - log.warning('Unexpected names under [project]: %s', ', '.join(unexpected_keys)) + log.warning("Unexpected names under [project]: %s", ', '.join(unexpected_keys)) if 'version' in proj: _check_type(proj, 'version', str) @@ -459,24 +459,24 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unrec_keys = set(readme.keys()) - {'text', 'file', 'content-type'} if unrec_keys: raise ConfigError( - 'Unrecognised keys in [project.readme]: {}'.format(unrec_keys) + "Unrecognised keys in [project.readme]: {}".format(unrec_keys) ) if 'content-type' in readme: mimetype = readme['content-type'] mtype_base = mimetype.split(';')[0].strip() # e.g. text/x-rst if mtype_base not in readme_ext_to_content_type.values(): raise ConfigError( - 'Unrecognised readme content-type: {!r}'.format(mtype_base) + "Unrecognised readme content-type: {!r}".format(mtype_base) ) # TODO: validate content-type parameters (charset, md variant)? else: raise ConfigError( - 'content-type field required in [project.readme] table' + "content-type field required in [project.readme] table" ) if 'file' in readme: if 'text' in readme: raise ConfigError( - '[project.readme] should specify file or text, not both' + "[project.readme] should specify file or text, not both" ) lc.referenced_files.append(readme['file']) desc_content, _ = description_from_file( @@ -486,11 +486,11 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: desc_content = readme['text'] else: raise ConfigError( - 'file or text field required in [project.readme] table' + "file or text field required in [project.readme] table" ) else: raise ConfigError( - 'project.readme should be a string or a table' + "project.readme should be a string or a table" ) md_dict['description'] = desc_content @@ -505,7 +505,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unrec_keys = set(license_tbl.keys()) - {'text', 'file'} if unrec_keys: raise ConfigError( - 'Unrecognised keys in [project.license]: {}'.format(unrec_keys) + "Unrecognised keys in [project.license]: {}".format(unrec_keys) ) # TODO: Do something with license info. @@ -515,14 +515,14 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: if 'file' in license_tbl: if 'text' in license_tbl: raise ConfigError( - '[project.license] should specify file or text, not both' + "[project.license] should specify file or text, not both" ) lc.referenced_files.append(license_tbl['file']) elif 'text' in license_tbl: pass else: raise ConfigError( - 'file or text field required in [project.license] table' + "file or text field required in [project.license] table" ) if 'authors' in proj: @@ -535,7 +535,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: if 'keywords' in proj: _check_list_of_str(proj, 'keywords') - md_dict['keywords'] = ','.join(proj['keywords']) + md_dict['keywords'] = ",".join(proj['keywords']) if 'classifiers' in proj: _check_list_of_str(proj, 'classifiers') @@ -545,23 +545,23 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: _check_type(proj, 'urls', dict) project_urls = md_dict['project_urls'] = [] for label, url in sorted(proj['urls'].items()): - project_urls.append('{}, {}'.format(label, url)) + project_urls.append("{}, {}".format(label, url)) if 'entry-points' in proj: _check_type(proj, 'entry-points', dict) for grp in proj['entry-points'].values(): if not isinstance(grp, dict): raise ConfigError( - 'projects.entry-points should only contain sub-tables' + "projects.entry-points should only contain sub-tables" ) if not all(isinstance(k, str) for k in grp.values()): raise ConfigError( - '[projects.entry-points.*] tables should have string values' + "[projects.entry-points.*] tables should have string values" ) if set(proj['entry-points'].keys()) & {'console_scripts', 'gui_scripts'}: raise ConfigError( - 'Scripts should be specified in [project.scripts] or ' - '[project.gui-scripts], not under [project.entry-points]' + "Scripts should be specified in [project.scripts] or " + "[project.gui-scripts], not under [project.entry-points]" ) lc.entrypoints = proj['entry-points'] @@ -569,7 +569,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: _check_type(proj, 'scripts', dict) if not all(isinstance(k, str) for k in proj['scripts'].values()): raise ConfigError( - '[projects.scripts] table should have string values' + "[projects.scripts] table should have string values" ) lc.entrypoints['console_scripts'] = proj['scripts'] @@ -577,7 +577,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: _check_type(proj, 'gui-scripts', dict) if not all(isinstance(k, str) for k in proj['gui-scripts'].values()): raise ConfigError( - '[projects.gui-scripts] table should have string values' + "[projects.gui-scripts] table should have string values" ) lc.entrypoints['gui_scripts'] = proj['gui-scripts'] @@ -616,35 +616,35 @@ def read_pep621_metadata(proj, path) -> LoadedConfig: unrec_dynamic = dynamic - {'version', 'description'} if unrec_dynamic: raise ConfigError( - 'flit only supports dynamic metadata for 'version' & 'description'' + "flit only supports dynamic metadata for 'version' & 'description'" ) if dynamic.intersection(proj): raise ConfigError( - 'keys listed in project.dynamic must not be in [project] table' + "keys listed in project.dynamic must not be in [project] table" ) lc.dynamic_metadata = dynamic if ('version' not in proj) and ('version' not in lc.dynamic_metadata): raise ConfigError( - 'version must be specified under [project] or listed as a dynamic field' + "version must be specified under [project] or listed as a dynamic field" ) if ('description' not in proj) and ('description' not in lc.dynamic_metadata): raise ConfigError( - 'description must be specified under [project] or listed as a dynamic field' + "description must be specified under [project] or listed as a dynamic field" ) return lc def pep621_people(people, group_name='author') -> dict: - '''Convert authors/maintainers from PEP 621 to core metadata fields''' + """Convert authors/maintainers from PEP 621 to core metadata fields""" names, emails = [], [] for person in people: if not isinstance(person, dict): - raise ConfigError('{} info must be list of dicts'.format(group_name)) + raise ConfigError("{} info must be list of dicts".format(group_name)) unrec_keys = set(person.keys()) - {'name', 'email'} if unrec_keys: raise ConfigError( - 'Unrecognised keys in {} info: {}'.format(group_name, unrec_keys) + "Unrecognised keys in {} info: {}".format(group_name, unrec_keys) ) if 'email' in person: email = person['email'] @@ -656,7 +656,7 @@ def pep621_people(people, group_name='author') -> dict: res = {} if names: - res[group_name] = ', '.join(names) + res[group_name] = ", ".join(names) if emails: - res[group_name + '_email'] = ', '.join(emails) + res[group_name + '_email'] = ", ".join(emails) return res From 7aaee9a4c8f7ee829705b3ca442cd3d0fc2031b5 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:09:02 -0400 Subject: [PATCH 29/39] Update flit_core/flit_core/config.py --- flit_core/flit_core/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index ab98b199..b9b47a5e 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -282,6 +282,7 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): raw_desc = f.read() except IOError as e: if os.environ.get('FLIT_ALLOW_INVALID'): + log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") return None, None if e.errno == errno.ENOENT: raise ConfigError( From 8240510492070f35af2356cb107f6870f54749f5 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:12:32 -0400 Subject: [PATCH 30/39] Update common.py --- flit_core/flit_core/common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 9a847a1a..3052cca3 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -56,9 +56,10 @@ def __init__(self, name, directory=Path()): .format(name, ", ".join([str(p) for p in sorted(existing)])) ) elif not existing: - if not os.environ.get("FLIT_ALLOW_INVALID"): + if os.environ.get("FLIT_ALLOW_INVALID"): + log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). No file/folder found for module.") + else: raise ValueError("No file/folder found for module {}".format(name)) - log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") else: self.source_dir = directory / self.prefix From 75491ea59dd30ef090f16fc4fc979f27b0d7f43d Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:13:22 -0400 Subject: [PATCH 31/39] Update common.py --- flit_core/flit_core/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 3052cca3..3b609bec 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -57,7 +57,7 @@ def __init__(self, name, directory=Path()): ) elif not existing: if os.environ.get("FLIT_ALLOW_INVALID"): - log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). No file/folder found for module.") + log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). No file/folder found for module {}".format(name)) else: raise ValueError("No file/folder found for module {}".format(name)) else: From 2d2d664b15ce612b9b3ef2a2a9dc0fc09d33608a Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:14:52 -0400 Subject: [PATCH 32/39] Update config.py --- flit_core/flit_core/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index b9b47a5e..5662ba2f 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -282,7 +282,9 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): raw_desc = f.read() except IOError as e: if os.environ.get('FLIT_ALLOW_INVALID'): - log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") + log.warning( + "Allowing invalid data (FLIT_ALLOW_INVALID set). Description file {} does not exist".format(desc_path) + ) return None, None if e.errno == errno.ENOENT: raise ConfigError( From aa85c27e21dbf7328a3b9c166abfd1ffdbf8ab41 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:16:33 -0400 Subject: [PATCH 33/39] Update config.py --- flit_core/flit_core/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 5662ba2f..2c7cd497 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -86,7 +86,7 @@ def __str__(self): def prep_toml_config(d, path): """Validate config loaded from pyproject.toml and prepare common metadata - + Returns a LoadedConfig object. """ dtool = d.get('tool', {}).get('flit', {}) @@ -309,11 +309,11 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): def _prep_metadata(md_sect, path): """Process & verify the metadata from a config file - + - Pull out the module name we're packaging. - Read description-file and check that it's valid rst - Convert dashes in key names to underscores - (e.g. home-page in config -> home_page in metadata) + (e.g. home-page in config -> home_page in metadata) """ if not set(md_sect).issuperset(metadata_required_fields): missing = metadata_required_fields - set(md_sect) @@ -332,7 +332,7 @@ def _prep_metadata(md_sect, path): desc_path = md_sect.get('description-file') res.referenced_files.append(desc_path) desc_content, mimetype = description_from_file(desc_path, path.parent) - md_dict['description'] = desc_content + md_dict['description'] = desc_content md_dict['description_content_type'] = mimetype if 'urls' in md_sect: From e232e551a7327cde303bd63d384819f2fde11c45 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:19:25 -0400 Subject: [PATCH 34/39] Update common.py --- flit_core/flit_core/common.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 3b609bec..2a99584c 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -57,9 +57,15 @@ def __init__(self, name, directory=Path()): ) elif not existing: if os.environ.get("FLIT_ALLOW_INVALID"): - log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). No file/folder found for module {}".format(name)) + log.warning( + "Allowing invalid data (FLIT_ALLOW_INVALID set). No file/folder found for module {}" + .format(name) + ) else: - raise ValueError("No file/folder found for module {}".format(name)) + raise ValueError( + "No file/folder found for module {}" + .format(name) + ) else: self.source_dir = directory / self.prefix From c3cb99ee383c360ebcf8b5ff2eb79fd883445504 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:20:08 -0400 Subject: [PATCH 35/39] Update config.py --- flit_core/flit_core/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flit_core/flit_core/config.py b/flit_core/flit_core/config.py index 2c7cd497..a15586a9 100644 --- a/flit_core/flit_core/config.py +++ b/flit_core/flit_core/config.py @@ -283,7 +283,8 @@ def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True): except IOError as e: if os.environ.get('FLIT_ALLOW_INVALID'): log.warning( - "Allowing invalid data (FLIT_ALLOW_INVALID set). Description file {} does not exist".format(desc_path) + "Allowing invalid data (FLIT_ALLOW_INVALID set). Description file {} does not exist" + .format(desc_path) ) return None, None if e.errno == errno.ENOENT: From a9537762d45bcfaa36fbae918db1b8fba6ccf964 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:21:07 -0400 Subject: [PATCH 36/39] Update common.py --- flit_core/flit_core/common.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/flit_core/flit_core/common.py b/flit_core/flit_core/common.py index 2a99584c..e97cdc77 100644 --- a/flit_core/flit_core/common.py +++ b/flit_core/flit_core/common.py @@ -62,10 +62,7 @@ def __init__(self, name, directory=Path()): .format(name) ) else: - raise ValueError( - "No file/folder found for module {}" - .format(name) - ) + raise ValueError("No file/folder found for module {}".format(name)) else: self.source_dir = directory / self.prefix From 8aeb8809ac05096261c49df92edd654bc8ea4162 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:21:46 -0400 Subject: [PATCH 37/39] Update missing-module.toml --- tests/samples/missing-module.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/samples/missing-module.toml b/tests/samples/missing-module.toml index 05e89575..96f601dd 100644 --- a/tests/samples/missing-module.toml +++ b/tests/samples/missing-module.toml @@ -2,7 +2,7 @@ requires = ["flit"] [tool.flit.metadata] -module = "nomodule" +module = "definitelymissingmodule" author = "Sir Robin" author-email = "robin@camelot.uk" home-page = "http://github.com/sirrobin/module1" From d55a4a6ff276950cf2fa5510ec20eb28966cfc29 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:28:57 -0400 Subject: [PATCH 38/39] Update test_install.py --- tests/test_install.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_install.py b/tests/test_install.py index 4156e5dc..a37dd1ba 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -282,16 +282,18 @@ def test_install_requires(self): def test_install_only_deps(self): """Test if we can install using --only-deps with the pyproject.toml, and without the README or module folder""" os.environ.setdefault('FLIT_ALLOW_INVALID', '1') - ins = Installer.from_ini_path( - samples_dir / 'missing-description-file.toml', user=False, python='mock_python' - ) + try: + ins = Installer.from_ini_path( + samples_dir / 'missing-description-file.toml', user=False, python='mock_python' + ) - with MockCommand('mock_python') as mockpy: - ins.install_requirements() - calls = mockpy.get_calls() - assert len(calls) == 1 - assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r'] - del os.environ['FLIT_ALLOW_INVALID'] + with MockCommand('mock_python') as mockpy: + ins.install_requirements() + calls = mockpy.get_calls() + assert len(calls) == 1 + assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r'] + finally: + del os.environ['FLIT_ALLOW_INVALID'] def test_install_only_deps_fail(self): with pytest.raises(ConfigError, match=r"Description file .* does not exist"): From dc80d698bda6ac2c06d1df3aec4e35462df4a887 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Sun, 12 Mar 2023 20:38:41 -0400 Subject: [PATCH 39/39] Update tests/test_install.py --- tests/test_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_install.py b/tests/test_install.py index a37dd1ba..65dd8415 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -300,7 +300,7 @@ def test_install_only_deps_fail(self): Installer.from_ini_path( samples_dir / 'missing-description-file.toml', user=False, python='mock_python' ) - with pytest.raises(ValueError, match=r"No file/folder found for module nomodule"): + with pytest.raises(ValueError, match=r"No file/folder found for module definitelymissingmodule"): Installer.from_ini_path( samples_dir / 'missing-module.toml', user=False, python='mock_python' )