Rett-oo
@Rett-oo

Как правильно организовать наследование?

Недавно решил переписать часть проекта, связанную с запросами к API маркетплейсов, используя асинхронщину. При этом решил сохранить старый, синхронный вариант. В голову пришла идея на выходе получить класс, в который будет передаваться, скажем, тип/мод SyncApi или AsyncApi и в зависимости от переданного параметра запросы будут синхронными/асинхронными соответственно.

Вот пример, что имеется и чего хотелось бы достичь:
# Как это выглядело изначально:
class Api:

    def __init__(self, base_url: AnyStr = None, headers: Dict = None) -> None:
        self.__base_url: str = base_url
        self.__headers: Dict = headers

    @property
    def base_url(self):
        ...

    @property
    def headers(self):
        ...

    def request(self):
        ...


class OzonApi(Api):

    __base_url: str = 'https://api-seller.ozon.ru'

    __headers: Dict = {
        'Host': 'api-seller.ozon.ru',
        'Client-Id': CLIENT_ID,
        'Api-Key': API_KEY,
        'Content-Type': 'application/json'
    }
    def __init__(self) -> None:
        super().__init__(base_url=self.__base_url, headers=self.__headers)

    def get_category_tree(self):
        _method: str = '/v1/description-category/tree'
        _json_body: Dict = {}
        resp: List[Dict] = self.request()

        return resp

    def get_orders(self):
        ...

    def get_actions(self):
        ...


# Такая же реализация
class WbStandartApi(Api):
    ...


На выходе получались классы с синхронными запросами к API, данные которых шли далее по пайплайну.

# Сейчас это выглядит так:

class BaseApi:

    @property
    @abstractmethod
    def domain(self):
        return NotImplementedError()

    @abstractmethod
    def fetch(self):
        return NotImplementedError()

    @abstractmethod
    def fetch_with_offset(self):
        return NotImplementedError()

    @abstractmethod
    def fetch_with_paging(self):
        return NotImplementedError()

    @abstractmethod
    def fetch_keep_last(self):
        return NotImplementedError()


class AsyncApi(BaseApi):

    def __init__(
        self,
        domain: AnyStr = None,
        headers: Dict = None,
        method: str = None,
        endpoint: str = None,
        dataclass: Callable = None
    ) -> None:
        self.__domain: str = domain
        self.__headers: Dict = headers
        self.__method: str = method
        self.__endpoint: str = endpoint
        self.__dataclass: Callable = dataclass
        self._session: ClientSession = None
        self.data: List[Dict] = []


    async def fetch(self) -> str:
        return 'This is fetch with async api'

    async def fetch_with_offset(self) -> str:
        return 'This is fetch_with_offset with async api'

    async def fetch_with_paging(self) -> str:
        return 'This is fetch_with_paging with async api'

    async def fetch_keep_last(self) -> str:
        return 'This is fetch_keep_last with async api'


class SyncApi(BaseApi):

    def __init__(
        self,
        domain: AnyStr = None,
        headers: Dict = None,
        method: str = None,
        endpoint: str = None
    ) -> None:
        self.__domain: str = domain
        self.__headers: Dict = headers
        self.__method: str = method
        self.__endpoint: str = endpoint
        self.__dataclass: Callable = None
        self._session: Session = None
        self.data: List[Dict] = []

    @property
    def domain(self) -> bytes:
        return b'This is domain sync api'

    def fetch(self) -> bytes:
        return b'This is fetch with sync api'

    def fetch_with_offset(self) -> bytes:
        return b'This is fetch_with_offset with sync api'

    def fetch_with_paging(self) -> bytes:
        return b'This is fetch_with_paging with sync api'

    def fetch_keep_last(self) -> bytes:
        return b'This is fetch_keep_last with sync api'


ApiT = Union[SyncApii, AsyncApii]


class OzonSellerApi:

    _domain = ''

    _headers = ''

    def __init__(self, engine: SyncApii | AsyncApii):
        self.engine: SyncApii | AsyncApii = engine(
            domain=self._domain,
            headers=self._headers,
            method=self.method,
            endpoint=self.endpoint,
            dataclass=self.dataclass
        )

    @property
    def endpoint(self):
        ...
    @property
    def method(self):
        ...
    @property
    def dataclass(self):
        ...

class OzonActionApi(OzonSellerApi):

    def __init__(self, engine: ApiT):
        super().__init__(engine)

    def get_actions(self):

        self.endpoint = '/endpoint'
        self.method = 'POST'
        self.json = {}
        self.dataclass = Actions

        if self.engine == SyncApii:
            return self.engine.fetch_with_offset()  # Sync request
        if self.engine == AsyncApii:
            return self.engine.fetch_with_offset() # Async request


class OzonOrdersApi(OzonSellerApi):

    def __init__(self, engine: ApiT):
        super().__init__(engine)

    def get_orders(self):

        self.endpoint = '/endpoint2'
        self.method = 'POST'
        self.json = {}
        self.dataclass = Orders

        if self.engine == SyncApii:
            return self.engine.fetch_with_offset()  # Sync request
        if self.engine == AsyncApii:
            return self.engine.fetch_with_offset() # Async request


class OzonSellerApiAdapter:

    def __init__(self, engine: ApiT):
        self.actions = OzonActionApi(engine)
        self.orders = OzonOrdersApi(engine)


class OzonFabric:

    def __init__(self, engine: ApiT):
        self.seller = OzonSellerApiAdapter(engine)


sf = OzonFabric(SyncApii).seller.actions.get_actions()  # Sync request
af = OzonFabric(AsyncApii).seller.actions.get_actions()  # Async request


Я прекрасно понимаю, что совмещение в одном классе синхронных и асинхронных функций это не лучший способ реализации, однако когда я разделял наследование синхронных и асинхронных, то код во многом дублировался, что я посчитал плохим знаком. Суть вопроса заключается в том, как лучше построить дерево наследования при использовании синхронного базового класса и асинхронного? По сути отличается во всем этом дереве это базовые классы и способы запроса к серверу, все остальные одинаковое. Возможно кто-то сталкивался с таким вопросом или читал что-либо, что может помочь?
  • Вопрос задан
  • 160 просмотров
Решения вопроса 1
Vindicar
@Vindicar
RTFM!
Не нужно мешать синхронный и асинхронный подходы в одном проекте. Если хочешь оставить синхронный код - сохрани его в отдельной ветке (ну или в отдельной папке, если не используешь контроль версий). Но в проекте ты будешь использовать либо одно, либо другое, так что смысла оставлять и то и то просто нет.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы