Skip to content

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
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)