From my understanding, a transaction corresponds to a single micropip.install() command.
The method add_requirement_inner() gets called for each package that we will be installing as
part of the current microppip transaction.
So, consider the following:
pkgawith optional dependency marker,allwhich will bring in packages,depaanddepbpkgbwith optional dependency marker,optional_featurewhich will bring in package,depc
Based on the above understanding and assumptions, as part of the change in the proposed pull request at pyodide/pyodide#2584, when we invoke, micropip.install(["pkg[all]", "pkgb[opt_feature]"]) which results in 4
calls to add_requirement_inner(). This is how the values of the key attributes/variables look like in each add_requirement_inner() call progressively:
-
Called with
pkga, the values of key attributes are:req.extras=extras: {'all'}req.marker = Noneself.ctx = {'implementation_name': 'cpython', 'implementation_version': '3.10.2', 'os_name': 'posix', 'platform_machine': 'x86_64', 'platform_release': '5.18.0-1-MANJARO ', 'platform_system': 'Linux', 'platform_version': '#1 SMP PREEMPT_DYNAMIC Mon May 9 07:56:17 UTC 2022', 'python_full_version': '3.10.2', 'platform_python_implementation': 'CPython', 'python _version': '3.10', 'sys_platform': 'linux'}self.ctx_extras: []
-
Called with
pkgb, the values of key attributes now are:req.extras=extras: {'opt_feature'}req.marker = Noneself.ctx = {'implementation_name': 'cpython', 'implementation_version': '3.10.2', 'os_name': 'posix', 'platform_machine': 'x86_64', 'platform_release': '5.18.0-1-MANJARO ', 'platform_system': 'Linux', 'platform_version': '#1 SMP PREEMPT_DYNAMIC Mon May 9 07:56:17 UTC 2022', 'python_full_version': '3.10.2', 'platform_python_implementation': 'CPython', 'python _version': '3.10', 'sys_platform': 'linux'}self.ctx_extras: [{'extra': 'all'}]# We populated this in the previous step
-
Called with
depb, the values of key attributes now are:req.extras = ()req.marker = extra == "all"# this attribute is how we can find out that this package is being installed due to the all optional feature of one of the other packagesself.ctx = {'implementation_name': 'cpython', 'implementation_version': '3.10.2', 'os_name': 'posix', 'platform_machine': 'x86_64', 'platform_release': '5.18.0-1-MANJARO ', 'platform_system': 'Linux', 'platform_version': '#1 SMP PREEMPT_DYNAMIC Mon May 9 07:56:17 UTC 2022', 'python_full_version': '3.10.2', 'platform_python_implementation': 'CPython', 'python _version': '3.10', 'sys_platform': 'linux'}self.ctx_extras: [{'extra': 'all'}, {'extra': 'opt_feature'}] # Now consists of both the extras ofpkgaandpkgb`
-
Called with
depa, the values of key attributes now are:req.extras = ()req.marker = extra == "all"# this attribute is how we can find out that this package is being installed due to the all optional feature of one of the other packages, note that this is same fordepbanddepaas they are both being installed as part of the same primary package's optional dependencyself.ctx = {'implementation_name': 'cpython', 'implementation_version': '3.10.2', 'os_name': 'posix', 'platform_machine': 'x86_64', 'platform_release': '5.18.0-1-MANJARO ', 'platform_system': 'Linux', 'platform_version': '#1 SMP PREEMPT_DYNAMIC Mon May 9 07:56:17 UTC 2022', 'python_full_version': '3.10.2', 'platform_python_implementation': 'CPython', 'python _version': '3.10', 'sys_platform': 'linux'}self.ctx_extras: [{'extra': 'all'}, {'extra': 'opt_feature'}] # Now consists of both the extras ofpkgaandpkgb`
-
Called with
depa, the values of key attributes now are:req.extras = ()req.marker = extra == "opt_feature"# this attribute is how we can find out that this package is being installed due to theopt_featureoptional feature of one of the other packageself.ctx = {'implementation_name': 'cpython', 'implementation_version': '3.10.2', 'os_name': 'posix', 'platform_machine': 'x86_64', 'platform_release': '5.18.0-1-MANJARO ', 'platform_system': 'Linux', 'platform_version': '#1 SMP PREEMPT_DYNAMIC Mon May 9 07:56:17 UTC 2022', 'python_full_version': '3.10.2', 'platform_python_implementation': 'CPython', 'python _version': '3.10', 'sys_platform': 'linux'}self.ctx_extras: [{'extra': 'all'}, {'extra': 'opt_feature'}] # Now consists of both the extras ofpkgaandpkgb`
Two key points from above which drove the implementation of the fix in the PR are:
- The
markerattribute is set for the optional dependencies and the evaluation result determines whether the dependency will be installed or not - The evaluation of the marker requires the
extrakey inself.ctxto have the right value. For example, fordepaanddepb, there must be a{'extra': 'all'}object inself.ctx. - The
extrasattribute is only set for the primary packages and hence has to be available during the above evaluation - Hence, we use the
self.ctx_extrasattribute to store all theextravalues we come across during the transaction and attempt the marker evaluation for all of these values. If any of the evaluations returntruewe include the dependency.
if req.marker:
def eval_marker(e: dict[str, str]) -> bool:
self.ctx.update(e)
# need the assertion here to make mypy happy:
# https://github.com/python/mypy/issues/4805
assert req.marker is not None
return req.marker.evaluate(self.ctx)
self.ctx.update({"extra": ""})
if not req.marker.evaluate(self.ctx) and not any(
[eval_marker(e) for e in self.ctx_extras]
):
return
Why do we have both req.marker.evaluate(self.ctx) and eval_marker(e) for e in self.ctx_extras? The reason is the current
package may have been brought into the transaction without any of the optional requirement specification, but has another marker, such as implementation_name. In this scenario, self.ctx_extras is empty and hence the eval_marker() function
will not be called at all.