본문 바로가기
파이썬/Tortoise-ORM

[Python] Tortoise ORM 사용법 - 모델(Medel)

by 책 읽는 개발자_테드 2022. 4. 26.
반응형

Tortoise ORM이란? https://scshim.tistory.com/576

Tortoise ORM 시작하기 https://scshim.tistory.com/577

 

목차

· 모델이란? 모델 사용하기

· 모델 클래스의 유용한 메서드


모델이란? 모델 사용하기


· 모델은 데이터베이스의 테이블을 관리하기 위한 클래스다.

· 모델을 사용하려면, 다음을 import 해야한다.

from tortoise.models import Model

 

· 모델을 다음과 같이 코드로 표현할 수 있다.

class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    created = fields.DatetimeField(auto_now_add=True)

    def __str__(self):
        return self.name


class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
    participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
    modified = fields.DatetimeField(auto_now=True)
    prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)

    def __str__(self):
        return self.name


class Team(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()

    def __str__(self):
        return self.name

 

- 모델 클래스에 있는 각 속성은 필드라고 하며, 다음 글에서 설명한다. https://scshim.tistory.com/579

 

· 모든 모델은 기본 모델(base model)에서 파생되야 한다. 또한 자신의 모델 하위 클래스에서 파생할 수 있으며, 다음과 같은 추상 모델을 만들 수 있다.

class AbstractTournament(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    created = fields.DatetimeField(auto_now_add=True)

    class Meta:
        abstract = True

    def __str__(self):
        return self.name
 

 

· Tortoise ORM에서 모델에 primary key를 갖게 할 수 있다. 

- primary key는 지정된 필드의 별칭(alias)이 될 예약된 pk 필드를 통해 접근할 수 있다.

ex) .filter(pk=...)  필터링을 수행할 때 별칭 필드를 필드 이름으로 사용할 수 있다.

 

· 인덱싱 가능한 모든 필드 타입의 단일(non-composite) primary key를 지원한다. 하지만 다음 필드 유형만 권장된다.

1. IntField

2. BigIntField

3. CharField

4. UUIDField

 

· pk 매개변수를 True로 설정하여 primary key를 정의해야 한다. primary key를 정의하지 않으면 id라는 이름으로 IntField 유형의 기본 키를 생성한다. 

ex) 

 
id = fields.IntField(pk=True)
checksum = fields.CharField(pk=True)
guid = fields.UUIDField(pk=True)

 

· Tortoise ORM에서 모델을 정의할 때 상속을 활용하여 많은 반복 작업을 줄일 수 있다.

· 부모 클래스는 Model 클래스로 제한되지 않고, 어떤 클래스든 가능하다. 

- 이러한 방식으로 자연스럽고, 유지 관리하기 쉬운 방식으로 모델을 정의할 수 있다.

from tortoise import fields
from tortoise.models import Model

class TimestampMixin():
    created_at = fields.DatetimeField(null=True, auto_now_add=True)
    modified_at = fields.DatetimeField(null=True, auto_now=True)

class NameMixin():
    name = fields.CharField(40, unique=True)

class MyAbstractBaseModel(Model):
    id = fields.IntField(pk=True)

    class Meta:
        abstract = True

class UserModel(TimestampMixin, MyAbstractBaseModel):
    # MyAbstractBaseModel에 정의된 id를 Overriding한다.
    id = fields.UUIDField(pk=True)

    # 추가적인 필드를 더한다
    first_name = fields.CharField(20, null=True)

    class Meta:
        table = "user"


class RoleModel(TimestampMixin, NameMixin, MyAbstractBaseModel):

    class Meta:
        table = "role"
 



· Meta 클래스는 모델을 위한 메타데이터를 설정할 때 사용한다.

 
from tortoise.models import Model
from tortoise import fields

class Tournament(Model):
   id = fields.IntField(pk=True, description="토너먼트 아이디")
   name = fields.TextField(description="토너먼트 이름")

   class Meta:
       table = "tournament"
       table_description = "토너먼트 테이블"

 

· ForeignKeyField를 통해 다른 모델에 대한 foreignkey 관계를 나타낼 수 있다.

· 아래 코드는 Tournament에 대한 외래키 참조를 생성한다. 앱 이름과 모델 이름으로 구성된 리터럴로 모델을 참조하여 생성한다.

- 모델은 기본 앱 이름으로 설정되고, Meta 클래스에서 app = ‘other’로 변경할 수 있다.

class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
    participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
    modified = fields.DatetimeField(auto_now=True)
    prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)

 

· ManyToManyField를 통해 하나의 모델과 다른 모델 간의 다대다 관계를 나타낼 수 있다.

fields.ManyToManyField('models.Team', related_name='events')
 

