When a model name clashes with a built-in name (e.g. Set), generation leads to weird behavior:
(dolce-py3.11) nchiang@rowlet ~/repos/dolce (nicholas/posts) $ prisma generate
Traceback (most recent call last):
File "/Users/nchiang/repos/dolce/.venv/bin/prisma", line 5, in <module>
from prisma.cli import main
File "/Users/nchiang/repos/dolce/.venv/lib/python3.11/site-packages/prisma/__init__.py", line 24, in <module>
from .client import *
File "/Users/nchiang/repos/dolce/.venv/lib/python3.11/site-packages/prisma/client.py", line 50, in <module>
from . import types, models, errors, actions
File "/Users/nchiang/repos/dolce/.venv/lib/python3.11/site-packages/prisma/types.py", line 111157, in <module>
from . import types, enums, models, fields
File "/Users/nchiang/repos/dolce/.venv/lib/python3.11/site-packages/prisma/models.py", line 3951, in <module>
_User_relational_fields: Set[str] = {
~~~^^^^^
TypeError: type 'Set' is not subscriptable
How to reproduce
Steps to reproduce the behavior:
Create a new Prisma schema that has a model named Set
Run generation once
Run it again
See error
Expected behavior
I should be able to name my Prisma models whatever I want to, even if they clash with built-in class names.
Prisma information
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
generator js {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearch"]
}
generator py {
provider = "prisma-client-py"
interface = "sync"
recursive_type_depth = 5
enable_experimental_decimal = True
}
// A user is a person who has created an account with us.
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The user's name, as designated by the user.
// @todo there may be multiple users with the same name...
name String @unique
// The user's description (e.g. a designer biography).
description String?
// The user's publicly visible username, as designated by the user.
username String? @unique
// The user's email address, as designated by the user.
email String? @unique
// The user's password, stored as an encrypted hash.
password Password?
// The user's avatar image URL.
avatar String? @unique
// URL to the user's website or portfolio (e.g. journalist bio pages).
url String? @unique
// The articles about this user.
articles Article[]
// The articles written by the user.
articlesWritten Article[] @relation("ArticlesWritten")
// The reviews written by the user.
reviews Review[]
// The posts uploaded by the user (this does not mean that the post was
// originally authored by this user, but that it was added to our database by
// this user).
posts Post[]
// The user's sets (i.e. arbitrary groupings of saved looks).
sets Set[]
// The looks this user has authored.
looks Look[]
// Whether the user is a curator (i.e. someone who can edit shows, etc).
curator Boolean @default(false)
// DESIGNER FIELDS - only applicable to fashion designers.
// Where the designer purports to be from.
country Country? @relation(fields: [countryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
countryId Int?
// The collections that the designer created or otherwise curated.
collections Collection[]
// The products that the designer designed.
products Product[]
// MODEL FIELDS - only applicable to fashion models.
// The looks that this model wore during runway shows.
looksModeled Look[] @relation("LooksModeled")
}
// A user's password, stored in Postgres as an encrypted hash.
model Password {
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The securely encrypted hash of the user's original password text.
hash String
// The user whose password this is.
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId Int @unique
}
// A company is a legal corporation. Companies can own many brands.
// e.g. The LVMH company owns Louis Vuitton, Dior, Givenchy, etc.
model Company {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The corporations legal name.
name String @unique
// The company's avatar URL (i.e. logo).
avatar String? @unique
// URL to the company's website.
url String? @unique
// A short description of the company, typically sourced from Wikipedia.
description String?
// The brands owned and operated by the corporation.
brands Brand[]
// The retailers owned and operated by the corporation.
retailers Retailer[]
// The country where the corporation is legally headquartered.
country Country @relation(fields: [countryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
countryId Int
// @todo perhaps store links to where this information was sourced from?
}
// A retailer is a recognizable commerce entity that sells products. Note that
// this is different than a company to allow companies to own many retailers.
// e.g. Neiman Marcus, Nordstrom, GOAT, StockX, Ebay, etc.
model Retailer {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The retailer's most recognizable name, styled in their preferred format.
name String @unique
// The retailer's avatar URL (i.e. logo).
avatar String? @unique
// URL to the retailer's website.
url String? @unique
// The company that owns and operates the retailer.
company Company? @relation(fields: [companyId], references: [id], onDelete: Cascade, onUpdate: Cascade)
companyId Int?
// A short description of the retailer, typically sourced from Wikipedia.
description String?
// The brands sold by the retailer.
// @todo perhaps this shouldn't be an explicit relation but rather implied by
// the products (and their associated brands) that the retailer sells.
brands Brand[]
// The prices (associated with products) sold by the retailer.
prices Price[]
// The countries in which the retailer operates.
countries Country[]
// The links (to collections or brands) on this retailer's website.
links Link[]
}
// Tiers attempt to encapsulate a brand's reputation, business model, and prices:
//
// 0 - $50k-‚àû bespoke. does not sell to the general public.
// 1 - $5-50k superpremium. e.g. Patek Philippe, Bottega, Hermes
// 2 - $1500-5k premium core. e.g. Rolex, Berluti, Omega, Cartier
// 3 - $300-1500 accessible core. e.g. GUCCI, Prada, Tod's, Montblanc
// 4 - $100-300 affordable luxury. e.g. Coach, Geox
//
// 5 - $80-$700 diffusion. secondary lines by luxury names. e.g. Marc by Marc Jacobs
// 6 - $40-500 high-end street. e.g. All Saints, Coast
// 7 - $20-120 mid-level high street. e.g. Topshop, M&S
// 8 - $5-30 value market. relies on huge sales. e.g. Primark, Shein, Walmart
//
// https://createafashionbrand.com/the-many-market-levels-of-fashion-brands/
// https://www.businessinsider.com/pyramid-of-luxury-brands-2015-3
//
// @todo perhaps this should be a model of its own?
enum Tier {
BESPOKE
SUPERPREMIUM
PREMIUM_CORE
ACCESSIBLE_CORE
AFFORDABLE_LUXURY
DIFFUSION
HIGH_STREET
MID_STREET
VALUE_MARKET
}
// A brand is a recognizable name. Brands with similar names are given tiers.
// e.g. GUESS is given tier 1 while GBG and GUESS FACTORY are given tier 2.
//
// Typically, a brand will be the name that appears on that tags of products.
// Occasionally, a brand will not have its own products (e.g. "Fashion East" is
// considered a brand even though they do not create their own products; they
// simply showcase other designer's brand's clothing at their runway shows).
model Brand {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The brand's most recognizable name, styled in the brand's preferred format.
name String @unique
// The URL friendly slug identifier for the brand. This is different than the
// integer ID column as I want to have a user-friendly URL for each brand.
// Ex: /shows/resort-2024/hermes is better than /shows/356 for SEO.
// @see {@link https://linear.app/nicholaschiang/issue/NC-673}
slug String @unique
// The brand's avatar URL (i.e. logo).
avatar String? @unique
// URL to the brand's website.
url String? @unique
// A short description of the brand, typically sourced from Wikipedia.
description String?
// The brand's tier. This will be NULL if it has not been assigned yet.
// @todo perhaps rename this to "BrandTier" to avoid confusion with "Level"?
tier Tier?
// The company that owns and operates the brand (if known).
company Company? @relation(fields: [companyId], references: [id], onDelete: Cascade, onUpdate: Cascade)
companyId Int?
// The products designed or otherwise produced by the brand.
products Product[]
// The runway shows presented by the brand.
shows Show[]
// The sizes used by the brand.
sizes Size[]
// The prices (associated with products) sold by the brand (i.e. MSRPs).
prices Price[]
// Links to the brand's page on retailer sites.
links Link[]
// The retailers that sell the brand.
retailers Retailer[]
// The collections designed by the brand.
collections Collection[]
// The country the brand purports to be from (if known).
country Country? @relation(fields: [countryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
countryId Int?
}
// A country is a sovereign state. Countries can have many brands and sizes.
model Country {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The country's full name, as designated by the United Nations.
name String @unique
// The designers that purport to be from the country.
designers User[]
// The companies that are legally headquartered in the country.
companies Company[]
// The brands that purportedly originate from the country.
brands Brand[]
// The retailers that operate in the country.
retailers Retailer[]
// The country's nationwide standardized sizes.
sizes Size[]
}
// A style group represents a collection of mutually exclusive styles.
// Allegorical to Linear's label groups (you can only filter on one at a time).
// e.g. the "Neckline" style group contains "Crewneck", "V-Neck", etc (a product
// can not have a crewneck and a v-neck at the same time).
model StyleGroup {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The style group's name.
name String @unique
// The styles that belong to the style group.
styles Style[]
}
// A product style category is a high-level grouping of products. Styles are a
// tad bit reminiscent of the typical issue tracking tool's "labels" feature.
// e.g. blazer, bomber, cardigan, quilted, raincoat, jeans, tuxedos, etc.
model Style {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The style category's name, styled in the preferred format.
name String @unique
// The products that belong to the style category.
products Product[]
// The sizes used by the style category.
sizes Size[]
// The items that have this style (e.g. "turtleneck").
items Item[]
// The collections that exclusively contain products from this style.
collections Collection[]
// The style group that the style belongs to, if any.
styleGroup StyleGroup? @relation(fields: [styleGroupId], references: [id], onDelete: Cascade, onUpdate: Cascade)
styleGroupId Int?
// The style subcategories that can be nested underneath this style.
// e.g. tops > t-shirts > crew neck, tops > t-shirts > v-neck, etc.
parentId Int?
parent Style? @relation("ParentChildStyle", fields: [parentId], references: [id])
children Style[] @relation("ParentChildStyle")
}
// A size is a measurement of a product's dimensions. Sizes can either be owned
// by a brand (for proprietary brand specific sizing systems) or a country (for
// nationwide standardized sizes). Users can then add multiple sizes to their
// profile. Our system will automatically suggest sizes to add based on the
// user's previous purchases and existing profile sizes.
model Size {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The size's name, as designated by the brand or country.
name String
// A unique slug derived from the name, sex, style, and brand. This exists
// primarily to make imports easier (i.e. we can use the slug to match sizes
// in a connectOrCreate statement instead of having to fetch the styleId).
slug String @unique
// The product style the size is used for (e.g. tops, outerwear, puffers).
style Style @relation(fields: [styleId], references: [id], onDelete: Cascade, onUpdate: Cascade)
styleId Int
// The original intended sex the size is specifying for.
sex Sex
// The size's chest measurement (cm) as designated by the brand.
chest Decimal?
// The size's shoulder measurement (cm) as designated by the brand.
shoulder Decimal?
// The size's waist measurement (cm) as designated by the brand.
waist Decimal?
// The size's sleeve measurement (cm) as designated by the brand.
sleeve Decimal?
// The brand whose size this is.
brand Brand? @relation(fields: [brandId], references: [id], onDelete: Cascade, onUpdate: Cascade)
brandId Int?
// The country whose size this is.
country Country? @relation(fields: [countryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
countryId Int?
// Equivalent sizes. A size can have zero or more equivalent sizes.
equivalents Size[] @relation("SizeEquivalents")
equivalentOf Size[] @relation("SizeEquivalents")
// The product variants that are available in this size.
variants Variant[]
// @todo ensure that a size always has either a country or a brand.
// @see https://github.com/prisma/prisma/issues/17319
// Each brand or country must have unique size names per category and sex.
@@unique([name, sex, styleId, brandId, countryId])
}
// A color is a label assigned to products by their designers.
// @todo perhaps standardize this by associating each color with an RGBA range?
model Color {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The color's name, as designated by the brand (e.g. "Beige", "Black", etc).
name String @unique
// The product variants that are available in this color.
variants Variant[]
// The items that have this color.
items Item[]
// @todo perhaps colors should be associated with brands? e.g. "Gucci Beige"?
}
// A sustainability is a label indicating some level of sustainability.
enum Sustainability {
RECYCLED // Certified recycled materials.
ORGANIC // Certified organic materials.
RESPONSIBLE_DOWN // Responsible Down Standard cerified.
RESPONSIBLE_FORESTRY // Wood-based fabrics from sustainably managed forests.
RESPONSIBLE_WOOL // Responsible Wool Standard certified.
RESPONSIBLE_CASHMERE // Responsible Cashmere Standard certified.
}
// A material is a fabric or other ingrediant used to formulate a product.
model Material {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The material's name, as designated by industry standard (e.g. "Cotton") or
// the brand for proprietary fabrics (e.g. "Bombtwill", "City Wool").
name String @unique
// The material's description, as designated by the brand (e.g. for
// proprietary fabrics like LENZING ECOVERO Viscose) or industry standard.
description String?
// The material's sustainability status, if any.
sustainability Sustainability?
// The product variants that are available in this material.
variants Variant[]
// The style subcategories that can be nested underneath this style.
// e.g. Viscose > LENZING ECOVERO Viscose, Wool > Merino > City Wool, etc.
parentId Int?
parent Material? @relation("ParentChildMaterial", fields: [parentId], references: [id])
children Material[] @relation("ParentChildMaterial")
// @todo perhaps materials should be associated with brands or countries
// similar to how sizes are associated with either a brand or country?
// @todo perhaps create a manufacturer model to associate with materials?
}
// A tag is an arbitrary label applied by a brand or retailer to their items.
// This was added primarily to preserve information from scraping Shopify. These
// often correlate with specific collections, seasons, or styles. There's no
// easy way to classify them at save time, so I just include them and will run
// SQL queries manually to re-classify them.
model Tag {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The tag's name, as designated by the brand or retailer.
name String @unique
// The product variants that are available in this tag.
variants Variant[]
}
// A variant specifies the properties of an item you can purchase. Each variant
// is associated with a unique SKU number. Variants are identified primarily by
// size and color. Each variant has prices from different vendors.
model Variant {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The variant's SKU, as designated by the brand.
sku String @unique
// The product the variant is of.
product Product @relation(fields: [productId], references: [id], onDelete: Cascade, onUpdate: Cascade)
productId Int
// The colors the variant consists of. Typically a single color but can be
// multiple if the variant contains a gradient or a mix of multiple colors.
colors Color[]
// The materials (a.k.a. fabrics) that the variant is made of.
materials Material[]
// The size of the variant.
size Size @relation(fields: [sizeId], references: [id], onDelete: Cascade, onUpdate: Cascade)
sizeId Int
// Images and videos of the product variant being modeled.
videos Video[]
images Image[]
// Arbitrary tags applied by the brand or retailer.
tags Tag[]
// The prices that are associated with the variant. Typically a single price
// but can be multiple if the item is sold by multiple retailers or if there
// are different prices per size. Can be empty if the variant is sold out.
prices Price[]
// The user-curated sets that the product variant is a part of.
sets Set[]
// The posts that include this product variant.
posts Post[]
// @todo each product can only have a single variant per color and size combo.
}
// A sex is an arbitrary label designated by a brand or designer to indicate a
// product's originally intended consumer.
enum Sex {
MAN
WOMAN
UNISEX
}
// A market is either primary (MSRP and retailers) or secondary (resale).
enum Market {
PRIMARY
SECONDARY
}
// A price is an encapsulation of a product's value. A price can be for all the
// sizes and color variants of a product (e.g. when being sold at retail value)
// or specific to a single size and color variant (e.g. GOAT, Ebay, StockX).
model Price {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The price's value in USD.
value Decimal
// The price's market (primary—MSRP and retailers—or secondary—resale value).
market Market
// The URL of the product's listing at this price.
url String
// The retailer that sells the product at this price.
retailer Retailer? @relation(fields: [retailerId], references: [id], onDelete: Cascade, onUpdate: Cascade)
retailerId Int?
// The brand that sells the product at this price.
brand Brand? @relation(fields: [brandId], references: [id], onDelete: Cascade, onUpdate: Cascade)
brandId Int?
// The product variant sold at this price (the size and color combo).
variant Variant @relation(fields: [variantId], references: [id], onDelete: Cascade, onUpdate: Cascade)
variantId Int
// Whether or not the price is still available (e.g. if it is sold out). This
// is stored on the price model instead of the variant model as different
// vendors can have different stocks of a given variant.
// @todo instead of this, perhaps we should have a "stock" numeric field that
// tracks how many units are available at this price from this retailer?
available Boolean @default(true)
// @todo ensure that a price always has either a retailer or a brand.
// @see https://github.com/prisma/prisma/issues/17319
// Each price must have a unique value and URL (note that I can't simply put a
// unique constraint on the URL due to secondary markets like GOAT that have a
// single URL for many different sizes at many different prices).
// Because each price contains the stock information for a specific size and
// color variant, each price must be unique to that variant.
@@unique([variantId, value, url])
}
// An image. Typically of a product being modeled.
// @todo perhaps we should also store the image's original source?
model Image {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The URL (either fully qualified or a relative path) to the largest size of
// the image available (the front-end optimizes images at runtime).
url String @unique
// The image position in the product or look's gallery (if applicable).
// @todo enforce unique image positions per product, variant, or look.
position Int?
// The image width (if known) in px.
width Int?
// The image height (if known) in px.
height Int?
// The product variant(s) the image is of.
// @todo if multiple products refer to the same image, we should make it
// associated with a look instead and then link the look with those products.
variants Variant[]
// The runway look the image is of.
look Look? @relation(fields: [lookId], references: [id], onDelete: Cascade, onUpdate: Cascade)
lookId Int?
// The post that the image is from.
post Post? @relation(fields: [postId], references: [id])
postId Int?
// @todo store information on the models in the image (e.g. insta, etc).
}
// A video. Typically of a product being modeled.
// @todo perhaps we should also store the image's original source?
model Video {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The video's URL (either fully qualified or a relative path).
url String @unique
// The video's mime type.
mimeType String
// The product variant(s) the video is of.
variants Variant[]
// The show the video is of.
show Show?
// The post that the video is from.
post Post? @relation(fields: [postId], references: [id])
postId Int?
// @todo store information on the models in the video (e.g. insta, etc).
}
// Levels attempt to encapsulate a product's quality, price, and availability:
//
// 0 - bespoke. made to measure e.g. by comission.
// 1 - haute couture. handmade approved by french law.
// 2 - handmade. e.g. one-of-one etsy items, products made a friend.
// 3 - ready-to-wear. widely available online or in-store.
enum Level {
BESPOKE
COUTURE
HANDMADE
RTW
}
// An item is a high-level product category (e.g. "white turtleneck sweater").
model Item {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The item's styles (e.g. "turtleneck", "sweater").
styles Style[]
// The item's colors (e.g. "white").
colors Color[]
// Products that satisfy the item specifications (i.e. a turtleneck sweater
// sold by Aritzia that has a white color variant).
//
// Note that I intentionally do not associate variants with items. Instead, it
// will be up to the client (i.e. the front-end) to show the correct product
// variant that has the item's correct colors.
products Product[]
// The posts that include this item category in them.
posts Post[]
}
// A product is an item that can be bought and sold.
model Product {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The product's name as designated by the brand and designer.
name String @unique
// The URL friendly slug identifier for the product.
// Ex: /products/hermes-frozen-shorts is better than /products/356 for SEO.
// @see {@link https://linear.app/nicholaschiang/issue/NC-673}
slug String @unique
// The product's description.
description String?
// The product's level.
// @todo perhaps rename this to "ProductLevel" to avoid confusion with "Tier"?
level Level
// The variants (colors + materials + sizes) the product was made in.
variants Variant[]
// The original MSRP value of the product in USD (if applicable).
// @todo perhaps this should be a field on the variant?
msrp Decimal?
// When a product was originally conceived.
designedAt DateTime
// When a product was first available to be purchased.
releasedAt DateTime
// The product's styles. Allegorical to labels (e.g. top, t-shirt, v-neck).
// @todo preserve relationships between the same product in two different
// styles (e.g. the "Agency Pant" and the "Agency Cropped Pant").
styles Style[]
// Item specifications that the product satisfies (e.g. a turtleneck sweater).
items Item[]
// The collections that feature the product.
collections Collection[]
// The product's designers. Typically, this will be a single person.
designers User[]
// The product's brands. Collaborations can have multiple brands.
brands Brand[]
// The product's looks (runway outfits that it was included in).
looks Look[]
// The posts that include this item.
posts Post[]
// @todo perhaps we should also store the product's original source?
// @todo products must have a unique name per brand(s).
}
// A set is an arbitrary grouping of looks created by a user to act as a sort of
// mood board. Users can "save" looks that they like to a "set" that can then be
// shared with other users. Users can discover sets by other users (e.g. I see a
// look that I like and then expore all the sets that include that look to find
// similar looks).
//
// Sets are separate from collections for simplicity. Collections are officially
// curated by a brand while sets are simple groupings of looks and products
// created by users. I may opt to combine these two data models in the future,
// but for now, simply adding an additional data model was easiest.
//
// Users can import looks and products from anywhere to add to their sets (e.g.
// if I see an outfit I like on Instagram, I can click "share" to DOLCE and the
// app will add it as a look to the selected set... that look will then be
// automatically augmented with possible products to purchase).
//
// The name "set" was inspired by the website "Polyvore" which allowed users to
// add products to a shared index called a "set".
// @see {@link https://en.wikipedia.org/wiki/Polyvore}
model Set {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The set's name (e.g. "Summer Essentials").
name String
// The set's description.
description String?
// The set's author (i.e. the user who created the set).
// @todo I should support multiple author(s) for a set (i.e. shared "sets") or
// perhaps advanced access control (i.e. users who can view, can edit, etc).
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int
// The set's looks.
looks Look[]
// The set's product variants.
// @todo perhaps we should also allow users to add products to a set without
// having to select a specific size and/or color (e.g. when they're adding
// products from the products list, they do not select a size)?
variants Variant[]
// A user can only have one set with a given name.
@@unique([name, authorId])
}
// A collection is an arbitrary grouping of products, typically done by a brand
// or a designer. Often, collections are created entirely by a single designer.
// Collections are separate from shows as every show has a collection but not
// every collection has a show (e.g. Mission Workshop "Merino Core" collection
// has no show v.s. Saint Laurent Fall-Winter 2023 has a show). Each show can
// also have multiple collections shown (e.g. Fashion East often showcases
// three collections from three different designers on the same runway).
model Collection {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The collection's name (e.g. "Hermès Spring-Summer 2023 Menswear").
name String @unique
// The collection's style category (if limited to a single category).
style Style? @relation(fields: [styleId], references: [id], onDelete: Cascade, onUpdate: Cascade)
styleId Int?
// The collection's season. Typically, collection have seasons. While unusual,
// collections can be released outside of a season (e.g. mw acre series).
season Season? @relation(fields: [seasonId], references: [id], onDelete: Cascade, onUpdate: Cascade)
seasonId Int?
// The show that the collection was debuted at.
show Show? @relation(fields: [showId], references: [id], onDelete: Cascade, onUpdate: Cascade)
showId Int?
// The collection's webpage (from the designer, brand, or retailer site).
links Link[]
// The products that belong to the collection.
products Product[]
// The designers that created the collection. Often, this is one person.
// @todo products are already associated with designers; do we need this?
designers User[]
// The brands that created the collection. Generally, we will only have one
// brand, but—according to ChatGPT—there have been runway collections that
// have been operated by multiple brands and showcased pieces from both of the
// brands. One example is the "Fashion East" show in London, which provides a
// platform for emerging designers to showcase their collections. The show
// often features a combination of individual designers and collaborative
// collections. Another example is the "Designer Collaborations" show at New
// York Fashion Week, which features collaborations between established
// designers and brands.
// @todo products are already associated with brands; do we need this?
brands Brand[]
}
// A link is exactly that. A link to an external website. Currently, this model
// is just used for collections, but will likely be used more in the future.
model Link {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The link's URL.
url String @unique
// The collection the link is associated with.
collection Collection? @relation(fields: [collectionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
collectionId Int?
// The brand that the link is associated with.
brand Brand? @relation(fields: [brandId], references: [id], onDelete: Cascade, onUpdate: Cascade)
brandId Int?
// The retailer that the link is associated with.
retailer Retailer? @relation(fields: [retailerId], references: [id], onDelete: Cascade, onUpdate: Cascade)
retailerId Int?
// Each collection can only have one link per brand or retailer.
@@unique([collectionId, brandId])
@@unique([collectionId, retailerId])
// Each brand can only have one link per retailer.
@@unique([brandId, retailerId])
}
// A widely accepted and used season name.
//
// While brands may use different season names, these are the ones used by Vogue
// and/or WWD (where I'm scraping data from), and thus these are the ones I use.
enum SeasonName {
RESORT
SPRING
PRE_FALL
FALL
}
// A fashion season is a widely accepted grouping of fashion releases.
model Season {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The name of the season, as widely accepted and recognized.
name SeasonName
// The year the season takes place in.
year Int
// The runway shows that took place during the season.
shows Show[]
// The collections that were released during the season.
collections Collection[]
// Each season must have a unique name and year.
@@unique([name, year])
}
// A look is an outfit that a model wore during a runway show.
model Look {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The look's number. Typically, looks are numbered sequentially.
number Int
// The look's show.
show Show? @relation(fields: [showId], references: [id], onDelete: Cascade, onUpdate: Cascade)
showId Int?
// The look's author (if not part of a show, this was created by a user).
author User? @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int?
// @todo ensure that a look always has either a show or an author.
// @see https://github.com/prisma/prisma/issues/17319
// The look's model (if known).
model User? @relation("LooksModeled", fields: [modelId], references: [id], onDelete: Cascade, onUpdate: Cascade)
modelId Int?
// The look's products (if known).
// @todo perhaps I should optionally include variants if a specific size/color
// of the product is shown in the look images and that information is known.
products Product[]
// The look's picture (if known).
images Image[]
// The user-curated sets that the look is a part of.
sets Set[]
// The posts that the look is a part of.
posts Post[]
// Each look must have a unique number in the show or by the author.
@@unique([showId, number])
@@unique([authorId, number])
}
// A post is an Instagram or TikTok or Threads or other social media post that
// contains product(s) or look(s).
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The post's original URL.
url String @unique
// The post's original description (i.e. the caption).
description String?
// The post's author (this is not necessarily the original author but the user
// that brought the post unto the DOLCE platform).
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int
// The post's images.
images Image[]
// The post's videos.
videos Video[]
// The post's items (overall type of item e.g. "white turtleneck sweater").
items Item[]
// The post's products (exact brand of the item if known).
products Product[]
// The post's variants (exact color/size of the product if known).
variants Variant[]
// The post's looks (exact collection of looks).
looks Look[]
}
// An article is exactly that: a work of writing about some fashion-related
// topic. For now, these generally fall into two categories:
// - biographies about fashion designers (e.g. imported from Wikipedia);
// - critic reviews of fashion shows (e.g. imported from Vogue and WWD).
// @see {@link https://linear.app/nicholaschiang/issue/NC-658}
model Article {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The date when the article was originally written or last edited (if known).
//
// The date when the article was originally written and the time when I
// imported it will often be different (which is why this field exists
// separately from the `createdAt` database field).
writtenAt DateTime?
// The article's canonical URL.
url String @unique
// The article title (usually included as the header on their webpage).
title String
// The article subtitle (typically included below the title on their webpage).
// This is often a short summary of the general gist of the article content.
// This field differs from the `summary` field as it is author-provided. The
// `summary` field is generated by OpenAI or written by one of our curators.
subtitle String?
// The article summary (a plain text string; generated via OpenAI).
summary String?
// The article content (an HTML string).
content String
// The article review sentiment score from 0-1 (if applicable).
//
// A score of 0.5 is neutral, 0 is negative, and 1 is positive.
//
// Critical reviews will use whatever scale the critic uses (e.g. 0-10) or
// will revert back to using a five-star scale if the critic does not assign
// a score in their review (it will then be assigned via OpenAI).
//
// This field should only ever be NULL if a critic review has been imported
// but no score has been assigned to it yet (e.g. when scraping Vogue) or if
// this article simply isn't a critic review.
score Decimal?
// The article author (if applicable; Wikipedia has too many authors).
author User? @relation("ArticlesWritten", fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int?
// The user that the article is about (if this is a designer biography).
user User? @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId Int?
// The show that the review is about (if this is a critic review).
show Show? @relation(fields: [showId], references: [id], onDelete: Cascade, onUpdate: Cascade)
showId Int?
// The publication that the article was posted to (Vogue, WWD, Wikipedia).
publication Publication @relation(fields: [publicationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
publicationId Int
// Each publication can only have one canonical article about a topic. This
// constraint exists primarily to ensure that I don't import duplicates.
// @@unique([publicationId, userId])
// @@unique([publicationId, showId])
// Each author can only submit one review for a show. I've yet to encounter
// the same journalist publishing two different reviews for the same show. If
// that does happen, I can always just replace their review with whichever is
// the most recent. If a single journalist publishes two reviews in two
// different publications for the same show, I should only count one of them
// towards the aggregate critic score.
@@unique([authorId, showId])
// Each publication can only have one canonical article with a given title.
@@unique([publicationId, title])
}
// A review is a review for a show from a consumer.
model Review {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The review sentiment score from 0-1.
//
// A score of 0.5 is neutral, 0 is negative, and 1 is positive.
//
// This will always increment by 0.2 (as we use a five-star scale to assign
// these score numbers for consumer reviews).
score Decimal
// The review content (a plain text string).
content String
// The review author.
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int
// The show that the review is about.
show Show @relation(fields: [showId], references: [id], onDelete: Cascade, onUpdate: Cascade)
showId Int
// Each user can only submit one review per show.
@@unique([authorId, showId])
}
// A publication is a resource that publishes fashion reviews (e.g. Vogue).
model Publication {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The publication's name.
name String @unique
// The publication's avatar URL (i.e. logo).
avatar String? @unique
// The publication's articles.
articles Article[]
}
// There are only a few locations where fashion shows are held. While these may
// not always be entirely accurate, they are more of a category of shows (e.g.
// "Spring Tokyo 2023") than a specific location.
//
// This field was inspired by the Vogue season names (e.g. "Tokyo Spring 2023")
// and the drop-down included on the WWD website (e.g. "New York", "Paris").
//
// @todo replace the "BRIDAL" location with some other show flag.
// @todo this really should probably be its own model instead of an enum.
enum Location {
NEW_YORK
LONDON
MILAN
PARIS
TOKYO
BERLIN
FLORENCE
LOS_ANGELES
MADRID
COPENHAGEN
SHANGHAI
AUSTRALIA
STOCKHOLM
MEXICO
MEXICO_CITY
KIEV
TBILISI
SEOUL
RUSSIA
UKRAINE
SAO_PAOLO
BRIDAL
}
// A show is a fashion runway show.
model Show {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
// The name of the show, as designated by the show's organizer.
// @todo often this will be the same as the collection name; do we need this?
// @todo often this is just a function of the brand + season names...
name String @unique
// The link to the show on the brand website (where the description is from).
url String @unique
// The show's sex (i.e. "womenswear", "menswear", or both).
// @todo remove this once we have full show > collection > product data, as
// the product object already has the sex field attached to it.
sex Sex
// The show's level (i.e. "ready-to-wear", "couture", etc).
// @todo remove this once we have full show > collection > product data, as
// the product object already has the level field attached to it.
level Level
// A description of the collection, typically provided by the brand.
description String?
// The critic's consensus on the show.
articlesConsensus String?
// The articles about the show (i.e. the show's critic reviews).
articles Article[]
// The consumer's consensus on the show.
reviewsConsensus String?
// Consumer reviews of the show.
reviews Review[]
// Video of the show (typically just a single shot of runway models walking).
video Video? @relation(fields: [videoId], references: [id], onDelete: Cascade, onUpdate: Cascade)
videoId Int? @unique
// The fashion season in which the show was presented. e.g. Spring 2021
// @todo collections are already associated with seasons; do we need this?
season Season @relation(fields: [seasonId], references: [id], onDelete: Cascade, onUpdate: Cascade)
seasonId Int
// The date when the show started (if known).
date DateTime?
// The runway show's location.
location Location?
// The collections that were presented at the show. Often, there is only one.
collections Collection[]
// The looks that were presented at the show.
looks Look[]
// The brand that hosted the show. This is different than the collection(s)
// brand(s) that were presented at the show. Often, they will be the same.
// Occasionally, however, the brand that hosts the show (e.g. "Fashion East")
// will not be the brand(s) that presented their products at the show.
brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade, onUpdate: Cascade)
brandId Int
// A brand can only have a single show per season per sex (i.e. "menswear",
// "womenswear", or both) per level (i.e. "couture", "ready-to-wear", etc) per
// location (e.g. an "Australia" collection alongside a "Paris" collection).
//
// Ex: https://www.vogue.com/fashion-shows/australia-spring-2015/maticevski
// Ex: https://www.vogue.com/fashion-shows/spring-2015-ready-to-wear/maticevski
//
// This constraint was inspired by Vogue's URLs (e.g. /resort-2024/hermes). It
// may need adjustment in the future if there is ever a brand that presents
// twice during a single season (but that probably means I need more seasons).
@@unique([brandId, seasonId, sex, level, location])
}
Environment & setup
OS: Mac OS
Database: PostgreSQL
Python version: Python 3.11.4
Prisma version: 5.4.2
prisma : 5.4.2
prisma client python : 0.11.0
platform : darwin
expected engine version : ac9d7041ed77bcc8a8dbd2ab6616b39013829574
installed extras : []
install path : /Users/nchiang/repos/dolce/.venv/lib/python3.11/site-packages/prisma
binary cache dir : /Users/nchiang/.cache/prisma-python/binaries/5.4.2/ac9d7041ed77bcc8a8dbd2ab6616b39013829574
Bug description
When a model name clashes with a built-in name (e.g.
Set
), generation leads to weird behavior:How to reproduce
Steps to reproduce the behavior:
Set
Expected behavior
I should be able to name my Prisma models whatever I want to, even if they clash with built-in class names.
Prisma information
Environment & setup