def fetch_last_version(self, package, allow_pre_releases, service_url, timeout): """ Fetch the last version of a package on Pypi. """ package, specifier = package specifier = SpecifierSet(specifier, allow_pre_releases) max_version = parse_version(self.default_version) package_json_url = '%s/%s/json' % (service_url, package) logger.info('> Fetching latest datas for %s...', package) socket.setdefaulttimeout(timeout) try: content = urlopen(package_json_url).read().decode('utf-8') except URLError as error: content = '{"releases": []}' logger.debug('!> %s %s', package_json_url, error.reason) results = json.loads(content) socket.setdefaulttimeout(None) for version in specifier.filter(results['releases']): version = parse_version(version) if version > max_version: max_version = version logger.debug('-> Last version of %s%s is %s.', package, specifier, max_version) return (package, str(max_version))
def upgrade_python(check, no_pyenv_update, local, force_version, venv): """Upgrade the Python version used in the Indico virtualenv. When using `--check`, only Python versions currently available in pyenv will be used; make sure to `pyenv update` first (this is done automatically when you run this command without `--check`). Be careful when using `--force-version` with a custom version; you may end up using an unsupported version where this command will no longer work. """ if not check and not no_pyenv_update: click.echo('updating pyenv') proc = subprocess.run(['pyenv', 'update'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.returncode: click.echo(proc.stdout) click.confirm('pyenv update failed - continue anyway?', abort=True) pyenv_mode = 'local' if local else 'global' pyenv_version_list = subprocess.run(['pyenv', 'install', '--list'], capture_output=True, encoding='utf-8').stdout.splitlines() pyenv_versions = [ Version(x) for line in pyenv_version_list if (x := line.strip()) and re.match(r'^\d+\.\d+\.\d+$', x) ] preferred_version_spec = SpecifierSet(indico.PREFERRED_PYTHON_VERSION_SPEC) current_version = Version('.'.join(map(str, sys.version_info[:3]))) if force_version: preferred_version = Version(force_version) if preferred_version not in preferred_version_spec: click.echo( f'Warning: {preferred_version} is not within {preferred_version_spec} spec' ) click.confirm('Continue anyway?', abort=True) else: if not (available_versions := preferred_version_spec.filter(pyenv_versions)): click.echo( f'Found no qualifying versions for {preferred_version_spec}') sys.exit(1) preferred_version = max(available_versions)
def test_specifier_filter( self, specifier_prereleases, specifier, prereleases, input, expected ): if specifier_prereleases is None: spec = SpecifierSet(specifier) else: spec = SpecifierSet(specifier, prereleases=specifier_prereleases) kwargs = {"prereleases": prereleases} if prereleases is not None else {} assert list(spec.filter(input, **kwargs)) == expected
def test_specifier_filter(self, specifier_prereleases, specifier, prereleases, input, expected): if specifier_prereleases is None: spec = SpecifierSet(specifier) else: spec = SpecifierSet(specifier, prereleases=specifier_prereleases) kwargs = ( {"prereleases": prereleases} if prereleases is not None else {} ) assert list(spec.filter(input, **kwargs)) == expected
def get_compatible_versions(self, version_range): """Gets the set of versions that satisfy the given version range. Args: version_range (str): Specifies the range of acceptable versions. This **must** be a valid PEP 440 range, not an NPM range. https://packaging.pypa.io/en/latest/specifiers/ https://www.python.org/dev/peps/pep-0440/ """ if isinstance(version_range, str): version_range = SpecifierSet(version_range) return version_range.filter(list(self._data['versions']))
def check_version(version, specifier, allow_pre=True): """Check version against specifier to see if version is specified.""" if not version or not specifier: print("Must provide a value for version and specifier") raise ValueError version_obj = Version(version) specifier_obj = SpecifierSet(specifier) if allow_pre: specifier_obj.prereleases = allow_pre if list(specifier_obj.filter([version_obj])): return True return False
def _select_version( self, semantic_version_str: str, available_versions: List[Version], ) -> Optional[str]: """Perform semantic version search on available versions. Args: semantic_version_str (str): the semantic version for which to filter available versions. available_versions (List[Version]): list of available versions. """ if semantic_version_str == "*": if len(available_versions) == 0: return None return str(max(available_versions)) spec = SpecifierSet(f"=={semantic_version_str}") available_versions_filtered = list(spec.filter(available_versions)) return (str(max(available_versions_filtered)) if available_versions_filtered != [] else None)
def cli(obj: Config, dirpath: Path, **options: Any) -> None: """Create packaging boilerplate for a new project""" if (dirpath / "setup.py").exists(): raise click.UsageError("setup.py already exists") if (dirpath / "setup.cfg").exists(): raise click.UsageError("setup.cfg already exists") if (dirpath / "pyproject.toml").exists(): raise click.UsageError("pyproject.toml already exists") defaults = obj.defaults["init"] pyreq_cfg = defaults.pop("python_requires") options = dict(defaults, **options) if "github_user" not in options: options["github_user"] = obj.gh.user.get()["login"] repo = git.Git(dirpath=dirpath) env = { "author": options["author"], "short_description": options["description"], "copyright_years": repo.get_commit_years(), "has_doctests": options.get("doctests", False), "has_tests": options.get("tests", False) or options.get("ci", False), "has_typing": options.get("typing", False), "has_ci": options.get("ci", False), "has_docs": options.get("docs", False), "has_pypi": False, "github_user": options["github_user"], "codecov_user": options.get("codecov_user", options["github_user"]), "keywords": [], "version": "0.1.0.dev1", "supports_pypy3": True, "extra_testenvs": {}, "default_branch": repo.get_default_branch(), "uses_versioningit": False, } log.info("Determining Python module ...") # "import_name", "is_flat_module", and "src_layout" env.update(inspecting.find_module(dirpath).dict()) if env["is_flat_module"]: log.info("Found flat module %s.py", env["import_name"]) else: log.info("Found package %s", env["import_name"]) if not env.pop("src_layout", False): log.info("Moving code to src/ directory ...") (dirpath / "src").mkdir(exist_ok=True) code_path = env["import_name"] if env["is_flat_module"]: code_path += ".py" (dirpath / code_path).rename(dirpath / "src" / code_path) if env["is_flat_module"] and env["has_typing"]: log.info("Unflattening for py.typed file ...") pkgdir = dirpath / "src" / env["import_name"] pkgdir.mkdir(parents=True, exist_ok=True) (dirpath / "src" / (env["import_name"] + ".py")).rename( dirpath / pkgdir / "__init__.py") env["is_flat_module"] = False env["name"] = options.get("project_name", env["import_name"]) env["repo_name"] = options.get("repo_name", env["name"]) env["rtfd_name"] = options.get("rtfd_name", env["name"]) env["author_email"] = (Environment().from_string( options["author_email"]).render(project_name=env["name"])) log.info("Checking for requirements.txt ...") req_vars = inspecting.parse_requirements(dirpath / "requirements.txt") if env["is_flat_module"]: initfile = dirpath / "src" / (env["import_name"] + ".py") else: initfile = dirpath / "src" / env["import_name"] / "__init__.py" log.info("Checking for __requires__ ...") src_vars = inspecting.extract_requires(initfile) requirements = {} for r in (req_vars.requires or []) + (src_vars.requires or []): req = Requirement(r) name = normalize(req.name) # `Requirement` objects don't have an `__eq__`, so we need to convert # them to `str` in order to compare them. reqstr = str(req) if name not in requirements: requirements[name] = (r, reqstr) elif reqstr != requirements[name][1]: raise click.UsageError( f"Two different requirements for {name} found:" f" {requirements[name][0]!r} and {r!r}") env["install_requires"] = [r for _, (r, _) in sorted(requirements.items())] python_requires = options.get("python_requires") if python_requires is not None: if re.fullmatch(r"\d+\.\d+", python_requires): python_requires = "~=" + python_requires else: pyreq_req = req_vars.python_requires pyreq_src = src_vars.python_requires if pyreq_req is not None and pyreq_src is not None: if SpecifierSet(pyreq_req) != SpecifierSet(pyreq_src): raise click.UsageError( f"Two different Python requirements found:" f" {pyreq_req!r} and {pyreq_src!r}") python_requires = pyreq_req elif pyreq_req is not None: python_requires = pyreq_req elif pyreq_src is not None: python_requires = pyreq_src else: python_requires = pyreq_cfg env["python_requires"] = python_requires try: pyspec = SpecifierSet(python_requires) except ValueError: raise click.UsageError( f"Invalid specifier for python_requires: {python_requires!r}") env["python_versions"] = list(pyspec.filter(obj.pyversions)) if not env["python_versions"]: raise click.UsageError( f"No Python versions in pyversions range matching {python_requires!r}" ) minver = str(min(env["python_versions"], key=PyVersion.parse)) if "command" not in options: env["commands"] = {} elif env["is_flat_module"]: env["commands"] = {options["command"]: f'{env["import_name"]}:main'} else: env["commands"] = { options["command"]: f'{env["import_name"]}.__main__:main' } if env["has_ci"]: env["extra_testenvs"]["lint"] = minver if env["has_typing"]: env["extra_testenvs"]["typing"] = minver project = Project(directory=dirpath, details=env) twriter = project.get_template_writer() twriter.write(".gitignore", force=False) twriter.write(".pre-commit-config.yaml", force=False) twriter.write("MANIFEST.in", force=False) twriter.write("README.rst", force=False) twriter.write("pyproject.toml", force=False) twriter.write("setup.cfg", force=False) twriter.write("tox.ini", force=False) if env["has_typing"]: log.info("Creating src/%s/py.typed ...", env["import_name"]) (project.directory / "src" / env["import_name"] / "py.typed").touch() if env["has_ci"]: twriter.write(".github/workflows/test.yml", force=False) if env["has_docs"]: twriter.write(".readthedocs.yml", force=False) twriter.write("docs/index.rst", force=False) twriter.write("docs/conf.py", force=False) twriter.write("docs/requirements.txt", force=False) if (dirpath / "LICENSE").exists(): log.info("Setting copyright year in LICENSE ...") ensure_license_years(dirpath / "LICENSE", env["copyright_years"]) else: twriter.write("LICENSE", force=False) log.info("Adding intro block to initfile ...") with InPlace(initfile, mode="t", encoding="utf-8") as fp: started = False for line in fp: if line.startswith("#!") or (line.lstrip().startswith("#") and re.search( r"coding[=:]\s*([-\w.]+)", line)): pass elif not started: print(twriter.render("init"), file=fp) started = True print(line, file=fp, end="") if not started: # if initfile is empty print(twriter.render("init"), file=fp, end="") with suppress(FileNotFoundError): (dirpath / "requirements.txt").unlink() runcmd("pre-commit", "install", cwd=dirpath) log.info("TODO: Run `pre-commit run -a` after adding new files")
def cli(obj, **options): if Path('setup.py').exists(): raise click.UsageError('setup.py already exists') if Path('setup.cfg').exists(): raise click.UsageError('setup.cfg already exists') defaults = obj.defaults['init'] pyreq_cfg = defaults.pop("python_requires") options = dict(defaults, **options) if "github_user" not in options: options["github_user"] = obj.gh.user.get()["login"] env = { "author": options["author"], "short_description": options["description"], "saythanks_to": options.get("saythanks_to"), "copyright_years": inspect_project.get_commit_years(Path()), "has_tests": options.get("tests", False) or options.get("travis", False), "has_travis": options.get("travis", False), "has_docs": options.get("docs", False), "has_pypi": False, "github_user": options["github_user"], "travis_user": options.get("travis_user", options["github_user"]), "codecov_user": options.get("codecov_user", options["github_user"]), } # "import_name" and "is_flat_module" env.update(inspect_project.find_module(Path())) env["project_name"] = options.get("project_name", env["import_name"]) env["repo_name"] = options.get("repo_name", env["project_name"]) env["rtfd_name"] = options.get("rtfd_name", env["project_name"]) env["author_email"] = util.jinja_env().from_string(options["author_email"])\ .render( project_name=env["project_name"] ) req_vars = inspect_project.parse_requirements('requirements.txt') if env["is_flat_module"]: init_src = Path(env["import_name"] + '.py') else: init_src = Path(env["import_name"]) / '__init__.py' src_vars = inspect_project.extract_requires(init_src) requirements = {} for r in (req_vars["__requires__"] or []) \ + (src_vars["__requires__"] or []): req = Requirement(r) name = normalize(req.name) # `Requirement` objects don't have an `__eq__`, so we need to convert # them to `str` in order to compare them. req = str(req) if name not in requirements: requirements[name] = (r, req) elif req != requirements[name][1]: raise click.UsageError( f'Two different requirements for {name} found:' f' {requirements[name][0]!r} and {r!r}') env["install_requires"] = [r for _, (r, _) in sorted(requirements.items())] python_requires = options.get("python_requires") if python_requires is not None: if re.fullmatch(r'\d+\.\d+', python_requires): python_requires = '~=' + python_requires else: pyreq_req = req_vars["__python_requires__"] pyreq_src = src_vars["__python_requires__"] if pyreq_req is not None and pyreq_src is not None: if SpecifierSet(pyreq_req) != SpecifierSet(pyreq_src): raise click.UsageError( f'Two different Python requirements found:' f' {pyreq_req!r} and {pyreq_src!r}') python_requires = pyreq_req elif pyreq_req is not None: python_requires = pyreq_req elif pyreq_src is not None: python_requires = pyreq_src else: python_requires = pyreq_cfg env["python_requires"] = python_requires try: pyspec = SpecifierSet(python_requires) except ValueError: raise click.UsageError( f'Invalid specifier for python_requires: {python_requires!r}') env["python_versions"] = list(pyspec.filter(obj.pyversions)) if "command" not in options: env["commands"] = {} elif env["is_flat_module"]: env["commands"] = {options["command"]: f'{env["import_name"]}:main'} else: env["commands"] = { options["command"]: f'{env["import_name"]}.__main__:main' } if options["importable"] is not None: env["importable"] = options["importable"] elif not env["install_requires"]: env["importable"] = True elif not env["is_flat_module"] \ and (Path(env["import_name"]) / '__main__.py').exists(): env["importable"] = True else: env["importable"] = False templated = [ '.gitignore', 'MANIFEST.in', 'README.rst', 'setup.cfg', 'setup.py', ] if env["has_tests"] or env["has_docs"]: templated.append('tox.ini') if env["has_travis"]: templated.append('.travis.yml') if env["has_docs"]: Path('docs').mkdir(exist_ok=True) templated.extend([ 'docs/index.rst', 'docs/conf.py', 'docs/requirements.txt', ]) for filename in templated: if not Path(filename).exists(): add_templated_file(filename, env) if Path('LICENSE').exists(): util.ensure_license_years('LICENSE', env["copyright_years"]) else: add_templated_file('LICENSE', env) with InPlace(init_src, mode='t', encoding='utf-8') as fp: started = False for line in fp: if line.startswith('#!') \ or (line.lstrip().startswith('#') and re.search(r'coding[=:]\s*([-\w.]+)', line)): pass elif not started: print( util.jinja_env().get_template('init.j2').render(env), file=fp, ) print(file=fp) started = True print(line, file=fp, end='') if not started: # if init_src is empty print(util.jinja_env().get_template('init.j2').render(env), file=fp) try: Path('requirements.txt').unlink() except FileNotFoundError: pass