· Tortoise ORM은 아직 초기 프로젝트이므로, 모델 및 모델 간의 다양한 관계에 대해 자동 완성을 지원하는 에디터의 도움을 받지 못할 수 있다. 하지만 관계를 담당하는 필드에 대한 몇 가지 애너테이션을 모델에 추가하면, 자동 완성을 수행할 수 있다.

from tortoise.models import Model
from tortoise import fields


class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)

    events: fields.ReverseRelation["Event"]

    def __str__(self):
        return self.name


class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
        "models.Tournament", related_name="events"
    )
    participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
        "models.Team", related_name="events", through="event_team"
    )

    def __str__(self):
        return self.name


class Team(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)

    events: fields.ManyToManyRelation[Event]

    def __str__(self):
        return self.nam
 

 

모델 클래스의 유용한 메서드


async create(**kwargs)

· 데이터베이스에 레코드를 생성하고, 객체를 반환한다.

- parameter: kwargs(Any) - model parameters

- Return type: Model

· 사용법:

user = await User.create(name=”...”, email=”...”)
 

- save()를 사용하면 동일한 기능을 낼 수 있다.

user = User(name=”...”, email=”...”)
await user.save()
 

 

· 예시

 
from tortoise import Tortoise, run_async
from tortoise.queryset import QuerySetSingle

from tortoise_exam.tutorial_exam.models import Tournament
import asyncio
QuerySetSingle

async def init():
   await Tortoise.init(
       db_url='sqlite://db.sqlite3',
       modules={'models': ['tortoise_exam.tutorial_exam.models']}
   )
   # Generate the schema
   await Tortoise.generate_schemas()


run_async(init())


async def run():
   # Create instance by save
   tournament = Tournament(name='New Tournament')
   await tournament.save()

   # Or by .create()
   await Tournament.create(name='Another Tournament')

   # get all
   tournaments = await Tournament.all()
   for tournament in tournaments:
       print(tournament.id, tournament.name)


loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()

결과



- 추가적으로 save 메서드로는 레코드를 업데이트할 수 있다.

async def run():
   # Create instance by save
   tournament = Tournament(name='New Tournament')
   await tournament.save()

   # Or by .create()
   await Tournament.create(name='Another Tournament')

   # get all
   tournaments = await Tournament.all()
   for tmp in tournaments:
       print(tmp.id, tmp.name)

   #Update
   tournament.name = "Old Tournament"
   await tournament.save()

   print('======After Update======')

   # get all
   tournaments = await Tournament.all()
   for tmp in tournaments:
       print(tmp.id, tmp.name)

 

결과

 

async bulk_create(objects, batch_size=None, using_db=None)

- Parameters: 

▶ objects (Iterable[Model]) - bulk create를 위한 리스트 객체

▶ batch_size (Optional[int]) - 얼마나 많은 객체가 하나의 쿼리에 생성되는지

▶ using_db (Optional[BaseDBAsyncClient]) - 디폴드 바운드 대신에 사용한 구체적인 DB 커넥션

 

· 예시

async def run_bulk_create():
   await Tournament.bulk_create([
       Tournament(name='first Tournament'),
       Tournament(name='second Tournament'),
       Tournament(name='third Tournament'),
   ])

   # get all
   tournaments = await Tournament.all()
   for tmp in tournaments:
       print(tmp.id, tmp.name)
 

 

async delete(using_db=None)

· 모델 객체를 삭제한다.

- Parameters: using_db (Optional[BaseDBAsyncClient]) - 기본 바인딩 대신 사용할 DB 연결 시정

- Raise:OperationalError - 객체가 영속된 적이 없는 경우 발생

- Return type: None

 

· 예시

async def run():
   # Create instance by save
   tournament = Tournament(name='New Tournament')
   await tournament.save()

   # 첫 번째 레코드 삭제
   await tournament.delete()

   # Or by .create()
   await Tournament.create(name='Another Tournament')

   # get all
   tournaments = await Tournament.all()
   for tournament in tournaments:
       print(tournament.id, tournament.name)


loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()

결과

 

- 두 개의 레코드를 생성했지만, 첫 번째 레코드가 삭제되어 하나의 레코드만 출력된다.

 

exists(*args, **kwargs)

· 제공된 필터 매개변수에 레코드가 존재하는지 여부를 True/False로 반환

- Parameters:

▶ args(Q) - Q 함수는 제약을 조건을 포함한다.

▶ kwargs(Any) - 간단한 필터 제약

- Return type: ExistsQuery


· 예시

async def run_exists():
   await Tournament.create(name="Exist Tournament")

   exists = await Tournament.exists(name="Non-Exist Tournament")

   if not exists:
       print("존재하지 않습니다.")
   else:
       print("존재합니다.")​

결과



async def run_exists():
   await Tournament.create(name="Exist Tournament")

   exists = await Tournament.exists(name="Exist Tournament")

   if not exists:
       print("존재하지 않습니다.")
   else:
       print("존재합니다.")

결과

 

filter(*args, **kwargs)

· 필터가 적용된 QuerySet todtjd

