SQL Alchemy: Struktur für praktische Anwendung

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hallo,

habe die Frage bei DB eingeordnet, falls Sie in der falschen Kategorie ist, bitte verschieben. :D

Ich versuche einen HTTP Server mit WebAPI für eine Angular Anwendung zu erstellen. Ich habe schon ein wenig was gemacht, habe aber das Gefühl, dass ich mich ganz schön (mit der Struktur) verzettelt habe. Ich verwende Flask, SQLite und SqlAlchemy.

Die Idee war folgende: Main startet den HTTP Server (Klasse in HTTPServer-Package definiert). In der Klasse des HTTP Servers werden die Hooks aus den anderen Packages eingebunden.

Dabei ist die Struktur für die Datenbank-Packages immer so aufgebaut:
Sql_accounts steht die Definition für SQLAlchemy und die Funktionen zum bearbeiten der Datenbank.
In der Sql_accounts_hooks stehen die "Routes" für den Webserver. Ich lasse in der Sql_accounts file eine engine erstelle und bei jedem Verbindungsaufbau erzeuge ich eine neue Session um die DB Aktionen durchzuführen. Analog dazu habe ich auch das Datenbank Package aufgebaut.
Dies mache ich so, weil ich mit Flask Threaded=True fahre.

Ist das der richtige weg für die SQLAlchemy Parts? Gibt es klügere Wege, wie man die Objekte mit SQLAlchemy-Beteiligung gestaltet?
Ich hab da doch Probleme bei initialisieren. Wie baut man normalerweise eine Struktur auf, um auf mehrere Datenbanken (bei mir sind es nur 2) zuzugreifen?

Beste Grüße
homerunjack

Mein Aufbau:

Main.py
System.ini (Pfade der Datenbanken)

--> Datenbank Package (Datenbankanbindung für Daten)
-----> __init__
-----> datenbank_connector.py (SQLAlchemy: Engine erstellen und Session-Generator)
-----> sql_tabelle1.py (Tabellendefinition SQLAlchemy und Funktionen zum erzeugen, bearbeiten, löschen von Einträgen, holt/erzeugt Verbindung von datenbank_connector)
-----> sql_tabelle1_hooks.py (WebApi Routes mit Verwendung von sql_accounts.py)
-----> …weitere T

--> Accounts Package (Datenbankanbindung für Accounts)
----> __init__
----> datenbank_connector.py (SQLAlchemy: Engine erstellen und Session-Generator)
----> sql_accounts.py (Tabellendefinition SQLAlchemy und Funktionen zum erzeugen, bearbeiten, löschen von Einträgen)
----> sql_accounts_hook.py (WebApi Routes mit Verwendung von sql_accounts.py)

--> HttpServer
----> __init__
----> webserver.py (Klasse mit Server und Verwendung der Hooks)

--> DATASTORE
----> ACCOUNTS
------> accounts.db
----> DATEN
------> db1.db
------> db2.db
------> …
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das du mehrere DBs hast ist milde ausgedrückt ungewöhnlich. Du verlierst Datenintegrität und die Möglichkeit komplexere Abfragen über Tabellen in geteilten DBs zu machen. Solltest du sein lassen.

Auch das permanente erstellen von Sessions klingt komisch. Eine scoped_session auf Moduleeneme für Threads sollte reichen, bei Start wird die konfiguriert mit der Engine. Danach noch eine Middleware zur Transaktionsbehandlung und fertig ist die Laube. Da gibts bestimmt auch schon was von Ratiopharm/PyPI.
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hi,
danke für deine Antwort.
Mehrere Datenbanken, naja, es sind ja SQLite Dateien. Die Accounts-DB stellen sozusagen die System-Datenbank dar. Die Daten-Datenbank stellt die erzeugten bzw. anfallenden Daten dar, die immer jährlich neu erstellt werden. Die haben sozusagen erstmal nichts miteinander zu tun...

D.h. von den Datenbanken soll es dann so aussehen:
Accounts (oder auch System/General)
Daten_2017
Daten_2018

