在 python 中支持多个数据加载器

support multiple dataloaders in python

我需要创建一个数据接口,可以从 excel 文件或 API 或我们的数据库中查询相同的数据。

设置它的最佳结构是什么?通常如何设置它以避免必须根据我们是否需要来自 excel/api/db 的数据手动切换导入。

您可以在此处使用 Driver/Factory 模式。基本上,您需要编写数据驱动程序,以从不同端点获取数据。在所有情况下,数据都是相同的。

这是一个标准的 OOP 用例,抽象在此类设计中起着至关重要的作用。您需要的是已知操作的标准 interface/abstraction,并在不同的驱动程序实现中实现它。

在你的例子中,你知道数据是一个具体的对象,你需要一个加载器(与驱动程序同义)来生成这个数据。

所以,定义数据对象。为了。例如

class MyData:
    def __init__(self, *args, **kwargs):
        # TODO- Accept the relevant args for data object here!
        pass

你可以在这里 add-ons。现在,您需要的是一个数据加载器,它基本上是抽象的。您可以在 运行 时间内决定加载程序的实现。因此,首先,确定一个抽象,可能如下所示

from abc import abstractmethod
class AbstractDataLoader:

    def __init__(self, *args, **kwargs):
        pass

    @abstractmethod
    def load(self, *args, **kwargs) -> MyData:
        pass

骨架已定义。现在您需要定义所需的各种数据加载器,它们从不同的端点(如数据库或文件或 API 等)中选择数据。让我们创建一些如下所示的实现。

class DBDataLoader(AbstractDataLoader):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # load db connections, other configs

    def load(self, *args, **kwargs) -> MyData:
        # TODO- Load data from DB
        pass


class ExcelDataLoader(AbstractDataLoader):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # load excel files, other configs

    def load(self, *args, **kwargs) -> MyData:
        # TODO- Load data from Excel
        pass


class APIDataLoader(AbstractDataLoader):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # load api connections, other configs

    def load(self, *args, **kwargs) -> MyData:
        # TODO- Load data from API
        pass

完成,我们有数据对象,驱动程序准备就绪。现在是关于配置和使用某个驱动程序。这可以通过命令式的方式完成,使用如下所示的工厂方法

class MyApp:
    def __init__(self, configured_loader):
        self.configured_loader = configured_loader

    def _resolve_loader(self):
        if self.configured_loader == 'db':
            return DBDataLoader()
        elif self.configured_loader == 'excel':
            return ExcelDataLoader()
        # ....

    def load_data(self) -> MyData:
        return self._resolve_loader().load()


if __name__ == '__main__':
    import sys

    loader = sys.argv[1]
    app = MyApp(loader)
    data = app.load_data()
    # Do with it whatever you want!

或者,一种使用声明方式的更好方法,即使用 env 文件等配置。例如,定义一个像 app.env 这样的 env 文件,其中包含一些像

这样的定义
myapp:data-loader=loaders.APIDataLoader
myapp:data-loader:api:endpoint=https://some-server/api/v1/data
..
..
..

并使用像 python-dotenv 这样的库,使其在 运行 时间内可用,然后直接使用 class 加载数据。

例如,

import os
import importlib
from dotenv import load_dotenv
class MyApp:
    def __init__(self):
        self.configured_loader = os.getenv("myapp:data-loader")

    def _resolve_loader(self):
        package_name, class_name = self.configured_loader.rsplit('.', 1)
        module = importlib.import_module(package_name)
        driver_class = getattr(module, class_name)
        return driver_class()
        # .... as of now, it creates an instance of APIDataLoader

    def load_data(self) -> MyData:
        return self._resolve_loader().load()


if __name__ == '__main__':
    # Loads the configs from app.env..
    load_dotenv(dotenv_path='app.env')
    app = MyApp()
    data = app.load_data()
    # Do with it whatever you want!

这总结了一种简单但可扩展的方法来解决您的问题。