Created
June 28, 2025 11:22
-
-
Save sourman/049aa95f0e0155ceb1338122d5c50524 to your computer and use it in GitHub Desktop.
python script: Convert supabase typescript to pydantic models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import sys | |
from typing import Dict, List, Any, Optional, Union | |
def extract_database_type(content: str): | |
# Very basic: extract enums under "Enums" in Database type | |
db_pattern = re.search(r"type Database = {(.+?)}", content, re.S) | |
if not db_pattern: | |
return {} | |
db_body = db_pattern.group(1) | |
enums = extract_enums_from_content(db_body) | |
return {"public": {"Enums": enums}} | |
def extract_enums_from_content(content: str): | |
enums = {} | |
enum_pattern = re.finditer(r"(\w+):\s*\['([^']+)'\]", content) | |
for m in enum_pattern: | |
name = m.group(1) | |
values = [v.strip().strip('"\'') for v in m.group(2).split(',')] | |
enums[name] = values | |
return enums | |
def extract_table_types(content: str): | |
tables = {} | |
table_pattern = re.finditer(r"(\w+):\s*{\s*Row:\s*{([^}]+)}", content) | |
for t in table_pattern: | |
table_name = t.group(1) | |
fields_content = t.group(2) | |
fields = {} | |
for fm in re.finditer(r"(\w+):\s*([^;\n]+)", fields_content): | |
fname, ftype = fm.group(1), fm.group(2).strip() | |
fields[fname] = ftype | |
tables[table_name] = fields | |
db_structure = extract_database_type(content) | |
return tables, db_structure | |
def parse_type_definition(type_str: str, db_structure: dict) -> str: | |
type_mapping = { | |
'string': 'str', | |
'number': 'float', | |
'boolean': 'bool', | |
'null': 'None', | |
'Json': 'Dict[str, Any]', | |
'string[]': 'List[str]', | |
'Database': 'Dict[str, Any]', | |
} | |
if type_str.endswith('[]'): | |
base_type = type_str[:-2] | |
return f"List[{parse_type_definition(base_type, db_structure)}]" | |
if "|" in type_str: | |
types = [t.strip() for t in type_str.split("|")] | |
parsed = [parse_type_definition(t, db_structure) for t in types] | |
if "None" in parsed: | |
others = [t for t in parsed if t != "None"] | |
if len(others) == 1: | |
return f"Optional[{others[0]}]" | |
return f"Optional[Union[{', '.join(others)}]]" | |
return f"Union[{', '.join(parsed)}]" | |
if type_str in type_mapping: | |
return type_mapping[type_str] | |
return type_str | |
def generate_pydantic_models(tables, enums, db_structure): | |
output = "from typing import Dict, List, Optional, Union, Any, Literal\nfrom datetime import datetime\nfrom pydantic import BaseModel, Field\nfrom enum import Enum\n\n" | |
# Enums | |
for enum_name, enum_values in enums.items(): | |
output += f"class {enum_name}(str, Enum):\n" | |
for value in enum_values: | |
enum_var = value.replace(" ", "_").replace("-", "_").upper() | |
output += f" {enum_var} = '{value}'\n" | |
output += "\n" | |
# Tables | |
for table_name, fields in tables.items(): | |
class_name = "".join(w.capitalize() for w in table_name.split("_")) | |
output += f"class {class_name}(BaseModel):\n" | |
for fname, ftype in fields.items(): | |
python_type = parse_type_definition(ftype, db_structure) | |
if 'created_at' in fname or 'updated_at' in fname: | |
python_type = 'datetime' | |
if '_' in fname: | |
output += f" {fname}: {python_type} = Field(alias='{fname}')\n" | |
else: | |
output += f" {fname}: {python_type}\n" | |
output += "\n class Config:\n populate_by_name = True\n\n" | |
return output | |
def main(): | |
if len(sys.argv) != 2: | |
print("Usage: python ts_to_pydantic.py path/to/database.types.ts") | |
sys.exit(1) | |
path = sys.argv[1] | |
with open(path, "r") as f: | |
content = f.read() | |
tables, db_structure = extract_table_types(content) | |
enums = db_structure.get("public", {}).get("Enums", {}) | |
print(generate_pydantic_models(tables, enums, db_structure)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment