Skip to content

Instantly share code, notes, and snippets.

@sourman
Created June 28, 2025 11:22
Show Gist options
  • Save sourman/049aa95f0e0155ceb1338122d5c50524 to your computer and use it in GitHub Desktop.
Save sourman/049aa95f0e0155ceb1338122d5c50524 to your computer and use it in GitHub Desktop.
python script: Convert supabase typescript to pydantic models
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