[
  {
    "path": ".github/workflows/python-publish.yml",
    "content": "name: Build and Publish to PyPI\n\non:\n  push:\n    branches:\n      - main  \n  release:\n    types: [created]\n\njobs:\n  build-and-publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Python\n        uses: actions/setup-python@v3\n        with:\n          python-version: '3.x'\n\n      - name: Build wheel\n        run: |\n          python -m pip install --upgrade build\n          python -m build\n\n      - name: Publish to PyPI\n        if: github.event_name == 'release' && github.event.action == 'created'\n        run: |\n          python -m pip install --upgrade twine\n          twine upload dist/*\n        env:\n          TWINE_USERNAME: __token__\n          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Jeff Emanuel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include requirements.txt\n"
  },
  {
    "path": "README.md",
    "content": "# SQLAlchemy Data Model Visualizer\n\n## Overview\n\nThis Python-based utility generates high-quality, readable visualizations of your SQLAlchemy ORM models with almost no effort. With a focus on clarity and detail, it uses Graphviz to render each model as a directed graph, making it easier to understand the relationships between tables in your database schema.\n\n![Example Data Model Diagram](https://raw.githubusercontent.com/Dicklesworthstone/sqlalchemy_data_model_visualizer/main/my_interactive_data_model_diagram.svg)\n\n## Try it Out Easily in Colab:\n\n[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1np5kPvDtdhq138eLHOGINYuTUMJo_wrj?usp=sharing)\n\n## Features\n\n- Automatically maps SQLAlchemy ORM models to a directed graph.\n- Table-like representation of each model with fields, types, and constraints.\n- Export diagrams to SVG format for high-quality viewing and printing using Roboto font. \n\n## Installation with pip and Usage:\n\n```bash\npip install sqlalchemy-data-model-visualizer\n\n# Suppose these are your SQLAlchemy data models defined above in the usual way, or imported from another file:\nmodels = [GenericUser, Customer, ContentCreator, UserSession, FileStorage, ServiceRequest, GenericAuditLog, GenericFeedback, GenericAPIKey, GenericNotification, GenericAPICreditLog, GenericSubscriptionType, GenericSubscription, GenericSubscriptionUsage, GenericBillingInfo]\noutput_file_name = 'my_data_model_diagram'\ngenerate_data_model_diagram(models, output_file_name)\nadd_web_font_and_interactivity('my_data_model_diagram.svg', 'my_interactive_data_model_diagram.svg')\n```\n\n## Installation from Source\n\nTo get started, clone the repository and install the required packages.\n\n```bash\ngit clone https://github.com/Dicklesworthstone/sqlalchemy_data_model_visualizer.git\ncd sqlalchemy_data_model_visualizer\npython3 -m venv venv\nsource venv/bin/activate\npython3 -m pip install --upgrade pip\npython3 -m pip install wheel\npip install -r requirements.txt\n```\n\n## Requirements\n\n- Python 3.x\n- SQLAlchemy\n- Graphviz\n- lxml\n\n## Usage\n\n### Generate Data Model Diagram\n\nFirst, paste in your SQLAlchemy models. A set of fairly complex data models are provided in the code directly as an example-- just replace these with your own from your application.\n\nThen, simply call the `generate_data_model_diagram` function. This will generate an SVG file with the name `my_data_model_diagram.svg`.\n\n## API Documentation\n\n### `generate_data_model_diagram(models, output_file='my_data_model_diagram', add_labels=True)`\n\n- `models`: List of SQLAlchemy models you want to visualize.\n- `output_file`: Name of the output SVG file.\n- `add_labels`: Set to False to hide labels on the edges between tables\n\n## Contributing\n\nContributions are welcome! Please open an issue or submit a pull request.\n\n## License\n\nThis project is licensed under the MIT License.\n\n---\n\nThanks for your interest in my open-source project! I hope you find it useful. You might also find my commercial web apps useful, and I would really appreciate it if you checked them out:\n\n**[YoutubeTranscriptOptimizer.com](https://youtubetranscriptoptimizer.com)** makes it really quick and easy to paste in a YouTube video URL and have it automatically generate not just a really accurate direct transcription, but also a super polished and beautifully formatted written document that can be used independently of the video.\n\nThe document basically sticks to the same material as discussed in the video, but it sounds much more like a real piece of writing and not just a transcript. It also lets you optionally generate quizzes based on the contents of the document, which can be either multiple choice or short-answer quizzes, and the multiple choice quizzes get turned into interactive HTML files that can be hosted and easily shared, where you can actually take the quiz and it will grade your answers and score the quiz for you.\n\n**[FixMyDocuments.com](https://fixmydocuments.com/)** lets you submit any kind of document— PDFs (including scanned PDFs that require OCR), MS Word and Powerpoint files, images, audio files (mp3, m4a, etc.) —and turn them into highly optimized versions in nice markdown formatting, from which HTML and PDF versions are automatically generated. Once converted, you can also edit them directly in the site using the built-in markdown editor, where it saves a running revision history and regenerates the PDF/HTML versions.\n\nIn addition to just getting the optimized version of the document, you can also generate many other kinds of \"derived documents\" from the original: interactive multiple-choice quizzes that you can actually take and get graded on; slick looking presentation slides as PDF or HTML (using LaTeX and Reveal.js), an in-depth summary, a concept mind map (using Mermaid diagrams) and outline, custom lesson plans where you can select your target audience, a readability analysis and grade-level versions of your original document (good for simplifying concepts for students), Anki Flashcards that you can import directly into the Anki app or use on the site in a nice interface, and more.\n"
  },
  {
    "path": "requirements.txt",
    "content": "sqlalchemy\ngraphviz\nlxml\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\nfrom pathlib import Path\n\n# Define the directory where this setup.py file is located\nhere = Path(__file__).parent\n\n# Read the contents of README file\nlong_description = (here / 'README.md').read_text(encoding='utf-8')\n\n# Read the contents of requirements file\nrequirements = (here / 'requirements.txt').read_text(encoding='utf-8').splitlines()\n\nsetup(\n    name='sqlalchemy_data_model_visualizer',\n    version='0.1.3',  # Update the version number for new releases\n    description='A tool to visualize SQLAlchemy data models with Graphviz.',\n    long_description=long_description,\n    long_description_content_type='text/markdown',\n    author='Jeffrey Emanuel',\n    author_email='jeff@pastel.network',\n    url='https://github.com/Dicklesworthstone/sqlalchemy_data_model_visualizer',\n    py_modules=['sqlalchemy_data_model_visualizer'],\n    install_requires=requirements,\n    classifiers=[\n        'Development Status :: 3 - Alpha',\n        'Intended Audience :: Developers',\n        'License :: OSI Approved :: MIT License',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n        'Programming Language :: Python :: 3.11',\n    ],\n    license='MIT',\n    keywords='sqlalchemy visualization graphviz data-model',\n    include_package_data=True,  # This tells setuptools to check MANIFEST.in for additional files\n)\n"
  },
  {
    "path": "sqlalchemy_data_model_visualizer.py",
    "content": "from datetime import datetime\nfrom typing import Optional\nfrom enum import Enum\nfrom decimal import Decimal\nfrom sqlalchemy.orm import sessionmaker, declarative_base, relationship\nfrom sqlalchemy import Column, String, DateTime, Integer, Numeric, Boolean, JSON, ForeignKey, LargeBinary, Text, UniqueConstraint, CheckConstraint, text as sql_text\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession\nfrom sqlalchemy import inspect\nimport graphviz\nfrom lxml import etree\nimport os\nimport re\nBase = declarative_base()\n\ndef generate_data_model_diagram(models, output_file='my_data_model_diagram', add_labels=True, view_diagram=True):\n    # Initialize graph with more advanced visual settings\n    dot = graphviz.Digraph(comment='Interactive Data Models', format='svg', \n                            graph_attr={'bgcolor': '#EEEEEE', 'rankdir': 'TB', 'splines': 'spline'},\n                            node_attr={'shape': 'none', 'fontsize': '12', 'fontname': 'Roboto'},\n                            edge_attr={'fontsize': '10', 'fontname': 'Roboto'})\n\n    # Iterate through each SQLAlchemy model\n    for model in models:\n        insp = inspect(model)\n        name = insp.class_.__name__\n\n        # Create an HTML-like label for each model as a rich table\n        label = f'''<\n        <TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n        <TR><TD COLSPAN=\"2\" BGCOLOR=\"#3F51B5\"><FONT COLOR=\"white\">{name}</FONT></TD></TR>\n        '''\n                \n        for column in insp.columns:\n            constraints = []\n            if column.primary_key:\n                constraints.append(\"PK\")\n            if column.unique:\n                constraints.append(\"Unique\")\n            if column.index:\n                constraints.append(\"Index\")\n            \n            constraint_str = ','.join(constraints)\n            color = \"#BBDEFB\"\n            \n            label += f'''<TR>\n                         <TD BGCOLOR=\"{color}\">{column.name}</TD>\n                         <TD BGCOLOR=\"{color}\">{column.type} ({constraint_str})</TD>\n                         </TR>'''\n        \n        label += '</TABLE>>'\n        \n        # Create the node with added hyperlink to detailed documentation\n        dot.node(name, label=label, URL=f\"http://{name}_details.html\")\n\n        # Add relationships with tooltips and advanced styling\n        for rel in insp.relationships:\n            target_name = rel.mapper.class_.__name__\n            tooltip = f\"Relation between {name} and {target_name}\"\n            dot.edge(name, target_name, label=rel.key if add_labels else None, tooltip=tooltip, color=\"#1E88E5\", style=\"dashed\")\n\n    # Render the graph to a file and open it\n    dot.render(output_file, view=view_diagram)           \n\n\ndef add_web_font_and_interactivity(input_svg_file, output_svg_file):\n    if not os.path.exists(input_svg_file):\n        print(f\"Error: {input_svg_file} does not exist.\")\n        return\n\n    parser = etree.XMLParser(remove_blank_text=True)\n    try:\n        tree = etree.parse(input_svg_file, parser)\n    except etree.XMLSyntaxError as e:\n        print(f\"Error parsing SVG: {e}\")\n        return\n\n    root = tree.getroot()\n\n    style_elem = etree.Element(\"style\")\n    style_elem.text = '''\n    @import url(\"https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i\");\n    '''\n    root.insert(0, style_elem)\n\n    for elem in root.iter():\n        if 'node' in elem.attrib.get('class', ''):\n            elem.attrib['class'] = 'table-hover'\n        if 'edge' in elem.attrib.get('class', ''):\n            source = elem.attrib.get('source')\n            target = elem.attrib.get('target')\n            elem.attrib['class'] = f'edge-hover edge-from-{source} edge-to-{target}'\n\n    tree.write(output_svg_file, pretty_print=True, xml_declaration=True, encoding='utf-8')\n\n# ________________________________________________________________\n\n\n# [Insert your sqlalchemy data model classes here below:]\n\nuse_demo = 0\n\nif use_demo:\n    class GenericUser(Base):\n        __tablename__ = 'generic_user'\n        email = Column(String, primary_key=True, index=True)\n        external_id = Column(String, unique=True, nullable=False)\n        is_active = Column(Boolean, default=True)\n        is_blocked = Column(Boolean, default=False)\n        last_ip_address = Column(String, nullable=True)\n        last_user_agent = Column(String, nullable=True)\n        last_estimated_location = Column(JSON, nullable=True)\n        preferences = Column(JSON)\n        registered_at = Column(DateTime, default=datetime.utcnow, index=True)\n        last_login = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, index=True)\n        is_deleted = Column(Boolean, default=False)\n        deleted_at = Column(DateTime, nullable=True)\n        customer = relationship(\"Customer\", uselist=False, back_populates=\"generic_user\")\n        content_creator = relationship(\"ContentCreator\", uselist=False, back_populates=\"generic_user\")\n        user_sessions = relationship(\"UserSession\", back_populates=\"generic_user\")\n        audit_logs = relationship(\"GenericAuditLog\", back_populates=\"actor\")\n        notifications = relationship(\"GenericNotification\", back_populates=\"recipient\")\n        \n    class Customer(Base):\n        __tablename__ = 'customer'\n        email = Column(String, ForeignKey('generic_user.email'), primary_key=True, index=True)\n        total_purchases = Column(Numeric(10, 10), default=0.0)\n        generic_user = relationship(\"GenericUser\", back_populates=\"customer\")\n        service_requests = relationship(\"ServiceRequest\", back_populates=\"customer\")\n        subscriptions = relationship(\"GenericSubscription\", back_populates=\"customer\")\n        subscription_usages = relationship(\"GenericSubscriptionUsage\", back_populates=\"customer\")\n        billing_infos = relationship(\"GenericBillingInfo\", back_populates=\"customer\")\n        feedbacks_provided = relationship(\"GenericFeedback\", back_populates=\"customer\")\n    \n    class ContentCreator(Base):\n        __tablename__ = 'content_creator'\n        email = Column(String, ForeignKey('generic_user.email'), primary_key=True, index=True)\n        projects_created = Column(Integer, default=0)\n        revenue_share = Column(Numeric(10, 10), default=0.7)\n        total_earned = Column(Numeric(10, 10), default=0.0)\n        last_project_created_at = Column(DateTime, nullable=True)\n        generic_user = relationship(\"GenericUser\", back_populates=\"content_creator\")\n        api_credit_logs = relationship(\"GenericAPICreditLog\", back_populates=\"content_creator\")\n        api_keys = relationship(\"GenericAPIKey\", back_populates=\"content_creator\")\n        feedbacks_received = relationship(\"GenericFeedback\", back_populates=\"content_creator\")\n    \n    class UserSession(Base):\n        __tablename__ = 'user_session'\n        id = Column(Integer, primary_key=True)\n        user_email = Column(String, ForeignKey('generic_user.email'), nullable=False)\n        session_token = Column(String, unique=True, nullable=False)\n        expires_at = Column(DateTime, nullable=False)\n        is_active = Column(Boolean, default=True)\n        created_at = Column(DateTime, default=datetime.utcnow)\n        updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\n        generic_user = relationship(\"GenericUser\", back_populates=\"user_sessions\")\n            \n    class FileStorage(Base):\n        __tablename__ = 'file_storage'\n        id = Column(Integer, primary_key=True, index=True)\n        file_data = Column(LargeBinary, nullable=False)\n        file_type = Column(String, nullable=False)\n        file_hash = Column(String, nullable=False, unique=True)\n        upload_date = Column(DateTime, default=datetime.utcnow)\n    \n    class ServiceRequest(Base):\n        __tablename__ = 'service_request'\n        unique_id_for_sharing = Column(String, primary_key=True, index=True)\n        status = Column(String, CheckConstraint(\"status IN ('pending', 'completed', 'failed')\"), default='pending')\n        ip_address = Column(String)\n        request_time = Column(DateTime, default=datetime.utcnow, index=True)\n        request_last_updated_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\n        user_input = Column(JSON)\n        input_data_string = Column(Text)\n        api_request = Column(JSON)\n        api_response = Column(JSON)\n        api_session_id = Column(String, nullable=True, unique=True)\n        total_cost = Column(Numeric(10, 10), nullable=True)\n        customer_email = Column(String, ForeignKey('customer.email'))\n        customer = relationship(\"Customer\", back_populates=\"service_requests\")\n    \n    # AuditLog\n    class GenericAuditLog(Base):\n        __tablename__ = 'generic_audit_log'\n        id = Column(Integer, primary_key=True, index=True)\n        action_type = Column(String, nullable=False, index=True)\n        outcome = Column(String, nullable=True)\n        field_affected = Column(String, nullable=True)\n        prev_value = Column(JSON, nullable=True)\n        new_value = Column(JSON, nullable=True)\n        actor_email = Column(String, ForeignKey('generic_user.email'), index=True)\n        related_request_id = Column(Integer, ForeignKey('generic_user_request.unique_id'))\n        timestamp = Column(DateTime, default=datetime.utcnow)\n        actor = relationship(\"GenericUser\", back_populates=\"audit_logs\")\n    \n    # Feedback\n    class GenericFeedback(Base):\n        __tablename__ = 'generic_feedback'\n        id = Column(Integer, primary_key=True, index=True)\n        score = Column(Integer, nullable=False)\n        commentary = Column(Text, nullable=True)\n        customer_email = Column(String, ForeignKey('customer.email'), index=True)\n        content_creator_email = Column(String, ForeignKey('content_creator.email'), index=True)\n        request_id = Column(Integer, ForeignKey('generic_user_request.unique_id'))\n        last_updated = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\n        is_removed = Column(Boolean, default=False)\n        removed_at = Column(DateTime, nullable=True)\n        customer = relationship(\"Customer\", back_populates=\"feedbacks_provided\")\n        content_creator = relationship(\"ContentCreator\", back_populates=\"feedbacks_received\")\n    \n    # APIKeys\n    class GenericAPIKey(Base):\n        __tablename__ = 'generic_api_key'\n        id = Column(Integer, primary_key=True, index=True)\n        api_key = Column(String, unique=True, nullable=False)\n        content_creator_email = Column(String, ForeignKey('content_creator.email'), index=True)\n        is_active = Column(Boolean, default=True)\n        is_revoked = Column(Boolean, default=False)\n        expires_at = Column(DateTime, nullable=True)\n        created_at = Column(DateTime, default=datetime.utcnow)\n        content_creator = relationship(\"ContentCreator\", back_populates=\"api_keys\")\n    \n    # Notification\n    class GenericNotification(Base):\n        __tablename__ = 'generic_notification'\n        id = Column(Integer, primary_key=True, index=True)\n        recipient_email = Column(String, ForeignKey('generic_user.email'), index=True)\n        notification_kind = Column(String, nullable=False)\n        is_read = Column(Boolean, default=False)\n        content = Column(Text, nullable=False)\n        created_at = Column(DateTime, default=datetime.utcnow)\n        read_at = Column(DateTime, nullable=True)\n        recipient = relationship(\"GenericUser\", back_populates=\"notifications\")\n    \n    # APICreditLog\n    class GenericAPICreditLog(Base):\n        __tablename__ = 'generic_api_credit_log'\n        id = Column(Integer, primary_key=True, index=True)\n        timestamp = Column(DateTime, default=datetime.utcnow)\n        is_paid = Column(Boolean, default=False)\n        status = Column(String, default='pending')\n        expense = Column(Numeric(10, 10), nullable=False)\n        request_id = Column(Integer, ForeignKey('generic_user_request.unique_id'))\n        token_count = Column(Integer, nullable=False)\n        content_creator_email = Column(String, ForeignKey('content_creator.email'))\n        content_creator = relationship(\"ContentCreator\", back_populates=\"api_credit_logs\")\n    \n    # SubscriptionType\n    class GenericSubscriptionType(Base):\n        __tablename__ = 'generic_subscription_type'\n        id = Column(Integer, primary_key=True, index=True)\n        name = Column(String, nullable=False)\n        monthly_fee = Column(Numeric(10, 10), nullable=False)\n        monthly_cap = Column(Integer, nullable=False)\n        created_at = Column(DateTime, default=datetime.utcnow)\n        updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\n        is_removed = Column(Boolean, default=False)\n        removed_at = Column(DateTime, nullable=True)\n        subscriptions = relationship(\"GenericSubscription\", back_populates=\"subscription_type\")\n    \n    # Subscription\n    class GenericSubscription(Base):\n        __tablename__ = 'generic_subscription'\n        id = Column(Integer, primary_key=True, index=True)\n        customer_email = Column(String, ForeignKey('customer.email'), index=True)\n        start_date = Column(DateTime, default=datetime.utcnow)\n        end_date = Column(DateTime, nullable=True)\n        current_use = Column(Integer, default=0)\n        subscription_type_id = Column(Integer, ForeignKey('generic_subscription_type.id'))\n        customer = relationship(\"Customer\", back_populates=\"subscriptions\")\n        subscription_type = relationship(\"GenericSubscriptionType\", back_populates=\"subscriptions\")\n        subscription_usages = relationship(\"GenericSubscriptionUsage\", back_populates=\"subscription\")\n    \n    # SubscriptionUsage\n    class GenericSubscriptionUsage(Base):\n        __tablename__ = 'generic_subscription_usage'\n        id = Column(Integer, primary_key=True, index=True)\n        customer_email = Column(String, ForeignKey('customer.email'), index=True)\n        use_count = Column(Integer, default=0)\n        last_use = Column(DateTime, nullable=True)\n        subscription_id = Column(Integer, ForeignKey('generic_subscription.id'))\n        subscription_type_id = Column(Integer, ForeignKey('generic_subscription_type.id'))\n        customer = relationship(\"Customer\", back_populates=\"subscription_usages\")\n        subscription = relationship(\"GenericSubscription\", back_populates=\"subscription_usages\")\n        subscription_type = relationship(\"GenericSubscriptionType\", backref=\"subscription_usages\")\n    \n    # BillingInfo\n    class GenericBillingInfo(Base):\n        __tablename__ = 'generic_billing_info'\n        id = Column(Integer, primary_key=True, index=True)\n        customer_email = Column(String, ForeignKey('customer.email'), index=True)\n        payment_type = Column(String, nullable=False)\n        payment_data = Column(JSON)\n        created_at = Column(DateTime, default=datetime.utcnow)\n        updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\n        is_removed = Column(Boolean, default=False)\n        removed_at = Column(DateTime, nullable=True)\n        customer = relationship(\"Customer\", back_populates=\"billing_infos\")\n    \n    models = [GenericUser, Customer, ContentCreator, UserSession, FileStorage, ServiceRequest, GenericAuditLog, GenericFeedback, GenericAPIKey, GenericNotification, GenericAPICreditLog, GenericSubscriptionType, GenericSubscription, GenericSubscriptionUsage, GenericBillingInfo]\n    \n    \n    output_file_name = 'my_data_model_diagram'\n    # Generate the diagram and add interactivity\n    generate_data_model_diagram(models, output_file_name, add_labels=True)\n    add_web_font_and_interactivity('my_data_model_diagram.svg', 'my_interactive_data_model_diagram.svg')\n"
  }
]