- Parameters:

▶ args(Q) - Q 함수는 제약을 조건을 포함한다.

▶ kwargs(Any) - 간단한 필터 제약

- Return type: QuerySet[Model]

 

· 예시

 
async def run():
   await Tournament.create(name='Korea Tournament')
   await Tournament.create(name='Korea Tournament')
   await Tournament.create(name='Japan Tournament')
   await Tournament.create(name='China Tournament')
   await Tournament.create(name='Korea Tournament')

   tournaments = await Tournament.filter(name="Korea Tournament")

   for tournament in tournaments:
       print(tournament.id, tournament.name)

결과



first()

· 첫 번째 레코드를 반환하는 QuerySet 생성

- Return type: QuerySetSingle[Optional[Model]]

 

· 예시

async def run_first():
   await Tournament.create(name='first Tournament')
   await Tournament.create(name='second Tournament')
   tournament = await Tournament.first()
   print(tournament.id, tournament.name)


loop = asyncio.get_event_loop()
loop.run_until_complete(run_first())
loop.close()
 

결과

 

get(*args, **kwargs)

· 제공된 매개변수 필터를 사용하여 모델 타입에 대한 하나의 레코드를 가져온다.

- Parameters: 

▶ args(Q) - Q 함수는 제약을 조건을 포함한다.

▶ kwargs(Any) - 간단한 필터 제약

 

- Raises:

▶MultipleObjectsReturned - 검색 결과가 하나 보다 많으면 발생

▶DoesNotExist - 오브젝트가 발결되지 않으면 발생

- Return type: QuerySetSingle[Model]

 

· 예시

async def run_get():
   await Tournament.create(name='first Tournament')
   await Tournament.create(name='second Tournament')
   tournament = await Tournament.get(id=1)
   print(tournament.id, tournament.name)​

 

 update_from_dict(data)

· 모델을 제공된 dict로 업데이트한다. 해당 메서드는 dict로 부터 모델을 대량 업데이트할 수 있고, 데이터타입 변환을 발생시킬 수 있다.

· 추가 필드를 무시하고, 해당 필드들과 함께 업데이트하지 않는다. 하지만 bad types 또는 여러 인스턴스 관계를 업데이트할 때 에러가 발생한다.

- Parameters: data(dic) 

- Return type: Model

- Returns: 모델 인스턴스

- Raises:

▶ ConfigurationError - remote instance를 업데이트할 때 발생 (예: reverse ForeignKey, ManyTo Many relation) 

▶ ValueError - 전달된 매개변수가 타입 호환성이 없을 때 발생 

 

· 예시

 
async def run_update_from_dict():
   tournament = await Tournament.create(name='Bad Tournament')
   print(tournament.id, tournament.name)

   tournament_dict = {'id': 777, 'name': 'Good Tournament'}
   tournament.update_from_dict(tournament_dict)
   print(tournament.id, tournament.name)

결과



async update_or_create(defaults=None, using_db=None, **kwargs)

· 주어진 kwargs로 객체를 업데이트하고, 필요하다면 새로운 객체를 생성하는 메서드

- Parameters: 

▶ defaults (Optional[dict]) - 객체를 업데이트하는데 사용되는 기본 값들

▶ using_db (Optional[BaseDBAsyncClient]) - 기본 바운드 대신에 사용할 DB 커넥션 지정

▶ kwargs (Any) - 쿼리 매개변수들

- Return type: Tuple[Model, bool]

 

· 예시

async def update_or_create():
   await Tournament.create(name='first Tournament')
   await Tournament.create(name='second Tournament')
   await Tournament.create(name='third Tournament')

   await Tournament.update_or_create(
       {
           "name": "test Tournament",
       },
       id=1,
   )
   await Tournament.update_or_create(
       {
           "name": "Hundredth Tournament",
       },
       id=100,
   )

   tournaments = await Tournament.all()
   for tournament in tournaments:
       print(tournament.id, tournament.name)​

결과

- 이미 존재하는 id(1)는 update되고, 존재하지 않는 id(100)는 create된다.



TODO: async fetch_related(*args, using_db=None), annotate(**kwargs), async refresh_from_db(fields=None, using_db=None), describe(serializable=True), exclude(*args, **kwargs), async get_or_create(defaults=None, using_db=None, **kwargs), get_or_none(*args, **kwargs), select_for_update(nowait=False, skip_locked=False, of=()), check(), clone(pk=<objct object>), register_listener(signal, listener), all()

 

출처

https://tortoise-orm.readthedocs.io/en/latest/models.html

 

반응형

'파이썬 > Tortoise-ORM' 카테고리의 다른 글

[Python] Tortoise ORM 사용법 - 필드(Fields)  (0) 2022.04.26
[Python] Tortoise ORM 시작하기  (0) 2022.04.23
[Python] Tortoise ORM이란?  (0) 2022.04.23

댓글