Source code for wuttasync.importing.wutta
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaSync -- Wutta Framework for data import/export and real-time sync
# Copyright © 2024-2026 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Wutta → Wutta import/export
"""
from collections import OrderedDict
from sqlalchemy_utils.functions import get_primary_keys
from wuttjamaican.db.util import make_topo_sortkey
from .base import FromSqlalchemyMirror
from .model import ToWutta
from .handlers import FromWuttaHandler, ToWuttaHandler, Orientation
[docs]
class FromWuttaMirror(FromSqlalchemyMirror): # pylint: disable=abstract-method
"""
Base class for Wutta → Wutta data :term:`importers/exporters
<importer>`.
This inherits from
:class:`~wuttasync.importing.base.FromSqlalchemyMirror`.
"""
[docs]
class FromWuttaToWuttaBase(FromWuttaHandler, ToWuttaHandler):
"""
Base class for Wutta → Wutta data :term:`import/export handlers
<import handler>`.
This inherits from
:class:`~wuttasync.importing.handlers.FromWuttaHandler` and
:class:`~wuttasync.importing.handlers.ToWuttaHandler`.
"""
dbkey = None
"""
Config key for the "other" (non-local) :term:`app database`.
Depending on context this will represent either the source or
target for import/export.
"""
def get_target_model(self): # pylint: disable=missing-function-docstring
return self.app.model
# TODO: pylint (correctly) flags this as duplicate code, matching
# on the wuttasync.importing.csv/versions module - should fix?
[docs]
def define_importers(self):
"""
This overrides typical (manual) importer definition, and
instead dynamically generates a set of importers, e.g. one per
table in the target DB.
It does this by calling :meth:`make_importer_factory()` for
each class found in the :term:`app model`.
"""
importers = {}
model = self.get_target_model()
# pylint: disable=duplicate-code
# mostly try to make an importer for every data model
for name in dir(model):
cls = getattr(model, name)
if (
isinstance(cls, type)
and issubclass(cls, model.Base)
and cls is not model.Base
):
importers[name] = self.make_importer_factory(cls, name)
# sort importers according to schema topography
topo_sortkey = make_topo_sortkey(model)
importers = OrderedDict(
[(name, importers[name]) for name in sorted(importers, key=topo_sortkey)]
)
return importers
[docs]
def make_importer_factory(self, model_class, name):
"""
Generate and return a new :term:`importer` class, targeting
the given :term:`data model` class.
The newly-created class will inherit from:
* :class:`FromWuttaMirror`
* :class:`~wuttasync.importing.model.ToWutta`
:param model_class: A data model class.
:param name: The "model name" for the importer/exporter. New
class name will be based on this, so e.g. ``Widget`` model
name becomes ``WidgetImporter`` class name.
:returns: The new class, meant to process import/export
targeting the given data model.
"""
return type(
f"{name}Importer",
(FromWuttaMirror, ToWutta),
{
"model_class": model_class,
"key": list(get_primary_keys(model_class)),
},
)
def is_default(self, key): # pylint: disable=empty-docstring
""" """
special = [
"Setting",
"Role",
"Permission",
"User",
"UserRole",
"UserAPIToken",
"Upgrade",
]
if key in special:
return False
return True
[docs]
class FromWuttaToWuttaImport(FromWuttaToWuttaBase):
"""
Handler for Wutta (other) → Wutta (local) data import.
This inherits from :class:`FromWuttaToWuttaBase`.
"""
orientation = Orientation.IMPORT
""" """ # nb. suppress docs
[docs]
def make_source_session(self):
"""
This makes a "normal" :term:`db session`, but will use the
engine corresponding to the
:attr:`~FromWuttaToWuttaBase.dbkey`.
"""
if (
not self.dbkey
or self.dbkey == "default"
or self.dbkey not in self.config.appdb_engines
):
raise ValueError(f"dbkey is not valid: {self.dbkey}")
engine = self.config.appdb_engines[self.dbkey]
return self.app.make_session(bind=engine)
[docs]
class FromWuttaToWuttaExport(FromWuttaToWuttaBase):
"""
Handler for Wutta (local) → Wutta (other) data export.
This inherits from :class:`FromWuttaToWuttaBase`.
"""
orientation = Orientation.EXPORT
""" """ # nb. suppress docs
[docs]
def make_target_session(self):
"""
This makes a "normal" :term:`db session`, but will use the
engine corresponding to the
:attr:`~FromWuttaToWuttaBase.dbkey`.
"""
if (
not self.dbkey
or self.dbkey == "default"
or self.dbkey not in self.config.appdb_engines
):
raise ValueError(f"dbkey is not valid: {self.dbkey}")
engine = self.config.appdb_engines[self.dbkey]
return self.app.make_session(bind=engine)