Aber unabhängig ob eine oder mehrere DBs: Wenn ich mir das jetzt neu zusammen bauen müsste, wie müssten die ganzen Elemente hierarchisch mit SQLAlchemy angeordnet werden? Reicht eine engine bei Multithreaded Flask? Von den scoped_sessions habe ich schon gelesen, jedoch noch nicht verwendet. Wann und wo hängt man die Hooks ein? Wo würde man am besten die Tabellendefinitionen laden?

Irgendwie fehlt mir der richtige Weg. Ich schau mal bei Ratiopharm :-P
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Trotzdem: eine DB. Das Jahr ist halt ein Wert.

Und dann reicht eine Engine.


Das mit deinen hooks verstehe ich nicht.
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hey deets,

danke für deine Antworten. Die letzten Tage war viel zu tun, deswegen erst jetzt eine Antwort.

Jetzt nehme ich mal an, ich nutze nur eine DB und eine Engine. Wie würde ich denn die Sachen strukturieren.

Ich habe:
- Webserver Objekt
- Tabellenbeschreibung
- Tabellenfunktionen
- Routes ("Hooks")

Wie hänge ich was wo ein? Gibt es da einen "richtigen" Weg? So habe ich es bisher gemacht:

Webserver Package:

Code: Alles auswählen

from flask import Flask
from flask_restful import Api

from datenbank_pckg.accounts_hooks import accounts_hooks as H_Accounts

class server():
    def __init__(self):

        self.app = Flask(__name__)
        self.api = Api(self.app)
        #Registrierung der Pfade
        H_Accounts(self.api)



    def start(self):
        # Webserver starten
        context = ('HTTPServer/server1.crt', 'HTTPServer/server1.key')
        self.app.run(host='0.0.0.0', port=5002, ssl_context=context, threaded=True, debug=False)
Datenbank Package (Infos zu Strukturen und Funktionen zum bearbeiten der Einträge):

1. Der Verbinder von SQL Alchemy

Code: Alles auswählen

from sqlalchemy import *
from sqlalchemy.orm import sessionmaker

from accounts_pckg.accounts import *
from .base import Base # = declarative_base()

class conDB():
    def __init__(self):
        #self.db = create_engine('sqlite:///'+a_conf['dir']+a_conf['db'])
        self.db = create_engine('sqlite:///'+ ???)
        self.db.execute('pragma foreign_keys=on')
        Base.metadata.create_all(self.db)

    def get_session(self):
        session_factory = sessionmaker(bind=self.db)
        Session = scoped_session(session_factory)
        # Session = sessionmaker(bind=self.db)
        session = Session()
        return Session

    def close_session(self, session):
        session.close()
2. Tabellenbeschreibung und Funktionen

Code: Alles auswählen

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy import *
from sqlalchemy.orm import sessionmaker
import config_pckg
from .base import Base2
from crypt_pckg.hash import *




class Sql_Accounts(Base2):

    __tablename__ = 'Accounts'
    id = Column(Integer, primary_key=True)
    anmeldename = Column(String(250), nullable=False)
    password = Column(String(250), nullable=False)
    email = Column(String(250), nullable=False)
    gesperrt = Column(Integer, nullable=False)
    ablaufdatum = Column(String(250), nullable=False)
    userlevel = Column(Integer, nullable=False)


class Accounts():

    def __init__(self, session):
        '''
        Übernahme des Session Objekts der Anwendung
        :param session:
        '''

        # Datenbank auslesen
        self.session = session
        # Initialisierung abgeschlossen

    def get_account(self, mid):
        a = self.session.query(Sql_Accounts).filter(Sql_Accounts.id == mid).first()
        dict = a.__dict__
        dict.pop('_sa_instance_state')
        return dict



    def create_account(self, dic):
        '''
        Erstellung eines Accounts 
        '''
        # Objekt Schiedsrichter (Tabelle) anlegen
        a = Sql_Accounts()

        a.anmeldename = dic['anmeldename']
        a.password = generate_password_hash512(dic['password'])
        a.email = dic['email']
        a.gesperrt = dic['gesperrt']
        a.ablaufdatum = dic['ablaufdatum']
        a.userlevel = dic['userlevel']

        # Objekt an Session übermitteln
        self.session.add(a)
        self.session.commit()
        print('Account wurde erstellt.')

