Пример #1
0
    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))
Пример #2
0
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)
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
    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']))
Пример #6
0
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
Пример #7
0
    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)
Пример #8
0
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")
Пример #9
0
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