Je crée une application backend avec SQLAlchemy en utilisant la base déclarative. L'ORM nécessite environ 15 tables dont chacune est mappée à un objet de classe dans SQLAlchemy. Parce que ces objets de classe sont tous définis à l'identique, je pensais qu'un modèle d'usine pouvait produire les classes de façon plus concise. Cependant, ces classes doivent non seulement être définies, mais elles doivent également être affectées à des noms de variables uniques afin de pouvoir être importées et utilisées dans le projet.Définition de classe Python dynamique dans SQLAlchemy
(Désolé si cette question est un peu long, je mis à jour comme j'ai mieux compris le problème.)
Parce que nous avons tant de colonnes (~ 1000), nous définissons leurs noms et types dans des fichiers texte externes Gardez les choses lisibles. Après avoir fait cela d'une façon de s'y prendre pour déclarer nos modèles est comme ceci:
class Foo1(Base):
__tablename___ = 'foo1'
class Foo2(Base):
__tablename___ = 'foo2'
... etc
puis je peux ajouter les colonnes en boucle sur le contenu du fichier texte externe et en utilisant la setattr()
sur chaque définition de classe.
Ceci est OK mais cela semble trop répétitif car nous avons environ 15 tables. Donc à la place, j'ai pris la peine d'écrire une fonction d'usine qui pourrait définir les classes dynamiquement.
def orm_factory(class_name):
class NewClass(Base):
__tablename__ = class_name.lower()
NewClass.__name__ = class_name.upper()
return NewClass
Encore une fois je peux boucler sur les colonnes et utiliser setattr()
. Quand je l'ai mis ensemble, il ressemble à ceci:
for class_name in class_name_list:
ORMClass = orm_factory(class_name)
header_keyword_list = get_header_keyword_list(class_name)
define_columns(ORMClass, header_keyword_list)
Où get_header_keyword_list
obtient les informations de colonne et define_columns
effectue l'affectation setattr()
. Lorsque je l'utilise et exécute Base.metadata.create_all()
, le schéma SQL est généré correctement.
Mais, quand j'essaie alors d'importer ces définitions de classe dans un autre modèle que je reçois une erreur comme ceci:
SAWarning: The classname 'NewClass' is already in the registry of this declarative base, mapped to <class 'ql_database_interface.IR_FLT_0'>
Ce, je me rends compte maintenant est logique totale basée sur ce que j'ai appris hier: Python class variable name vs __name__.
Vous pouvez résoudre ce problème en utilisant type
comme générateur de classe dans votre fonction d'usine (comme le font deux des réponses ci-dessous). Cependant, cela ne résout pas le problème de pouvoir importer la classe car pendant que les classes sont construites dynamiquement dans la fonction d'usine, la variable à laquelle la sortie de cette fonction est assignée est statique. Même s'il était dynamique, comme une clé de dictionnaire, il doit être dans l'espace de nom du module pour pouvoir être importé d'un autre module. Voir ma réponse pour plus de détails.
Pourquoi avez-vous besoin de modifier GLOBALS si vous ajoutez les classes construites à un conteneur champ extérieur (list_of_classes.append ou un dict)? Le conteneur lui-même sera importable entre les modules car le conteneur sera dans la portée du module. – cowbert