Python requires: reusing python code in recipes [EXPERIMENTAL]¶
This is an experimental feature subject to breaking changes in future releases.
python_requires() feature allows extending the recipes reusing Python code from other conanfile.py easily, even for inheritance
approaches. The code to be reused will be in a conanfile.py recipe, and will be managed as any other Conan package.
Let’s create (for example) some reusable base class:
from conans import ConanFile class MyBase(ConanFile): def source(self): self.output.info("My cool source!") def build(self): self.output.info("My cool build!") def package(self): self.output.info("My cool package!") def package_info(self): self.output.info("My cool package_info!")
With this conanfile, we can export it to the local cache to make it available, and also upload it to our remote:
$ conan export . MyBase/0.1@user/channel $ conan upload MyBase/0.1@user/channel -r=myremote
It is not necessary to “create” any package binaries, or to
upload --all, because there are no binaries for this recipe.
python_requires(), we can write a new package recipe like:
from conans import python_requires base = python_requires("MyBase/0.1@user/channel") class PkgTest(base.MyBase): pass
If we run a
conan create, on this recipe, we can see how it is effectively reusing the above code:
$ conan create . Pkg/0.1@user/channel Pkg/0.1@lasote/testing: Installing package Requirements Pkg/0.1@lasote/testing from local cache - Cache Python requires MyConanfileBase/1.1@lasote/testing Packages Pkg/0.1@lasote/testing:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Build ... Pkg/0.1@lasote/testing: Configuring sources Pkg/0.1@lasote/testing: My cool source! ... Pkg/0.1@lasote/testing: Calling build() Pkg/0.1@lasote/testing: My cool build! ... Pkg/0.1@lasote/testing: Calling package() Pkg/0.1@lasote/testing: My cool package! ... Pkg/0.1@lasote/testing: My cool package_info!
It is not mandatory to extend the reused
MyBase class, it is possible to reuse just functions too:
from conans import ConanFile def my_build(settings): # doing custom stuff based on settings class MyBase(ConanFile): pass
$ conan export . MyBuild/0.1@user/channel $ conan upload MyBuild/0.1@user/channel -r=myremote
from conans import ConanFile, python_requires base = python_requires("MyBuild/0.1@user/channel") class PkgTest(ConanFile): ... def build(self): base.my_build(self.settings)
Version ranges are possible with the version ranges notation
, similar to regular requirements.
python_requires() are also possible
from conans import python_requires base = python_requires("MyBase/[~0.1]@user/channel") other = python_requires("Other/1.2@user/channel") class Pkg(base.MyBase): def source(self): other.some_function()
It is possible to structure the code in different files too:
from conans import ConanFile import mydata # reuse the strings from here class MyConanfileBase(ConanFile): exports = "*.py" def source(self): self.output.info(mydata.src)
src = "My cool source!" build = "My cool build!" pkg = "My cool package!" info = "My cool package_info!"
This would be created with the same
conan export and consumed with the same
base = python_requires("MyBase/0.1@user/channel") as above.
There are a few important considerations regarding
- They are required at every step of the Conan commands. If you are creating a package that
MyBasepackage should already be available in the local cache or be downloaded from the remotes. Otherwise, Conan will raise a “missing package” error.
- They do not affect the package binary ID (hash). Depending on different version, or different channel of
python_requires()do not change the package IDs as the normal dependencies do.
- They are imported only once. The Python code that is reused is imported only once, the first time it is required. Subsequent requirements of that Conan recipe will reuse the previously imported module. Global initialization at parsing time and global state are discouraged.
- They are transitive. One recipe using
python_requires()can also be consumed with a
python_requires()from another package recipe.
- They are not automatically updated with the
--updateargument from remotes.
- Different packages can require different versions in their
python_requires(). They are private to each recipe, so they do not conflict with each other. However, it is the responsibility of the user to maintain consistency.
- They are not overridden from downstream consumers. Again, as they are private, they are not affected by other packages, even consumers