Ejemplo n.º 1
0
    def test_schemes(self):
        cases = (
            ('normalized', (_normalized_key, NV, NM)),
            ('legacy', (_legacy_key, LV, LM)),
            ('semantic', (_semantic_key, SV, SM)),
        )

        for name, values in cases:
            scheme = get_scheme(name)
            key, version, matcher = values
            self.assertIs(key, scheme.key)
            self.assertIs(matcher, scheme.matcher)
            self.assertIs(version, scheme.matcher.version_class)

        self.assertIs(get_scheme('default'), get_scheme('normalized'))

        self.assertRaises(ValueError, get_scheme, 'random')
Ejemplo n.º 2
0
    def test_schemes(self):
        cases = (
            ('normalized', (_normalized_key, NV, NM)),
            ('legacy', (_legacy_key, LV, LM)),
            ('semantic', (_semantic_key, SV, SM)),
        )

        for name, values in cases:
            scheme = get_scheme(name)
            key, version, matcher = values
            self.assertIs(key, scheme.key)
            self.assertIs(matcher, scheme.matcher)
            self.assertIs(version, scheme.matcher.version_class)

        self.assertIs(get_scheme('default'), get_scheme('normalized'))

        self.assertRaises(ValueError, get_scheme, 'random')
Ejemplo n.º 3
0
class _PackageManager:
    version_scheme = version.get_scheme("normalized")

    def __init__(self):
        self.builtin_packages = {}
        self.builtin_packages.update(js_pyodide._module.packages.dependencies)
        self.installed_packages = {}

    def install(
        self,
        requirements: Union[str, List[str]],
        ctx=None,
        wheel_installer=None,
        resolve=_nullop,
        reject=_nullop,
    ):
        try:
            if ctx is None:
                ctx = {"extra": None}

            if wheel_installer is None:
                wheel_installer = _RawWheelInstaller()

            complete_ctx = dict(markers.DEFAULT_CONTEXT)
            complete_ctx.update(ctx)

            if isinstance(requirements, str):
                requirements = [requirements]

            transaction: Dict[str, Any] = {
                "wheels": [],
                "pyodide_packages": set(),
                "locked": dict(self.installed_packages),
            }
            for requirement in requirements:
                self.add_requirement(requirement, complete_ctx, transaction)
        except Exception as e:
            reject(str(e))

        resolve_count = [len(transaction["wheels"])]

        def do_resolve(*args):
            resolve_count[0] -= 1
            if resolve_count[0] == 0:
                resolve(f'Installed {", ".join(self.installed_packages.keys())}')

        # Install built-in packages
        pyodide_packages = transaction["pyodide_packages"]
        if len(pyodide_packages):
            resolve_count[0] += 1
            self.installed_packages.update(dict((k, None) for k in pyodide_packages))
            js_pyodide.loadPackage(list(pyodide_packages)).then(do_resolve)

        # Now install PyPI packages
        for name, wheel, ver in transaction["wheels"]:
            wheel_installer(name, wheel, do_resolve, reject)
            self.installed_packages[name] = ver

    def add_requirement(self, requirement: str, ctx, transaction):
        if requirement.startswith(("http://", "https://")):
            # custom download location
            name, wheel, version = _parse_wheel_url(requirement)
            transaction["wheels"].append((name, wheel, version))
            return

        req = util.parse_requirement(requirement)

        # If it's a Pyodide package, use that instead of the one on PyPI
        if req.name in self.builtin_packages:
            transaction["pyodide_packages"].add(req.name)
            return

        if req.marker:
            if not markers.evaluator.evaluate(req.marker, ctx):
                return

        matcher = self.version_scheme.matcher(req.requirement)

        # If we already have something that will work, don't
        # fetch again
        for name, ver in transaction["locked"].items():
            if name == req.name:
                if matcher.match(ver):
                    break
                else:
                    raise ValueError(
                        f"Requested '{requirement}', "
                        f"but {name}=={ver} is already installed"
                    )
        else:
            metadata = _get_pypi_json(req.name)
            wheel, ver = self.find_wheel(metadata, req)
            transaction["locked"][req.name] = ver

            recurs_reqs = metadata.get("info", {}).get("requires_dist") or []
            for recurs_req in recurs_reqs:
                self.add_requirement(recurs_req, ctx, transaction)

            transaction["wheels"].append((req.name, wheel, ver))

    def find_wheel(self, metadata, req):
        releases = []
        for ver, files in metadata.get("releases", {}).items():
            ver = self.version_scheme.suggest(ver)
            if ver is not None:
                releases.append((ver, files))
        releases = sorted(releases, reverse=True)
        matcher = self.version_scheme.matcher(req.requirement)
        for ver, meta in releases:
            if matcher.match(ver):
                for fileinfo in meta:
                    if fileinfo["filename"].endswith("py3-none-any.whl"):
                        return fileinfo, ver

        raise ValueError(f"Couldn't find a pure Python 3 wheel for '{req.requirement}'")
