I'm trying to find the best for users of my python library to implement an abstract class I wrote. Namely, my abstract class define an API to access specific values stored in a database, but I would like to let the user choose how to store it (simple text file, json, sqlite, etc.)
My problem is, how should I retrieve the class the user create and use it in my library ?
This is the solution I came up with, but I don't find it very graceful and wonder if there is a more pythonic way.
In my library:
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def get(self, index):
pass
@abstractmethod
def insert(self, data):
pass
def get_database():
"""call this anywhere I need a concrete database class"""
return Database.__subclasses__()[-1]
In the user code
class SqliteDatabase(Database):
def get(self, index):
# sqlite SELECT and such
return data
def insert(self, data):
# sqlite INSERT INTO
# return data with index provided
return data
Of course, I will return a better error than IndexError if there is no subclass defined, but you get the idea.
Thank you in advance !
-
Can you elaborate a bit more? Where is the problem?James– James2017年11月27日 18:36:22 +00:00Commented Nov 27, 2017 at 18:36
-
Why are you retrieving the class like that? Shouldn't the client pass in an instance of their subclass?Blorgbeard– Blorgbeard2017年11月27日 18:37:28 +00:00Commented Nov 27, 2017 at 18:37
-
@James There is no problem per se, cause everything work in the current state. I just want to know if there is a better way to do this.galactics– galactics2017年11月27日 19:25:07 +00:00Commented Nov 27, 2017 at 19:25
-
@Blorgbeard They could, but I'd rather not have dependency injection if it's avoidable. A simple subclassing should be enough, and my library would do the legworkgalactics– galactics2017年11月27日 19:28:52 +00:00Commented Nov 27, 2017 at 19:28
-
I wouldn't call myself fluent in python, but I would consider this API quite surprising. I'd expect at least to pass the class to the library rather than have it detect it. What if I have a couple of different implementations for different scenarios?Blorgbeard– Blorgbeard2017年11月27日 19:32:43 +00:00Commented Nov 27, 2017 at 19:32
1 Answer 1
I finally settled for something else, as Blorgbeard suggested
_databases = {}
def register(dbname="default"):
def wrapper(klass):
_databases[dbname] = klass
return klass
return wrapper
def get_db(name="default"):
return _databases[name]
And the user only needs to declare
@register()
class SqliteDatabase:
def __get__(self, index):
# retrieve data
if data is None:
raise KeyError(index)
return data
This way, anybody can declare as many as databases as they want. If you have improvements over this version, I'll gladly take them.