Python requires¶
简介¶
python_requires
功能是一种在不同配方之间共享文件和代码的非常方便的方式。 Python require 是一种特殊的配方,它不创建软件包,而仅旨在被其他配方重用。
我们想要重用的一个非常简单的配方可能是
from conan import ConanFile
myvar = 123
def myfunct():
return 234
class Pkg(ConanFile):
name = "pyreq"
version = "0.1"
package_type = "python-require"
然后我们将使用 conan create .
使其可用于其他软件包。 请注意,python-require
软件包不创建二进制文件,它只是配方部分。
$ conan create .
# It will only export the recipe, but will NOT create binaries
# python-requires do NOT have binaries
我们可以通过在 python_requires
属性中声明依赖项来重用上述配方功能,并且可以使用 self.python_requires["<name>"].module
访问其成员。
from conan import ConanFile
class Pkg(ConanFile):
name = "pkg"
version = "0.1"
python_requires = "pyreq/0.1"
def build(self):
v = self.python_requires["pyreq"].module.myvar # v will be 123
f = self.python_requires["pyreq"].module.myfunct() # f will be 234
self.output.info(f"{v}, {f}")
$ conan create .
...
pkg/0.1: 123, 234
Python requires 也可以使用版本范围,如果这些 python-requires
需要随着时间的推移而发展,那么在许多情况下建议这样做
from conan import ConanFile
class Pkg(ConanFile):
python_requires = "pyreq/[>=1.0 <2]"
也可以使用 python_requires = "pyreq/0.1", "other/1.2"
要求多个 python-requires
。
扩展基类¶
一个常见的用例是声明一个基类,该基类具有我们希望通过继承在多个配方中重用的方法。 我们将在 python-requires 软件包中编写这个基类
from conan import ConanFile
class MyBase:
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!")
class PyReq(ConanFile):
name = "pyreq"
version = "0.1"
package_type = "python-require"
并使其可用于重用,如下所示:
$ conan create .
请注意,配方文件中有两个类
MyBase
是用于继承的类,并且不扩展ConanFile
。
PyReq
是定义当前正在导出的软件包的类,它是引用pyreq/0.1
的配方。
一旦我们想要重用的基类的软件包可用,我们就可以在其他配方中使用它来继承该基类的功能。 我们需要像以前一样声明 python_requires
,并且我们需要告诉 Conan 在属性 python_requires_extend
中要使用的基类。 在这里,我们的配方将从类 MyBase
继承
from conan import ConanFile
class Pkg(ConanFile):
name = "pkg"
version = "0.1"
python_requires = "pyreq/0.1"
python_requires_extend = "pyreq.MyBase"
生成的继承等效于将我们的 Pkg
类声明为 class Pkg(pyreq.MyBase, ConanFile)
。 因此,创建软件包后,我们可以看到如何重用基类中的方法
$ conan create .
...
pkg/0.1: My cool source!
pkg/0.1: My cool build!
pkg/0.1: My cool package!
pkg/0.1: My cool package_info!
...
通常,基类属性不会被继承,应尽可能避免使用。 有一些方法可以替代其中的一些方法,例如 export()
或 set_version()
。 对于特殊情况,请参阅 init()
方法文档以获取有关扩展继承属性的更多信息。
可以重新实现一些基类方法,也可以使用 Python super()
语法显式调用基类方法
from conan import ConanFile
class Pkg(ConanFile):
name = "pkg"
version = "0.1"
python_requires = "pyreq/0.1"
python_requires_extend = "pyreq.MyBase"
def source(self):
super().source() # call the base class method
self.output.info("MY OWN SOURCE") # Your own implementation
不强制调用基类方法,可以完全覆盖而不调用 super()
。 也可以更改调用顺序,并且可以调用您自己的代码,然后再调用 super()
。
重用文件¶
可以访问使用 python_requires
使用的配方导出的文件。 我们可以有这个配方,以及一个包含“Hello”文本的 *myfile.txt* 文件。
from conan import ConanFile
class PyReq(ConanFile):
name = "pyreq"
version = "1.0"
package_type = "python-require"
exports = "*"
$ echo "Hello" > myfile.txt
$ conan create .
现在已经创建了 python-require,我们可以使用 path
属性访问其路径(*myfile.txt* 所在的位置)
import os
from conan import ConanFile
from conan.tools.files import load
class Pkg(ConanFile):
python_requires = "pyreq/0.1"
def build(self):
pyreq_path = self.python_requires["pyreq"].path
myfile_path = os.path.join(pyreq_path, "myfile.txt")
content = load(self, myfile_path) # content = "Hello"
self.output.info(content)
# we could also copy the file, instead of reading it
请注意,仅 exports
适用于这种情况,但不适用于 exports_sources
。
测试 python-requires¶
可以通过添加 test_package/conanfile.py
,使用 test_package
测试 python_require
。
from conan import ConanFile
def mynumber():
return 42
class PyReq(ConanFile):
name = "pyreq"
version = "1.0"
package_type = "python-require"
from conan import ConanFile
class Tool(ConanFile):
# Literal "tested_reference_str", Conan will dynamically replace it
python_requires = "tested_reference_str"
def test(self):
pyreq = self.python_requires["pyreq"].module
mynumber = pyreq.mynumber()
self.output.info("{}!!!".format(mynumber))
从 Conan 2.1 开始,python_requires = "tested_reference_str"
是强制性的。 在没有此声明的情况下自动注入 python_requires
已被弃用,并且将在以后的版本中删除。
请注意,test_package/conanfile.py
不需要对 python_requires
进行任何类型的声明,这是自动和隐式完成的。 我们现在可以使用以下命令创建和测试它:
$ conan create .
...
pyreq/0.1 (test package): 42!!!
package_id 中的影响¶
python_requires
将影响使用这些依赖项的**使用者软件包**的 package_id
。 默认情况下,策略为 minor_mode
,这意味着
对 python-require 的**修订版本**的 **patch** 版本的更改不会影响软件包 ID。 因此,依赖于
"pyreq/1.2.3"
或"pyreq/1.2.4"
将导致相同的软件包 ID(两者都将在哈希计算中映射到"pyreq/1.2.Z"
)。 如果要更改通用代码,但不希望使用者受到影响或触发依赖项的重新构建,请增加 patch 版本。对 **minor** 版本的更改将产生不同的软件包 ID。 因此,如果您依赖于
"pyreq/1.2.3"
,并且将版本提升到"pyreq/1.3.0"
,那么您将需要构建使用该新 python-require 的新二进制文件。 如果您要确保需要此 python-require 的软件包将使用代码中的这些更改来构建,请增加 minor 或 major 版本。
在大多数情况下,使用版本范围 python_requires = "pyreq/[>=1.0 <2.0]"
是正确的方法,因为这意味着不包括 **major** 版本提升,因为它们需要使用者本身进行更改。 这样就可以发布 pyreq/2.0
的新 major 版本,并让使用者逐步更改其需求 python_requires = "pyreq/[>=2.0 <3.0]"
,修复配方,并在不破坏整个项目的情况下前进。
与常规 requires
一样,可以使用 core.package_id:default_python_mode
配置自定义此默认值。
还可以使用 package_id()
方法自定义每个软件包的 python_requires
的影响
from conan import ConanFile class Pkg(ConanFile): python_requires ="pyreq/[>=1.0]" def package_id(self): self.info.python_requires.patch_mode()
python_requires 的解析¶
使用 python_requires
时,应考虑一些重要事项
Python requires 配方仅由解释器加载一次,并且它们对所有使用者都是通用的。 不要在
python_requires
配方中使用任何全局状态。Python requires 对使用者是私有的。 它们不是传递性的。 不同的使用者可以要求相同
python-require
的不同版本。 由于是私有的,因此无法以任何方式从下游覆盖它们。python_requires
不能使用常规的requires
或tool_requires
。 可以拥有一个requirements()
(以及类似)方法,以便配方继承,但python_requires
类本身不能使用它们。python_requires
不能被“别名化”。python_requires
可以使用原生的 Pythonimport
导入其他 Python 文件,只要这些文件与配方一起导出。python_requires
也可以用作可编辑的包。python_requires
在锁定文件中被锁定,以保证可重现性,就像其他的requires
和tool_requires
被锁定一样。
注意
最佳实践
即使
python-requires
可以传递性地python_requires
其他python-requires
配方,但不建议这样做。 多层次的继承和重用可能会变得非常复杂且难以管理,建议保持层次结构扁平化。不要尝试将 Python 继承与
python_requires_extend
继承机制混合使用,它们是不兼容的,可能会崩溃。不要对
python-requires
使用多重继承