3. Hooks

Code: Alles auswählen

from flask_restful import Resource, Api
from flask_jsonpify import jsonpify

def accounts_hooks(api):
    api.add_resource(Api_account_new, '/newaccount/<variable_id>')


class Api_account_new(Resource):
    def get(self, variable_id):

        # Funktionen aus Klasse Accounts verwenden
        con = conDB('Pfad...')
        x = Accounts(conDB.get_session())
        # Ergebnisse zurückgeben...
        return jsonpify({'account':'new', 'nr':variable_id})

Würde mich freuen, wenn mir jemand mal einen Tipp geben kann, wie ich die SQL Alchemy so einbinden kann, dass alles noch recht modular bleibt, d.h. ohne viel Aufwand neue Tabellen und Funktionen hinzufügen kann.

Beste Grüße
homerunjack
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das klingt so als ob du quasi pro Tabelle eine REST-Schnittstelle anbieten willst. Ich kenne mich mit Flask jetzt nicht so aus (persoenlich nutze ich bottle, und Web-Programmierung ist ein Ding der Vergangenheit). Aber ich bin mir recht sicher, dass es fuer so etwas schon etwas gibt.

Wenn ich googele, finde ich zB https://flask-restless.readthedocs.io/en/stable/

Ist es das, was du suchst?
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hi,

eine API auf REST Basis soll es schon sein. Ich schaue mir das Flask Restless mal an, hört sich erstmal interessant an.

Mein Ziel war es erstmal, dass SQL Alchemy ein wenig zu verstehen und sinnvoll einzubinden.
Das klingt so als ob du quasi pro Tabelle eine REST-Schnittstelle anbieten willst.
Es ist eher so. Ich möchte für jeden Part des Programms den ich einbinde die REST mitliefern und einbinden, damit alles modular bleibt und ich nach belieben auch Module weglassen oder hinzufügen kann, ohne dass so viele Abhängigkeiten im bestehenden Code mich daran hindern.

Daher wie bei meinen Code Schnippsel zu sehen, würde ich beim Webserver die Schnittstelle einbinden. Mehr Schnittmengen braucht der Webserver mit dem Rest des Moduls eigentlich nicht. Die Module bzw. Programmteile, die eine REST mitliefern, können ja x-beliebige Funktionalitäten haben.
(persoenlich nutze ich bottle, und Web-Programmierung ist ein Ding der Vergangenheit)
Was macht man denn in der Zukunft? :D


Beste Grüße
homerunjack
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich denke sowas wie RESTless ist prinzipiell schon ein guter Ansatz. Inwiefern dein Wunsch nach Kompositionalitaet konzeptionell gut geht bin ich mir unsicher. Eine Datenbank die ihr Geld wert ist (im Sinne von etwas anderem als trivialer Datenspeicherung, sondern wirklich miteinander interagierende Strukturen) braucht ueblicherweise spezifischeren Anwendungscode, der dann einem solchen simplen CRUD-Ansatz widerspricht. Aber das mag bei deiner Anwendung konkret anders sein.

Der Webprogrammierung im allgemeinen gehoert die Zukunft. Persoenlich habe ich mich auf die langsame Wasserrutsche hin zur Fruehverrentung gesetzt, weil ich nicht mehr Lust habe, Nachmittags ein anderes JS-Framework zu benutzen als das, mit dem ich am Morgen angefangen habe ;) Und darum Richtung embedded und C++-Programmierung umgeschwenkt. Das ist aber keine Abwertung von Web, sondern ganz persoenlich.
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hi,

die Kompositionalitaet wäre mir schon sehr wichtig, damit alles eine ordentliche und übersichtliche Struktur hat.
Ich schaue mir das Restless jetzt mal genauer an.

