Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9a2df29

Browse files
Create sqlalchemy_data_model_visualizer.py
1 parent d33dd67 commit 9a2df29

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

‎sqlalchemy_data_model_visualizer.py‎

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
from enum import Enum
4+
from decimal import Decimal
5+
from sqlalchemy.orm import sessionmaker, declarative_base, relationship
6+
from sqlalchemy import Column, String, DateTime, Integer, Numeric, Boolean, JSON, ForeignKey, LargeBinary, Text, UniqueConstraint, CheckConstraint, text as sql_text
7+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
8+
from sqlalchemy import inspect
9+
import graphviz
10+
from lxml import etree
11+
import os
12+
import re
13+
Base = declarative_base()
14+
15+
def generate_data_model_diagram(models, output_file='my_data_model_diagram'):
16+
# Initialize graph with more advanced visual settings
17+
dot = graphviz.Digraph(comment='Interactive Data Models', format='svg',
18+
graph_attr={'bgcolor': '#EEEEEE', 'rankdir': 'TB', 'splines': 'spline'},
19+
node_attr={'shape': 'none', 'fontsize': '12', 'fontname': 'Roboto'},
20+
edge_attr={'fontsize': '10', 'fontname': 'Roboto'})
21+
22+
# Iterate through each SQLAlchemy model
23+
for model in models:
24+
insp = inspect(model)
25+
name = insp.class_.__name__
26+
27+
# Create an HTML-like label for each model as a rich table
28+
label = f'''<
29+
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
30+
<TR><TD COLSPAN="2" BGCOLOR="#3F51B5"><FONT COLOR="white">{name}</FONT></TD></TR>
31+
'''
32+
33+
for column in insp.columns:
34+
constraints = []
35+
if column.primary_key:
36+
constraints.append("PK")
37+
if column.unique:
38+
constraints.append("Unique")
39+
if column.index:
40+
constraints.append("Index")
41+
42+
constraint_str = ','.join(constraints)
43+
color = "#BBDEFB"
44+
45+
label += f'''<TR>
46+
<TD BGCOLOR="{color}">{column.name}</TD>
47+
<TD BGCOLOR="{color}">{column.type} ({constraint_str})</TD>
48+
</TR>'''
49+
50+
label += '</TABLE>>'
51+
52+
# Create the node with added hyperlink to detailed documentation
53+
dot.node(name, label=label, URL=f"http://{name}_details.html")
54+
55+
# Add relationships with tooltips and advanced styling
56+
for rel in insp.relationships:
57+
target_name = rel.mapper.class_.__name__
58+
tooltip = f"Relation between {name} and {target_name}"
59+
dot.edge(name, target_name, label=rel.key, tooltip=tooltip, color="#1E88E5", style="dashed")
60+
61+
# Render the graph to a file and open it
62+
dot.render(output_file, view=True)
63+
64+
65+
def add_web_font_and_interactivity(input_svg_file, output_svg_file):
66+
if not os.path.exists(input_svg_file):
67+
print(f"Error: {input_svg_file} does not exist.")
68+
return
69+
70+
parser = etree.XMLParser(remove_blank_text=True)
71+
try:
72+
tree = etree.parse(input_svg_file, parser)
73+
except etree.XMLSyntaxError as e:
74+
print(f"Error parsing SVG: {e}")
75+
return
76+
77+
root = tree.getroot()
78+
79+
style_elem = etree.Element("style")
80+
style_elem.text = '''
81+
@import url("https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i");
82+
'''
83+
root.insert(0, style_elem)
84+
85+
for elem in root.iter():
86+
if 'node' in elem.attrib.get('class', ''):
87+
elem.attrib['class'] = 'table-hover'
88+
if 'edge' in elem.attrib.get('class', ''):
89+
source = elem.attrib.get('source')
90+
target = elem.attrib.get('target')
91+
elem.attrib['class'] = f'edge-hover edge-from-{source} edge-to-{target}'
92+
93+
tree.write(output_svg_file, pretty_print=True, xml_declaration=True, encoding='utf-8')
94+
95+
# ________________________________________________________________
96+
97+
98+
# [Insert your sqlalchemy data model classes here below:]
99+
100+
class GenericUser(Base):
101+
__tablename__ = 'generic_user'
102+
email = Column(String, primary_key=True, index=True)
103+
external_id = Column(String, unique=True, nullable=False)
104+
is_active = Column(Boolean, default=True)
105+
is_blocked = Column(Boolean, default=False)
106+
last_ip_address = Column(String, nullable=True)
107+
last_user_agent = Column(String, nullable=True)
108+
last_estimated_location = Column(JSON, nullable=True)
109+
preferences = Column(JSON)
110+
registered_at = Column(DateTime, default=datetime.utcnow, index=True)
111+
last_login = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, index=True)
112+
is_deleted = Column(Boolean, default=False)
113+
deleted_at = Column(DateTime, nullable=True)
114+
customer = relationship("Customer", uselist=False, back_populates="generic_user")
115+
content_creator = relationship("ContentCreator", uselist=False, back_populates="generic_user")
116+
user_sessions = relationship("UserSession", back_populates="generic_user")
117+
audit_logs = relationship("GenericAuditLog", back_populates="actor")
118+
notifications = relationship("GenericNotification", back_populates="recipient")
119+
120+
class Customer(Base):
121+
__tablename__ = 'customer'
122+
email = Column(String, ForeignKey('generic_user.email'), primary_key=True, index=True)
123+
total_purchases = Column(Numeric(10, 10), default=0.0)
124+
generic_user = relationship("GenericUser", back_populates="customer")
125+
service_requests = relationship("ServiceRequest", back_populates="customer")
126+
subscriptions = relationship("GenericSubscription", back_populates="customer")
127+
subscription_usages = relationship("GenericSubscriptionUsage", back_populates="customer")
128+
billing_infos = relationship("GenericBillingInfo", back_populates="customer")
129+
feedbacks_provided = relationship("GenericFeedback", back_populates="customer")
130+
131+
class ContentCreator(Base):
132+
__tablename__ = 'content_creator'
133+
email = Column(String, ForeignKey('generic_user.email'), primary_key=True, index=True)
134+
projects_created = Column(Integer, default=0)
135+
revenue_share = Column(Numeric(10, 10), default=0.7)
136+
total_earned = Column(Numeric(10, 10), default=0.0)
137+
last_project_created_at = Column(DateTime, nullable=True)
138+
generic_user = relationship("GenericUser", back_populates="content_creator")
139+
api_credit_logs = relationship("GenericAPICreditLog", back_populates="content_creator")
140+
api_keys = relationship("GenericAPIKey", back_populates="content_creator")
141+
feedbacks_received = relationship("GenericFeedback", back_populates="content_creator")
142+
143+
class UserSession(Base):
144+
__tablename__ = 'user_session'
145+
id = Column(Integer, primary_key=True)
146+
user_email = Column(String, ForeignKey('generic_user.email'), nullable=False)
147+
session_token = Column(String, unique=True, nullable=False)
148+
expires_at = Column(DateTime, nullable=False)
149+
is_active = Column(Boolean, default=True)
150+
created_at = Column(DateTime, default=datetime.utcnow)
151+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
152+
generic_user = relationship("GenericUser", back_populates="user_sessions")
153+
154+
class FileStorage(Base):
155+
__tablename__ = 'file_storage'
156+
id = Column(Integer, primary_key=True, index=True)
157+
file_data = Column(LargeBinary, nullable=False)
158+
file_type = Column(String, nullable=False)
159+
file_hash = Column(String, nullable=False, unique=True)
160+
upload_date = Column(DateTime, default=datetime.utcnow)
161+
162+
class ServiceRequest(Base):
163+
__tablename__ = 'service_request'
164+
unique_id_for_sharing = Column(String, primary_key=True, index=True)
165+
status = Column(String, CheckConstraint("status IN ('pending', 'completed', 'failed')"), default='pending')
166+
ip_address = Column(String)
167+
request_time = Column(DateTime, default=datetime.utcnow, index=True)
168+
request_last_updated_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
169+
user_input = Column(JSON)
170+
input_data_string = Column(Text)
171+
api_request = Column(JSON)
172+
api_response = Column(JSON)
173+
api_session_id = Column(String, nullable=True, unique=True)
174+
total_cost = Column(Numeric(10, 10), nullable=True)
175+
customer_email = Column(String, ForeignKey('customer.email'))
176+
customer = relationship("Customer", back_populates="service_requests")
177+
178+
# AuditLog
179+
class GenericAuditLog(Base):
180+
__tablename__ = 'generic_audit_log'
181+
id = Column(Integer, primary_key=True, index=True)
182+
action_type = Column(String, nullable=False, index=True)
183+
outcome = Column(String, nullable=True)
184+
field_affected = Column(String, nullable=True)
185+
prev_value = Column(JSON, nullable=True)
186+
new_value = Column(JSON, nullable=True)
187+
actor_email = Column(String, ForeignKey('generic_user.email'), index=True)
188+
related_request_id = Column(Integer, ForeignKey('generic_user_request.unique_id'))
189+
timestamp = Column(DateTime, default=datetime.utcnow)
190+
actor = relationship("GenericUser", back_populates="audit_logs")
191+
192+
# Feedback
193+
class GenericFeedback(Base):
194+
__tablename__ = 'generic_feedback'
195+
id = Column(Integer, primary_key=True, index=True)
196+
score = Column(Integer, nullable=False)
197+
commentary = Column(Text, nullable=True)
198+
customer_email = Column(String, ForeignKey('customer.email'), index=True)
199+
content_creator_email = Column(String, ForeignKey('content_creator.email'), index=True)
200+
request_id = Column(Integer, ForeignKey('generic_user_request.unique_id'))
201+
last_updated = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
202+
is_removed = Column(Boolean, default=False)
203+
removed_at = Column(DateTime, nullable=True)
204+
customer = relationship("Customer", back_populates="feedbacks_provided")
205+
content_creator = relationship("ContentCreator", back_populates="feedbacks_received")
206+
207+
# APIKeys
208+
class GenericAPIKey(Base):
209+
__tablename__ = 'generic_api_key'
210+
id = Column(Integer, primary_key=True, index=True)
211+
api_key = Column(String, unique=True, nullable=False)
212+
content_creator_email = Column(String, ForeignKey('content_creator.email'), index=True)
213+
is_active = Column(Boolean, default=True)
214+
is_revoked = Column(Boolean, default=False)
215+
expires_at = Column(DateTime, nullable=True)
216+
created_at = Column(DateTime, default=datetime.utcnow)
217+
content_creator = relationship("ContentCreator", back_populates="api_keys")
218+
219+
# Notification
220+
class GenericNotification(Base):
221+
__tablename__ = 'generic_notification'
222+
id = Column(Integer, primary_key=True, index=True)
223+
recipient_email = Column(String, ForeignKey('generic_user.email'), index=True)
224+
notification_kind = Column(String, nullable=False)
225+
is_read = Column(Boolean, default=False)
226+
content = Column(Text, nullable=False)
227+
created_at = Column(DateTime, default=datetime.utcnow)
228+
read_at = Column(DateTime, nullable=True)
229+
recipient = relationship("GenericUser", back_populates="notifications")
230+
231+
# APICreditLog
232+
class GenericAPICreditLog(Base):
233+
__tablename__ = 'generic_api_credit_log'
234+
id = Column(Integer, primary_key=True, index=True)
235+
timestamp = Column(DateTime, default=datetime.utcnow)
236+
is_paid = Column(Boolean, default=False)
237+
status = Column(String, default='pending')
238+
expense = Column(Numeric(10, 10), nullable=False)
239+
request_id = Column(Integer, ForeignKey('generic_user_request.unique_id'))
240+
token_count = Column(Integer, nullable=False)
241+
content_creator_email = Column(String, ForeignKey('content_creator.email'))
242+
content_creator = relationship("ContentCreator", back_populates="api_credit_logs")
243+
244+
# SubscriptionType
245+
class GenericSubscriptionType(Base):
246+
__tablename__ = 'generic_subscription_type'
247+
id = Column(Integer, primary_key=True, index=True)
248+
name = Column(String, nullable=False)
249+
monthly_fee = Column(Numeric(10, 10), nullable=False)
250+
monthly_cap = Column(Integer, nullable=False)
251+
created_at = Column(DateTime, default=datetime.utcnow)
252+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
253+
is_removed = Column(Boolean, default=False)
254+
removed_at = Column(DateTime, nullable=True)
255+
subscriptions = relationship("GenericSubscription", back_populates="subscription_type")
256+
257+
# Subscription
258+
class GenericSubscription(Base):
259+
__tablename__ = 'generic_subscription'
260+
id = Column(Integer, primary_key=True, index=True)
261+
customer_email = Column(String, ForeignKey('customer.email'), index=True)
262+
start_date = Column(DateTime, default=datetime.utcnow)
263+
end_date = Column(DateTime, nullable=True)
264+
current_use = Column(Integer, default=0)
265+
subscription_type_id = Column(Integer, ForeignKey('generic_subscription_type.id'))
266+
customer = relationship("Customer", back_populates="subscriptions")
267+
subscription_type = relationship("GenericSubscriptionType", back_populates="subscriptions")
268+
subscription_usages = relationship("GenericSubscriptionUsage", back_populates="subscription")
269+
270+
# SubscriptionUsage
271+
class GenericSubscriptionUsage(Base):
272+
__tablename__ = 'generic_subscription_usage'
273+
id = Column(Integer, primary_key=True, index=True)
274+
customer_email = Column(String, ForeignKey('customer.email'), index=True)
275+
use_count = Column(Integer, default=0)
276+
last_use = Column(DateTime, nullable=True)
277+
subscription_id = Column(Integer, ForeignKey('generic_subscription.id'))
278+
subscription_type_id = Column(Integer, ForeignKey('generic_subscription_type.id'))
279+
customer = relationship("Customer", back_populates="subscription_usages")
280+
subscription = relationship("GenericSubscription", back_populates="subscription_usages")
281+
subscription_type = relationship("GenericSubscriptionType", backref="subscription_usages")
282+
283+
# BillingInfo
284+
class GenericBillingInfo(Base):
285+
__tablename__ = 'generic_billing_info'
286+
id = Column(Integer, primary_key=True, index=True)
287+
customer_email = Column(String, ForeignKey('customer.email'), index=True)
288+
payment_type = Column(String, nullable=False)
289+
payment_data = Column(JSON)
290+
created_at = Column(DateTime, default=datetime.utcnow)
291+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
292+
is_removed = Column(Boolean, default=False)
293+
removed_at = Column(DateTime, nullable=True)
294+
customer = relationship("Customer", back_populates="billing_infos")
295+
296+
models = [GenericUser, Customer, ContentCreator, UserSession, FileStorage, ServiceRequest, GenericAuditLog, GenericFeedback, GenericAPIKey, GenericNotification, GenericAPICreditLog, GenericSubscriptionType, GenericSubscription, GenericSubscriptionUsage, GenericBillingInfo]
297+
298+
299+
output_file_name = 'my_data_model_diagram'
300+
# Generate the diagram and add interactivity
301+
generate_data_model_diagram(models, output_file_name)
302+
add_web_font_and_interactivity('my_data_model_diagram.svg', 'my_interactive_data_model_diagram.svg')

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /