0

I have a package with some code for injecting some database dependency.

The folder structure is the following:

  • services.py <- the interface has to be injected there
  • table_interface.py
  • table_mongo_db.py
  • table_odbc.py

The issue comes with the two specific implementations, that respectively requires the packages pymongo and pyodbc, which are optionals.

The beginning of the code of table_odbc.py is the following:

import logging

import pandas as pd

try:
    import pyodbc
except ModuleNotFoundError:
    logging.getLogger(__name__).warning("Module pyodbc not loaded")

from .table_interface import InterfaceTable


class OdbcTable(InterfaceTable):
    def __init__(
        self,
        odbc_connection: pyodbc.Connection,  # issue there
    ):
        self._odbc_connection = odbc_connection

    def read(self):
        query = "{}"
        df = pd.read_sql(query, self._odbc_connection)
        return df

It is roughly the same code for mongoDB, but with type hinting pymongo.collection.Collection

The code fails to import with NameError, saying it doesn't recognize pyodbc in the type hint (in environement that didn't install it).

I found two solutions:

  • remove the type hints (which I don't want)
  • in case of failing imports, define the following custom class:
try:
    import pyodbc
except ModuleNotFoundError:
    logging.getLogger(__name__).warning("Module pyodbc not loaded")

    class pyodbc:
        """
        Dummy class for type hint
        """
        Connection = None

I don't like much the last solution but it is the one I am going currently with but I was wondering if an alternative exists.

Coding thermodynamist
  • 1,340
  • 1
  • 10
  • 18
  • If your `__init__()` just consists the `pass` statement, why have it at all? If your `OdbcTable` doesn't define or override any methods, the whole body of your class could just be the `pass` statement ([as is often done with declaring a custom Python exception](https://stackoverflow.com/a/1319675/1643973)). – jjramsey Feb 11 '22 at 16:00
  • It is actually not a pass statement, that was just for the example. I edit the question to reflect that – Coding thermodynamist Feb 11 '22 at 16:34
  • 1
    If you can't load the `pyodbc` module, how can `OdbcTable` have a useful definition, then? Looks like your constructor depends on the `odbc_connection` argument actually having the type `pyodbc.Connection`. – jjramsey Feb 11 '22 at 16:57
  • If it can't load it, off course the user won't be able to use it. But as is, the user wouldn't be able to install the package at all due to an import error. It is an optional dependency for user with `pyodbc` installed. However, the user could also choose to not use pyodbc and if not, the user should not have to install a package that won't be used (zero-cost abstraction: you don't pay for what you don't use). The user can choose to use for example the mongo implementation, or implement itself the interface and inject it into the service (dependency injection pattern) – Coding thermodynamist Feb 11 '22 at 17:11
  • 1
    Seems like the simplest solution would be to not define the `OdbcTable` class if `pyodbc` isn't installed. – jjramsey Feb 11 '22 at 17:46
  • That's a fairly good point, I changed that and moved the try except block into the init sequence of the package to deal with the optional dependencies. Thanks a lot ! – Coding thermodynamist Feb 13 '22 at 12:50

0 Answers0