Wäre meine Struktur ein so schlechter Weg? (Ich muss ja nur noch das SQL Alchemy irgendwie logisch da rein basteln...)
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich sage nicht, dass es schlecht ist. Dafuer fehlt mir die Information.

Prinzipiell ist dein Vorgehen begruessenswert. Zu versuchen, sauber zu arbeiten, klare Schnittstellen auch innerhalb der eigenen Anwendung zu schaffen - das ist gut.

Es scheint aber etwas, als ob du das auf biegen und brechen erreichen willst. Vor allem bei den separaten Datenbanken bin ich da misstrauisch geworden.

Und eine Webandwendung, bei der so problemlos Teile an und ausgeknipst werden koennen, ist nach meiner Erfahrung (und haben tu ich die schon ;) ) eher unwahrscheinlich. Dazu muesste man dann eben mehr ueber den Zweck als ueber die Mittel reden. Solange wir das nicht tun, kann ich dir auch nicht versichern, dass das schon alles knorke ist.
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hi,
habe mich jetzt ein wenig in Flask Restless eingelesen bzw. auch ein paar Tutorials angesehen.

Kurz zusammengefasst: Für eine Tabelle in der Datenbank wird automatisch eine API angelegt. Das ist eine wirklich feine Sache, da ich das ja in meinen Code Schnippsel alles selber integriert habe, heißt Datensatz anlegen, löschen, bearbeiten. Das scheint ziemlich simpel zu funktionieren.

Was aber scheinbar fehlt ist eine "Custom Route" Funktion, um auch Funktionen bereitzustellen, die nicht auf die Datenbank zugreifen. In meinem Fall z.B. bei Accounts eine "Passwort vergessen" Funktion, die eine E-Mail an den User verschickt (und dann auch in der Datenbank bei Accounts eine UUID erzeugt, die für den Link in der Mail verwendet wird.)

Gut, aber prinzipiell hätte ich mir bei meinem Code vieles sparen können. Ist ja bei den reinen Tabellen eigentlich fast alles doppelt. Aber man lernt ja nie aus...
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@homerunjack: das erklärt immer noch nicht, was Du eigentlich machen willst. Es ist unüblich, per Web-API direkt auf den Tabellen zu arbeiten, da das jegliche Art von Zugriffskontrolle verhindert. Außerdem ist damit die Datenbankstruktur nach außen hin sichtbar, was Änderungen sehr erschwert.
Direkt auf den Tabellen zu arbeiten ist meist auch zu Low-Level. Der Client möchte auf einem höheren Abstraktionsniveau arbeiten.
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hey Sirius,

ich möchte eine WebAPI für einen Angular-Client erstellen. Dabei ist die Python Anwendung fast ausschließlich für die Datenhaltung zuständig (und für den Versand von ein paar Emails und ein paar Cron-Jobs..)

Dabei soll es eine Tabelle für die User (Accounts) geben, eine weitere Tabelle für Veranstaltungen. Dann soll eine Verknüpfungstabelle erzeugt werden, wo User einer Veranstaltung zugewiesen werden. Die User können dann die Teilnahme an der Veranstaltung bestätigen. Das wäre der Kern der Anwendung.

Daher ist Python fast ausschließlich für die Verwaltung zuständig, und der Client, die Angular Anwendung für das organisieren der Verknüpfungstabelle. Zusätzlich sendet Python noch Emails mit Bestätigungslinks raus und/oder überprüft zyklisch, ob es schon Antworten gibt und sendet gegebenenfalls erneut eine Mail. Da kommt dann aber noch viel drumherum, da es für einen speziellen Anwendungsfall entwickelt wird.