Ejemplo n.º 4
0
class _PackageManager:
    version_scheme = version.get_scheme("normalized")

    def __init__(self):
        self.builtin_packages = {}
        self.builtin_packages.update(
            js_pyodide._module.packages.dependencies.object_entries())
        self.installed_packages = {}

    async def install(self, requirements: Union[str, List[str]], ctx=None):
        if ctx is None:
            ctx = {"extra": None}

        complete_ctx = dict(markers.DEFAULT_CONTEXT)
        complete_ctx.update(ctx)

        if isinstance(requirements, str):
            requirements = [requirements]

        transaction: Dict[str, Any] = {
            "wheels": [],
            "pyodide_packages": set(),
            "locked": dict(self.installed_packages),
        }
        requirement_promises = []
        for requirement in requirements:
            requirement_promises.append(
                self.add_requirement(requirement, complete_ctx, transaction))

        await gather(*requirement_promises)

        wheel_promises = []

        # Install built-in packages
        pyodide_packages = transaction["pyodide_packages"]
        if len(pyodide_packages):
            # Note: branch never happens in out-of-browser testing because we
            # report that all dependencies are empty.
            self.installed_packages.update(
                dict((k, None) for k in pyodide_packages))
            wheel_promises.append(
                js_pyodide.loadPackage(list(pyodide_packages)))

        # Now install PyPI packages
        for name, wheel, ver in transaction["wheels"]:
            wheel_promises.append(_install_wheel(name, wheel))
            self.installed_packages[name] = ver
        await gather(*wheel_promises)
        return f'Installed {", ".join(self.installed_packages.keys())}'

    async def add_requirement(self, requirement: str, ctx, transaction):
        if requirement.endswith(".whl"):
            # custom download location
            name, wheel, version = _parse_wheel_url(requirement)
            transaction["wheels"].append((name, wheel, version))
            return

        req = util.parse_requirement(requirement)

        # If it's a Pyodide package, use that instead of the one on PyPI
        if req.name in self.builtin_packages:
            transaction["pyodide_packages"].add(req.name)
            return

        if req.marker:
            if not markers.evaluator.evaluate(req.marker, ctx):
                return

        matcher = self.version_scheme.matcher(req.requirement)

        # If we already have something that will work, don't
        # fetch again
        for name, ver in transaction["locked"].items():
            if name == req.name:
                if matcher.match(ver):
                    break
                else:
                    raise ValueError(f"Requested '{requirement}', "
                                     f"but {name}=={ver} is already installed")
        else:
            metadata = await _get_pypi_json(req.name)
            wheel, ver = self.find_wheel(metadata, req)
            transaction["locked"][req.name] = ver

            recurs_reqs = metadata.get("info", {}).get("requires_dist") or []
            for recurs_req in recurs_reqs:
                await self.add_requirement(recurs_req, ctx, transaction)

            transaction["wheels"].append((req.name, wheel, ver))

    def find_wheel(self, metadata, req):
        releases = []
        for ver, files in metadata.get("releases", {}).items():
            ver = self.version_scheme.suggest(ver)
            if ver is not None:
                releases.append((ver, files))

        def version_number(release):
            return version.NormalizedVersion(release[0])

        releases = sorted(releases, key=version_number, reverse=True)
        matcher = self.version_scheme.matcher(req.requirement)
        for ver, meta in releases:
            if matcher.match(ver):
                for fileinfo in meta:
                    if fileinfo["filename"].endswith("py3-none-any.whl"):
                        return fileinfo, ver

        raise ValueError(
            f"Couldn't find a pure Python 3 wheel for '{req.requirement}'")