Skip to content

Commit

Permalink
add auto publich
Browse files Browse the repository at this point in the history
  • Loading branch information
enzofrnt committed Mar 10, 2024
1 parent 66f5c90 commit 02a0a81
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 62 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
release:
types: [published]

permissions:
contents: read

jobs:
deploy:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name : check for current folder
run: |
ls
- name: build package
run:
python -m build
- name: Publish build
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
135 changes: 73 additions & 62 deletions django_model_to_typescript_types/modeltotypescriptconverter.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
# https://gist.github.com/emoss08/c87c9864ce2af470dc301ff64e39f857
# https://gist.github.com/guizesilva/474fce56fcd5ab766e65e11e0dbff545
import os

from django.apps import apps
from django.core.wsgi import get_wsgi_application

class ModelToTypeScriptConverter:
def __init__(self, apps_to_include=['app'], path_for_interfaces='/tmp/tsinterface/', seperated_files=False):
def __init__(self, apps_to_include=['app'], path_for_interfaces='/tmp/tsinterface/', separated_files=False):
self.apps_to_include = apps_to_include.split(',')
self.path_for_interfaces = path_for_interfaces
if isinstance(seperated_files, str):
self.seperated_files = seperated_files.lower() in ('true')
else:
self.seperated_files = bool(seperated_files)
self.separated_files = separated_files if isinstance(separated_files, bool) else separated_files.lower() in ('true', '1', 't')
self.field_type_mapping = {
"AutoField": "number",
"BooleanField": "boolean",
Expand All @@ -21,98 +17,113 @@ def __init__(self, apps_to_include=['app'], path_for_interfaces='/tmp/tsinterfac
"DateTimeField": "Date",
"DecimalField": "number",
"FloatField": "number",
# "ForeignKey": "number", The foreign key is handled separately
# ForeignKey is handled separately
"IntegerField": "number",
"JSONField": "JSON",
"ManyToManyField": None,
"OneToOneField": "number",
# ManyToManyField and OneToOneField are handled separately
"PositiveIntegerField": "number",
"PositiveSmallIntegerField": "number",
"TextField": "string",
"UUIDField": "string",
"BigAutoField": "number",
}
# New attribute to keep track of model relationships
self.model_relations = {}

def get_wsgi_application(self):
get_wsgi_application()

def to_camel_case(self, snake_str):
"""Transform a string from snake_case to camelCase."""
components = snake_str.split("_")
return components[0] + "".join(x.title() for x in components[1:])
return components[0] + ''.join(x.title() for x in components[1:])

def to_type_union(self, field):
"""Transform field options to TypeScript union type."""
choices = field.choices
return " | ".join([f'"{choice[0]}"' for choice in choices])

def generate_interfaces(self):
all_models = apps.get_models()
os.makedirs(os.path.dirname(self.path_for_interfaces), exist_ok=True)

if self.seperated_files:
for model in all_models:
if model._meta.app_label in self.apps_to_include:
self.generate_interface_file(model)
else:
self.generate_single_interface_file(all_models)

def generate_field_line(self, field):
if field.get_internal_type() == 'ForeignKey':
# Handling ForeignKey specifically
related_model = field.related_model
print("related name of the foreign key : "+field.remote_field.related_name or f'{related_model._meta.model_name}_set')
# You could choose to reference the primary key type of the related model
# or simply use the related model's name as a type
_type = related_model.__name__ + " | " + self.field_type_mapping.get(related_model._meta.pk.get_internal_type(), None)
# Assuming we always import the related model's type at the top of the file
needs_import = True
else:
_type = self.field_type_mapping.get(field.get_internal_type(), None)
needs_import = False
return ' | '.join([f'"{choice[0]}"' for choice in choices])

if _type is None:
return None, needs_import
def collect_model_relations(self, all_models):
"""Collects relations for each model to handle related_names in interfaces."""
for model in all_models:
if model._meta.app_label not in self.apps_to_include:
continue

if field.choices:
_type = self.to_type_union(field)

name = self.to_camel_case(field.name)
if field.null:
name += "?"
for field in model._meta.fields + model._meta.many_to_many:
if hasattr(field, 'remote_field') and field.remote_field:
related_model = field.related_model
related_name = field.remote_field.related_name or f'{model._meta.model_name}_set'
if related_model not in self.model_relations:
self.model_relations[related_model] = []
self.model_relations[related_model].append((model, related_name))

return f"{name}: {_type};", needs_import
def generate_interface_file(self, model):
filename = f"{self.path_for_interfaces}{model.__name__.lower()}.ts"
with open(filename, "w") as file:
print(f"Generating {model.__name__}.ts")
file.write(self.generate_interface_definition(model))

def generate_interface_definition(self, model):
lines = [f"export interface {model.__name__}{{\n"]
lines = [f"export interface {model.__name__} {{\n"]
imports_needed = set()
for field in model._meta.fields:

# Generate field lines
for field in model._meta.fields + model._meta.many_to_many:
field_line, needs_import = self.generate_field_line(field)
if field_line:
lines.append(f"\t{field_line}\n")
if needs_import and self.seperated_files :
if needs_import and self.separated_files:
related_model = field.related_model
imports_needed.add(related_model.__name__)

# Add related names as optional fields
if model in self.model_relations:
for related_model, related_name in self.model_relations[model]:
type_name = related_model.__name__
type_name = type_name + "[] | " + self.field_type_mapping.get(related_model._meta.pk.get_internal_type(), None) + "[]"
lines.append(f"\t{self.to_camel_case(related_name)}?: {type_name};\n")

# Handle imports
header = ""
if imports_needed:
# Generate import lines for needed models
for import_model in imports_needed:
header += f"import {{ {import_model} }} from './{import_model}';\n"
header += "\n" # Add a newline after imports for readability
header += f"import {{ {import_model} }} from './{import_model.lower()}';\n"
header += "\n"
lines.insert(0, header)
lines.append("}\n\n")
return "".join(lines)

def generate_interface_file(self, model):
with open(f"{self.path_for_interfaces}{model.__name__.lower()}.ts", "w") as file:
print(f"Generating {model.__name__}.ts")
file.write(self.generate_interface_definition(model))
def generate_field_line(self, field):
if field.get_internal_type() in ['ForeignKey', 'OneToOneField']:
related_model = field.related_model
_type = related_model.__name__ + " | " + self.field_type_mapping.get(related_model._meta.pk.get_internal_type(), None)
needs_import = True
elif field.get_internal_type() == 'ManyToManyField':
related_model = field.related_model
_type = f"{related_model.__name__}[]" + " | " + self.field_type_mapping.get(related_model._meta.pk.get_internal_type(), None) + "[]"
needs_import = True
else:
_type = self.field_type_mapping.get(field.get_internal_type(), "any")
needs_import = False

def generate_single_interface_file(self, all_models):
with open(f"{self.path_for_interfaces}interfaces.ts", "w") as file:
print("Generating interfaces.ts")
if field.choices:
_type = self.to_type_union(field)

name = self.to_camel_case(field.name)
if field.null or field.get_internal_type() in ['ForeignKey', 'OneToOneField', 'ManyToManyField']:
name += "?"

return f"{name}: {_type};", needs_import

def generate_interfaces(self):
all_models = apps.get_models()
os.makedirs(os.path.dirname(self.path_for_interfaces), exist_ok=True)

# Collect relationships between models
self.collect_model_relations(all_models)

if self.separated_files:
for model in all_models:
if model._meta.app_label in self.apps_to_include:
file.write(self.generate_interface_definition(model))

self.generate_interface_file(model)
else:
self.generate_single_interface_file(all_models)

0 comments on commit 02a0a81

Please sign in to comment.