Es ist aber am Ende ein reiner HTTPServer, der über die API angesteuert wird.
Es ist unüblich, per Web-API direkt auf den Tabellen zu arbeiten, da das jegliche Art von Zugriffskontrolle verhindert.
Hier würde ich nochmal widersprechen. Es gibt doch eine HTTP Basis Authentification. Hier kann ich doch jeder Benutzerrolle auch gewissen Zugriffe erlauben?!?
Das Flask Restless die Tabellen direkt mit der Api verknüpft, ist für die reine Verwaltung der Datenbank meiner Meinung nach sehr sinnvoll.
Die Funktionen, die über die Verwaltung (z.B. der User Accounts) hinaus geht (z.B. Email raus hauen bei neuer Verknüpfung) müssen natürlichen individuell gestaltet werden.

Wie würdest du denn bei reiner Verwaltung die Api aufbauen? Als Beispiel: Accounts - User anlegen, bearbeiten, löschen...

Mit besten Grüßen
homerunjack
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

So hat Sirius3 das nicht gemeint. Natürlich kann man Zugriffe auf den Webserver selbst beschränken. Wenn du dann aber einfach die Tabellen darbietest, kann ich als User A Einträge für User B machen.

Es muss dir immer bewusst sein, das deine HTTP-API von jedem benutzt werden kann wie er will. Du kannst da nichts sicherstellen, nur weil deine angular app das nicht zulässt.

Darum schreibt man eben dezidierte API-Methoden, die zB die eingeloggte User Id für alles benutzen, statt die vertrauensselig reingereicht zu bekommen.
homerunjack
User
Beiträge: 24
Registriert: Donnerstag 21. Juli 2016, 12:12

Hi,

ok, ich verstehe schon, was ihr meint.

Wenn ich über die HTTP die Basis Auth verwende und der Angular Client die Benutzerkennung des aktuellen Users mitsendet, dann sollte es doch aber ausreichend sein?

Falls ich jetzt immer noch total falsch liege, hast du eventuell irgendwo ein Beispiel / Tutorial / kleines Codebeispiel, damit ich mir soetwas mal ansehen kann :D


Beste Grüße
homerunjack
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

HTTP-Auth ist *sehr* ungewoehnlich. Das gibt es, aber das ist voellig marginal. Alleine schon weil bei HTTP (ohne S) immer die Credentials schoen lesbar fuer jeden Mann-in-der-Mitte mitlesbar sind.

Stattdessen benutzt man einfach Cookies zur Identifikation einer Session, fuer deren Erzeugung es einen expliziten login gibt.

Und Beispiele sind letztlich jede REST-API. Die Komponenten die dir ja sehr wichtig sind (und nicht zu unrecht, so soll das jetzt nicht rueberkommen), geben ja schon eine gewisse Struktur vor. Wenn du zb ein

/event/<id>/attendees

Namensraum hast, der Infos fuer ein Event vorhaelt, und die POST-Methode (oder PUT, die Unterscheidung finde ich oft artifiziell) darauf erzeugt ein neues User->Event-Mapping, dann wird das halt hinter den Kulissen mit der gerade vorgefundenen User-ID erzeugt. Der naechste Aufruf von

/event/<id>/attendees

ergibt dann halt die Liste mit dem neuen User drin.

Ob jeder x-beliebige User alle Attendees sehen darf, oder nur sich selbst (um zu checken, ob man schon drin ist), haengt jetzt wieder sehr von deiner Anwendung ab. Ein Admin zB duerfte wohl alle sehen, ein normaler User ggf. nur Leute, die als "Freunde" klassifiziert sind.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@homerunjack: die User-ID darf nie vom Client kommen und ungeprüft weiterverarbeitet werden, denn der Client könnte auch irgendeine andere ID als die eigene schicken. Wie __deets__ schon geschrieben hat, wird normalerweise ein Login über eine eigene Seite gemacht und die Daten (wie z.b. User-ID) in einer Session auf dem Server gespeichert. Die Session-ID ist so lang, dass man sie nicht erraten kann, also gefahrlos als Authentifizierungsmerkmal verwenden kann. Deine REST-API muß dann aber mit der Session-Information arbeiten, keine Ahnung, ob das dann ein fertiges Paket unterstützt.
Antworten