Files
Nicholas Tindle af572a5caa Allow changes to Spec (#272)
* feat: initial implementation of post endpoints, non working due to circular dependency

* fix: linting

* fix: recursive import

* feat: refactor to support module additions

* feat: fix up add route endpoint, move models to api layer

* feat: add deletes
2024-05-24 07:34:46 -05:00

458 lines
14 KiB
Python

import logging
from datetime import datetime
from typing import List, Optional
import prisma
from prisma.enums import AccessLevel, Role
from prisma.models import ObjectField, ObjectType, Specification
from pydantic import BaseModel, Field
from codex.common.parse_prisma import parse_prisma_schema
logger = logging.getLogger(__name__)
class Identifiers(BaseModel):
user_id: str | None = None
cloud_services_id: str | None = None
app_id: str | None = None
interview_id: str | None = None
spec_id: str | None = None
compiled_route_id: str | None = None
function_id: str | None = None
completed_app_id: str | None = None
deployment_id: str | None = None
class Pagination(BaseModel):
total_items: int = Field(..., description="Total number of items.", examples=[42])
total_pages: int = Field(..., description="Total number of pages.", examples=[97])
current_page: int = Field(
..., description="Current_page page number.", examples=[1]
)
page_size: int = Field(..., description="Number of items per page.", examples=[25])
###### USERS ######
class UserBase(BaseModel):
discord_id: Optional[str] = Field(
None, description="The unique Discord ID of the user"
)
cloud_services_id: str = Field(
..., description="The unique identifier of the user in cloud services"
)
class UserCreate(UserBase):
pass
class UserUpdate(BaseModel):
id: str = Field(..., description="The unique identifier of the user")
role: Optional[Role] = Field(None, description="The new role of the user")
class UserResponse(BaseModel):
id: str
discord_id: str
cloud_services_id: str
createdAt: datetime
role: Role
class UsersListResponse(BaseModel):
users: List[UserResponse]
pagination: Optional[Pagination] = None
####### APPS #######
class FunctionRequest(BaseModel):
"""
A request to generate a correctly formated function spec
"""
name: str
description: str
inputs: str
outputs: str
class ApplicationBase(BaseModel):
name: str = Field(..., description="The name of the application")
description: Optional[str] = Field(
None, description="A description of the application"
)
class ApplicationCreate(ApplicationBase):
pass
class ApplicationResponse(BaseModel):
id: str
createdAt: datetime
updatedAt: datetime
name: str
userid: str
cloud_services_id: str
description: str | None = None
class ApplicationsListResponse(BaseModel):
applications: List[ApplicationResponse]
pagination: Optional[Pagination] = None
###### SPECS ######
class InterviewNextRequest(BaseModel):
msg: str
class ParamModel(BaseModel):
id: str
createdAt: datetime
name: str
description: str
param_type: str
class RequestObjectModel(BaseModel):
id: str
createdAt: datetime
name: str
description: str
params: List[ParamModel] = []
class ResponseObjectModel(BaseModel):
id: str
createdAt: datetime
name: str
description: str
params: List[ParamModel] = []
class APIRouteSpecModel(BaseModel):
id: str
createdAt: datetime
method: str
path: str
description: str
requestObject: Optional[RequestObjectModel] = None
responseObject: Optional[ResponseObjectModel] = None
class SpecificationCreate(BaseModel):
description: str
class SpecificationUpdate(prisma.models.Specification, BaseModel):
apiRouteSpecs: List[APIRouteSpecModel] = []
class InputParamModel(BaseModel):
name: str
description: str
param_type: str
class InputRequestResponseModel(BaseModel):
name: str
description: str
params: List[InputParamModel] = []
class SpecificationAddRouteToModule(BaseModel):
function_name: str
access_level: AccessLevel
allowed_access_roles: List[str]
method: str
path: str
description: str
requestObject: Optional["ObjectTypeModel"] = None
responseObject: Optional["ObjectTypeModel"] = None
class DatabaseEnums(BaseModel):
name: str
description: str
values: list[str]
definition: str
def __str__(self):
return f"**Enum: {self.name}**\n\n**Values**:\n{', '.join(self.values)}\n"
class DatabaseTable(BaseModel):
name: str | None = None
description: str
definition: str # prisma model for a table
def __str__(self):
return f"**Table: {self.name}**\n\n\n\n**Definition**:\n```\n{self.definition}\n```\n"
class DatabaseSchema(BaseModel):
name: str # name of the database schema
description: str # context on what the database schema is
tables: List[DatabaseTable] # list of tables in the database schema
enums: List[DatabaseEnums]
def __str__(self):
tables_str = "\n".join(str(table) for table in self.tables)
enum_str = "\n".join(str(enum) for enum in self.enums)
return f"## {self.name}\n**Description**: {self.description}\n**Tables**:\n{tables_str}\n**Enums**:\n{enum_str}\n"
class ModuleWrapper(BaseModel):
id: str
name: str
description: str
interactions: str
apiRouteSpecs: List[APIRouteSpecModel] = []
class SpecificationResponse(BaseModel):
id: str
createdAt: datetime
name: str
context: str
modules: List[ModuleWrapper] = []
databaseSchema: Optional[DatabaseSchema] = None
@staticmethod
def from_specification(specification: Specification) -> "SpecificationResponse":
logger.debug(specification.model_dump_json())
module_out = []
modules: list[prisma.models.Module] | None = (
specification.Modules if specification.Modules else None
)
if modules is None:
raise ValueError("No routes found for the specification")
for module in modules:
if module.ApiRouteSpecs:
routes = []
for route in module.ApiRouteSpecs:
routes.append(
APIRouteSpecModel(
id=route.id,
createdAt=route.createdAt,
method=str(route.method),
path=route.path,
description=route.description,
requestObject=RequestObjectModel(
id=route.RequestObject.id,
createdAt=route.RequestObject.createdAt,
name=route.RequestObject.name,
description=route.RequestObject.description or "",
params=[
ParamModel(
id=param.id,
createdAt=param.createdAt,
name=param.name,
description=param.description or "",
param_type=param.typeName,
)
for param in route.RequestObject.Fields or []
],
)
if route.RequestObject
else None,
responseObject=ResponseObjectModel(
id=route.ResponseObject.id,
createdAt=route.ResponseObject.createdAt,
name=route.ResponseObject.name,
description=route.ResponseObject.description or "",
params=[
ParamModel(
id=param.id,
createdAt=param.createdAt,
name=param.name,
description=param.description or "",
param_type=param.typeName,
)
for param in route.ResponseObject.Fields or []
],
)
if route.ResponseObject
else None,
)
)
module_out.append(
ModuleWrapper(
id=module.id,
apiRouteSpecs=routes,
name=module.name,
description=module.description,
interactions=module.interactions,
)
)
else:
module_out.append(
ModuleWrapper(
id=module.id,
name=module.name,
description=module.description,
interactions=module.interactions,
)
)
db_schema = None
if specification.DatabaseSchema:
def convert_to_table(table: prisma.models.DatabaseTable) -> DatabaseTable:
return DatabaseTable(
name=table.name or "ERROR: Unknown Table Name",
description=table.description,
definition=table.definition,
)
def convert_to_enum(table: prisma.models.DatabaseTable) -> DatabaseEnums:
return DatabaseEnums(
name=table.name or "ERROR: Unknown ENUM Name",
description=table.description,
values=parse_prisma_schema(table.definition)
.enums[table.name or "ERROR: Unknown ENUM Name"]
.values,
definition=table.definition,
)
db_schema = DatabaseSchema(
name=specification.DatabaseSchema.name or "Database Schema",
tables=[
convert_to_table(table)
for table in specification.DatabaseSchema.DatabaseTables or []
if not table.isEnum
],
enums=[
convert_to_enum(table)
for table in specification.DatabaseSchema.DatabaseTables or []
if table.isEnum
],
description=specification.DatabaseSchema.description,
)
ret_obj = SpecificationResponse(
id=specification.id,
createdAt=specification.createdAt,
name="",
context="",
modules=module_out,
databaseSchema=db_schema,
)
return ret_obj
class SpecificationsListResponse(BaseModel):
specs: List[SpecificationResponse] | List[None] = []
pagination: Optional[Pagination] = None
### Deliverables ###
class CompiledRouteModel(BaseModel):
id: str
createdAt: datetime
description: str
code: str
class DeliverableResponse(BaseModel):
id: str
created_at: datetime
name: str
description: str
class DeliverablesListResponse(BaseModel):
deliverables: List[DeliverableResponse]
pagination: Optional[Pagination] = None
### Deployements ###
class DeploymentMetadata(BaseModel):
id: str
created_at: datetime
repo: str
class DeploymentRequest(BaseModel):
zip_file: bool
github_repo: bool
hosted: bool
class DeploymentResponse(DeploymentMetadata):
pass
class DeploymentsListResponse(BaseModel):
deployments: List[DeploymentMetadata]
pagination: Optional[Pagination] = None
class ObjectTypeModel(BaseModel):
name: str = Field(description="The name of the object")
code: Optional[str] = Field(description="The code of the object", default=None)
description: Optional[str] = Field(
description="The description of the object", default=None
)
Fields: List["ObjectFieldModel"] = Field(description="The fields of the object")
is_pydantic: bool = Field(
description="Whether the object is a pydantic model", default=True
)
is_implemented: bool = Field(
description="Whether the object is implemented", default=True
)
is_enum: bool = Field(description="Whether the object is an enum", default=False)
def __init__(self, db_obj: ObjectType | None = None, **data):
if not db_obj:
super().__init__(**data)
return
super().__init__(
name=db_obj.name,
code=db_obj.code,
description=db_obj.description,
is_pydantic=db_obj.isPydantic,
is_enum=db_obj.isEnum,
Fields=[ObjectFieldModel(db_obj=f) for f in db_obj.Fields or []],
**data,
)
class ObjectFieldModel(BaseModel):
name: str = Field(description="The name of the field")
description: Optional[str] = Field(
description="The description of the field", default=None
)
type: str = Field(
description="The type of the field. Can be a string like List[str] or an use any of they related types like list[User]",
)
value: Optional[str] = Field(description="The value of the field", default=None)
related_types: Optional[List[ObjectTypeModel]] = Field(
description="The related types of the field", default=[]
)
def __init__(self, db_obj: ObjectField | None = None, **data):
if not db_obj:
super().__init__(**data)
return
super().__init__(
name=db_obj.name,
description=db_obj.description,
type=db_obj.typeName,
value=db_obj.value,
related_types=[
ObjectTypeModel(db_obj=t) for t in db_obj.RelatedTypes or []
],
**data,
)