hatchling-0.15.0/LICENSE.txt0000644000000000000000000000210013615410400013532 0ustar0000000000000000MIT License Copyright (c) 2021-present Ofek Lev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hatchling-0.15.0/README.md0000644000000000000000000000017613615410400013201 0ustar0000000000000000# Hatchling ----- This is the extensible, standards compliant build backend used by [Hatch](https://github.com/ofek/hatch). hatchling-0.15.0/pyproject.toml0000644000000000000000000000013213615410400014626 0ustar0000000000000000[build-system] requires = [] build-backend = 'hatchling.ouroboros' backend-path = ['src'] hatchling-0.15.0/scripts/update_licenses.py0000644000000000000000000000400013615410400017120 0ustar0000000000000000import json import pathlib import time from contextlib import closing from io import StringIO import httpx VERSION = '3.15' LICENSES_URL = f'https://raw.githubusercontent.com/spdx/license-list-data/v{VERSION}/json/licenses.json' EXCEPTIONS_URL = f'https://raw.githubusercontent.com/spdx/license-list-data/v{VERSION}/json/exceptions.json' def download_data(url): for _ in range(600): try: response = httpx.get(url) response.raise_for_status() except Exception: time.sleep(1) continue else: return json.loads(response.content.decode('utf-8')) else: raise Exception('Download failed') def main(): licenses = {} for license_data in download_data(LICENSES_URL)['licenses']: license_id = license_data['licenseId'] deprecated = license_data['isDeprecatedLicenseId'] licenses[license_id.lower()] = {'id': license_id, 'deprecated': deprecated} exceptions = {} for exception_data in download_data(EXCEPTIONS_URL)['exceptions']: exception_id = exception_data['licenseExceptionId'] deprecated = exception_data['isDeprecatedLicenseId'] exceptions[exception_id.lower()] = {'id': exception_id, 'deprecated': deprecated} project_root = pathlib.Path(__file__).resolve().parent.parent data_file = project_root / 'hatchling' / 'licenses' / 'supported.py' with closing(StringIO()) as file_contents: file_contents.write(f'VERSION = {VERSION!r}\n\nLICENSES = {{\n') for normalized_name, data in sorted(licenses.items()): file_contents.write(f' {normalized_name!r}: {data!r},\n') file_contents.write('}\n\nEXCEPTIONS = {\n') for normalized_name, data in sorted(exceptions.items()): file_contents.write(f' {normalized_name!r}: {data!r},\n') file_contents.write('}\n') with data_file.open('w', encoding='utf-8') as f: f.write(file_contents.getvalue()) if __name__ == '__main__': main() hatchling-0.15.0/src/hatchling/__about__.py0000644000000000000000000000002713615410400016725 0ustar0000000000000000__version__ = '0.15.0' hatchling-0.15.0/src/hatchling/__init__.py0000644000000000000000000000000013615410400016545 0ustar0000000000000000hatchling-0.15.0/src/hatchling/__main__.py0000644000000000000000000000014113615410400016534 0ustar0000000000000000import sys if __name__ == '__main__': from .cli import hatchling sys.exit(hatchling()) hatchling-0.15.0/src/hatchling/build.py0000644000000000000000000000337413615410400016126 0ustar0000000000000000import os def get_requires_for_build_sdist(config_settings=None): """ https://www.python.org/dev/peps/pep-0517/#get-requires-for-build-sdist """ from .builders.sdist import SdistBuilder builder = SdistBuilder(os.getcwd()) return builder.config.dependencies def build_sdist(sdist_directory, config_settings=None): """ https://www.python.org/dev/peps/pep-0517/#build-sdist """ from .builders.sdist import SdistBuilder builder = SdistBuilder(os.getcwd()) return os.path.basename(next(builder.build(sdist_directory, ['standard']))) def get_requires_for_build_wheel(config_settings=None): """ https://www.python.org/dev/peps/pep-0517/#get-requires-for-build-wheel """ from .builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd()) return builder.config.dependencies def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """ https://www.python.org/dev/peps/pep-0517/#build-wheel """ from .builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd()) return os.path.basename(next(builder.build(wheel_directory, ['standard']))) def get_requires_for_build_editable(config_settings=None): """ https://www.python.org/dev/peps/pep-0660/#get-requires-for-build-editable """ from .builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd()) return builder.config.dependencies def build_editable(wheel_directory, config_settings=None, metadata_directory=None): """ https://www.python.org/dev/peps/pep-0660/#build-editable """ from .builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd()) return os.path.basename(next(builder.build(wheel_directory, ['editable']))) hatchling-0.15.0/src/hatchling/ouroboros.py0000644000000000000000000000727213615410400017061 0ustar0000000000000000import os CONFIG = { 'project': { 'name': 'hatchling', 'description': 'Modern, extensible Python build backend', 'readme': 'README.md', 'authors': [{'name': 'Ofek Lev', 'email': 'oss@ofek.dev'}], 'urls': { 'Homepage': 'https://ofek.dev/hatch/latest/', 'Funding': 'https://github.com/sponsors/ofek', 'History': 'https://ofek.dev/hatch/dev/meta/history/', 'Tracker': 'https://github.com/ofek/hatch/issues', 'Source': 'https://github.com/ofek/hatch/tree/master/backend', }, 'license': 'MIT', 'keywords': ['build', 'hatch', 'packaging'], 'classifiers': [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Libraries :: Python Modules', ], 'dependencies': [ 'editables~=0.2; python_version > "3"', 'importlib-metadata; python_version < "3.8"', 'packaging~=21.3; python_version > "3"', 'packaging~=20.9; python_version < "3"', 'pathspec~=0.9', 'pluggy~=1.0.0; python_version > "3"', 'pluggy~=0.13; python_version < "3"', 'toml~=0.10.2; python_version < "3"', 'tomli~=2.0.0; python_version > "3"', ], 'scripts': {'hatchling': 'hatchling.cli:hatchling'}, 'dynamic': ['version'], }, 'tool': { 'hatch': { 'version': {'path': 'src/hatchling/__about__.py'}, }, }, } def build_sdist(sdist_directory, config_settings=None): """ https://www.python.org/dev/peps/pep-0517/#build-sdist """ from .builders.sdist import SdistBuilder builder = SdistBuilder(os.getcwd(), config=CONFIG) return os.path.basename(next(builder.build(sdist_directory, ['standard']))) def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """ https://www.python.org/dev/peps/pep-0517/#build-wheel """ from .builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd(), config=CONFIG) return os.path.basename(next(builder.build(wheel_directory, ['standard']))) def build_editable(wheel_directory, config_settings=None, metadata_directory=None): """ https://www.python.org/dev/peps/pep-0660/#build-editable """ from .builders.wheel import WheelBuilder builder = WheelBuilder(os.getcwd(), config=CONFIG) return os.path.basename(next(builder.build(wheel_directory, ['editable']))) def get_requires_for_build_sdist(config_settings=None): """ https://www.python.org/dev/peps/pep-0517/#get-requires-for-build-sdist """ return CONFIG['project']['dependencies'] def get_requires_for_build_wheel(config_settings=None): """ https://www.python.org/dev/peps/pep-0517/#get-requires-for-build-wheel """ return CONFIG['project']['dependencies'] def get_requires_for_build_editable(config_settings=None): """ https://www.python.org/dev/peps/pep-0660/#get-requires-for-build-editable """ return CONFIG['project']['dependencies'] hatchling-0.15.0/src/hatchling/bridge/__init__.py0000644000000000000000000000000013615410400020001 0ustar0000000000000000hatchling-0.15.0/src/hatchling/bridge/app.py0000644000000000000000000001041113615410400017031 0ustar0000000000000000import os import pickle import sys class InvokedApplication(object): def display_info(self, *args, **kwargs): send_app_command('display_info', *args, **kwargs) def display_waiting(self, *args, **kwargs): send_app_command('display_waiting', *args, **kwargs) def display_success(self, *args, **kwargs): send_app_command('display_success', *args, **kwargs) def display_warning(self, *args, **kwargs): send_app_command('display_warning', *args, **kwargs) def display_error(self, *args, **kwargs): send_app_command('display_error', *args, **kwargs) def display_debug(self, *args, **kwargs): send_app_command('display_debug', *args, **kwargs) def display_mini_header(self, *args, **kwargs): send_app_command('display_mini_header', *args, **kwargs) def abort(self, *args, **kwargs): send_app_command('abort', *args, **kwargs) sys.exit(kwargs.get('code', 1)) def get_safe_application(self): return SafeApplication(self) class Application(object): """ The way output is displayed can be [configured](../config/hatch.md#terminal) by users. """ def __init__(self): self.__verbosity = int(os.environ.get('HATCH_VERBOSE', '0')) - int(os.environ.get('HATCH_QUIET', '0')) def display_info(self, message='', **kwargs): """ Meant to be used for messages conveying basic information. """ if self.__verbosity >= 0: print(message) def display_waiting(self, message='', **kwargs): """ Meant to be used for messages shown before potentially time consuming operations. """ if self.__verbosity >= 0: print(message) def display_success(self, message='', **kwargs): """ Meant to be used for messages indicating some positive outcome. """ if self.__verbosity >= 0: print(message) def display_warning(self, message='', **kwargs): """ Meant to be used for messages conveying important information. """ if self.__verbosity >= -1: print(message) def display_error(self, message='', **kwargs): """ Meant to be used for messages indicating some unrecoverable error. """ if self.__verbosity >= -2: print(message) def display_debug(self, message='', level=1, **kwargs): """ Meant to be used for messages that are not useful for most user experiences. The `level` option must be between 1 and 3 (inclusive). """ if not 1 <= level <= 3: raise ValueError('Debug output can only have verbosity levels between 1 and 3 (inclusive)') elif self.__verbosity >= level: print(message) def display_mini_header(self, message='', **kwargs): if self.__verbosity >= 0: print('[{}]'.format(message)) def abort(self, message='', code=1, **kwargs): """ Terminate the program with the given return code. """ if message and self.__verbosity >= -2: print(message) sys.exit(code) def get_safe_application(self): return SafeApplication(self) class SafeApplication: def __init__(self, app): self.abort = app.abort self.display_info = app.display_info self.display_error = app.display_error self.display_success = app.display_success self.display_waiting = app.display_waiting self.display_warning = app.display_warning self.display_debug = app.display_debug self.display_mini_header = app.display_mini_header if sys.version_info[0] >= 3: def format_app_command(method, *args, **kwargs): # TODO: increase protocol version when Python 2 support is dropped procedure = pickle.dumps((method, args, kwargs), 2) return '__HATCH__:{}'.format(''.join('%02x' % i for i in procedure)) else: def format_app_command(method, *args, **kwargs): procedure = pickle.dumps((method, args, kwargs), 2) return '__HATCH__:{}'.format(''.join('%02x' % ord(c) for c in procedure)) def send_app_command(method, *args, **kwargs): _send_app_command(format_app_command(method, *args, **kwargs)) def _send_app_command(command): print(command) hatchling-0.15.0/src/hatchling/builders/__init__.py0000644000000000000000000000000013615410400020356 0ustar0000000000000000hatchling-0.15.0/src/hatchling/builders/config.py0000644000000000000000000005330113615410400020100 0ustar0000000000000000import os from collections import OrderedDict from contextlib import contextmanager from io import open import pathspec from ..utils.fs import locate_file from .constants import DEFAULT_BUILD_DIRECTORY, BuildEnvVars class BuilderConfig(object): def __init__(self, builder, root, plugin_name, build_config, target_config): self.__builder = builder self.__root = root self.__plugin_name = plugin_name self.__build_config = build_config self.__target_config = target_config self.__hook_config = None self.__versions = None self.__dependencies = None self.__packages = None self.__package_sources = None # Possible pathspec.PathSpec self.__include_spec = None self.__exclude_spec = None self.__artifact_spec = None self.build_artifact_spec = None # These are used to create the pathspecs and will never be `None` after the first match attempt self.__include_patterns = None self.__exclude_patterns = None self.__artifact_patterns = None # Common options self.__directory = None self.__ignore_vcs = None self.__only_packages = None self.__reproducible = None self.__dev_mode_dirs = None @property def builder(self): return self.__builder @property def root(self): return self.__root @property def plugin_name(self): return self.__plugin_name @property def build_config(self): return self.__build_config @property def target_config(self): return self.__target_config def include_path(self, relative_path, is_package=True): return ( self.path_is_build_artifact(relative_path) or self.path_is_artifact(relative_path) or ( not (self.only_packages and not is_package) and (self.path_is_included(relative_path) and not self.path_is_excluded(relative_path)) ) ) def path_is_included(self, relative_path): if self.include_spec is None: return True return self.include_spec.match_file(relative_path) def path_is_excluded(self, relative_path): if self.exclude_spec is None: return False return self.exclude_spec.match_file(relative_path) def path_is_artifact(self, relative_path): if self.artifact_spec is None: return False return self.artifact_spec.match_file(relative_path) def path_is_build_artifact(self, relative_path): if self.build_artifact_spec is None: return False return self.build_artifact_spec.match_file(relative_path) @property def include_spec(self): if self.__include_patterns is None: if 'include' in self.target_config: include_config = self.target_config include_location = 'tool.hatch.build.targets.{}.include'.format(self.plugin_name) else: include_config = self.build_config include_location = 'tool.hatch.build.include' all_include_patterns = [] include_patterns = include_config.get('include', self.default_include()) if not isinstance(include_patterns, list): raise TypeError('Field `{}` must be an array of strings'.format(include_location)) for i, include_pattern in enumerate(include_patterns, 1): if not isinstance(include_pattern, str): raise TypeError('Pattern #{} in field `{}` must be a string'.format(i, include_location)) elif not include_pattern: raise ValueError('Pattern #{} in field `{}` cannot be an empty string'.format(i, include_location)) all_include_patterns.append(include_pattern) for relative_path in self.packages: # Matching only at the root requires a forward slash, back slashes do not work. As such, # normalize to forward slashes for consistency. all_include_patterns.append('/{}/'.format(relative_path.replace(os.path.sep, '/'))) if all_include_patterns: self.__include_spec = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, all_include_patterns ) self.__include_patterns = all_include_patterns return self.__include_spec @property def exclude_spec(self): if self.__exclude_patterns is None: if 'exclude' in self.target_config: exclude_config = self.target_config exclude_location = 'tool.hatch.build.targets.{}.exclude'.format(self.plugin_name) else: exclude_config = self.build_config exclude_location = 'tool.hatch.build.exclude' all_exclude_patterns = self.default_global_exclude() exclude_patterns = exclude_config.get('exclude', self.default_exclude()) if not isinstance(exclude_patterns, list): raise TypeError('Field `{}` must be an array of strings'.format(exclude_location)) for i, exclude_pattern in enumerate(exclude_patterns, 1): if not isinstance(exclude_pattern, str): raise TypeError('Pattern #{} in field `{}` must be a string'.format(i, exclude_location)) elif not exclude_pattern: raise ValueError('Pattern #{} in field `{}` cannot be an empty string'.format(i, exclude_location)) all_exclude_patterns.append(exclude_pattern) if not self.ignore_vcs: all_exclude_patterns.extend(self.load_vcs_ignore_patterns()) if all_exclude_patterns: self.__exclude_spec = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, all_exclude_patterns ) self.__exclude_patterns = all_exclude_patterns return self.__exclude_spec @property def artifact_spec(self): if self.__artifact_patterns is None: if 'artifacts' in self.target_config: artifact_config = self.target_config artifact_location = 'tool.hatch.build.targets.{}.artifacts'.format(self.plugin_name) else: artifact_config = self.build_config artifact_location = 'tool.hatch.build.artifacts' all_artifact_patterns = [] artifact_patterns = artifact_config.get('artifacts', []) if not isinstance(artifact_patterns, list): raise TypeError('Field `{}` must be an array of strings'.format(artifact_location)) for i, artifact_pattern in enumerate(artifact_patterns, 1): if not isinstance(artifact_pattern, str): raise TypeError('Pattern #{} in field `{}` must be a string'.format(i, artifact_location)) elif not artifact_pattern: raise ValueError('Pattern #{} in field `{}` cannot be an empty string'.format(i, artifact_location)) all_artifact_patterns.append(artifact_pattern) if all_artifact_patterns: self.__artifact_spec = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, all_artifact_patterns ) self.__artifact_patterns = all_artifact_patterns return self.__artifact_spec @property def hook_config(self): if self.__hook_config is None: hook_config = OrderedDict() target_hook_config = self.target_config.get('hooks', {}) if not isinstance(target_hook_config, dict): raise TypeError('Field `tool.hatch.build.targets.{}.hooks` must be a table'.format(self.plugin_name)) for hook_name, config in target_hook_config.items(): if not isinstance(config, dict): raise TypeError( 'Field `tool.hatch.build.targets.{}.hooks.{}` must be a table'.format( self.plugin_name, hook_name ) ) hook_config[hook_name] = config global_hook_config = self.build_config.get('hooks', {}) if not isinstance(global_hook_config, dict): raise TypeError('Field `tool.hatch.build.hooks` must be a table') for hook_name, config in global_hook_config.items(): if not isinstance(config, dict): raise TypeError('Field `tool.hatch.build.hooks.{}` must be a table'.format(hook_name)) hook_config.setdefault(hook_name, config) final_hook_config = OrderedDict() if not env_var_enabled(BuildEnvVars.NO_HOOKS): all_hooks_enabled = env_var_enabled(BuildEnvVars.HOOKS_ENABLE) for hook_name, config in hook_config.items(): if ( all_hooks_enabled or config.get('enable-by-default', True) or env_var_enabled('{}{}'.format(BuildEnvVars.HOOK_ENABLE_PREFIX, hook_name.upper())) ): final_hook_config[hook_name] = config self.__hook_config = final_hook_config return self.__hook_config @property def directory(self): if self.__directory is None: if 'directory' in self.target_config: directory = self.target_config['directory'] if not isinstance(directory, str): raise TypeError( 'Field `tool.hatch.build.targets.{}.directory` must be a string'.format(self.plugin_name) ) else: directory = self.build_config.get('directory', DEFAULT_BUILD_DIRECTORY) if not isinstance(directory, str): raise TypeError('Field `tool.hatch.build.directory` must be a string') self.__directory = self.normalize_build_directory(directory) return self.__directory @property def ignore_vcs(self): if self.__ignore_vcs is None: if 'ignore-vcs' in self.target_config: ignore_vcs = self.target_config['ignore-vcs'] if not isinstance(ignore_vcs, bool): raise TypeError( 'Field `tool.hatch.build.targets.{}.ignore-vcs` must be a boolean'.format(self.plugin_name) ) else: ignore_vcs = self.build_config.get('ignore-vcs', False) if not isinstance(ignore_vcs, bool): raise TypeError('Field `tool.hatch.build.ignore-vcs` must be a boolean') self.__ignore_vcs = ignore_vcs return self.__ignore_vcs @property def only_packages(self): """ Whether or not the target should ignore non-artifact files that do not reside within a Python package. """ if self.__only_packages is None: if 'only-packages' in self.target_config: only_packages = self.target_config['only-packages'] if not isinstance(only_packages, bool): raise TypeError( 'Field `tool.hatch.build.targets.{}.only-packages` must be a boolean'.format(self.plugin_name) ) else: only_packages = self.build_config.get('only-packages', False) if not isinstance(only_packages, bool): raise TypeError('Field `tool.hatch.build.only-packages` must be a boolean') self.__only_packages = only_packages return self.__only_packages @property def reproducible(self): """ Whether or not the target should be built in a reproducible manner, defaulting to true. """ if self.__reproducible is None: if 'reproducible' in self.target_config: reproducible = self.target_config['reproducible'] if not isinstance(reproducible, bool): raise TypeError( 'Field `tool.hatch.build.targets.{}.reproducible` must be a boolean'.format(self.plugin_name) ) else: reproducible = self.build_config.get('reproducible', True) if not isinstance(reproducible, bool): raise TypeError('Field `tool.hatch.build.reproducible` must be a boolean') self.__reproducible = reproducible return self.__reproducible @property def dev_mode_dirs(self): """ Directories which must be added to Python's search path in [dev mode](../config/environment.md#dev-mode). """ if self.__dev_mode_dirs is None: if 'dev-mode-dirs' in self.target_config: dev_mode_dirs_config = self.target_config dev_mode_dirs_location = 'tool.hatch.build.targets.{}.dev-mode-dirs'.format(self.plugin_name) else: dev_mode_dirs_config = self.build_config dev_mode_dirs_location = 'tool.hatch.build.dev-mode-dirs' all_dev_mode_dirs = [] dev_mode_dirs = dev_mode_dirs_config.get('dev-mode-dirs', []) if not isinstance(dev_mode_dirs, list): raise TypeError('Field `{}` must be an array of strings'.format(dev_mode_dirs_location)) for i, dev_mode_dir in enumerate(dev_mode_dirs, 1): if not isinstance(dev_mode_dir, str): raise TypeError('Directory #{} in field `{}` must be a string'.format(i, dev_mode_dirs_location)) elif not dev_mode_dir: raise ValueError( 'Directory #{} in field `{}` cannot be an empty string'.format(i, dev_mode_dirs_location) ) all_dev_mode_dirs.append(dev_mode_dir) self.__dev_mode_dirs = all_dev_mode_dirs return self.__dev_mode_dirs @property def versions(self): if self.__versions is None: # Used as an ordered set all_versions = OrderedDict() versions = self.target_config.get('versions', []) if not isinstance(versions, list): raise TypeError( 'Field `tool.hatch.build.targets.{}.versions` must be an array of strings'.format(self.plugin_name) ) for i, version in enumerate(versions, 1): if not isinstance(version, str): raise TypeError( 'Version #{} in field `tool.hatch.build.targets.{}.versions` must be a string'.format( i, self.plugin_name ) ) elif not version: raise ValueError( 'Version #{} in field `tool.hatch.build.targets.{}.versions` cannot be an empty string'.format( i, self.plugin_name ) ) all_versions[version] = None if not all_versions: default_versions = self.__builder.get_default_versions() for version in default_versions: all_versions[version] = None else: unknown_versions = set(all_versions) - set(self.__builder.get_version_api()) if unknown_versions: raise ValueError( 'Unknown versions in field `tool.hatch.build.targets.{}.versions`: {}'.format( self.plugin_name, ', '.join(map(str, sorted(unknown_versions))) ) ) self.__versions = list(all_versions) return self.__versions @property def dependencies(self): if self.__dependencies is None: # Used as an ordered set dependencies = OrderedDict() target_dependencies = self.target_config.get('dependencies', []) if not isinstance(target_dependencies, list): raise TypeError( 'Field `tool.hatch.build.targets.{}.dependencies` must be an array'.format(self.plugin_name) ) for i, dependency in enumerate(target_dependencies, 1): if not isinstance(dependency, str): raise TypeError( 'Dependency #{} of field `tool.hatch.build.targets.{}.dependencies` must be a string'.format( i, self.plugin_name ) ) dependencies[dependency] = None global_dependencies = self.build_config.get('dependencies', []) if not isinstance(global_dependencies, list): raise TypeError('Field `tool.hatch.build.dependencies` must be an array') for i, dependency in enumerate(global_dependencies, 1): if not isinstance(dependency, str): raise TypeError( 'Dependency #{} of field `tool.hatch.build.dependencies` must be a string'.format(i) ) dependencies[dependency] = None for hook_name, config in self.hook_config.items(): hook_dependencies = config.get('dependencies', []) if not isinstance(hook_dependencies, list): raise TypeError('Option `dependencies` of build hook `{}` must be an array'.format(hook_name)) for i, dependency in enumerate(hook_dependencies, 1): if not isinstance(dependency, str): raise TypeError( 'Dependency #{} of option `dependencies` of build hook `{}` must be a string'.format( i, hook_name ) ) dependencies[dependency] = None self.__dependencies = list(dependencies) return self.__dependencies @property def packages(self): if self.__packages is None: if 'packages' in self.target_config: package_config = self.target_config package_location = 'tool.hatch.build.targets.{}.packages'.format(self.plugin_name) else: package_config = self.build_config package_location = 'tool.hatch.build.packages' all_packages = set() packages = package_config.get('packages', self.default_packages()) if not isinstance(packages, list): raise TypeError('Field `{}` must be an array of strings'.format(package_location)) for i, package in enumerate(packages, 1): if not isinstance(package, str): raise TypeError('Package #{} in field `{}` must be a string'.format(i, package_location)) elif not package: raise ValueError('Package #{} in field `{}` cannot be an empty string'.format(i, package_location)) all_packages.add(os.path.normpath(package).lstrip(os.path.sep)) unique_packages = {} packages = [] package_sources = [] for relative_path in sorted(all_packages): source, package = os.path.split(relative_path) if package in unique_packages: raise ValueError( 'Package `{}` of field `{}` is already defined by path `{}`'.format( package, package_location, unique_packages[package] ) ) unique_packages[package] = relative_path if source: package_sources.append(source + os.path.sep) packages.append(relative_path) self.__packages = packages self.__package_sources = package_sources return self.__packages @property def package_sources(self): if self.__package_sources is None: _ = self.packages return self.__package_sources def get_distribution_path(self, relative_path): # src/foo/bar.py -> foo/bar.py for package_source in self.package_sources: if relative_path.startswith(package_source): return relative_path[len(package_source) :] return relative_path def load_vcs_ignore_patterns(self): # https://git-scm.com/docs/gitignore#_pattern_format default_exclusion_file = locate_file(self.root, '.gitignore') if default_exclusion_file is None: return [] with open(default_exclusion_file, 'r', encoding='utf-8') as f: return f.readlines() def normalize_build_directory(self, build_directory): if not os.path.isabs(build_directory): build_directory = os.path.join(self.root, build_directory) return os.path.normpath(build_directory) def default_include(self): return [] def default_exclude(self): return [] def default_packages(self): return [] def default_global_exclude(self): return ['.git', '__pycache__', '*.py[cod]'] @contextmanager def set_build_data(self, build_data): try: # Include anything the hooks indicate build_artifacts = build_data['artifacts'] if build_artifacts: self.build_artifact_spec = pathspec.PathSpec.from_lines( pathspec.patterns.GitWildMatchPattern, build_artifacts ) yield finally: self.build_artifact_spec = None def env_var_enabled(env_var, default=False): if env_var in os.environ: return os.environ[env_var] in ('1', 'true') else: return default hatchling-0.15.0/src/hatchling/builders/constants.py0000644000000000000000000000054713615410400020653 0ustar0000000000000000DEFAULT_BUILD_DIRECTORY = 'dist' class BuildEnvVars: LOCATION = 'HATCH_BUILD_LOCATION' HOOKS_ONLY = 'HATCH_BUILD_HOOKS_ONLY' NO_HOOKS = 'HATCH_BUILD_NO_HOOKS' HOOKS_ENABLE = 'HATCH_BUILD_HOOKS_ENABLE' HOOK_ENABLE_PREFIX = 'HATCH_BUILD_HOOK_ENABLE_' CLEAN = 'HATCH_BUILD_CLEAN' CLEAN_HOOKS_AFTER = 'HATCH_BUILD_CLEAN_HOOKS_AFTER' hatchling-0.15.0/src/hatchling/builders/custom.py0000644000000000000000000000271013615410400020143 0ustar0000000000000000import os from ..metadata.core import ProjectMetadata from ..plugin.utils import load_plugin_from_script from .plugin.interface import BuilderInterface class CustomBuilder(object): PLUGIN_NAME = 'custom' def __new__(cls, root, plugin_manager=None, config=None, metadata=None, app=None): project_metadata = ProjectMetadata(root, plugin_manager, config) target_config = project_metadata.hatch.build_targets.get(cls.PLUGIN_NAME, {}) if not isinstance(target_config, dict): raise TypeError('Field `tool.hatch.build.targets.{}` must be a table'.format(cls.PLUGIN_NAME)) build_script = target_config.get('path', 'hatch_build.py') if not isinstance(build_script, str): raise TypeError('Option `path` for builder `{}` must be a string'.format(cls.PLUGIN_NAME)) elif not build_script: raise ValueError('Option `path` for builder `{}` must not be empty if defined'.format(cls.PLUGIN_NAME)) path = os.path.normpath(os.path.join(root, build_script)) if not os.path.isfile(path): raise OSError('Build script does not exist: {}'.format(build_script)) hook_class = load_plugin_from_script(path, build_script, BuilderInterface, 'builder') hook = hook_class(root, plugin_manager=plugin_manager, config=config, metadata=metadata, app=app) # Always keep the name to avoid confusion hook.PLUGIN_NAME = cls.PLUGIN_NAME return hook hatchling-0.15.0/src/hatchling/builders/sdist.py0000644000000000000000000002347613615410400017773 0ustar0000000000000000import gzip import os import tarfile import tempfile from contextlib import closing from copy import copy from io import BytesIO from time import time as get_current_timestamp from ..metadata.utils import DEFAULT_METADATA_VERSION, get_core_metadata_constructors from .plugin.interface import BuilderInterface from .utils import get_reproducible_timestamp, normalize_archive_path, normalize_file_permissions, replace_file class SdistArchive(object): def __init__(self, name, reproducible): """ https://www.python.org/dev/peps/pep-0517/#source-distributions """ self.name = name self.reproducible = reproducible if reproducible: self.timestamp = get_reproducible_timestamp() else: self.timestamp = None raw_fd, self.path = tempfile.mkstemp(suffix='.tar.gz') self.fd = os.fdopen(raw_fd, 'w+b') self.gz = gzip.GzipFile(fileobj=self.fd, mode='wb', mtime=self.timestamp) self.tf = tarfile.TarFile(fileobj=self.gz, mode='w', format=tarfile.PAX_FORMAT) self.gettarinfo = lambda *args, **kwargs: self.normalize_tar_metadata(self.tf.gettarinfo(*args, **kwargs)) def create_file(self, contents, *relative_paths): contents = contents.encode('utf-8') tar_info = tarfile.TarInfo(normalize_archive_path(os.path.join(self.name, *relative_paths))) tar_info.mtime = self.timestamp if self.reproducible else int(get_current_timestamp()) tar_info.size = len(contents) with closing(BytesIO(contents)) as buffer: self.tf.addfile(tar_info, buffer) def normalize_tar_metadata(self, tar_info): if not self.reproducible: return tar_info tar_info = copy(tar_info) tar_info.uid = 0 tar_info.gid = 0 tar_info.uname = '' tar_info.gname = '' tar_info.mode = normalize_file_permissions(tar_info.mode) tar_info.mtime = self.timestamp return tar_info def __getattr__(self, name): attr = getattr(self.tf, name) setattr(self, name, attr) return attr def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.tf.close() self.gz.close() self.fd.close() class SdistBuilder(BuilderInterface): """ Build an archive of the source files """ PLUGIN_NAME = 'sdist' def __init__(self, *args, **kwargs): super(SdistBuilder, self).__init__(*args, **kwargs) self.__core_metadata_constructor = None self.__support_legacy = None def get_version_api(self): return {'standard': self.build_standard} def get_default_versions(self): return ['standard'] def clean(self, directory, versions): for filename in os.listdir(directory): if filename.endswith('.tar.gz'): os.remove(os.path.join(directory, filename)) def build_standard(self, directory, **build_data): found_packages = set() with SdistArchive(self.project_id, self.config.reproducible) as archive: for included_file in self.recurse_project_files(): if self.support_legacy: possible_package, file_name = os.path.split(included_file.relative_path) if file_name == '__init__.py': found_packages.add(possible_package) tar_info = archive.gettarinfo( included_file.path, arcname=normalize_archive_path(os.path.join(self.project_id, included_file.distribution_path)), ) if tar_info.isfile(): with open(included_file.path, 'rb') as f: archive.addfile(tar_info, f) else: # no cov # TODO: Investigate if this is necessary (for symlinks, etc.) archive.addfile(tar_info) archive.create_file(self.core_metadata_constructor(self.metadata), 'PKG-INFO') if self.support_legacy: archive.create_file(self.construct_setup_py_file(sorted(found_packages)), 'setup.py') target = os.path.join(directory, '{}.tar.gz'.format(self.project_id)) replace_file(archive.path, target) return target def construct_setup_py_file(self, packages): contents = '# -*- coding: utf-8 -*-\nfrom setuptools import setup\n\n' contents += 'setup(\n' contents += ' name={!r},\n'.format(self.metadata.core.name) contents += ' version={!r},\n'.format(self.metadata.version) if self.metadata.core.description: contents += ' description={!r},\n'.format(self.metadata.core.description) if self.metadata.core.readme: contents += ' long_description={!r},\n'.format(self.metadata.core.readme) authors_data = self.metadata.core.authors_data if authors_data['name']: contents += ' author={!r},\n'.format(', '.join(authors_data['name'])) if authors_data['email']: contents += ' author_email={!r},\n'.format(', '.join(authors_data['email'])) maintainers_data = self.metadata.core.maintainers_data if maintainers_data['name']: contents += ' maintainer={!r},\n'.format(', '.join(maintainers_data['name'])) if maintainers_data['email']: contents += ' maintainer_email={!r},\n'.format(', '.join(maintainers_data['email'])) if self.metadata.core.classifiers: contents += ' classifiers=[\n' for classifier in self.metadata.core.classifiers: contents += ' {!r},\n'.format(classifier) contents += ' ],\n' if self.metadata.core.dependencies: contents += ' install_requires=[\n' for specifier in self.metadata.core.dependencies: contents += ' {!r},\n'.format(specifier) contents += ' ],\n' if self.metadata.core.optional_dependencies: contents += ' extras_require={\n' for option, specifiers in self.metadata.core.optional_dependencies.items(): if not specifiers: continue contents += ' {!r}: [\n'.format(option) for specifier in specifiers: contents += ' {!r},\n'.format(specifier) contents += ' ],\n' contents += ' },\n' if self.metadata.core.scripts or self.metadata.core.gui_scripts or self.metadata.core.entry_points: contents += ' entry_points={\n' if self.metadata.core.scripts: contents += " 'console_scripts': [\n" for name, object_ref in self.metadata.core.scripts.items(): contents += ' {!r},\n'.format('{} = {}'.format(name, object_ref)) contents += ' ],\n' if self.metadata.core.gui_scripts: contents += " 'gui_scripts': [\n" for name, object_ref in self.metadata.core.gui_scripts.items(): contents += ' {!r},\n'.format('{} = {}'.format(name, object_ref)) contents += ' ],\n' if self.metadata.core.entry_points: for group, entry_points in self.metadata.core.entry_points.items(): contents += ' {!r}: [\n'.format(group) for name, object_ref in entry_points.items(): contents += ' {!r},\n'.format('{} = {}'.format(name, object_ref)) contents += ' ],\n' contents += ' },\n' if packages: contents += ' packages=[\n' for package in packages: contents += ' {!r},\n'.format(package.replace(os.path.sep, '.')) contents += ' ],\n' contents += ')\n' return contents def get_default_build_data(self): build_data = {'artifacts': []} if not self.config.include_path('pyproject.toml'): build_data['artifacts'].append('/pyproject.toml') readme_path = self.metadata.core.readme_path if readme_path and not self.config.include_path(readme_path): build_data['artifacts'].append('/{}'.format(readme_path)) license_files = self.metadata.core.license_files if license_files: for license_file in license_files: if not self.config.include_path(license_file): build_data['artifacts'].append('/{}'.format(license_file)) return build_data @property def core_metadata_constructor(self): if self.__core_metadata_constructor is None: core_metadata_version = self.target_config.get('core-metadata-version', DEFAULT_METADATA_VERSION) if not isinstance(core_metadata_version, str): raise TypeError( 'Field `tool.hatch.build.targets.{}.core-metadata-version` must be a string'.format( self.PLUGIN_NAME ) ) constructors = get_core_metadata_constructors() if core_metadata_version not in constructors: raise ValueError( 'Unknown metadata version `{}` for field `tool.hatch.build.targets.{}.core-metadata-version`. ' 'Available: {}'.format(core_metadata_version, self.PLUGIN_NAME, ', '.join(sorted(constructors))) ) self.__core_metadata_constructor = constructors[core_metadata_version] return self.__core_metadata_constructor @property def support_legacy(self): if self.__support_legacy is None: self.__support_legacy = bool(self.target_config.get('support-legacy', False)) return self.__support_legacy hatchling-0.15.0/src/hatchling/builders/utils.py0000644000000000000000000000375213615410400020000 0ustar0000000000000000import os import shutil import sys from base64 import urlsafe_b64encode if sys.version_info[0] >= 3: def replace_file(src, dst): try: os.replace(src, dst) # Happens when on different filesystems like /tmp or caused by layering in containers except OSError: shutil.copy2(src, dst) os.remove(src) else: # no cov def replace_file(src, dst): shutil.copy2(src, dst) os.remove(src) def get_known_python_major_versions(): return map(str, sorted((2, 3))) def normalize_archive_path(path): if os.sep != '/': return path.replace(os.sep, '/') return path def format_file_hash(digest): # https://www.python.org/dev/peps/pep-0427/#signed-wheel-files return urlsafe_b64encode(digest).decode('ascii').rstrip('=') def get_reproducible_timestamp(): """ Returns an `int` derived from the `SOURCE_DATE_EPOCH` environment variable; see https://reproducible-builds.org/specs/source-date-epoch/. The default value will always be: `1580601600` """ return int(os.environ.get('SOURCE_DATE_EPOCH', '1580601600')) def normalize_file_permissions(st_mode): """ https://github.com/takluyver/flit/blob/6a2a8c6462e49f584941c667b70a6f48a7b3f9ab/flit_core/flit_core/common.py#L257 Normalize the permission bits in the st_mode field from stat to 644/755. Popular VCSs only track whether a file is executable or not. The exact permissions can vary on systems with different umasks. Normalizing to 644 (non executable) or 755 (executable) makes builds more reproducible. """ # Set 644 permissions, leaving higher bits of st_mode unchanged new_mode = (st_mode | 0o644) & ~0o133 if st_mode & 0o100: # no cov new_mode |= 0o111 # Executable: 644 -> 755 return new_mode def set_zip_info_mode(zip_info, mode=0o644): """ https://github.com/takluyver/flit/commit/3889583719888aef9f28baaa010e698cb7884904 """ zip_info.external_attr = mode << 16 hatchling-0.15.0/src/hatchling/builders/wheel.py0000644000000000000000000004466313615410400017752 0ustar0000000000000000import hashlib import os import stat import sys import tempfile import zipfile from contextlib import closing from ..__about__ import __version__ from ..metadata.utils import DEFAULT_METADATA_VERSION, get_core_metadata_constructors from .config import BuilderConfig from .plugin.interface import BuilderInterface from .utils import ( format_file_hash, get_known_python_major_versions, get_reproducible_timestamp, normalize_archive_path, normalize_file_permissions, replace_file, set_zip_info_mode, ) try: from io import StringIO except ImportError: # no cov from StringIO import StringIO try: from editables import EditableProject except ImportError: # no cov EditableProject = None EDITABLES_MINIMUM_VERSION = '0.2' class WheelArchive(object): def __init__(self, metadata_directory, reproducible): """ https://www.python.org/dev/peps/pep-0427/#abstract """ self.metadata_directory = metadata_directory self.reproducible = reproducible if self.reproducible: self.time_tuple = self.get_reproducible_time_tuple() else: self.time_tuple = None raw_fd, self.path = tempfile.mkstemp(suffix='.whl') self.fd = os.fdopen(raw_fd, 'w+b') self.zf = zipfile.ZipFile(self.fd, 'w', compression=zipfile.ZIP_DEFLATED) if sys.version_info >= (3, 6): @staticmethod def get_reproducible_time_tuple(): from datetime import datetime, timezone d = datetime.fromtimestamp(get_reproducible_timestamp(), timezone.utc) return d.year, d.month, d.day, d.hour, d.minute, d.second def add_file(self, included_file): relative_path = normalize_archive_path(included_file.distribution_path) file_stat = os.stat(included_file.path) if self.reproducible: zip_info = zipfile.ZipInfo(relative_path, self.time_tuple) # https://github.com/takluyver/flit/pull/66 new_mode = normalize_file_permissions(file_stat.st_mode) set_zip_info_mode(zip_info, new_mode & 0xFFFF) if stat.S_ISDIR(file_stat.st_mode): # no cov zip_info.external_attr |= 0x10 else: zip_info = zipfile.ZipInfo.from_file(included_file.path, relative_path) zip_info.compress_type = zipfile.ZIP_DEFLATED hash_obj = hashlib.sha256() with open(included_file.path, 'rb') as in_file, self.zf.open(zip_info, 'w') as out_file: while True: chunk = in_file.read(16384) if not chunk: break hash_obj.update(chunk) out_file.write(chunk) hash_digest = format_file_hash(hash_obj.digest()) return relative_path, hash_digest, file_stat.st_size else: # no cov @staticmethod def get_reproducible_time_tuple(): from datetime import datetime d = datetime.utcfromtimestamp(get_reproducible_timestamp()) return d.year, d.month, d.day, d.hour, d.minute, d.second def add_file(self, included_file): relative_path = normalize_archive_path(included_file.distribution_path) self.zf.write(included_file.path, arcname=relative_path) hash_obj = hashlib.sha256() with open(included_file.path, 'rb') as in_file: while True: chunk = in_file.read(16384) if not chunk: break hash_obj.update(chunk) hash_digest = format_file_hash(hash_obj.digest()) return relative_path, hash_digest, os.stat(included_file.path).st_size def write_metadata(self, relative_path, contents): relative_path = '{}/{}'.format(self.metadata_directory, normalize_archive_path(relative_path)) return self.write_file(relative_path, contents) def write_file(self, relative_path, contents): if not isinstance(contents, bytes): contents = contents.encode('utf-8') time_tuple = self.time_tuple or (2020, 2, 2, 0, 0, 0) zip_info = zipfile.ZipInfo(relative_path, time_tuple) set_zip_info_mode(zip_info) hash_obj = hashlib.sha256(contents) hash_digest = format_file_hash(hash_obj.digest()) self.zf.writestr(zip_info, contents, compress_type=zipfile.ZIP_DEFLATED) return relative_path, hash_digest, len(contents) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.zf.close() self.fd.close() class WheelBuilderConfig(BuilderConfig): def __init__(self, *args, **kwargs): super(WheelBuilderConfig, self).__init__(*args, **kwargs) self.__include_defined = bool( self.target_config.get('include', self.build_config.get('include')) or self.target_config.get('packages', self.build_config.get('packages')) ) self.__include = [] self.__exclude = [] self.__packages = [] def set_default_file_selection(self): if self.__include or self.__exclude or self.__packages: return project_name = self.builder.normalize_file_name_component(self.builder.metadata.core.name) if os.path.isfile(os.path.join(self.root, project_name, '__init__.py')): self.__include.append('/{}'.format(project_name)) elif os.path.isfile(os.path.join(self.root, 'src', project_name, '__init__.py')): self.__packages.append('src/{}'.format(project_name)) else: from glob import glob possible_namespace_packages = glob(os.path.join(self.root, '*', project_name, '__init__.py')) if possible_namespace_packages: relative_path = os.path.relpath(possible_namespace_packages[0], self.root) namespace = relative_path.split(os.path.sep)[0] self.__include.append('/{}'.format(namespace)) else: self.__include.append('*.py') self.__exclude.append('test*') def default_include(self): if not self.__include_defined: self.set_default_file_selection() return self.__include def default_exclude(self): if not self.__include_defined: self.set_default_file_selection() return self.__exclude def default_packages(self): if not self.__include_defined: self.set_default_file_selection() return self.__packages class WheelBuilder(BuilderInterface): """ Build a binary distribution (.whl file) """ PLUGIN_NAME = 'wheel' def __init__(self, *args, **kwargs): super(WheelBuilder, self).__init__(*args, **kwargs) self.__core_metadata_constructor = None def get_version_api(self): return {'standard': self.build_standard, 'editable': self.build_editable} def get_default_versions(self): return ['standard'] def clean(self, directory, versions): for filename in os.listdir(directory): if filename.endswith('.whl'): os.remove(os.path.join(directory, filename)) def build_standard(self, directory, **build_data): if 'tag' not in build_data: if build_data['infer_tag']: from packaging.tags import sys_tags best_matching_tag = next(sys_tags()) tag_parts = (best_matching_tag.interpreter, best_matching_tag.abi, best_matching_tag.platform) build_data['tag'] = '-'.join(tag_parts) else: build_data['tag'] = self.get_default_tag() metadata_directory = '{}.dist-info'.format(self.project_id) with WheelArchive(metadata_directory, self.config.reproducible) as archive, closing(StringIO()) as records: for included_file in self.recurse_project_files(): record = archive.add_file(included_file) records.write(self.format_record(record)) self.write_metadata(archive, records, build_data) records.write(u'{}/RECORD,,\n'.format(metadata_directory)) archive.write_metadata('RECORD', records.getvalue()) target = os.path.join(directory, '{}-{}.whl'.format(self.project_id, build_data['tag'])) replace_file(archive.path, target) return target def build_editable(self, directory, **build_data): if sys.version_info[0] < 3 or self.config.dev_mode_dirs: return self.build_editable_pth(directory, **build_data) else: return self.build_editable_standard(directory, **build_data) def build_editable_standard(self, directory, **build_data): build_data['tag'] = self.get_default_tag() metadata_directory = '{}.dist-info'.format(self.project_id) with WheelArchive(metadata_directory, self.config.reproducible) as archive, closing(StringIO()) as records: exposed_packages = {} for included_file in self.recurse_project_files(): if not included_file.path.endswith('.py'): continue relative_path = included_file.relative_path distribution_path = included_file.distribution_path path_parts = relative_path.split(os.sep) # Root file if len(path_parts) == 1: # no cov exposed_packages[os.path.splitext(relative_path)[0]] = os.path.join(self.root, relative_path) continue # Root package root_module = path_parts[0] if distribution_path == relative_path: exposed_packages[root_module] = os.path.join(self.root, root_module) else: distribution_module = distribution_path.split(os.sep)[0] exposed_packages[distribution_module] = os.path.join( self.root, '{}{}'.format(relative_path[: relative_path.index(distribution_path)], distribution_module), ) editable_project = EditableProject(self.metadata.core.name.replace('-', '_'), self.root) for module, relative_path in exposed_packages.items(): editable_project.map(module, relative_path) for filename, content in sorted(editable_project.files()): record = archive.write_file(filename, content) records.write(self.format_record(record)) extra_dependencies = [] for dependency in editable_project.dependencies(): if dependency == 'editables': dependency += '~={}'.format(EDITABLES_MINIMUM_VERSION) else: # no cov pass extra_dependencies.append(dependency) self.write_metadata(archive, records, build_data, extra_dependencies=extra_dependencies) records.write(u'{}/RECORD,,\n'.format(metadata_directory)) archive.write_metadata('RECORD', records.getvalue()) target = os.path.join(directory, '{}-{}.whl'.format(self.project_id, build_data['tag'])) replace_file(archive.path, target) return target if sys.version_info[0] >= 3: def build_editable_pth(self, directory, **build_data): build_data['tag'] = self.get_default_tag() metadata_directory = '{}.dist-info'.format(self.project_id) with WheelArchive(metadata_directory, self.config.reproducible) as archive, closing(StringIO()) as records: editable_project = EditableProject(self.metadata.core.name.replace('-', '_'), self.root) for relative_directory in self.config.dev_mode_dirs: editable_project.add_to_path(relative_directory) for filename, content in sorted(editable_project.files()): record = archive.write_file(filename, content) records.write(self.format_record(record)) extra_dependencies = [] for dependency in editable_project.dependencies(): if dependency == 'editables': dependency += '~={}'.format(EDITABLES_MINIMUM_VERSION) else: # no cov pass extra_dependencies.append(dependency) self.write_metadata(archive, records, build_data, extra_dependencies=extra_dependencies) records.write(u'{}/RECORD,,\n'.format(metadata_directory)) archive.write_metadata('RECORD', records.getvalue()) target = os.path.join(directory, '{}-{}.whl'.format(self.project_id, build_data['tag'])) replace_file(archive.path, target) return target else: # no cov def build_editable_pth(self, directory, **build_data): build_data['tag'] = self.get_default_tag() metadata_directory = '{}.dist-info'.format(self.project_id) with WheelArchive(metadata_directory, self.config.reproducible) as archive, closing(StringIO()) as records: directories = [] for relative_directory in self.config.dev_mode_dirs: directories.append(os.path.normpath(os.path.join(self.root, relative_directory))) record = archive.write_file( '{}.pth'.format(self.metadata.core.name.replace('-', '_')), '\n'.join(directories) ) records.write(self.format_record(record)) self.write_metadata(archive, records, build_data) records.write(u'{}/RECORD,,\n'.format(metadata_directory)) archive.write_metadata('RECORD', records.getvalue()) target = os.path.join(directory, '{}-{}.whl'.format(self.project_id, build_data['tag'])) replace_file(archive.path, target) return target def write_metadata(self, archive, records, build_data, extra_dependencies=()): # <<< IMPORTANT >>> # Ensure calls are ordered by the number of path components followed by the name of the components # METADATA self.write_project_metadata(archive, records, extra_dependencies=extra_dependencies) # WHEEL self.write_archive_metadata(archive, records, build_data) # entry_points.txt self.write_entry_points_file(archive, records) # license_files/ self.add_licenses(archive, records) def write_archive_metadata(self, archive, records, build_data): from packaging.tags import parse_tag metadata = 'Wheel-Version: 1.0\nGenerator: hatch {}\nRoot-Is-Purelib: {}\n'.format( __version__, 'true' if build_data['pure_python'] else 'false' ) for tag in sorted(map(str, parse_tag(build_data['tag']))): metadata += 'Tag: {}\n'.format(tag) record = archive.write_metadata('WHEEL', metadata) records.write(self.format_record(record)) def write_entry_points_file(self, archive, records): record = archive.write_metadata('entry_points.txt', self.construct_entry_points_file()) records.write(self.format_record(record)) def write_project_metadata(self, archive, records, extra_dependencies=()): record = archive.write_metadata( 'METADATA', self.core_metadata_constructor(self.metadata, extra_dependencies=extra_dependencies) ) records.write(self.format_record(record)) def add_licenses(self, archive, records): for relative_path in self.metadata.core.license_files: license_file = os.path.normpath(os.path.join(self.root, relative_path)) with open(license_file, 'rb') as f: record = archive.write_metadata('license_files/{}'.format(relative_path), f.read()) records.write(self.format_record(record)) def construct_entry_points_file(self): core_metadata = self.metadata.core metadata_file = '' if core_metadata.scripts: metadata_file += '\n[console_scripts]\n' for name, object_ref in core_metadata.scripts.items(): metadata_file += '{} = {}\n'.format(name, object_ref) if core_metadata.gui_scripts: metadata_file += '\n[gui_scripts]\n' for name, object_ref in core_metadata.gui_scripts.items(): metadata_file += '{} = {}\n'.format(name, object_ref) if core_metadata.entry_points: for group, entry_points in core_metadata.entry_points.items(): metadata_file += '\n[{}]\n'.format(group) for name, object_ref in entry_points.items(): metadata_file += '{} = {}\n'.format(name, object_ref) return metadata_file.lstrip() def get_default_tag(self): supported_python_versions = [] for major_version in get_known_python_major_versions(): for minor_version in range(100): if self.metadata.core.python_constraint.contains('{}.{}'.format(major_version, minor_version)): supported_python_versions.append('py{}'.format(major_version)) break return '{}-none-any'.format('.'.join(supported_python_versions)) def get_default_build_data(self): return {'infer_tag': False, 'pure_python': True} @property def core_metadata_constructor(self): if self.__core_metadata_constructor is None: core_metadata_version = self.target_config.get('core-metadata-version', DEFAULT_METADATA_VERSION) if not isinstance(core_metadata_version, str): raise TypeError( 'Field `tool.hatch.build.targets.{}.core-metadata-version` must be a string'.format( self.PLUGIN_NAME ) ) constructors = get_core_metadata_constructors() if core_metadata_version not in constructors: raise ValueError( 'Unknown metadata version `{}` for field `tool.hatch.build.targets.{}.core-metadata-version`. ' 'Available: {}'.format(core_metadata_version, self.PLUGIN_NAME, ', '.join(sorted(constructors))) ) self.__core_metadata_constructor = constructors[core_metadata_version] return self.__core_metadata_constructor @classmethod def get_config_class(cls): return WheelBuilderConfig @staticmethod def format_record(record): return u'{},sha256={},{}\n'.format(*record) hatchling-0.15.0/src/hatchling/builders/hooks/__init__.py0000644000000000000000000000000013615410400021501 0ustar0000000000000000hatchling-0.15.0/src/hatchling/builders/hooks/custom.py0000644000000000000000000000202613615410400021266 0ustar0000000000000000import os from ...plugin.utils import load_plugin_from_script from .plugin.interface import BuildHookInterface class CustomBuildHook(object): PLUGIN_NAME = 'custom' def __new__(cls, root, config, *args, **kwargs): build_script = config.get('path', 'hatch_build.py') if not isinstance(build_script, str): raise TypeError('Option `path` for build hook `{}` must be a string'.format(cls.PLUGIN_NAME)) elif not build_script: raise ValueError('Option `path` for build hook `{}` must not be empty if defined'.format(cls.PLUGIN_NAME)) path = os.path.normpath(os.path.join(root, build_script)) if not os.path.isfile(path): raise OSError('Build script does not exist: {}'.format(build_script)) hook_class = load_plugin_from_script(path, build_script, BuildHookInterface, 'build_hook') hook = hook_class(root, config, *args, **kwargs) # Always keep the name to avoid confusion hook.PLUGIN_NAME = cls.PLUGIN_NAME return hook hatchling-0.15.0/src/hatchling/builders/hooks/plugin/__init__.py0000644000000000000000000000000013615410400022777 0ustar0000000000000000hatchling-0.15.0/src/hatchling/builders/hooks/plugin/hooks.py0000644000000000000000000000021513615410400022373 0ustar0000000000000000from ....plugin import hookimpl from ..custom import CustomBuildHook @hookimpl def hatch_register_build_hook(): return CustomBuildHook hatchling-0.15.0/src/hatchling/builders/hooks/plugin/interface.py0000644000000000000000000000646713615410400023227 0ustar0000000000000000class BuildHookInterface(object): # no cov """ Example usage: === ":octicons-file-code-16: plugin.py" ```python from hatchling.builders.hooks.plugin.interface import BuildHookInterface class SpecialBuildHook(BuildHookInterface): PLUGIN_NAME = 'special' ... ``` === ":octicons-file-code-16: hooks.py" ```python from hatchling.plugin import hookimpl from .plugin import SpecialBuildHook @hookimpl def hatch_register_build_hook(): return SpecialBuildHook ``` """ PLUGIN_NAME = '' """The name used for selection.""" def __init__(self, root, config, build_config, metadata, directory, target_name, app=None): self.__root = root self.__config = config self.__build_config = build_config self.__metadata = metadata self.__directory = directory self.__target_name = target_name self.__app = app @property def app(self): """ An instance of [Application](utilities.md#hatchling.bridge.app.Application). """ if self.__app is None: from ....bridge.app import Application self.__app = Application().get_safe_application() return self.__app @property def root(self): """ The root of the project tree. """ return self.__root @property def config(self): """ The cumulative hook configuration. === ":octicons-file-code-16: pyproject.toml" ```toml [tool.hatch.build.hooks.] [tool.hatch.build.targets..hooks.] ``` === ":octicons-file-code-16: hatch.toml" ```toml [build.hooks.] [build.targets..hooks.] ``` """ return self.__config @property def metadata(self): # Undocumented for now return self.__metadata @property def build_config(self): """ An instance of [BuilderConfig](utilities.md#hatchling.builders.config.BuilderConfig). """ return self.__build_config @property def directory(self): """ The build directory. """ return self.__directory @property def target_name(self): """ The plugin name of the build target. """ return self.__target_name def clean(self, versions): """ This occurs before the build process if the `-c`/`--clean` flag was passed to the [`build`](../cli/reference.md#hatch-build) command, or when invoking the [`clean`](../cli/reference.md#hatch-clean) command. """ def initialize(self, version, build_data): """ This occurs immediately before each build. Any modifications to the build data will be seen by the build target. """ def finalize(self, version, build_data, artifact_path): """ This occurs immediately after each build and will not run if the `--hooks-only` flag was passed to the [`build`](../cli/reference.md#hatch-build) command. The build data will reflect any modifications done by the target during the build. """ hatchling-0.15.0/src/hatchling/builders/plugin/__init__.py0000644000000000000000000000000013615410400021654 0ustar0000000000000000hatchling-0.15.0/src/hatchling/builders/plugin/hooks.py0000644000000000000000000000034513615410400021254 0ustar0000000000000000from ...plugin import hookimpl from ..custom import CustomBuilder from ..sdist import SdistBuilder from ..wheel import WheelBuilder @hookimpl def hatch_register_builder(): return [CustomBuilder, SdistBuilder, WheelBuilder] hatchling-0.15.0/src/hatchling/builders/plugin/interface.py0000644000000000000000000002621613615410400022076 0ustar0000000000000000import os import re from collections import OrderedDict from ..config import BuilderConfig, env_var_enabled from ..constants import BuildEnvVars class IncludedFile(object): __slots__ = ('path', 'relative_path', 'distribution_path') def __init__(self, path, relative_path, distribution_path): self.path = path self.relative_path = relative_path self.distribution_path = distribution_path class BuilderInterface(object): """ Example usage: === ":octicons-file-code-16: plugin.py" ```python from hatchling.builders.plugin.interface import BuilderInterface class SpecialBuilder(BuilderInterface): PLUGIN_NAME = 'special' ... ``` === ":octicons-file-code-16: hooks.py" ```python from hatchling.plugin import hookimpl from .plugin import SpecialBuilder @hookimpl def hatch_register_builder(): return SpecialBuilder ``` """ PLUGIN_NAME = '' """The name used for selection.""" def __init__(self, root, plugin_manager=None, config=None, metadata=None, app=None): self.__root = root self.__plugin_manager = plugin_manager self.__raw_config = config self.__metadata = metadata self.__app = app self.__config = None self.__project_config = None self.__hatch_config = None self.__build_config = None self.__build_targets = None self.__target_config = None # Metadata self.__project_id = None def build( self, directory=None, versions=None, hooks_only=None, clean=None, clean_hooks_after=None, clean_only=False, ): # Fail early for invalid project metadata self.metadata.core.validate_fields() if directory is None: if BuildEnvVars.LOCATION in os.environ: directory = self.config.normalize_build_directory(os.environ[BuildEnvVars.LOCATION]) else: directory = self.config.directory if not os.path.isdir(directory): os.makedirs(directory) version_api = self.get_version_api() if not versions: versions = self.config.versions else: unknown_versions = set(versions) - set(version_api) if unknown_versions: raise ValueError( 'Unknown versions for target `{}`: {}'.format( self.PLUGIN_NAME, ', '.join(map(str, sorted(unknown_versions))) ) ) if hooks_only is None: hooks_only = env_var_enabled(BuildEnvVars.HOOKS_ONLY) configured_build_hooks = self.get_build_hooks(directory) build_hooks = list(configured_build_hooks.values()) if clean_only: clean = True elif clean is None: clean = env_var_enabled(BuildEnvVars.CLEAN) if clean: if not hooks_only: self.clean(directory, versions) for build_hook in build_hooks: build_hook.clean(versions) if clean_only: return if clean_hooks_after is None: clean_hooks_after = env_var_enabled(BuildEnvVars.CLEAN_HOOKS_AFTER) for version in versions: self.app.display_debug('Building `{}` version `{}`'.format(self.PLUGIN_NAME, version)) build_data = self.get_default_build_data() # Make sure reserved fields are set build_data.setdefault('artifacts', []) # Pass all the configured build hooks for future unforeseen scenarios needing ultimate control build_data['configured_build_hooks'] = configured_build_hooks # Execute all `initialize` build hooks for build_hook in build_hooks: build_hook.initialize(version, build_data) if hooks_only: self.app.display_debug('Only ran build hooks for `{}` version `{}`'.format(self.PLUGIN_NAME, version)) continue # Build the artifact with self.config.set_build_data(build_data): artifact = version_api[version](directory, **build_data) # Execute all `finalize` build hooks for build_hook in build_hooks: build_hook.finalize(version, build_data, artifact) if clean_hooks_after: for build_hook in build_hooks: build_hook.clean([version]) yield artifact def recurse_project_files(self): """ Returns a consistently generated series of file objects for every file that should be distributed. Each file object has three `str` attributes: - `path` - the absolute path - `relative_path` - the path relative to the project root - `distribution_path` - the path to be distributed as """ for root, dirs, files in os.walk(self.root): relative_path = os.path.relpath(root, self.root) # First iteration if relative_path == '.': relative_path = '' dirs[:] = sorted( d for d in dirs # The trailing slash is necessary so e.g. `bar/` matches `foo/bar` if not self.config.path_is_excluded('{}/'.format(os.path.join(relative_path, d))) ) files = sorted(files) is_package = '__init__.py' in files for f in files: relative_file_path = os.path.join(relative_path, f) if self.config.include_path(relative_file_path, is_package=is_package): yield IncludedFile( os.path.join(root, f), relative_file_path, self.config.get_distribution_path(relative_file_path) ) @property def root(self): """ The root of the project tree. """ return self.__root @property def plugin_manager(self): if self.__plugin_manager is None: from ...plugin.manager import PluginManager self.__plugin_manager = PluginManager() return self.__plugin_manager @property def metadata(self): if self.__metadata is None: from ...metadata.core import ProjectMetadata self.__metadata = ProjectMetadata(self.root, self.plugin_manager, self.__raw_config) return self.__metadata @property def app(self): """ An instance of [Application](utilities.md#hatchling.bridge.app.Application). """ if self.__app is None: from ...bridge.app import Application self.__app = Application().get_safe_application() return self.__app @property def raw_config(self): if self.__raw_config is None: self.__raw_config = self.metadata.config return self.__raw_config @property def project_config(self): if self.__project_config is None: self.__project_config = self.metadata.core.config return self.__project_config @property def hatch_config(self): if self.__hatch_config is None: self.__hatch_config = self.metadata.hatch.config return self.__hatch_config @property def config(self): """ An instance of [BuilderConfig](utilities.md#hatchling.builders.config.BuilderConfig). """ if self.__config is None: self.__config = self.get_config_class()( self, self.root, self.PLUGIN_NAME, self.build_config, self.target_config ) return self.__config @property def build_config(self): """ === ":octicons-file-code-16: pyproject.toml" ```toml [tool.hatch.build] ``` === ":octicons-file-code-16: hatch.toml" ```toml [build] ``` """ if self.__build_config is None: self.__build_config = self.metadata.hatch.build_config return self.__build_config @property def target_config(self): """ === ":octicons-file-code-16: pyproject.toml" ```toml [tool.hatch.build.targets.] ``` === ":octicons-file-code-16: hatch.toml" ```toml [build.targets.] ``` """ if self.__target_config is None: target_config = self.metadata.hatch.build_targets.get(self.PLUGIN_NAME, {}) if not isinstance(target_config, dict): raise TypeError('Field `tool.hatch.build.targets.{}` must be a table'.format(self.PLUGIN_NAME)) self.__target_config = target_config return self.__target_config @property def project_id(self): if self.__project_id is None: self.__project_id = '{}-{}'.format( # https://discuss.python.org/t/clarify-naming-of-dist-info-directories/5565 self.normalize_file_name_component(self.metadata.core.name), self.metadata.version, ) return self.__project_id def get_build_hooks(self, directory): configured_build_hooks = OrderedDict() for hook_name, config in self.config.hook_config.items(): build_hook = self.plugin_manager.build_hook.get(hook_name) if build_hook is None: raise ValueError('Unknown build hook: {}'.format(hook_name)) configured_build_hooks[hook_name] = build_hook( self.root, config, self.config, self.metadata, directory, self.PLUGIN_NAME, self.app ) return configured_build_hooks def get_version_api(self): """ :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right: A mapping of `str` versions to a callable that is used for building. Each callable must have the following signature: ```python def ...(build_dir: str, build_data: dict) -> str: ``` The return value must be the absolute path to the built artifact. """ raise NotImplementedError def get_default_versions(self): """ A list of versions to build when users do not specify any, defaulting to all versions. """ return list(self.get_version_api()) def get_default_build_data(self): """ A mapping that can be modified by [build hooks](build-hook.md) to influence the behavior of builds. """ return {} def clean(self, directory, versions): """ Called before builds if the `-c`/`--clean` flag was passed to the [`build`](../cli/reference.md#hatch-build) command. """ @classmethod def get_config_class(cls): """ Must return a subclass of [BuilderConfig](utilities.md#hatchling.builders.config.BuilderConfig). """ return BuilderConfig @staticmethod def normalize_file_name_component(file_name): """ https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode """ return re.sub(r'[^\w\d.]+', '_', file_name, re.UNICODE) hatchling-0.15.0/src/hatchling/cli/__init__.py0000644000000000000000000000121113615410400017321 0ustar0000000000000000import argparse import sys from .build import build_command from .dep import dep_command def hatchling(): # TODO: remove when we drop Python 2 if sys.version_info[0] < 3: # no cov parser = argparse.ArgumentParser(prog='hatchling') subparsers = parser.add_subparsers() else: parser = argparse.ArgumentParser(prog='hatchling', allow_abbrev=False) subparsers = parser.add_subparsers(required=True) defaults = {'metavar': ''} build_command(subparsers, defaults) dep_command(subparsers, defaults) kwargs = vars(parser.parse_args()) command = kwargs.pop('func') command(**kwargs) hatchling-0.15.0/src/hatchling/cli/build/__init__.py0000644000000000000000000000737013615410400020434 0ustar0000000000000000import argparse def build_impl(called_by_app, directory, targets, hooks_only, no_hooks, clean, clean_hooks_after, clean_only): import os from collections import OrderedDict from ...builders.constants import BuildEnvVars from ...metadata.core import ProjectMetadata from ...plugin.manager import PluginManager if called_by_app: from ...bridge.app import InvokedApplication app = InvokedApplication() else: # no cov from ...bridge.app import Application app = Application() if hooks_only and no_hooks: app.abort('Cannot use both --hooks-only and --no-hooks together') root = os.getcwd() plugin_manager = PluginManager() metadata = ProjectMetadata(root, plugin_manager) target_data = OrderedDict() if targets: for data in targets: target_name, _, version_data = data.partition(':') versions = version_data.split(',') if version_data else [] target_data.setdefault(target_name, []).extend(versions) else: # no cov targets = metadata.hatch.build_targets or ['sdist', 'wheel'] for target_name in targets: target_data[target_name] = [] builders = OrderedDict() unknown_targets = [] for target_name in target_data: builder_class = plugin_manager.builder.get(target_name) if builder_class is None: unknown_targets.append(target_name) else: builders[target_name] = builder_class if unknown_targets: app.abort('Unknown build targets: {}'.format(', '.join(sorted(unknown_targets)))) # We guarantee that builds occur within the project directory root = os.getcwd() if no_hooks: os.environ[BuildEnvVars.NO_HOOKS] = 'true' for i, (target_name, versions) in enumerate(target_data.items()): # Separate targets with a blank line if not clean_only and i != 0: # no cov app.display_info() builder_class = builders[target_name] # Display name before instantiation in case of errors if not clean_only: app.display_mini_header(target_name) builder = builder_class(root, plugin_manager=plugin_manager, metadata=metadata, app=app.get_safe_application()) for artifact in builder.build( directory=directory, versions=versions, hooks_only=hooks_only, clean=clean, clean_hooks_after=clean_hooks_after, clean_only=clean_only, ): if os.path.isfile(artifact) and artifact.startswith(root): app.display_info(os.path.relpath(artifact, root)) else: # no cov app.display_info(artifact) def build_command(subparsers, defaults): parser = subparsers.add_parser('build') parser.add_argument( '-d', '--directory', dest='directory', help='The directory in which to build artifacts', **defaults ) parser.add_argument( '-t', '--target', dest='targets', action='append', help='Comma-separated list of targets to build, overriding project defaults', **defaults ) parser.add_argument('--hooks-only', dest='hooks_only', action='store_true', default=None) parser.add_argument('--no-hooks', dest='no_hooks', action='store_true', default=None) parser.add_argument('-c', '--clean', dest='clean', action='store_true', default=None) parser.add_argument('--clean-hooks-after', dest='clean_hooks_after', action='store_true', default=None) parser.add_argument('--clean-only', dest='clean_only', action='store_true') parser.add_argument('--app', dest='called_by_app', action='store_true', help=argparse.SUPPRESS) parser.set_defaults(func=build_impl) hatchling-0.15.0/src/hatchling/cli/dep/__init__.py0000644000000000000000000000164213615410400020101 0ustar0000000000000000import sys def synced_impl(dependencies, python): import subprocess from ast import literal_eval from packaging.requirements import Requirement from ...dep.core import dependencies_in_sync sys_path = None if python: output = subprocess.check_output([python, '-c', 'import sys;print([path for path in sys.path if path])']) sys_path = literal_eval(output.strip().decode('utf-8')) sys.exit(0 if dependencies_in_sync(map(Requirement, dependencies), sys_path) else 1) def synced_command(subparsers, defaults): parser = subparsers.add_parser('synced') parser.add_argument('dependencies', nargs='+') parser.add_argument('-p', '--python', dest='python', **defaults) parser.set_defaults(func=synced_impl) def dep_command(subparsers, defaults): parser = subparsers.add_parser('dep') subparsers = parser.add_subparsers() synced_command(subparsers, defaults) hatchling-0.15.0/src/hatchling/dep/__init__.py0000644000000000000000000000000013615410400017315 0ustar0000000000000000hatchling-0.15.0/src/hatchling/dep/core.py0000644000000000000000000001027613615410400016526 0ustar0000000000000000import re import sys from packaging.markers import default_environment from packaging.requirements import Requirement try: from importlib.metadata import Distribution, DistributionFinder except ImportError: # no cov from importlib_metadata import Distribution, DistributionFinder class DistributionCache: def __init__(self, sys_path): self._resolver = Distribution.discover(context=DistributionFinder.Context(path=sys_path)) self._distributions = {} self._search_exhausted = False self._canonical_regex = re.compile(r'[-_.]+') def __getitem__(self, item): item = self._canonical_regex.sub('-', item).lower() possible_distribution = self._distributions.get(item) if possible_distribution is not None: return possible_distribution # Be safe even though the code as-is will never reach this since # the first unknown distribution will fail fast elif self._search_exhausted: # no cov return for distribution in self._resolver: name = self._canonical_regex.sub('-', distribution.metadata.get('Name')).lower() self._distributions[name] = distribution if name == item: return distribution self._search_exhausted = True def dependency_in_sync(requirement, environment, installed_distributions): if requirement.marker and not requirement.marker.evaluate(environment): return True distribution = installed_distributions[requirement.name] if distribution is None: return False extras = requirement.extras if extras: transitive_requirements = distribution.metadata.get_all('Requires-Dist', []) if not transitive_requirements: return False available_extras = distribution.metadata.get_all('Provides-Extra', []) for requirement_string in transitive_requirements: transitive_requirement = Requirement(requirement_string) if not transitive_requirement.marker: continue for extra in extras: # FIXME: This may cause a build to never be ready if newer versions do not provide the desired # extra and it's just a user error/typo. See: https://github.com/pypa/pip/issues/7122 if extra not in available_extras: return False extra_environment = dict(environment) extra_environment['extra'] = extra if not dependency_in_sync(transitive_requirement, extra_environment, installed_distributions): return False if requirement.specifier and not requirement.specifier.contains(distribution.version): return False # TODO: handle https://discuss.python.org/t/11938 elif requirement.url: direct_url_file = distribution.read_text('direct_url.json') if direct_url_file is not None: import json # https://packaging.python.org/specifications/direct-url/ direct_url_data = json.loads(direct_url_file) if 'vcs_info' in direct_url_data: url = direct_url_data['url'] vcs_info = direct_url_data['vcs_info'] vcs = vcs_info['vcs'] commit_id = vcs_info['commit_id'] # Try a few variations, see: # https://www.python.org/dev/peps/pep-0440/#direct-references if requirement.url != '{}+{}@{}'.format(vcs, url, commit_id): if 'requested_revision' in vcs_info: requested_revision = vcs_info['requested_revision'] if requirement.url != '{}+{}@{}#{}'.format(vcs, url, requested_revision, commit_id): return False else: return False return True def dependencies_in_sync(requirements, sys_path=None, environment=None): if sys_path is None: sys_path = sys.path if environment is None: environment = default_environment() installed_distributions = DistributionCache(sys_path) return all(dependency_in_sync(requirement, environment, installed_distributions) for requirement in requirements) hatchling-0.15.0/src/hatchling/licenses/__init__.py0000644000000000000000000000000013615410400020352 0ustar0000000000000000hatchling-0.15.0/src/hatchling/licenses/parse.py0000644000000000000000000000472413615410400017746 0ustar0000000000000000from .supported import EXCEPTIONS, LICENSES def normalize_license_expression(license_expression): if not license_expression: return license_expression # First normalize to lower case so we can look up licenses/exceptions # and so boolean operators are Python-compatible license_expression = license_expression.lower() # Then pad parentheses so tokenization can be achieved by merely splitting on white space license_expression = license_expression.replace('(', ' ( ').replace(')', ' ) ') # Now we begin parsing tokens = license_expression.split() # Rather than implementing boolean logic we create an expression that Python can parse. # Everything that is not involved with the grammar itself is treated as `False` and the # expression should evaluate as such. python_tokens = [] for token in tokens: if token not in ('or', 'and', 'with', '(', ')'): python_tokens.append('False') elif token == 'with': python_tokens.append('or') elif token == '(' and python_tokens and python_tokens[-1] not in ('or', 'and'): raise ValueError('Invalid license expression') else: python_tokens.append(token) python_expression = ' '.join(python_tokens) try: assert eval(python_expression) is False except Exception: raise ValueError('Invalid license expression') # Take a final pass to check for unknown licenses/exceptions normalized_tokens = [] for token in tokens: if token in ('or', 'and', 'with', '(', ')'): normalized_tokens.append(token.upper()) continue if normalized_tokens and normalized_tokens[-1] == 'WITH': if token not in EXCEPTIONS: raise ValueError('Unknown license exception: {}'.format(token)) normalized_tokens.append(EXCEPTIONS[token]['id']) else: if token.endswith('+'): token = token[:-1] suffix = '+' else: suffix = '' if token not in LICENSES: raise ValueError('Unknown license: {}'.format(token)) normalized_tokens.append(LICENSES[token]['id'] + suffix) # Construct the normalized expression normalized_expression = ' '.join(normalized_tokens) # Fix internal padding for parentheses normalized_expression = normalized_expression.replace('( ', '(').replace(' )', ')') return normalized_expression hatchling-0.15.0/src/hatchling/licenses/supported.py0000644000000000000000000010056313615410400020657 0ustar0000000000000000VERSION = '3.15' LICENSES = { '0bsd': {'id': '0BSD', 'deprecated': False}, 'aal': {'id': 'AAL', 'deprecated': False}, 'abstyles': {'id': 'Abstyles', 'deprecated': False}, 'adobe-2006': {'id': 'Adobe-2006', 'deprecated': False}, 'adobe-glyph': {'id': 'Adobe-Glyph', 'deprecated': False}, 'adsl': {'id': 'ADSL', 'deprecated': False}, 'afl-1.1': {'id': 'AFL-1.1', 'deprecated': False}, 'afl-1.2': {'id': 'AFL-1.2', 'deprecated': False}, 'afl-2.0': {'id': 'AFL-2.0', 'deprecated': False}, 'afl-2.1': {'id': 'AFL-2.1', 'deprecated': False}, 'afl-3.0': {'id': 'AFL-3.0', 'deprecated': False}, 'afmparse': {'id': 'Afmparse', 'deprecated': False}, 'agpl-1.0': {'id': 'AGPL-1.0', 'deprecated': True}, 'agpl-1.0-only': {'id': 'AGPL-1.0-only', 'deprecated': False}, 'agpl-1.0-or-later': {'id': 'AGPL-1.0-or-later', 'deprecated': False}, 'agpl-3.0': {'id': 'AGPL-3.0', 'deprecated': True}, 'agpl-3.0-only': {'id': 'AGPL-3.0-only', 'deprecated': False}, 'agpl-3.0-or-later': {'id': 'AGPL-3.0-or-later', 'deprecated': False}, 'aladdin': {'id': 'Aladdin', 'deprecated': False}, 'amdplpa': {'id': 'AMDPLPA', 'deprecated': False}, 'aml': {'id': 'AML', 'deprecated': False}, 'ampas': {'id': 'AMPAS', 'deprecated': False}, 'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False}, 'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False}, 'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False}, 'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False}, 'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False}, 'apafml': {'id': 'APAFML', 'deprecated': False}, 'apl-1.0': {'id': 'APL-1.0', 'deprecated': False}, 'apsl-1.0': {'id': 'APSL-1.0', 'deprecated': False}, 'apsl-1.1': {'id': 'APSL-1.1', 'deprecated': False}, 'apsl-1.2': {'id': 'APSL-1.2', 'deprecated': False}, 'apsl-2.0': {'id': 'APSL-2.0', 'deprecated': False}, 'artistic-1.0': {'id': 'Artistic-1.0', 'deprecated': False}, 'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False}, 'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False}, 'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False}, 'bahyph': {'id': 'Bahyph', 'deprecated': False}, 'barr': {'id': 'Barr', 'deprecated': False}, 'beerware': {'id': 'Beerware', 'deprecated': False}, 'bittorrent-1.0': {'id': 'BitTorrent-1.0', 'deprecated': False}, 'bittorrent-1.1': {'id': 'BitTorrent-1.1', 'deprecated': False}, 'blessing': {'id': 'blessing', 'deprecated': False}, 'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False}, 'borceux': {'id': 'Borceux', 'deprecated': False}, 'bsd-1-clause': {'id': 'BSD-1-Clause', 'deprecated': False}, 'bsd-2-clause': {'id': 'BSD-2-Clause', 'deprecated': False}, 'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True}, 'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True}, 'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False}, 'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False}, 'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False}, 'bsd-3-clause-attribution': {'id': 'BSD-3-Clause-Attribution', 'deprecated': False}, 'bsd-3-clause-clear': {'id': 'BSD-3-Clause-Clear', 'deprecated': False}, 'bsd-3-clause-lbnl': {'id': 'BSD-3-Clause-LBNL', 'deprecated': False}, 'bsd-3-clause-modification': {'id': 'BSD-3-Clause-Modification', 'deprecated': False}, 'bsd-3-clause-no-military-license': {'id': 'BSD-3-Clause-No-Military-License', 'deprecated': False}, 'bsd-3-clause-no-nuclear-license': {'id': 'BSD-3-Clause-No-Nuclear-License', 'deprecated': False}, 'bsd-3-clause-no-nuclear-license-2014': {'id': 'BSD-3-Clause-No-Nuclear-License-2014', 'deprecated': False}, 'bsd-3-clause-no-nuclear-warranty': {'id': 'BSD-3-Clause-No-Nuclear-Warranty', 'deprecated': False}, 'bsd-3-clause-open-mpi': {'id': 'BSD-3-Clause-Open-MPI', 'deprecated': False}, 'bsd-4-clause': {'id': 'BSD-4-Clause', 'deprecated': False}, 'bsd-4-clause-shortened': {'id': 'BSD-4-Clause-Shortened', 'deprecated': False}, 'bsd-4-clause-uc': {'id': 'BSD-4-Clause-UC', 'deprecated': False}, 'bsd-protection': {'id': 'BSD-Protection', 'deprecated': False}, 'bsd-source-code': {'id': 'BSD-Source-Code', 'deprecated': False}, 'bsl-1.0': {'id': 'BSL-1.0', 'deprecated': False}, 'busl-1.1': {'id': 'BUSL-1.1', 'deprecated': False}, 'bzip2-1.0.5': {'id': 'bzip2-1.0.5', 'deprecated': False}, 'bzip2-1.0.6': {'id': 'bzip2-1.0.6', 'deprecated': False}, 'c-uda-1.0': {'id': 'C-UDA-1.0', 'deprecated': False}, 'cal-1.0': {'id': 'CAL-1.0', 'deprecated': False}, 'cal-1.0-combined-work-exception': {'id': 'CAL-1.0-Combined-Work-Exception', 'deprecated': False}, 'caldera': {'id': 'Caldera', 'deprecated': False}, 'catosl-1.1': {'id': 'CATOSL-1.1', 'deprecated': False}, 'cc-by-1.0': {'id': 'CC-BY-1.0', 'deprecated': False}, 'cc-by-2.0': {'id': 'CC-BY-2.0', 'deprecated': False}, 'cc-by-2.5': {'id': 'CC-BY-2.5', 'deprecated': False}, 'cc-by-2.5-au': {'id': 'CC-BY-2.5-AU', 'deprecated': False}, 'cc-by-3.0': {'id': 'CC-BY-3.0', 'deprecated': False}, 'cc-by-3.0-at': {'id': 'CC-BY-3.0-AT', 'deprecated': False}, 'cc-by-3.0-de': {'id': 'CC-BY-3.0-DE', 'deprecated': False}, 'cc-by-3.0-nl': {'id': 'CC-BY-3.0-NL', 'deprecated': False}, 'cc-by-3.0-us': {'id': 'CC-BY-3.0-US', 'deprecated': False}, 'cc-by-4.0': {'id': 'CC-BY-4.0', 'deprecated': False}, 'cc-by-nc-1.0': {'id': 'CC-BY-NC-1.0', 'deprecated': False}, 'cc-by-nc-2.0': {'id': 'CC-BY-NC-2.0', 'deprecated': False}, 'cc-by-nc-2.5': {'id': 'CC-BY-NC-2.5', 'deprecated': False}, 'cc-by-nc-3.0': {'id': 'CC-BY-NC-3.0', 'deprecated': False}, 'cc-by-nc-3.0-de': {'id': 'CC-BY-NC-3.0-DE', 'deprecated': False}, 'cc-by-nc-4.0': {'id': 'CC-BY-NC-4.0', 'deprecated': False}, 'cc-by-nc-nd-1.0': {'id': 'CC-BY-NC-ND-1.0', 'deprecated': False}, 'cc-by-nc-nd-2.0': {'id': 'CC-BY-NC-ND-2.0', 'deprecated': False}, 'cc-by-nc-nd-2.5': {'id': 'CC-BY-NC-ND-2.5', 'deprecated': False}, 'cc-by-nc-nd-3.0': {'id': 'CC-BY-NC-ND-3.0', 'deprecated': False}, 'cc-by-nc-nd-3.0-de': {'id': 'CC-BY-NC-ND-3.0-DE', 'deprecated': False}, 'cc-by-nc-nd-3.0-igo': {'id': 'CC-BY-NC-ND-3.0-IGO', 'deprecated': False}, 'cc-by-nc-nd-4.0': {'id': 'CC-BY-NC-ND-4.0', 'deprecated': False}, 'cc-by-nc-sa-1.0': {'id': 'CC-BY-NC-SA-1.0', 'deprecated': False}, 'cc-by-nc-sa-2.0': {'id': 'CC-BY-NC-SA-2.0', 'deprecated': False}, 'cc-by-nc-sa-2.0-fr': {'id': 'CC-BY-NC-SA-2.0-FR', 'deprecated': False}, 'cc-by-nc-sa-2.0-uk': {'id': 'CC-BY-NC-SA-2.0-UK', 'deprecated': False}, 'cc-by-nc-sa-2.5': {'id': 'CC-BY-NC-SA-2.5', 'deprecated': False}, 'cc-by-nc-sa-3.0': {'id': 'CC-BY-NC-SA-3.0', 'deprecated': False}, 'cc-by-nc-sa-3.0-de': {'id': 'CC-BY-NC-SA-3.0-DE', 'deprecated': False}, 'cc-by-nc-sa-3.0-igo': {'id': 'CC-BY-NC-SA-3.0-IGO', 'deprecated': False}, 'cc-by-nc-sa-4.0': {'id': 'CC-BY-NC-SA-4.0', 'deprecated': False}, 'cc-by-nd-1.0': {'id': 'CC-BY-ND-1.0', 'deprecated': False}, 'cc-by-nd-2.0': {'id': 'CC-BY-ND-2.0', 'deprecated': False}, 'cc-by-nd-2.5': {'id': 'CC-BY-ND-2.5', 'deprecated': False}, 'cc-by-nd-3.0': {'id': 'CC-BY-ND-3.0', 'deprecated': False}, 'cc-by-nd-3.0-de': {'id': 'CC-BY-ND-3.0-DE', 'deprecated': False}, 'cc-by-nd-4.0': {'id': 'CC-BY-ND-4.0', 'deprecated': False}, 'cc-by-sa-1.0': {'id': 'CC-BY-SA-1.0', 'deprecated': False}, 'cc-by-sa-2.0': {'id': 'CC-BY-SA-2.0', 'deprecated': False}, 'cc-by-sa-2.0-uk': {'id': 'CC-BY-SA-2.0-UK', 'deprecated': False}, 'cc-by-sa-2.1-jp': {'id': 'CC-BY-SA-2.1-JP', 'deprecated': False}, 'cc-by-sa-2.5': {'id': 'CC-BY-SA-2.5', 'deprecated': False}, 'cc-by-sa-3.0': {'id': 'CC-BY-SA-3.0', 'deprecated': False}, 'cc-by-sa-3.0-at': {'id': 'CC-BY-SA-3.0-AT', 'deprecated': False}, 'cc-by-sa-3.0-de': {'id': 'CC-BY-SA-3.0-DE', 'deprecated': False}, 'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False}, 'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False}, 'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False}, 'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False}, 'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False}, 'cdl-1.0': {'id': 'CDL-1.0', 'deprecated': False}, 'cdla-permissive-1.0': {'id': 'CDLA-Permissive-1.0', 'deprecated': False}, 'cdla-permissive-2.0': {'id': 'CDLA-Permissive-2.0', 'deprecated': False}, 'cdla-sharing-1.0': {'id': 'CDLA-Sharing-1.0', 'deprecated': False}, 'cecill-1.0': {'id': 'CECILL-1.0', 'deprecated': False}, 'cecill-1.1': {'id': 'CECILL-1.1', 'deprecated': False}, 'cecill-2.0': {'id': 'CECILL-2.0', 'deprecated': False}, 'cecill-2.1': {'id': 'CECILL-2.1', 'deprecated': False}, 'cecill-b': {'id': 'CECILL-B', 'deprecated': False}, 'cecill-c': {'id': 'CECILL-C', 'deprecated': False}, 'cern-ohl-1.1': {'id': 'CERN-OHL-1.1', 'deprecated': False}, 'cern-ohl-1.2': {'id': 'CERN-OHL-1.2', 'deprecated': False}, 'cern-ohl-p-2.0': {'id': 'CERN-OHL-P-2.0', 'deprecated': False}, 'cern-ohl-s-2.0': {'id': 'CERN-OHL-S-2.0', 'deprecated': False}, 'cern-ohl-w-2.0': {'id': 'CERN-OHL-W-2.0', 'deprecated': False}, 'clartistic': {'id': 'ClArtistic', 'deprecated': False}, 'cnri-jython': {'id': 'CNRI-Jython', 'deprecated': False}, 'cnri-python': {'id': 'CNRI-Python', 'deprecated': False}, 'cnri-python-gpl-compatible': {'id': 'CNRI-Python-GPL-Compatible', 'deprecated': False}, 'coil-1.0': {'id': 'COIL-1.0', 'deprecated': False}, 'community-spec-1.0': {'id': 'Community-Spec-1.0', 'deprecated': False}, 'condor-1.1': {'id': 'Condor-1.1', 'deprecated': False}, 'copyleft-next-0.3.0': {'id': 'copyleft-next-0.3.0', 'deprecated': False}, 'copyleft-next-0.3.1': {'id': 'copyleft-next-0.3.1', 'deprecated': False}, 'cpal-1.0': {'id': 'CPAL-1.0', 'deprecated': False}, 'cpl-1.0': {'id': 'CPL-1.0', 'deprecated': False}, 'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False}, 'crossword': {'id': 'Crossword', 'deprecated': False}, 'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False}, 'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False}, 'cube': {'id': 'Cube', 'deprecated': False}, 'curl': {'id': 'curl', 'deprecated': False}, 'd-fsl-1.0': {'id': 'D-FSL-1.0', 'deprecated': False}, 'diffmark': {'id': 'diffmark', 'deprecated': False}, 'doc': {'id': 'DOC', 'deprecated': False}, 'dotseqn': {'id': 'Dotseqn', 'deprecated': False}, 'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False}, 'dsdp': {'id': 'DSDP', 'deprecated': False}, 'dvipdfm': {'id': 'dvipdfm', 'deprecated': False}, 'ecl-1.0': {'id': 'ECL-1.0', 'deprecated': False}, 'ecl-2.0': {'id': 'ECL-2.0', 'deprecated': False}, 'ecos-2.0': {'id': 'eCos-2.0', 'deprecated': True}, 'efl-1.0': {'id': 'EFL-1.0', 'deprecated': False}, 'efl-2.0': {'id': 'EFL-2.0', 'deprecated': False}, 'egenix': {'id': 'eGenix', 'deprecated': False}, 'entessa': {'id': 'Entessa', 'deprecated': False}, 'epics': {'id': 'EPICS', 'deprecated': False}, 'epl-1.0': {'id': 'EPL-1.0', 'deprecated': False}, 'epl-2.0': {'id': 'EPL-2.0', 'deprecated': False}, 'erlpl-1.1': {'id': 'ErlPL-1.1', 'deprecated': False}, 'etalab-2.0': {'id': 'etalab-2.0', 'deprecated': False}, 'eudatagrid': {'id': 'EUDatagrid', 'deprecated': False}, 'eupl-1.0': {'id': 'EUPL-1.0', 'deprecated': False}, 'eupl-1.1': {'id': 'EUPL-1.1', 'deprecated': False}, 'eupl-1.2': {'id': 'EUPL-1.2', 'deprecated': False}, 'eurosym': {'id': 'Eurosym', 'deprecated': False}, 'fair': {'id': 'Fair', 'deprecated': False}, 'fdk-aac': {'id': 'FDK-AAC', 'deprecated': False}, 'frameworx-1.0': {'id': 'Frameworx-1.0', 'deprecated': False}, 'freebsd-doc': {'id': 'FreeBSD-DOC', 'deprecated': False}, 'freeimage': {'id': 'FreeImage', 'deprecated': False}, 'fsfap': {'id': 'FSFAP', 'deprecated': False}, 'fsful': {'id': 'FSFUL', 'deprecated': False}, 'fsfullr': {'id': 'FSFULLR', 'deprecated': False}, 'ftl': {'id': 'FTL', 'deprecated': False}, 'gd': {'id': 'GD', 'deprecated': False}, 'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True}, 'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False}, 'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False}, 'gfdl-1.1-no-invariants-only': {'id': 'GFDL-1.1-no-invariants-only', 'deprecated': False}, 'gfdl-1.1-no-invariants-or-later': {'id': 'GFDL-1.1-no-invariants-or-later', 'deprecated': False}, 'gfdl-1.1-only': {'id': 'GFDL-1.1-only', 'deprecated': False}, 'gfdl-1.1-or-later': {'id': 'GFDL-1.1-or-later', 'deprecated': False}, 'gfdl-1.2': {'id': 'GFDL-1.2', 'deprecated': True}, 'gfdl-1.2-invariants-only': {'id': 'GFDL-1.2-invariants-only', 'deprecated': False}, 'gfdl-1.2-invariants-or-later': {'id': 'GFDL-1.2-invariants-or-later', 'deprecated': False}, 'gfdl-1.2-no-invariants-only': {'id': 'GFDL-1.2-no-invariants-only', 'deprecated': False}, 'gfdl-1.2-no-invariants-or-later': {'id': 'GFDL-1.2-no-invariants-or-later', 'deprecated': False}, 'gfdl-1.2-only': {'id': 'GFDL-1.2-only', 'deprecated': False}, 'gfdl-1.2-or-later': {'id': 'GFDL-1.2-or-later', 'deprecated': False}, 'gfdl-1.3': {'id': 'GFDL-1.3', 'deprecated': True}, 'gfdl-1.3-invariants-only': {'id': 'GFDL-1.3-invariants-only', 'deprecated': False}, 'gfdl-1.3-invariants-or-later': {'id': 'GFDL-1.3-invariants-or-later', 'deprecated': False}, 'gfdl-1.3-no-invariants-only': {'id': 'GFDL-1.3-no-invariants-only', 'deprecated': False}, 'gfdl-1.3-no-invariants-or-later': {'id': 'GFDL-1.3-no-invariants-or-later', 'deprecated': False}, 'gfdl-1.3-only': {'id': 'GFDL-1.3-only', 'deprecated': False}, 'gfdl-1.3-or-later': {'id': 'GFDL-1.3-or-later', 'deprecated': False}, 'giftware': {'id': 'Giftware', 'deprecated': False}, 'gl2ps': {'id': 'GL2PS', 'deprecated': False}, 'glide': {'id': 'Glide', 'deprecated': False}, 'glulxe': {'id': 'Glulxe', 'deprecated': False}, 'glwtpl': {'id': 'GLWTPL', 'deprecated': False}, 'gnuplot': {'id': 'gnuplot', 'deprecated': False}, 'gpl-1.0': {'id': 'GPL-1.0', 'deprecated': True}, 'gpl-1.0+': {'id': 'GPL-1.0+', 'deprecated': True}, 'gpl-1.0-only': {'id': 'GPL-1.0-only', 'deprecated': False}, 'gpl-1.0-or-later': {'id': 'GPL-1.0-or-later', 'deprecated': False}, 'gpl-2.0': {'id': 'GPL-2.0', 'deprecated': True}, 'gpl-2.0+': {'id': 'GPL-2.0+', 'deprecated': True}, 'gpl-2.0-only': {'id': 'GPL-2.0-only', 'deprecated': False}, 'gpl-2.0-or-later': {'id': 'GPL-2.0-or-later', 'deprecated': False}, 'gpl-2.0-with-autoconf-exception': {'id': 'GPL-2.0-with-autoconf-exception', 'deprecated': True}, 'gpl-2.0-with-bison-exception': {'id': 'GPL-2.0-with-bison-exception', 'deprecated': True}, 'gpl-2.0-with-classpath-exception': {'id': 'GPL-2.0-with-classpath-exception', 'deprecated': True}, 'gpl-2.0-with-font-exception': {'id': 'GPL-2.0-with-font-exception', 'deprecated': True}, 'gpl-2.0-with-gcc-exception': {'id': 'GPL-2.0-with-GCC-exception', 'deprecated': True}, 'gpl-3.0': {'id': 'GPL-3.0', 'deprecated': True}, 'gpl-3.0+': {'id': 'GPL-3.0+', 'deprecated': True}, 'gpl-3.0-only': {'id': 'GPL-3.0-only', 'deprecated': False}, 'gpl-3.0-or-later': {'id': 'GPL-3.0-or-later', 'deprecated': False}, 'gpl-3.0-with-autoconf-exception': {'id': 'GPL-3.0-with-autoconf-exception', 'deprecated': True}, 'gpl-3.0-with-gcc-exception': {'id': 'GPL-3.0-with-GCC-exception', 'deprecated': True}, 'gsoap-1.3b': {'id': 'gSOAP-1.3b', 'deprecated': False}, 'haskellreport': {'id': 'HaskellReport', 'deprecated': False}, 'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False}, 'hpnd': {'id': 'HPND', 'deprecated': False}, 'hpnd-sell-variant': {'id': 'HPND-sell-variant', 'deprecated': False}, 'htmltidy': {'id': 'HTMLTIDY', 'deprecated': False}, 'ibm-pibs': {'id': 'IBM-pibs', 'deprecated': False}, 'icu': {'id': 'ICU', 'deprecated': False}, 'ijg': {'id': 'IJG', 'deprecated': False}, 'imagemagick': {'id': 'ImageMagick', 'deprecated': False}, 'imatix': {'id': 'iMatix', 'deprecated': False}, 'imlib2': {'id': 'Imlib2', 'deprecated': False}, 'info-zip': {'id': 'Info-ZIP', 'deprecated': False}, 'intel': {'id': 'Intel', 'deprecated': False}, 'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False}, 'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False}, 'ipa': {'id': 'IPA', 'deprecated': False}, 'ipl-1.0': {'id': 'IPL-1.0', 'deprecated': False}, 'isc': {'id': 'ISC', 'deprecated': False}, 'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False}, 'jpnic': {'id': 'JPNIC', 'deprecated': False}, 'json': {'id': 'JSON', 'deprecated': False}, 'lal-1.2': {'id': 'LAL-1.2', 'deprecated': False}, 'lal-1.3': {'id': 'LAL-1.3', 'deprecated': False}, 'latex2e': {'id': 'Latex2e', 'deprecated': False}, 'leptonica': {'id': 'Leptonica', 'deprecated': False}, 'lgpl-2.0': {'id': 'LGPL-2.0', 'deprecated': True}, 'lgpl-2.0+': {'id': 'LGPL-2.0+', 'deprecated': True}, 'lgpl-2.0-only': {'id': 'LGPL-2.0-only', 'deprecated': False}, 'lgpl-2.0-or-later': {'id': 'LGPL-2.0-or-later', 'deprecated': False}, 'lgpl-2.1': {'id': 'LGPL-2.1', 'deprecated': True}, 'lgpl-2.1+': {'id': 'LGPL-2.1+', 'deprecated': True}, 'lgpl-2.1-only': {'id': 'LGPL-2.1-only', 'deprecated': False}, 'lgpl-2.1-or-later': {'id': 'LGPL-2.1-or-later', 'deprecated': False}, 'lgpl-3.0': {'id': 'LGPL-3.0', 'deprecated': True}, 'lgpl-3.0+': {'id': 'LGPL-3.0+', 'deprecated': True}, 'lgpl-3.0-only': {'id': 'LGPL-3.0-only', 'deprecated': False}, 'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False}, 'lgpllr': {'id': 'LGPLLR', 'deprecated': False}, 'libpng': {'id': 'Libpng', 'deprecated': False}, 'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False}, 'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False}, 'libtiff': {'id': 'libtiff', 'deprecated': False}, 'liliq-p-1.1': {'id': 'LiLiQ-P-1.1', 'deprecated': False}, 'liliq-r-1.1': {'id': 'LiLiQ-R-1.1', 'deprecated': False}, 'liliq-rplus-1.1': {'id': 'LiLiQ-Rplus-1.1', 'deprecated': False}, 'linux-man-pages-copyleft': {'id': 'Linux-man-pages-copyleft', 'deprecated': False}, 'linux-openib': {'id': 'Linux-OpenIB', 'deprecated': False}, 'lpl-1.0': {'id': 'LPL-1.0', 'deprecated': False}, 'lpl-1.02': {'id': 'LPL-1.02', 'deprecated': False}, 'lppl-1.0': {'id': 'LPPL-1.0', 'deprecated': False}, 'lppl-1.1': {'id': 'LPPL-1.1', 'deprecated': False}, 'lppl-1.2': {'id': 'LPPL-1.2', 'deprecated': False}, 'lppl-1.3a': {'id': 'LPPL-1.3a', 'deprecated': False}, 'lppl-1.3c': {'id': 'LPPL-1.3c', 'deprecated': False}, 'makeindex': {'id': 'MakeIndex', 'deprecated': False}, 'miros': {'id': 'MirOS', 'deprecated': False}, 'mit': {'id': 'MIT', 'deprecated': False}, 'mit-0': {'id': 'MIT-0', 'deprecated': False}, 'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False}, 'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False}, 'mit-enna': {'id': 'MIT-enna', 'deprecated': False}, 'mit-feh': {'id': 'MIT-feh', 'deprecated': False}, 'mit-modern-variant': {'id': 'MIT-Modern-Variant', 'deprecated': False}, 'mit-open-group': {'id': 'MIT-open-group', 'deprecated': False}, 'mitnfa': {'id': 'MITNFA', 'deprecated': False}, 'motosoto': {'id': 'Motosoto', 'deprecated': False}, 'mpich2': {'id': 'mpich2', 'deprecated': False}, 'mpl-1.0': {'id': 'MPL-1.0', 'deprecated': False}, 'mpl-1.1': {'id': 'MPL-1.1', 'deprecated': False}, 'mpl-2.0': {'id': 'MPL-2.0', 'deprecated': False}, 'mpl-2.0-no-copyleft-exception': {'id': 'MPL-2.0-no-copyleft-exception', 'deprecated': False}, 'ms-pl': {'id': 'MS-PL', 'deprecated': False}, 'ms-rl': {'id': 'MS-RL', 'deprecated': False}, 'mtll': {'id': 'MTLL', 'deprecated': False}, 'mulanpsl-1.0': {'id': 'MulanPSL-1.0', 'deprecated': False}, 'mulanpsl-2.0': {'id': 'MulanPSL-2.0', 'deprecated': False}, 'multics': {'id': 'Multics', 'deprecated': False}, 'mup': {'id': 'Mup', 'deprecated': False}, 'naist-2003': {'id': 'NAIST-2003', 'deprecated': False}, 'nasa-1.3': {'id': 'NASA-1.3', 'deprecated': False}, 'naumen': {'id': 'Naumen', 'deprecated': False}, 'nbpl-1.0': {'id': 'NBPL-1.0', 'deprecated': False}, 'ncgl-uk-2.0': {'id': 'NCGL-UK-2.0', 'deprecated': False}, 'ncsa': {'id': 'NCSA', 'deprecated': False}, 'net-snmp': {'id': 'Net-SNMP', 'deprecated': False}, 'netcdf': {'id': 'NetCDF', 'deprecated': False}, 'newsletr': {'id': 'Newsletr', 'deprecated': False}, 'ngpl': {'id': 'NGPL', 'deprecated': False}, 'nist-pd': {'id': 'NIST-PD', 'deprecated': False}, 'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False}, 'nlod-1.0': {'id': 'NLOD-1.0', 'deprecated': False}, 'nlod-2.0': {'id': 'NLOD-2.0', 'deprecated': False}, 'nlpl': {'id': 'NLPL', 'deprecated': False}, 'nokia': {'id': 'Nokia', 'deprecated': False}, 'nosl': {'id': 'NOSL', 'deprecated': False}, 'noweb': {'id': 'Noweb', 'deprecated': False}, 'npl-1.0': {'id': 'NPL-1.0', 'deprecated': False}, 'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False}, 'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False}, 'nrl': {'id': 'NRL', 'deprecated': False}, 'ntp': {'id': 'NTP', 'deprecated': False}, 'ntp-0': {'id': 'NTP-0', 'deprecated': False}, 'nunit': {'id': 'Nunit', 'deprecated': True}, 'o-uda-1.0': {'id': 'O-UDA-1.0', 'deprecated': False}, 'occt-pl': {'id': 'OCCT-PL', 'deprecated': False}, 'oclc-2.0': {'id': 'OCLC-2.0', 'deprecated': False}, 'odbl-1.0': {'id': 'ODbL-1.0', 'deprecated': False}, 'odc-by-1.0': {'id': 'ODC-By-1.0', 'deprecated': False}, 'ofl-1.0': {'id': 'OFL-1.0', 'deprecated': False}, 'ofl-1.0-no-rfn': {'id': 'OFL-1.0-no-RFN', 'deprecated': False}, 'ofl-1.0-rfn': {'id': 'OFL-1.0-RFN', 'deprecated': False}, 'ofl-1.1': {'id': 'OFL-1.1', 'deprecated': False}, 'ofl-1.1-no-rfn': {'id': 'OFL-1.1-no-RFN', 'deprecated': False}, 'ofl-1.1-rfn': {'id': 'OFL-1.1-RFN', 'deprecated': False}, 'ogc-1.0': {'id': 'OGC-1.0', 'deprecated': False}, 'ogdl-taiwan-1.0': {'id': 'OGDL-Taiwan-1.0', 'deprecated': False}, 'ogl-canada-2.0': {'id': 'OGL-Canada-2.0', 'deprecated': False}, 'ogl-uk-1.0': {'id': 'OGL-UK-1.0', 'deprecated': False}, 'ogl-uk-2.0': {'id': 'OGL-UK-2.0', 'deprecated': False}, 'ogl-uk-3.0': {'id': 'OGL-UK-3.0', 'deprecated': False}, 'ogtsl': {'id': 'OGTSL', 'deprecated': False}, 'oldap-1.1': {'id': 'OLDAP-1.1', 'deprecated': False}, 'oldap-1.2': {'id': 'OLDAP-1.2', 'deprecated': False}, 'oldap-1.3': {'id': 'OLDAP-1.3', 'deprecated': False}, 'oldap-1.4': {'id': 'OLDAP-1.4', 'deprecated': False}, 'oldap-2.0': {'id': 'OLDAP-2.0', 'deprecated': False}, 'oldap-2.0.1': {'id': 'OLDAP-2.0.1', 'deprecated': False}, 'oldap-2.1': {'id': 'OLDAP-2.1', 'deprecated': False}, 'oldap-2.2': {'id': 'OLDAP-2.2', 'deprecated': False}, 'oldap-2.2.1': {'id': 'OLDAP-2.2.1', 'deprecated': False}, 'oldap-2.2.2': {'id': 'OLDAP-2.2.2', 'deprecated': False}, 'oldap-2.3': {'id': 'OLDAP-2.3', 'deprecated': False}, 'oldap-2.4': {'id': 'OLDAP-2.4', 'deprecated': False}, 'oldap-2.5': {'id': 'OLDAP-2.5', 'deprecated': False}, 'oldap-2.6': {'id': 'OLDAP-2.6', 'deprecated': False}, 'oldap-2.7': {'id': 'OLDAP-2.7', 'deprecated': False}, 'oldap-2.8': {'id': 'OLDAP-2.8', 'deprecated': False}, 'oml': {'id': 'OML', 'deprecated': False}, 'openssl': {'id': 'OpenSSL', 'deprecated': False}, 'opl-1.0': {'id': 'OPL-1.0', 'deprecated': False}, 'opubl-1.0': {'id': 'OPUBL-1.0', 'deprecated': False}, 'oset-pl-2.1': {'id': 'OSET-PL-2.1', 'deprecated': False}, 'osl-1.0': {'id': 'OSL-1.0', 'deprecated': False}, 'osl-1.1': {'id': 'OSL-1.1', 'deprecated': False}, 'osl-2.0': {'id': 'OSL-2.0', 'deprecated': False}, 'osl-2.1': {'id': 'OSL-2.1', 'deprecated': False}, 'osl-3.0': {'id': 'OSL-3.0', 'deprecated': False}, 'parity-6.0.0': {'id': 'Parity-6.0.0', 'deprecated': False}, 'parity-7.0.0': {'id': 'Parity-7.0.0', 'deprecated': False}, 'pddl-1.0': {'id': 'PDDL-1.0', 'deprecated': False}, 'php-3.0': {'id': 'PHP-3.0', 'deprecated': False}, 'php-3.01': {'id': 'PHP-3.01', 'deprecated': False}, 'plexus': {'id': 'Plexus', 'deprecated': False}, 'polyform-noncommercial-1.0.0': {'id': 'PolyForm-Noncommercial-1.0.0', 'deprecated': False}, 'polyform-small-business-1.0.0': {'id': 'PolyForm-Small-Business-1.0.0', 'deprecated': False}, 'postgresql': {'id': 'PostgreSQL', 'deprecated': False}, 'psf-2.0': {'id': 'PSF-2.0', 'deprecated': False}, 'psfrag': {'id': 'psfrag', 'deprecated': False}, 'psutils': {'id': 'psutils', 'deprecated': False}, 'python-2.0': {'id': 'Python-2.0', 'deprecated': False}, 'qhull': {'id': 'Qhull', 'deprecated': False}, 'qpl-1.0': {'id': 'QPL-1.0', 'deprecated': False}, 'rdisc': {'id': 'Rdisc', 'deprecated': False}, 'rhecos-1.1': {'id': 'RHeCos-1.1', 'deprecated': False}, 'rpl-1.1': {'id': 'RPL-1.1', 'deprecated': False}, 'rpl-1.5': {'id': 'RPL-1.5', 'deprecated': False}, 'rpsl-1.0': {'id': 'RPSL-1.0', 'deprecated': False}, 'rsa-md': {'id': 'RSA-MD', 'deprecated': False}, 'rscpl': {'id': 'RSCPL', 'deprecated': False}, 'ruby': {'id': 'Ruby', 'deprecated': False}, 'sax-pd': {'id': 'SAX-PD', 'deprecated': False}, 'saxpath': {'id': 'Saxpath', 'deprecated': False}, 'scea': {'id': 'SCEA', 'deprecated': False}, 'sendmail': {'id': 'Sendmail', 'deprecated': False}, 'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False}, 'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False}, 'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False}, 'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False}, 'shl-0.5': {'id': 'SHL-0.5', 'deprecated': False}, 'shl-0.51': {'id': 'SHL-0.51', 'deprecated': False}, 'simpl-2.0': {'id': 'SimPL-2.0', 'deprecated': False}, 'sissl': {'id': 'SISSL', 'deprecated': False}, 'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False}, 'sleepycat': {'id': 'Sleepycat', 'deprecated': False}, 'smlnj': {'id': 'SMLNJ', 'deprecated': False}, 'smppl': {'id': 'SMPPL', 'deprecated': False}, 'snia': {'id': 'SNIA', 'deprecated': False}, 'spencer-86': {'id': 'Spencer-86', 'deprecated': False}, 'spencer-94': {'id': 'Spencer-94', 'deprecated': False}, 'spencer-99': {'id': 'Spencer-99', 'deprecated': False}, 'spl-1.0': {'id': 'SPL-1.0', 'deprecated': False}, 'ssh-openssh': {'id': 'SSH-OpenSSH', 'deprecated': False}, 'ssh-short': {'id': 'SSH-short', 'deprecated': False}, 'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False}, 'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True}, 'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False}, 'swl': {'id': 'SWL', 'deprecated': False}, 'tapr-ohl-1.0': {'id': 'TAPR-OHL-1.0', 'deprecated': False}, 'tcl': {'id': 'TCL', 'deprecated': False}, 'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False}, 'tmate': {'id': 'TMate', 'deprecated': False}, 'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False}, 'tosl': {'id': 'TOSL', 'deprecated': False}, 'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False}, 'tu-berlin-2.0': {'id': 'TU-Berlin-2.0', 'deprecated': False}, 'ucl-1.0': {'id': 'UCL-1.0', 'deprecated': False}, 'unicode-dfs-2015': {'id': 'Unicode-DFS-2015', 'deprecated': False}, 'unicode-dfs-2016': {'id': 'Unicode-DFS-2016', 'deprecated': False}, 'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False}, 'unlicense': {'id': 'Unlicense', 'deprecated': False}, 'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False}, 'vim': {'id': 'Vim', 'deprecated': False}, 'vostrom': {'id': 'VOSTROM', 'deprecated': False}, 'vsl-1.0': {'id': 'VSL-1.0', 'deprecated': False}, 'w3c': {'id': 'W3C', 'deprecated': False}, 'w3c-19980720': {'id': 'W3C-19980720', 'deprecated': False}, 'w3c-20150513': {'id': 'W3C-20150513', 'deprecated': False}, 'watcom-1.0': {'id': 'Watcom-1.0', 'deprecated': False}, 'wsuipa': {'id': 'Wsuipa', 'deprecated': False}, 'wtfpl': {'id': 'WTFPL', 'deprecated': False}, 'wxwindows': {'id': 'wxWindows', 'deprecated': True}, 'x11': {'id': 'X11', 'deprecated': False}, 'xerox': {'id': 'Xerox', 'deprecated': False}, 'xfree86-1.1': {'id': 'XFree86-1.1', 'deprecated': False}, 'xinetd': {'id': 'xinetd', 'deprecated': False}, 'xnet': {'id': 'Xnet', 'deprecated': False}, 'xpp': {'id': 'xpp', 'deprecated': False}, 'xskat': {'id': 'XSkat', 'deprecated': False}, 'ypl-1.0': {'id': 'YPL-1.0', 'deprecated': False}, 'ypl-1.1': {'id': 'YPL-1.1', 'deprecated': False}, 'zed': {'id': 'Zed', 'deprecated': False}, 'zend-2.0': {'id': 'Zend-2.0', 'deprecated': False}, 'zimbra-1.3': {'id': 'Zimbra-1.3', 'deprecated': False}, 'zimbra-1.4': {'id': 'Zimbra-1.4', 'deprecated': False}, 'zlib': {'id': 'Zlib', 'deprecated': False}, 'zlib-acknowledgement': {'id': 'zlib-acknowledgement', 'deprecated': False}, 'zpl-1.1': {'id': 'ZPL-1.1', 'deprecated': False}, 'zpl-2.0': {'id': 'ZPL-2.0', 'deprecated': False}, 'zpl-2.1': {'id': 'ZPL-2.1', 'deprecated': False}, } EXCEPTIONS = { '389-exception': {'id': '389-exception', 'deprecated': False}, 'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0', 'deprecated': False}, 'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0', 'deprecated': False}, 'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False}, 'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False}, 'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False}, 'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False}, 'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False}, 'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False}, 'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception', 'deprecated': False}, 'fltk-exception': {'id': 'FLTK-exception', 'deprecated': False}, 'font-exception-2.0': {'id': 'Font-exception-2.0', 'deprecated': False}, 'freertos-exception-2.0': {'id': 'freertos-exception-2.0', 'deprecated': False}, 'gcc-exception-2.0': {'id': 'GCC-exception-2.0', 'deprecated': False}, 'gcc-exception-3.1': {'id': 'GCC-exception-3.1', 'deprecated': False}, 'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False}, 'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False}, 'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False}, 'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False}, 'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False}, 'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False}, 'libtool-exception': {'id': 'Libtool-exception', 'deprecated': False}, 'linux-syscall-note': {'id': 'Linux-syscall-note', 'deprecated': False}, 'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False}, 'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False}, 'mif-exception': {'id': 'mif-exception', 'deprecated': False}, 'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True}, 'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False}, 'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False}, 'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False}, 'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False}, 'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False}, 'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False}, 'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1', 'deprecated': False}, 'qwt-exception-1.0': {'id': 'Qwt-exception-1.0', 'deprecated': False}, 'shl-2.0': {'id': 'SHL-2.0', 'deprecated': False}, 'shl-2.1': {'id': 'SHL-2.1', 'deprecated': False}, 'swift-exception': {'id': 'Swift-exception', 'deprecated': False}, 'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0', 'deprecated': False}, 'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0', 'deprecated': False}, 'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1', 'deprecated': False}, } hatchling-0.15.0/src/hatchling/metadata/__init__.py0000644000000000000000000000000013615410400020325 0ustar0000000000000000hatchling-0.15.0/src/hatchling/metadata/core.py0000644000000000000000000012465513615410400017545 0ustar0000000000000000import os import re import sys from collections import OrderedDict from copy import deepcopy from ..utils.fs import locate_file # TODO: remove when we drop Python 2 if sys.version_info[0] < 3: # no cov from io import open import toml from ..utils.compat import byteify_object def load_toml(path): with open(path, 'r', encoding='utf-8') as f: # this is to support any `isinstance(metadata_entry, str)` return byteify_object(toml.loads(f.read())) def ensure_string(obj): if not isinstance(obj, bytes): return obj.encode('utf-8') return obj else: import tomli def load_toml(path): with open(path, 'r', encoding='utf-8') as f: return tomli.loads(f.read()) def ensure_string(obj): return obj class ProjectMetadata(object): def __init__(self, root, plugin_manager, config=None): self.root = root self.plugin_manager = plugin_manager self._config = config self._build = None self._core = None self._hatch = None self._version = None self._project_file = None # App already loaded config if config is not None and root is not None: self._project_file = os.path.join(root, 'pyproject.toml') @property def version(self): """ https://www.python.org/dev/peps/pep-0621/#version """ if self._version is None: self._set_version() return self._version @property def config(self): if self._config is None: project_file = locate_file(self.root, 'pyproject.toml') if project_file is None: self._config = {} else: self._project_file = project_file self._config = load_toml(project_file) return self._config @property def build(self): if self._build is None: build_metadata = self.config.get('build-system', {}) if not isinstance(build_metadata, dict): raise TypeError('The `build-system` configuration must be a table') self._build = BuildMetadata(self.root, build_metadata) return self._build @property def core(self): if self._core is None: if 'project' not in self.config: raise ValueError('Missing `project` metadata table in configuration') core_metadata = self.config['project'] if not isinstance(core_metadata, dict): raise TypeError('The `project` configuration must be a table') metadata = CoreMetadata(self.root, core_metadata) metadata_hooks = self.hatch.metadata_hooks if metadata_hooks: self._set_version(metadata) core_metadata['version'] = self.version for metadata_hook in metadata_hooks.values(): metadata_hook.update(core_metadata) self._core = metadata return self._core @property def hatch(self): if self._hatch is None: tool_config = self.config.get('tool', {}) if not isinstance(tool_config, dict): raise TypeError('The `tool` configuration must be a table') hatch_config = tool_config.get('hatch', {}) if not isinstance(hatch_config, dict): raise TypeError('The `tool.hatch` configuration must be a table') if self._project_file is not None: hatch_file = os.path.join(os.path.dirname(self._project_file), 'hatch.toml') else: hatch_file = locate_file(self.root, 'hatch.toml') if hatch_file and os.path.isfile(hatch_file): config = load_toml(hatch_file) hatch_config = hatch_config.copy() hatch_config.update(config) self._hatch = HatchMetadata(self.root, hatch_config, self.plugin_manager) return self._hatch def _set_version(self, core_metadata=None): if core_metadata is None: core_metadata = self.core version = core_metadata.version if version is None: version = self.hatch.version from packaging.version import Version self._version = str(Version(version)) class BuildMetadata(object): """ https://www.python.org/dev/peps/pep-0517/ """ def __init__(self, root, config): self.root = root self.config = config self._requires = None self._requires_complex = None self._build_backend = None self._backend_path = None @property def requires_complex(self): if self._requires_complex is None: from packaging.requirements import InvalidRequirement, Requirement requires = self.config.get('requires', []) if not isinstance(requires, list): raise TypeError('Field `build-system.requires` must be an array') requires_complex = [] for i, entry in enumerate(requires, 1): if not isinstance(entry, str): raise TypeError('Dependency #{} of field `build-system.requires` must be a string'.format(i)) try: requires_complex.append(Requirement(entry)) except InvalidRequirement as e: raise ValueError('Dependency #{} of field `build-system.requires` is invalid: {}'.format(i, e)) self._requires_complex = requires_complex return self._requires_complex @property def requires(self): if self._requires is None: self._requires = [str(r) for r in self.requires_complex] return self._requires @property def build_backend(self): if self._build_backend is None: build_backend = self.config.get('build-backend', '') if not isinstance(build_backend, str): raise TypeError('Field `build-system.build-backend` must be a string') self._build_backend = build_backend return self._build_backend @property def backend_path(self): if self._backend_path is None: backend_path = self.config.get('backend-path', []) if not isinstance(backend_path, list): raise TypeError('Field `build-system.backend-path` must be an array') for i, entry in enumerate(backend_path, 1): if not isinstance(entry, str): raise TypeError('Entry #{} of field `build-system.backend-path` must be a string'.format(i)) self._backend_path = backend_path return self._backend_path class CoreMetadata(object): """ https://www.python.org/dev/peps/pep-0621/ """ def __init__(self, root, config): self.root = root self.config = config self._name = None self._version = None self._description = None self._readme = None self._readme_content_type = None self._readme_path = None self._requires_python = None self._python_constraint = None self._license = None self._license_expression = None self._license_files = None self._authors = None self._authors_data = None self._maintainers = None self._maintainers_data = None self._keywords = None self._classifiers = None self._urls = None self._scripts = None self._gui_scripts = None self._entry_points = None self._dependencies_complex = None self._dependencies = None self._optional_dependencies = None self._dynamic = None @property def name(self): """ https://www.python.org/dev/peps/pep-0621/#name """ if self._name is None: if 'name' in self.dynamic: raise ValueError('Static metadata field `name` cannot be present in field `project.dynamic`') elif 'name' in self.config: name = self.config['name'] else: name = '' if not name: raise ValueError('Missing required field `project.name`') elif not isinstance(name, str): raise TypeError('Field `project.name` must be a string') # https://www.python.org/dev/peps/pep-0508/#names if not re.search('^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$', name, re.IGNORECASE): raise ValueError( 'Required field `project.name` must only contain ASCII letters/digits, ' 'underscores, hyphens, and periods.' ) # https://www.python.org/dev/peps/pep-0503/#normalized-names self._name = re.sub(r'[-_.]+', '-', name).lower() return self._name @property def version(self): """ https://www.python.org/dev/peps/pep-0621/#version """ if self._version is None: if 'version' not in self.config: if 'version' not in self.dynamic: raise ValueError( 'Field `project.version` can only be resolved dynamically ' 'if `version` is in field `project.dynamic`' ) else: if 'version' in self.dynamic: raise ValueError( 'Metadata field `version` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) version = self.config['version'] if not isinstance(version, str): raise TypeError('Field `project.version` must be a string') self._version = version return self._version @property def description(self): """ https://www.python.org/dev/peps/pep-0621/#description """ if self._description is None: if 'description' in self.config: description = self.config['description'] if 'description' in self.dynamic: raise ValueError( 'Metadata field `description` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: description = '' if not isinstance(description, str): raise TypeError('Field `project.description` must be a string') self._description = description return self._description @property def readme(self): """ https://www.python.org/dev/peps/pep-0621/#readme """ if self._readme is None: if 'readme' in self.config: readme = self.config['readme'] if 'readme' in self.dynamic: raise ValueError( 'Metadata field `readme` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: readme = None if readme is None: self._readme = '' self._readme_content_type = 'text/markdown' self._readme_path = '' elif isinstance(readme, str): normalized_path = readme.lower() if normalized_path.endswith('.md'): content_type = 'text/markdown' elif normalized_path.endswith('.rst'): content_type = 'text/x-rst' elif normalized_path.endswith('.txt'): content_type = 'text/plain' else: raise TypeError( 'Unable to determine the content-type based on the extension of readme file: {}'.format(readme) ) readme_path = os.path.normpath(os.path.join(self.root, readme)) if not os.path.isfile(readme_path): raise OSError('Readme file does not exist: {}'.format(readme)) with open(readme_path, 'r', encoding='utf-8') as f: self._readme = f.read() self._readme_content_type = content_type self._readme_path = readme elif isinstance(readme, dict): content_type = readme.get('content-type') if content_type is None: raise ValueError('Field `content-type` is required in the `project.readme` table') elif not isinstance(content_type, str): raise TypeError('Field `content-type` in the `project.readme` table must be a string') elif content_type not in ('text/markdown', 'text/x-rst', 'text/plain'): raise ValueError( 'Field `content-type` in the `project.readme` table must be one of the following: ' 'text/markdown, text/x-rst, text/plain' ) if 'file' in readme and 'text' in readme: raise ValueError('Cannot specify both `file` and `text` in the `project.readme` table') if 'file' in readme: relative_path = readme['file'] if not isinstance(relative_path, str): raise TypeError('Field `file` in the `project.readme` table must be a string') path = os.path.normpath(os.path.join(self.root, relative_path)) if not os.path.isfile(path): raise OSError('Readme file does not exist: {}'.format(relative_path)) with open(path, 'r', encoding=readme.get('charset', 'utf-8')) as f: contents = f.read() readme_path = relative_path elif 'text' in readme: contents = readme['text'] if not isinstance(contents, str): raise TypeError('Field `text` in the `project.readme` table must be a string') readme_path = '' else: raise ValueError('Must specify either `file` or `text` in the `project.readme` table') self._readme = contents self._readme_content_type = content_type self._readme_path = readme_path else: raise TypeError('Field `project.readme` must be a string or a table') self._readme = ensure_string(self._readme) return self._readme @property def readme_content_type(self): """ https://www.python.org/dev/peps/pep-0621/#readme """ if self._readme_content_type is None: _ = self.readme return self._readme_content_type @property def readme_path(self): """ https://www.python.org/dev/peps/pep-0621/#readme """ if self._readme_path is None: _ = self.readme return self._readme_path @property def requires_python(self): """ https://www.python.org/dev/peps/pep-0621/#requires-python """ if self._requires_python is None: from packaging.specifiers import InvalidSpecifier, SpecifierSet if 'requires-python' in self.config: requires_python = self.config['requires-python'] if 'requires-python' in self.dynamic: raise ValueError( 'Metadata field `requires-python` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: requires_python = '' if not isinstance(requires_python, str): raise TypeError('Field `project.requires-python` must be a string') try: self._python_constraint = SpecifierSet(requires_python) except InvalidSpecifier as e: raise ValueError('Field `project.requires-python` is invalid: {}'.format(e)) self._requires_python = str(self._python_constraint) return self._requires_python @property def python_constraint(self): if self._python_constraint is None: _ = self.requires_python return self._python_constraint @property def license(self): """ https://www.python.org/dev/peps/pep-0621/#license """ if self._license is None: if 'license' in self.config: data = self.config['license'] if 'license' in self.dynamic: raise ValueError( 'Metadata field `license` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: data = None if data is None: self._license = '' self._license_expression = '' elif isinstance(data, str): from ..licenses.parse import normalize_license_expression self._license_expression = normalize_license_expression(data) self._license = '' elif isinstance(data, dict): if 'file' in data and 'text' in data: raise ValueError('Cannot specify both `file` and `text` in the `project.license` table') if 'file' in data: relative_path = data['file'] if not isinstance(relative_path, str): raise TypeError('Field `file` in the `project.license` table must be a string') path = os.path.normpath(os.path.join(self.root, relative_path)) if not os.path.isfile(path): raise OSError('License file does not exist: {}'.format(relative_path)) with open(path, 'r', encoding='utf-8') as f: contents = f.read() elif 'text' in data: contents = data['text'] if not isinstance(contents, str): raise TypeError('Field `text` in the `project.license` table must be a string') else: raise ValueError('Must specify either `file` or `text` in the `project.license` table') self._license = contents self._license_expression = '' else: raise TypeError('Field `project.license` must be a string or a table') return self._license @property def license_expression(self): """ https://www.python.org/dev/peps/pep-0639/ """ if self._license_expression is None: _ = self.license return self._license_expression @property def license_files(self): """ https://www.python.org/dev/peps/pep-0639/ """ if self._license_files is None: if 'license-files' not in self.config: data = {'globs': ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']} else: if 'license-files' in self.dynamic: raise ValueError( 'Metadata field `license-files` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) data = self.config['license-files'] if not isinstance(data, dict): raise TypeError('Field `project.license-files` must be a table') elif 'paths' in data and 'globs' in data: raise ValueError('Cannot specify both `paths` and `globs` in the `project.license-files` table') license_files = [] if 'paths' in data: paths = data['paths'] if not isinstance(paths, list): raise TypeError('Field `paths` in the `project.license-files` table must be an array') for i, relative_path in enumerate(paths, 1): if not isinstance(relative_path, str): raise TypeError( 'Entry #{} in field `paths` in the `project.license-files` table must be a string'.format(i) ) path = os.path.normpath(os.path.join(self.root, relative_path)) if not os.path.isfile(path): raise OSError('License file does not exist: {}'.format(relative_path)) license_files.append(os.path.relpath(path, self.root).replace('\\', '/')) elif 'globs' in data: from glob import glob globs = data['globs'] if not isinstance(globs, list): raise TypeError('Field `globs` in the `project.license-files` table must be an array') for i, pattern in enumerate(globs, 1): if not isinstance(pattern, str): raise TypeError( 'Entry #{} in field `globs` in the `project.license-files` table must be a string'.format(i) ) full_pattern = os.path.normpath(os.path.join(self.root, pattern)) for path in glob(full_pattern): if os.path.isfile(path): license_files.append(os.path.relpath(path, self.root).replace('\\', '/')) else: raise ValueError( 'Must specify either `paths` or `globs` in the `project.license-files` table if defined' ) self._license_files = sorted(license_files) return self._license_files @property def authors(self): """ https://www.python.org/dev/peps/pep-0621/#authors-maintainers """ if self._authors is None: if 'authors' in self.config: authors = self.config['authors'] if 'authors' in self.dynamic: raise ValueError( 'Metadata field `authors` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: authors = [] if not isinstance(authors, list): raise TypeError('Field `project.authors` must be an array') try: from email.headerregistry import Address # TODO: remove when we drop Python 2 except ImportError: # no cov Address = ( lambda display_name='', addr_spec='': addr_spec if not display_name else '{} <{}>'.format(display_name, addr_spec) ) authors = deepcopy(authors) authors_data = {'name': [], 'email': []} for i, data in enumerate(authors, 1): if not isinstance(data, dict): raise TypeError('Author #{} of field `project.authors` must be an inline table'.format(i)) name = data.get('name', '') if not isinstance(name, str): raise TypeError('Name of author #{} of field `project.authors` must be a string'.format(i)) email = data.get('email', '') if not isinstance(email, str): raise TypeError('Email of author #{} of field `project.authors` must be a string'.format(i)) if name and email: authors_data['email'].append(str(Address(display_name=name, addr_spec=email))) elif email: authors_data['email'].append(str(Address(addr_spec=email))) elif name: authors_data['name'].append(name) else: raise ValueError( 'Author #{} of field `project.authors` must specify either `name` or `email`'.format(i) ) self._authors = authors self._authors_data = authors_data return self._authors @property def authors_data(self): """ https://www.python.org/dev/peps/pep-0621/#authors-maintainers """ if self._authors_data is None: _ = self.authors return self._authors_data @property def maintainers(self): """ https://www.python.org/dev/peps/pep-0621/#authors-maintainers """ if self._maintainers is None: if 'maintainers' in self.config: maintainers = self.config['maintainers'] if 'maintainers' in self.dynamic: raise ValueError( 'Metadata field `maintainers` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: maintainers = [] if not isinstance(maintainers, list): raise TypeError('Field `project.maintainers` must be an array') try: from email.headerregistry import Address # TODO: remove when we drop Python 2 except ImportError: # no cov Address = ( lambda display_name='', addr_spec='': addr_spec if not display_name else '{} <{}>'.format(display_name, addr_spec) ) maintainers = deepcopy(maintainers) maintainers_data = {'name': [], 'email': []} for i, data in enumerate(maintainers, 1): if not isinstance(data, dict): raise TypeError('Maintainer #{} of field `project.maintainers` must be an inline table'.format(i)) name = data.get('name', '') if not isinstance(name, str): raise TypeError('Name of maintainer #{} of field `project.maintainers` must be a string'.format(i)) email = data.get('email', '') if not isinstance(email, str): raise TypeError('Email of maintainer #{} of field `project.maintainers` must be a string'.format(i)) if name and email: maintainers_data['email'].append(str(Address(display_name=name, addr_spec=email))) elif email: maintainers_data['email'].append(str(Address(addr_spec=email))) elif name: maintainers_data['name'].append(name) else: raise ValueError( 'Maintainer #{} of field `project.maintainers` must specify either `name` or `email`'.format(i) ) self._maintainers = maintainers self._maintainers_data = maintainers_data return self._maintainers @property def maintainers_data(self): """ https://www.python.org/dev/peps/pep-0621/#authors-maintainers """ if self._maintainers_data is None: _ = self.maintainers return self._maintainers_data @property def keywords(self): """ https://www.python.org/dev/peps/pep-0621/#keywords """ if self._keywords is None: if 'keywords' in self.config: keywords = self.config['keywords'] if 'keywords' in self.dynamic: raise ValueError( 'Metadata field `keywords` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: keywords = [] if not isinstance(keywords, list): raise TypeError('Field `project.keywords` must be an array') unique_keywords = set() for i, keyword in enumerate(keywords, 1): if not isinstance(keyword, str): raise TypeError('Keyword #{} of field `project.keywords` must be a string'.format(i)) unique_keywords.add(keyword) self._keywords = sorted(unique_keywords) return self._keywords @property def classifiers(self): """ https://www.python.org/dev/peps/pep-0621/#classifiers """ if self._classifiers is None: if 'classifiers' in self.config: classifiers = self.config['classifiers'] if 'classifiers' in self.dynamic: raise ValueError( 'Metadata field `classifiers` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: classifiers = [] if not isinstance(classifiers, list): raise TypeError('Field `project.classifiers` must be an array') unique_classifiers = set() for i, classifier in enumerate(classifiers, 1): if not isinstance(classifier, str): raise TypeError('Classifier #{} of field `project.classifiers` must be a string'.format(i)) unique_classifiers.add(classifier) self._classifiers = sorted(unique_classifiers) return self._classifiers @property def urls(self): """ https://www.python.org/dev/peps/pep-0621/#urls """ if self._urls is None: if 'urls' in self.config: urls = self.config['urls'] if 'urls' in self.dynamic: raise ValueError( 'Metadata field `urls` cannot be both statically defined and listed in field `project.dynamic`' ) else: urls = {} if not isinstance(urls, dict): raise TypeError('Field `project.urls` must be a table') sorted_urls = OrderedDict() for label, url in sorted(urls.items()): if not isinstance(url, str): raise TypeError('URL `{}` of field `project.urls` must be a string'.format(label)) sorted_urls[label] = url self._urls = sorted_urls return self._urls @property def scripts(self): """ https://www.python.org/dev/peps/pep-0621/#entry-points """ if self._scripts is None: if 'scripts' in self.config: scripts = self.config['scripts'] if 'scripts' in self.dynamic: raise ValueError( 'Metadata field `scripts` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: scripts = {} if not isinstance(scripts, dict): raise TypeError('Field `project.scripts` must be a table') sorted_scripts = OrderedDict() for name, object_ref in sorted(scripts.items()): if not isinstance(object_ref, str): raise TypeError('Object reference `{}` of field `project.scripts` must be a string'.format(name)) sorted_scripts[name] = object_ref self._scripts = sorted_scripts return self._scripts @property def gui_scripts(self): """ https://www.python.org/dev/peps/pep-0621/#entry-points """ if self._gui_scripts is None: if 'gui-scripts' in self.config: gui_scripts = self.config['gui-scripts'] if 'gui-scripts' in self.dynamic: raise ValueError( 'Metadata field `gui-scripts` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: gui_scripts = {} if not isinstance(gui_scripts, dict): raise TypeError('Field `project.gui-scripts` must be a table') sorted_gui_scripts = OrderedDict() for name, object_ref in sorted(gui_scripts.items()): if not isinstance(object_ref, str): raise TypeError( 'Object reference `{}` of field `project.gui-scripts` must be a string'.format(name) ) sorted_gui_scripts[name] = object_ref self._gui_scripts = sorted_gui_scripts return self._gui_scripts @property def entry_points(self): """ https://www.python.org/dev/peps/pep-0621/#entry-points """ if self._entry_points is None: if 'entry-points' in self.config: defined_entry_point_groups = self.config['entry-points'] if 'entry-points' in self.dynamic: raise ValueError( 'Metadata field `entry-points` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: defined_entry_point_groups = {} if not isinstance(defined_entry_point_groups, dict): raise TypeError('Field `project.entry-points` must be a table') for forbidden_field in ('scripts', 'gui-scripts'): if forbidden_field in defined_entry_point_groups: raise ValueError( 'Field `{0}` must be defined as `project.{0}` instead of in ' 'the `project.entry-points` table'.format(forbidden_field) ) entry_point_groups = OrderedDict() for group, entry_point_data in sorted(defined_entry_point_groups.items()): if not isinstance(entry_point_data, dict): raise TypeError('Field `project.entry-points.{}` must be a table'.format(group)) entry_points = OrderedDict() for name, object_ref in sorted(entry_point_data.items()): if not isinstance(object_ref, str): raise TypeError( 'Object reference `{}` of field `project.entry-points.{}` ' 'must be a string'.format(name, group) ) entry_points[name] = object_ref if entry_points: entry_point_groups[group] = entry_points self._entry_points = entry_point_groups return self._entry_points @property def dependencies_complex(self): """ https://www.python.org/dev/peps/pep-0621/#dependencies-optional-dependencies """ if self._dependencies_complex is None: from packaging.requirements import InvalidRequirement, Requirement if 'dependencies' in self.config: dependencies = self.config['dependencies'] if 'dependencies' in self.dynamic: raise ValueError( 'Metadata field `dependencies` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: dependencies = [] if not isinstance(dependencies, list): raise TypeError('Field `project.dependencies` must be an array') dependencies_complex = [] for i, entry in enumerate(dependencies, 1): if not isinstance(entry, str): raise TypeError('Dependency #{} of field `project.dependencies` must be a string'.format(i)) try: dependencies_complex.append(Requirement(entry)) except InvalidRequirement as e: raise ValueError('Dependency #{} of field `project.dependencies` is invalid: {}'.format(i, e)) self._dependencies_complex = dependencies_complex return self._dependencies_complex @property def dependencies(self): """ https://www.python.org/dev/peps/pep-0621/#dependencies-optional-dependencies """ if self._dependencies is None: self._dependencies = sorted(map(str, self.dependencies_complex), key=lambda d: d.lower()) return self._dependencies @property def optional_dependencies(self): """ https://www.python.org/dev/peps/pep-0621/#dependencies-optional-dependencies """ if self._optional_dependencies is None: from packaging.requirements import InvalidRequirement, Requirement if 'optional-dependencies' in self.config: optional_dependencies = self.config['optional-dependencies'] if 'optional-dependencies' in self.dynamic: raise ValueError( 'Metadata field `optional-dependencies` cannot be both statically defined and ' 'listed in field `project.dynamic`' ) else: optional_dependencies = {} if not isinstance(optional_dependencies, dict): raise TypeError('Field `project.optional-dependencies` must be a table') optional_dependency_entries = OrderedDict() for option, dependencies in sorted(optional_dependencies.items()): if not isinstance(dependencies, list): raise TypeError( 'Dependencies for option `{}` of field `project.optional-dependencies` ' 'must be an array'.format(option) ) entries = [] for i, entry in enumerate(dependencies, 1): if not isinstance(entry, str): raise TypeError( 'Dependency #{} of option `{}` of field `project.optional-dependencies` ' 'must be a string'.format(i, option) ) try: Requirement(entry) except InvalidRequirement as e: raise ValueError( 'Dependency #{} of option `{}` of field `project.optional-dependencies` ' 'is invalid: {}'.format(i, option, e) ) else: entries.append(entry) optional_dependency_entries[option] = sorted(entries, key=lambda s: s.lower()) self._optional_dependencies = optional_dependency_entries return self._optional_dependencies @property def dynamic(self): """ https://www.python.org/dev/peps/pep-0621/#dynamic """ if self._dynamic is None: dynamic = self.config.get('dynamic', []) if not isinstance(dynamic, list): raise TypeError('Field `project.dynamic` must be an array') for i, field in enumerate(dynamic, 1): if not isinstance(field, str): raise TypeError('Field #{} of field `project.dynamic` must be a string'.format(i)) self._dynamic = dynamic return self._dynamic def validate_fields(self): # Trigger validation for everything for attribute in dir(self): getattr(self, attribute) class HatchMetadata(object): def __init__(self, root, config, plugin_manager): self.root = root self.config = config self.plugin_manager = plugin_manager self._metadata_config = None self._metadata_hooks = None self._build_config = None self._build_targets = None self._version_source = None self._version = None @property def metadata_config(self): if self._metadata_config is None: metadata_config = self.config.get('metadata', {}) if not isinstance(metadata_config, dict): raise TypeError('Field `tool.hatch.metadata` must be a table') self._metadata_config = metadata_config return self._metadata_config @property def metadata_hooks(self): if self._metadata_hooks is None: hook_config = self.metadata_config configured_metadata_hooks = OrderedDict() for hook_name, config in hook_config.items(): metadata_hook = self.plugin_manager.metadata_hook.get(hook_name) if metadata_hook is None: raise ValueError('Unknown metadata hook: {}'.format(hook_name)) configured_metadata_hooks[hook_name] = metadata_hook(self.root, config) self._metadata_hooks = configured_metadata_hooks return self._metadata_hooks @property def build_config(self): if self._build_config is None: build_config = self.config.get('build', {}) if not isinstance(build_config, dict): raise TypeError('Field `tool.hatch.build` must be a table') self._build_config = build_config return self._build_config @property def build_targets(self): if self._build_targets is None: build_targets = self.build_config.get('targets', {}) if not isinstance(build_targets, dict): raise TypeError('Field `tool.hatch.build.targets` must be a table') self._build_targets = build_targets return self._build_targets @property def version(self): if self._version is None: try: self._version = self.version_source.get_version_data()['version'] except Exception as e: raise type(e)( 'Error getting the version from source `{}`: {}'.format(self.version_source.PLUGIN_NAME, e) ) # TODO: from None return self._version @property def version_source(self): if self._version_source is None: if 'version' not in self.config: raise ValueError('Missing `tool.hatch.version` configuration') options = self.config['version'] if not isinstance(options, dict): raise TypeError('Field `tool.hatch.version` must be a table') options = deepcopy(options) source = options.get('source', 'regex') if not source: raise ValueError( 'The `source` option under the `tool.hatch.version` table must not be empty if defined' ) elif not isinstance(source, str): raise TypeError('Field `tool.hatch.version.source` must be a string') version_source = self.plugin_manager.version_source.get(source) if version_source is None: raise ValueError('Unknown version source: {}'.format(source)) self._version_source = version_source(self.root, options) return self._version_source hatchling-0.15.0/src/hatchling/metadata/custom.py0000644000000000000000000000210513615410400020110 0ustar0000000000000000import os from ..plugin.utils import load_plugin_from_script from .plugin.interface import MetadataHookInterface class CustomMetadataHook(object): PLUGIN_NAME = 'custom' def __new__(cls, root, config, *args, **kwargs): build_script = config.get('path', 'hatch_build.py') if not isinstance(build_script, str): raise TypeError('Option `path` for metadata hook `{}` must be a string'.format(cls.PLUGIN_NAME)) elif not build_script: raise ValueError( 'Option `path` for metadata hook `{}` must not be empty if defined'.format(cls.PLUGIN_NAME) ) path = os.path.normpath(os.path.join(root, build_script)) if not os.path.isfile(path): raise OSError('Build script does not exist: {}'.format(build_script)) hook_class = load_plugin_from_script(path, build_script, MetadataHookInterface, 'metadata_hook') hook = hook_class(root, config, *args, **kwargs) # Always keep the name to avoid confusion hook.PLUGIN_NAME = cls.PLUGIN_NAME return hook hatchling-0.15.0/src/hatchling/metadata/utils.py0000644000000000000000000002611413615410400017744 0ustar0000000000000000DEFAULT_METADATA_VERSION = '2.1' def get_core_metadata_constructors(): """ https://packaging.python.org/specifications/core-metadata/ """ return { '1.2': construct_metadata_file_1_2, '2.1': construct_metadata_file_2_1, '2.2': construct_metadata_file_2_2, '2.3': construct_metadata_file_2_3, } def construct_metadata_file_1_2(metadata, extra_dependencies=()): """ https://www.python.org/dev/peps/pep-0345/ """ metadata_file = 'Metadata-Version: 1.2\n' metadata_file += 'Name: {}\n'.format(metadata.core.name) metadata_file += 'Version: {}\n'.format(metadata.version) if metadata.core.description: metadata_file += 'Summary: {}\n'.format(metadata.core.description) if metadata.core.urls: for label, url in metadata.core.urls.items(): metadata_file += 'Project-URL: {}, {}\n'.format(label, url) authors_data = metadata.core.authors_data if authors_data['name']: metadata_file += 'Author: {}\n'.format(', '.join(authors_data['name'])) if authors_data['email']: metadata_file += 'Author-email: {}\n'.format(', '.join(authors_data['email'])) maintainers_data = metadata.core.maintainers_data if maintainers_data['name']: metadata_file += 'Maintainer: {}\n'.format(', '.join(maintainers_data['name'])) if maintainers_data['email']: metadata_file += 'Maintainer-email: {}\n'.format(', '.join(maintainers_data['email'])) if metadata.core.license: license_start = 'License: ' indent = ' ' * (len(license_start) - 1) metadata_file += license_start for i, line in enumerate(metadata.core.license.splitlines()): if i == 0: metadata_file += '{}\n'.format(line) else: metadata_file += '{}{}\n'.format(indent, line) if metadata.core.keywords: metadata_file += 'Keywords: {}\n'.format(','.join(metadata.core.keywords)) if metadata.core.classifiers: for classifier in metadata.core.classifiers: metadata_file += 'Classifier: {}\n'.format(classifier) if metadata.core.requires_python: metadata_file += 'Requires-Python: {}\n'.format(metadata.core.requires_python) if metadata.core.dependencies: for dependency in metadata.core.dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if extra_dependencies: for dependency in extra_dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) return metadata_file def construct_metadata_file_2_1(metadata, extra_dependencies=()): """ https://www.python.org/dev/peps/pep-0566/ """ metadata_file = 'Metadata-Version: 2.1\n' metadata_file += 'Name: {}\n'.format(metadata.core.name) metadata_file += 'Version: {}\n'.format(metadata.version) if metadata.core.description: metadata_file += 'Summary: {}\n'.format(metadata.core.description) if metadata.core.urls: for label, url in metadata.core.urls.items(): metadata_file += 'Project-URL: {}, {}\n'.format(label, url) authors_data = metadata.core.authors_data if authors_data['name']: metadata_file += 'Author: {}\n'.format(', '.join(authors_data['name'])) if authors_data['email']: metadata_file += 'Author-email: {}\n'.format(', '.join(authors_data['email'])) maintainers_data = metadata.core.maintainers_data if maintainers_data['name']: metadata_file += 'Maintainer: {}\n'.format(', '.join(maintainers_data['name'])) if maintainers_data['email']: metadata_file += 'Maintainer-email: {}\n'.format(', '.join(maintainers_data['email'])) if metadata.core.license: license_start = 'License: ' indent = ' ' * (len(license_start) - 1) metadata_file += license_start for i, line in enumerate(metadata.core.license.splitlines()): if i == 0: metadata_file += '{}\n'.format(line) else: metadata_file += '{}{}\n'.format(indent, line) if metadata.core.keywords: metadata_file += 'Keywords: {}\n'.format(','.join(metadata.core.keywords)) if metadata.core.classifiers: for classifier in metadata.core.classifiers: metadata_file += 'Classifier: {}\n'.format(classifier) if metadata.core.requires_python: metadata_file += 'Requires-Python: {}\n'.format(metadata.core.requires_python) if metadata.core.dependencies: for dependency in metadata.core.dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if extra_dependencies: for dependency in extra_dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if metadata.core.optional_dependencies: for option, dependencies in metadata.core.optional_dependencies.items(): metadata_file += 'Provides-Extra: {}\n'.format(option) for dependency in dependencies: if ';' in dependency: metadata_file += 'Requires-Dist: {} and extra == "{}"\n'.format(dependency, option) else: metadata_file += 'Requires-Dist: {}; extra == "{}"\n'.format(dependency, option) if metadata.core.readme: metadata_file += 'Description-Content-Type: {}\n'.format(metadata.core.readme_content_type) metadata_file += '\n{}'.format(metadata.core.readme) return metadata_file def construct_metadata_file_2_2(metadata, extra_dependencies=()): """ https://www.python.org/dev/peps/pep-0643/ """ metadata_file = 'Metadata-Version: 2.2\n' metadata_file += 'Name: {}\n'.format(metadata.core.name) metadata_file += 'Version: {}\n'.format(metadata.version) if metadata.core.description: metadata_file += 'Summary: {}\n'.format(metadata.core.description) if metadata.core.urls: for label, url in metadata.core.urls.items(): metadata_file += 'Project-URL: {}, {}\n'.format(label, url) authors_data = metadata.core.authors_data if authors_data['name']: metadata_file += 'Author: {}\n'.format(', '.join(authors_data['name'])) if authors_data['email']: metadata_file += 'Author-email: {}\n'.format(', '.join(authors_data['email'])) maintainers_data = metadata.core.maintainers_data if maintainers_data['name']: metadata_file += 'Maintainer: {}\n'.format(', '.join(maintainers_data['name'])) if maintainers_data['email']: metadata_file += 'Maintainer-email: {}\n'.format(', '.join(maintainers_data['email'])) if metadata.core.license: license_start = 'License: ' indent = ' ' * (len(license_start) - 1) metadata_file += license_start for i, line in enumerate(metadata.core.license.splitlines()): if i == 0: metadata_file += '{}\n'.format(line) else: metadata_file += '{}{}\n'.format(indent, line) if metadata.core.keywords: metadata_file += 'Keywords: {}\n'.format(','.join(metadata.core.keywords)) if metadata.core.classifiers: for classifier in metadata.core.classifiers: metadata_file += 'Classifier: {}\n'.format(classifier) if metadata.core.requires_python: metadata_file += 'Requires-Python: {}\n'.format(metadata.core.requires_python) if metadata.core.dependencies: for dependency in metadata.core.dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if extra_dependencies: for dependency in extra_dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if metadata.core.optional_dependencies: for option, dependencies in metadata.core.optional_dependencies.items(): metadata_file += 'Provides-Extra: {}\n'.format(option) for dependency in dependencies: if ';' in dependency: metadata_file += 'Requires-Dist: {} and extra == "{}"\n'.format(dependency, option) else: metadata_file += 'Requires-Dist: {}; extra == "{}"\n'.format(dependency, option) if metadata.core.readme: metadata_file += 'Description-Content-Type: {}\n'.format(metadata.core.readme_content_type) metadata_file += '\n{}'.format(metadata.core.readme) return metadata_file def construct_metadata_file_2_3(metadata, extra_dependencies=()): """ https://www.python.org/dev/peps/pep-0639/ """ metadata_file = 'Metadata-Version: 2.3\n' metadata_file += 'Name: {}\n'.format(metadata.core.name) metadata_file += 'Version: {}\n'.format(metadata.version) if metadata.core.description: metadata_file += 'Summary: {}\n'.format(metadata.core.description) if metadata.core.urls: for label, url in metadata.core.urls.items(): metadata_file += 'Project-URL: {}, {}\n'.format(label, url) authors_data = metadata.core.authors_data if authors_data['name']: metadata_file += 'Author: {}\n'.format(', '.join(authors_data['name'])) if authors_data['email']: metadata_file += 'Author-email: {}\n'.format(', '.join(authors_data['email'])) maintainers_data = metadata.core.maintainers_data if maintainers_data['name']: metadata_file += 'Maintainer: {}\n'.format(', '.join(maintainers_data['name'])) if maintainers_data['email']: metadata_file += 'Maintainer-email: {}\n'.format(', '.join(maintainers_data['email'])) if metadata.core.license_expression: metadata_file += 'License-Expression: {}\n'.format(metadata.core.license_expression) if metadata.core.license_files: for license_file in metadata.core.license_files: metadata_file += 'License-File: {}\n'.format(license_file) if metadata.core.keywords: metadata_file += 'Keywords: {}\n'.format(','.join(metadata.core.keywords)) if metadata.core.classifiers: for classifier in metadata.core.classifiers: metadata_file += 'Classifier: {}\n'.format(classifier) if metadata.core.requires_python: metadata_file += 'Requires-Python: {}\n'.format(metadata.core.requires_python) if metadata.core.dependencies: for dependency in metadata.core.dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if extra_dependencies: for dependency in extra_dependencies: metadata_file += 'Requires-Dist: {}\n'.format(dependency) if metadata.core.optional_dependencies: for option, dependencies in metadata.core.optional_dependencies.items(): metadata_file += 'Provides-Extra: {}\n'.format(option) for dependency in dependencies: if ';' in dependency: metadata_file += 'Requires-Dist: {} and extra == "{}"\n'.format(dependency, option) else: metadata_file += 'Requires-Dist: {}; extra == "{}"\n'.format(dependency, option) if metadata.core.readme: metadata_file += 'Description-Content-Type: {}\n'.format(metadata.core.readme_content_type) metadata_file += '\n{}'.format(metadata.core.readme) return metadata_file hatchling-0.15.0/src/hatchling/metadata/plugin/__init__.py0000644000000000000000000000000013615410400021623 0ustar0000000000000000hatchling-0.15.0/src/hatchling/metadata/plugin/hooks.py0000644000000000000000000000022513615410400021220 0ustar0000000000000000from ...plugin import hookimpl from ..custom import CustomMetadataHook @hookimpl def hatch_register_metadata_hook(): return CustomMetadataHook hatchling-0.15.0/src/hatchling/metadata/plugin/interface.py0000644000000000000000000000316213615410400022040 0ustar0000000000000000class MetadataHookInterface(object): # no cov """ Example usage: === ":octicons-file-code-16: plugin.py" ```python from hatchling.metadata.plugin.interface import MetadataHookInterface class SpecialMetadataHook(MetadataHookInterface): PLUGIN_NAME = 'special' ... ``` === ":octicons-file-code-16: hooks.py" ```python from hatchling.plugin import hookimpl from .plugin import SpecialMetadataHook @hookimpl def hatch_register_metadata_hook(): return SpecialMetadataHook ``` """ PLUGIN_NAME = '' """The name used for selection.""" def __init__(self, root, config): self.__root = root self.__config = config @property def root(self): """ The root of the project tree. """ return self.__root @property def config(self): """ The hook configuration. === ":octicons-file-code-16: pyproject.toml" ```toml [tool.hatch.metadata.] ``` === ":octicons-file-code-16: hatch.toml" ```toml [metadata.] ``` """ return self.__config def update(self, metadata): """ :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right: This updates the metadata mapping of the `project` table in-place. If you are setting a field that has been listed as dynamic, you must also remove it from the list. """ raise NotImplementedError hatchling-0.15.0/src/hatchling/plugin/__init__.py0000644000000000000000000000007113615410400020053 0ustar0000000000000000import pluggy hookimpl = pluggy.HookimplMarker('hatch') hatchling-0.15.0/src/hatchling/plugin/manager.py0000644000000000000000000000574013615410400017736 0ustar0000000000000000from collections import OrderedDict import pluggy class PluginManager(object): def __init__(self): self.manager = pluggy.PluginManager('hatch') self.third_party_plugins = ThirdPartyPlugins(self.manager) self.initialized = False def initialize(self): from . import specs self.manager.add_hookspecs(specs) def __getattr__(self, name): if not self.initialized: self.initialize() self.initialized = True hook_name = 'hatch_register_{}'.format(name) getattr(self, hook_name, None)() register = ClassRegister(getattr(self.manager.hook, hook_name), 'PLUGIN_NAME', self.third_party_plugins) setattr(self, name, register) return register def hatch_register_version_source(self): from ..version.source.plugin import hooks self.manager.register(hooks) def hatch_register_builder(self): from ..builders.plugin import hooks self.manager.register(hooks) def hatch_register_build_hook(self): from ..builders.hooks.plugin import hooks self.manager.register(hooks) def hatch_register_metadata_hook(self): from ..metadata.plugin import hooks self.manager.register(hooks) class ClassRegister: def __init__(self, registration_method, identifier, third_party_plugins): self.registration_method = registration_method self.identifier = identifier self.third_party_plugins = third_party_plugins def collect(self, include_third_party=True): if include_third_party and not self.third_party_plugins.loaded: self.third_party_plugins.load() classes = OrderedDict() for registered_classes in self.registration_method(): if not isinstance(registered_classes, list): registered_classes = [registered_classes] for registered_class in registered_classes: name = getattr(registered_class, self.identifier, None) if not name: # no cov raise ValueError('Class `{}` does not have a {} attribute.'.format(registered_class.__name__, name)) elif name in classes: # no cov raise ValueError( 'Class `{}` defines its name as `{}` but that name is already used by ' '`{}`.'.format(registered_class.__name__, name, classes[name].__name__) ) classes[name] = registered_class return classes def get(self, name): if not self.third_party_plugins.loaded: classes = self.collect(include_third_party=False) if name in classes: return classes[name] return self.collect().get(name) class ThirdPartyPlugins(object): def __init__(self, manager): self.manager = manager self.loaded = False def load(self): self.manager.load_setuptools_entrypoints('hatch') self.loaded = True hatchling-0.15.0/src/hatchling/plugin/specs.py0000644000000000000000000000102513615410400017431 0ustar0000000000000000import pluggy hookspec = pluggy.HookspecMarker('hatch') @hookspec def hatch_register_version_source(): """Register new classes that adhere to the version source interface.""" @hookspec def hatch_register_builder(): """Register new classes that adhere to the builder interface.""" @hookspec def hatch_register_build_hook(): """Register new classes that adhere to the build hook interface.""" @hookspec def hatch_register_metadata_hook(): """Register new classes that adhere to the metadata hook interface.""" hatchling-0.15.0/src/hatchling/plugin/utils.py0000644000000000000000000000507713615410400017467 0ustar0000000000000000import sys if sys.version_info[0] >= 3: def load_plugin_from_script(path, script_name, plugin_class, plugin_id): import importlib spec = importlib.util.spec_from_file_location(script_name, path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) plugin_finder = 'get_{}'.format(plugin_id) names = dir(module) if plugin_finder in names: return getattr(module, plugin_finder)() subclasses = [] for name in names: obj = getattr(module, name) if obj is plugin_class: continue try: if issubclass(obj, plugin_class): subclasses.append(obj) except TypeError: continue if not subclasses: raise ValueError( 'Unable to find a subclass of `{}` in `{}`: {}'.format(plugin_class.__name__, script_name, path) ) elif len(subclasses) > 1: raise ValueError( 'Multiple subclasses of `{}` found in `{}`, select one by defining a function named `{}`: {}'.format( plugin_class.__name__, script_name, plugin_finder, path ) ) else: return subclasses[0] else: # no cov def load_plugin_from_script(path, script_name, plugin_class, plugin_id): from io import open with open(path, 'r', encoding='utf-8') as f: compiled = compile(f.read(), filename=script_name, mode='exec') local_variables = {} exec(compiled, globals(), local_variables) plugin_finder = 'get_{}'.format(plugin_id) if plugin_finder in local_variables: return local_variables[plugin_finder]() subclasses = [] for obj in local_variables.values(): if obj is plugin_class: continue try: if issubclass(obj, plugin_class): subclasses.append(obj) except TypeError: continue if not subclasses: raise ValueError( 'Unable to find a subclass of `{}` in `{}`: {}'.format(plugin_class.__name__, script_name, path) ) elif len(subclasses) > 1: raise ValueError( 'Multiple subclasses of `{}` found in `{}`, select one by defining a function named `{}`: {}'.format( plugin_class.__name__, script_name, plugin_finder, path ) ) else: return subclasses[0] hatchling-0.15.0/src/hatchling/utils/__init__.py0000644000000000000000000000000013615410400017705 0ustar0000000000000000hatchling-0.15.0/src/hatchling/utils/compat.py0000644000000000000000000000113313615410400017441 0ustar0000000000000000import json # https://stackoverflow.com/a/33571117/5854007 def byteify_object(obj): json_text = json.dumps(obj) return _byteify(json.loads(json_text, object_hook=_byteify), ignore_dicts=True) def _byteify(data, ignore_dicts=False): if isinstance(data, unicode): return data.encode('utf-8') if isinstance(data, list): return [_byteify(item, ignore_dicts=True) for item in data] if isinstance(data, dict) and not ignore_dicts: return {_byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) for key, value in data.iteritems()} return data hatchling-0.15.0/src/hatchling/utils/fs.py0000644000000000000000000000044613615410400016574 0ustar0000000000000000import os def locate_file(root, file_name): while True: file_path = os.path.join(root, file_name) if os.path.isfile(file_path): return file_path new_root = os.path.dirname(root) if new_root == root: return root = new_root hatchling-0.15.0/src/hatchling/version/__init__.py0000644000000000000000000000000013615410400020232 0ustar0000000000000000hatchling-0.15.0/src/hatchling/version/source/__init__.py0000644000000000000000000000000013615410400021532 0ustar0000000000000000hatchling-0.15.0/src/hatchling/version/source/code.py0000644000000000000000000000234413615410400020722 0ustar0000000000000000import os from io import open from .plugin.interface import VersionSourceInterface class CodeSource(VersionSourceInterface): PLUGIN_NAME = 'code' def get_version_data(self): relative_path = self.config.get('path') if not relative_path: raise ValueError('option `path` must be specified') elif not isinstance(relative_path, str): raise TypeError('option `path` must be a string') path = os.path.normpath(os.path.join(self.root, relative_path)) if not os.path.isfile(path): raise OSError('file does not exist: {}'.format(relative_path)) expression = self.config.get('expression') or '__version__' if not isinstance(expression, str): raise TypeError('option `expression` must be a string') with open(path, 'r', encoding='utf-8') as f: contents = f.read() global_variables = {} # Load the file exec(contents, global_variables) # Execute the expression to determine the version version = eval(expression, global_variables) return {'version': version} def set_version(self, version, version_data): raise NotImplementedError('Cannot rewrite loaded code') hatchling-0.15.0/src/hatchling/version/source/regex.py0000644000000000000000000000345313615410400021124 0ustar0000000000000000import os import re from io import open from .plugin.interface import VersionSourceInterface DEFAULT_PATTERN = r'^(__version__|VERSION) *= *([\'"])v?(?P.+?)\2' class RegexSource(VersionSourceInterface): PLUGIN_NAME = 'regex' def get_version_data(self): relative_path = self.config.get('path') if not relative_path: raise ValueError('option `path` must be specified') elif not isinstance(relative_path, str): raise TypeError('option `path` must be a string') path = os.path.normpath(os.path.join(self.root, relative_path)) if not os.path.isfile(path): raise OSError('file does not exist: {}'.format(relative_path)) pattern = self.config.get('pattern') or DEFAULT_PATTERN if not isinstance(pattern, str): raise TypeError('option `pattern` must be a string') with open(path, 'r', encoding='utf-8') as f: contents = f.read() match = re.search(pattern, contents, flags=re.MULTILINE) if not match: raise ValueError('unable to parse the version from the file: {}'.format(relative_path)) groups = match.groupdict() if 'version' not in groups: raise ValueError('no group named `version` was defined in the pattern') return {'version': groups['version'], 'file_contents': contents, 'version_location': match.span('version')} def set_version(self, version, version_data): file_contents = version_data['file_contents'] start, end = version_data['version_location'] with open(self.config['path'], 'w', encoding='utf-8') as f: # TODO: f.write(f'{file_contents[:start]}{version}{file_contents[end:]}') f.write('{}{}{}'.format(file_contents[:start], version, file_contents[end:])) hatchling-0.15.0/src/hatchling/version/source/plugin/__init__.py0000644000000000000000000000000013615410400023030 0ustar0000000000000000hatchling-0.15.0/src/hatchling/version/source/plugin/hooks.py0000644000000000000000000000026413615410400022430 0ustar0000000000000000from ....plugin import hookimpl from ..code import CodeSource from ..regex import RegexSource @hookimpl def hatch_register_version_source(): return [CodeSource, RegexSource] hatchling-0.15.0/src/hatchling/version/source/plugin/interface.py0000644000000000000000000000405713615410400023251 0ustar0000000000000000class VersionSourceInterface(object): # no cov """ Example usage: === ":octicons-file-code-16: plugin.py" ```python from hatchling.version.source.plugin.interface import VersionSourceInterface class SpecialVersionSource(VersionSourceInterface): PLUGIN_NAME = 'special' ... ``` === ":octicons-file-code-16: hooks.py" ```python from hatchling.plugin import hookimpl from .plugin import SpecialVersionSource @hookimpl def hatch_register_version_source(): return SpecialVersionSource ``` """ PLUGIN_NAME = '' """The name used for selection.""" def __init__(self, root, config): self.__root = root self.__config = config @property def root(self): """ The root of the project tree as a string. """ return self.__root @property def config(self): """ === ":octicons-file-code-16: pyproject.toml" ```toml [tool.hatch.version] ``` === ":octicons-file-code-16: hatch.toml" ```toml [version] ``` """ return self.__config def get_version_data(self): """ :material-align-horizontal-left: **REQUIRED** :material-align-horizontal-right: This should return a mapping with a `version` key representing the current version of the project and will be displayed when invoking the [`version`](../cli/reference.md#hatch-version) command without any arguments. The mapping can contain anything else and will be passed to [set_version](version-source.md#hatchling.version.source.plugin.interface.VersionSourceInterface.set_version) when updating the version. """ raise NotImplementedError def set_version(self, version, version_data): """ This should update the version to the first argument with the data provided during retrieval. """ raise NotImplementedError hatchling-0.15.0/tests/__init__.py0000644000000000000000000000000013615410400015157 0ustar0000000000000000hatchling-0.15.0/tests/downstream/integrate.py0000644000000000000000000002131713615410400017603 0ustar0000000000000000import errno import json import os import platform import shutil import stat import subprocess import sys import tempfile from contextlib import contextmanager from zipfile import ZipFile import requests import toml from packaging.requirements import Requirement from packaging.specifiers import SpecifierSet from virtualenv import cli_run try: from shutil import which except ImportError: from distutils import spawn which = spawn.find_executable # type: ignore HERE = os.path.dirname(os.path.abspath(__file__)) ON_WINDOWS = platform.system() == 'Windows' def handle_remove_readonly(func, path, exc): # no cov # PermissionError: [WinError 5] Access is denied: '...\\.git\\...' if func in (os.rmdir, os.remove, os.unlink) and exc[1].errno == errno.EACCES: os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) func(path) else: raise class EnvVars(dict): def __init__(self, env_vars=None, ignore=None): super(EnvVars, self).__init__(os.environ) self.old_env = dict(self) if env_vars is not None: self.update(env_vars) if ignore is not None: for env_var in ignore: self.pop(env_var, None) def __enter__(self): os.environ.clear() os.environ.update(self) def __exit__(self, exc_type, exc_value, traceback): os.environ.clear() os.environ.update(self.old_env) def python_version_supported(project_config): requires_python = project_config['project'].get('requires-python', '') if requires_python: python_constraint = SpecifierSet(requires_python) if not python_constraint.contains(str('.'.join(map(str, sys.version_info[:2])))): return False return True def download_file(url, file_name): response = requests.get(url, stream=True) with open(file_name, 'wb') as f: for chunk in response.iter_content(16384): f.write(chunk) @contextmanager def temp_dir(): d = tempfile.mkdtemp() try: d = os.path.realpath(d) yield d finally: shutil.rmtree(d, ignore_errors=False, onerror=handle_remove_readonly) def main(): original_backend_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(HERE))), 'backend') with temp_dir() as links_dir, temp_dir() as build_dir: print('<<<<< Copying backend >>>>>') backend_path = os.path.join(build_dir, 'backend') shutil.copytree(original_backend_path, backend_path) # Increment the minor version version_file = os.path.join(backend_path, 'src', 'hatchling', '__about__.py') with open(version_file, 'r') as f: lines = f.readlines() for i, line in enumerate(lines): if line.startswith('__version__'): version = line.strip().split(' = ')[1].strip('\'"') version_parts = version.split('.') version_parts[1] = str(int(version_parts[1]) + 1) lines[i] = line.replace(version, '.'.join(version_parts)) break else: raise ValueError('No version found') with open(version_file, 'w') as f: f.writelines(lines) print('<<<<< Building backend >>>>>') subprocess.check_call([sys.executable, '-m', 'build', '--wheel', '-o', links_dir, backend_path]) subprocess.check_call( [ sys.executable, '-m', 'pip', 'download', '-q', '--disable-pip-version-check', '--no-python-version-warning', '-d', links_dir, os.path.join(links_dir, os.listdir(links_dir)[0]), ] ) for project in os.listdir(HERE): project_dir = os.path.join(HERE, project) if not os.path.isdir(project_dir): continue print('<<<<< Project: {} >>>>>'.format(project)) project_config = {} potential_project_file = os.path.join(project_dir, 'pyproject.toml') # Not yet ported if os.path.isfile(potential_project_file): with open(potential_project_file, 'r') as f: project_config.update(toml.loads(f.read())) if not python_version_supported(project_config): print('--> Unsupported version of Python, skipping') continue with open(os.path.join(project_dir, 'data.json'), 'r') as f: test_data = json.loads(f.read()) with temp_dir() as d: if 'repo_url' in test_data: print('--> Cloning repository') repo_dir = os.path.join(d, 'repo') subprocess.check_call(['git', 'clone', '-q', '--depth', '1', test_data['repo_url'], repo_dir]) else: archive_name = '{}.zip'.format(project) archive_path = os.path.join(d, archive_name) print('--> Downloading archive') download_file(test_data['archive_url'], archive_path) with ZipFile(archive_path) as zip_file: zip_file.extractall(d) entries = os.listdir(d) entries.remove(archive_name) repo_dir = os.path.join(d, entries[0]) project_file = os.path.join(repo_dir, 'pyproject.toml') if project_config: shutil.copyfile(potential_project_file, project_file) else: if not os.path.isfile(project_file): sys.exit('--> Missing file: pyproject.toml') with open(project_file, 'r') as f: project_config.update(toml.loads(f.read())) for requirement in project_config.get('build-system', {}).get('requires', []): if Requirement(requirement).name == 'hatchling': break else: sys.exit('--> Field `build-system.requires` must specify `hatchling` as a requirement') if not python_version_supported(project_config): print('--> Unsupported version of Python, skipping') continue for file_name in ('MANIFEST.in', 'setup.cfg', 'setup.py'): possible_path = os.path.join(repo_dir, file_name) if os.path.isfile(possible_path): os.remove(possible_path) venv_dir = os.path.join(d, '.venv') print('--> Creating virtual environment') cli_run([venv_dir, '--no-download', '--no-periodic-update']) env_vars = dict(test_data.get('env_vars', {})) env_vars['VIRTUAL_ENV'] = venv_dir env_vars['PATH'] = '{}{}{}'.format( os.path.join(venv_dir, 'Scripts' if ON_WINDOWS else 'bin'), os.pathsep, os.environ['PATH'] ) with EnvVars(env_vars, ignore=('__PYVENV_LAUNCHER__', 'PYTHONHOME')): print('--> Installing project') subprocess.check_call( [ which('pip'), 'install', '-q', '--disable-pip-version-check', '--no-python-version-warning', '--find-links', links_dir, '--no-deps', repo_dir, ] ) print('--> Installing dependencies') subprocess.check_call( [ which('pip'), 'install', '-q', '--disable-pip-version-check', '--no-python-version-warning', repo_dir, ] ) print('--> Testing package') for statement in test_data['statements']: subprocess.check_call([which('python'), '-c', statement]) scripts = project_config['project'].get('scripts', {}) if scripts: print('--> Testing scripts') for script in scripts: if not which(script): sys.exit('--> Could not locate script: {}'.format(script)) print('--> Success!') if __name__ == '__main__': main() hatchling-0.15.0/tests/downstream/requirements.txt0000644000000000000000000000006213615410400020525 0ustar0000000000000000build packaging requests toml virtualenv>=20.13.1 hatchling-0.15.0/tests/downstream/datadogpy/data.json0000644000000000000000000000017213615410400021023 0ustar0000000000000000{ "repo_url": "https://github.com/DataDog/datadogpy", "statements": [ "from datadog import initialize, api" ] } hatchling-0.15.0/tests/downstream/datadogpy/pyproject.toml0000644000000000000000000000270713615410400022141 0ustar0000000000000000[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "datadog" description = "The Datadog Python library" readme = "README.md" license = "BSD-3-Clause" keywords = [ "datadog", ] authors = [ { name = "Datadog, Inc.", email = "dev@datadoghq.com" }, ] classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", 'Programming Language :: Python :: Implementation :: CPython', "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ "requests>=2.6.0", "typing; python_version<'3.5'", "configparser<5; python_version<'3.0'", ] dynamic = ["version"] [project.urls] "Bug Tracker" = "https://github.com/DataDog/datadogpy/issues" Documentation = "https://datadogpy.readthedocs.io/en/latest/" "Source Code" = "https://github.com/DataDog/datadogpy" [project.scripts] dog = "datadog.dogshell:main" dogwrap = "datadog.dogshell.wrap:main" dogshell = "datadog.dogshell:main" dogshellwrap = "datadog.dogshell.wrap:main" [tool.hatch.version] path = "datadog/version.py" [tool.hatch.build] packages = ["datadog"] [tool.hatch.build.targets.sdist] include = [ "/LICENSE", "/tests", ] [tool.hatch.build.targets.wheel] hatchling-0.15.0/tests/downstream/hatch-showcase/data.json0000644000000000000000000000033613615410400021752 0ustar0000000000000000{ "repo_url": "https://github.com/ofek/hatch-showcase", "statements": [ "from hatch_showcase.fib import fibonacci; assert fibonacci(32) == 2178309" ], "env_vars": { "HATCH_BUILD_HOOKS_ENABLE": "true" } } hatchling-0.15.0/PKG-INFO0000644000000000000000000000345313615410400013020 0ustar0000000000000000Metadata-Version: 2.1 Name: hatchling Version: 0.15.0 Summary: Modern, extensible Python build backend Project-URL: Funding, https://github.com/sponsors/ofek Project-URL: History, https://ofek.dev/hatch/dev/meta/history/ Project-URL: Homepage, https://ofek.dev/hatch/latest/ Project-URL: Source, https://github.com/ofek/hatch/tree/master/backend Project-URL: Tracker, https://github.com/ofek/hatch/issues Author-email: Ofek Lev Keywords: build,hatch,packaging Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Dist: editables~=0.2; python_version > "3" Requires-Dist: importlib-metadata; python_version < "3.8" Requires-Dist: packaging~=20.9; python_version < "3" Requires-Dist: packaging~=21.3; python_version > "3" Requires-Dist: pathspec~=0.9 Requires-Dist: pluggy~=0.13; python_version < "3" Requires-Dist: pluggy~=1.0.0; python_version > "3" Requires-Dist: tomli~=2.0.0; python_version > "3" Requires-Dist: toml~=0.10.2; python_version < "3" Description-Content-Type: text/markdown # Hatchling ----- This is the extensible, standards compliant build backend used by [Hatch](https://github.com/ofek/hatch).