LGTM Devlog 29:Firestore 및 init 하위 클래스 dunders 및 메타클래스의 ORM
33989 단어 pythondevjournal
예를 들어 최근에 변경된 게임 대상은 다음과 같다.
class Game:
@classmethod
def from_user(cls, user: User) -> Union[Game, NoGameType]:
""" Create a game from a user """
key = cls.make_key(user)
game = cls(key)
game.user = user
docs = db.collection("game").where("user_key", "==", user.key).stream()
for _ in docs:
return game
return NoGame
@classmethod
def new_from_fork(cls, user: User, fork_url: str) -> Game:
""" Save game with fork """
if not fork_url:
raise ValueError("Fork can't be blank")
key = cls.make_key(user)
game = cls(key)
game.user = user
game_doc = db.collection("game").document(game.key).get()
if game_doc.exists:
game_doc.reference.set(
{
"fork_url": fork_url,
"user_uid": user.uid,
"user_key": user.key,
},
merge=True,
)
else:
game_doc.reference.set(
{
"fork_url": fork_url,
"user_uid": user.uid,
"user_key": user.key,
"joined": firestore.SERVER_TIMESTAMP,
}
)
return game
@staticmethod
def make_key(user: User) -> str:
""" Game's key ARE user key due to 1:1 relationship """
return user.key
key: str
user: Union[User, NoUserType]
def __init__(self, key: str):
self.key = key
self.user = NoUser
def assign_to_uid(self, uid: str) -> None:
""" Assign a user to this game """
doc = db.collection("game").document(self.key).get()
if doc.exists:
doc.reference.set({"user_uid": uid}, merge=True)
def start_first_quest(self) -> None:
""" Create starting quest if not exist """
QuestClass = Quest.get_first_quest()
quest = QuestClass.from_game(self)
quest.execute_stages(TickType.FULL)
quest.save()
def __repr__(self):
return f"{self.__class__.__name__}(key={self.key})"
우리는 창설Game
대상의 두 가지 함수가 있다. new_from_fork()
와 from_user()
이다.또 하나의 방법assign_to_uid()
은 하나의 속성을 데이터베이스에 기록하는 것이다.보기에는 괜찮은데
User
대상과 Quest
대상에는 중복된 코드가 많다.따라서 나는 많은 중복 함수를 ORM 기류로 비뚤어져서
User
, Quest
와 Game
모두 이 함수를 계승할 수 있고 이 함수들은 데이터베이스에서 자신들을 저장하고 복원할 수 있다고 결정했다.변경한 새 객체
Game
는 다음과 같습니다.class Game(Orm, collection="game", parent_orm=User):
data: GameData
storage_model = GameData
@classmethod
def from_user(cls, user: User) -> Game:
key = cls.make_key(user)
game = cls(key)
game.parent_key = user.key
return game
@staticmethod
def make_key(user: User) -> str:
""" Game's key ARE user key due to 1:1 relationship """
return user.key
def set_fork_url(self, fork_url: str) -> None:
self.data.fork_url = fork_url
훨씬 깨끗해졌어!네가 알아차릴 수 있는 첫 번째 말은:class Game(Orm, collection="game", parent_orm=User):
이것은 Python의 간결한 특성으로, PEP 487 과 같이 사용자 정의 원류를 허용합니다.이것은 __init_subclass__
dunder가 동력을 제공한다. 이 dunder는 초기화 클래스에서 운행할 때(초기화 실례 때__init__
에 비해) 이 예에서 보면 이렇다(Orm
기류에서 나온다). def __init_subclass__(
cls, collection: str, parent_orm: Union[Type[Orm], NoParentType] = NoParent
):
""" Set collection and parent """
cls.collection = collection
cls.parent_orm = parent_orm
cls.col_ref = db.collection(collection)
그래서 여기서 발생하는 것은 Game
클래스가 초기화될 때 collection
클래스 변수와 parent_orm
클래스 변수를 설정하는 것이다.비록 우리는 __init__
를 사용하여 집합과 부모 대상을 설정할 수 있지만 이 방법은 Game
클래스 사이에 좋은 관심사 분리를 제공했다. 그의 클래스 속성은 게임 집합과 관련이 있다.그리고 Game
류의 특정한 실례로 이 집합의 특정한 항목과 관련이 있다.col_ref
의 설정__init_subclass__
은 우리가 Game.col_ref
조작을 실행할 수 있도록 허락한다. 실례화된 게임 대상을 필요로 하지 않는다(우리는 모든 게임을 인용할 때 이렇게 할 필요가 없다).다른 변화는 삭제
new_from_fork()
와 짧은set_fork_url()
설정기로 교체하는 것이다.왜냐하면 new_from_fork()
와from_user()
모두 user
파라미터가 필요하다는 것을 깨달았기 때문에 여기에 중복이 하나 있다.assign_to_uid()
현재 Orm
기류의 parent_key
속성을 사용할 수 있습니다.임무의 물건은 새로운QuestPage
물체로 옮겨졌는데, 그곳에서 더욱 관련이 있다.ORM
기본 ORM 기본 클래스의 작업은Firestore 대상과 Python 대상 사이에 데이터를 비추는 것입니다.그것은
Game
, User
와 새로운QuestPage
대상에 필요한 모든 공유 코드를 가지고 있다.그것의 상반부는 이렇게 보인다.
class Orm(ABC):
""" ORM base class links stuff together """
@property
@abstractmethod
def storage_model(cls) -> Type[BaseModel]:
""" Storage model """
return NotImplemented
collection: ClassVar[str]
parent_orm: ClassVar[Union[Type[Orm], NoParentType]]
col_ref: CollectionReference
def __init_subclass__(
cls, collection: str, parent_orm: Union[Type[Orm], NoParentType] = NoParent
):
""" Set collection and parent """
cls.collection = collection
cls.parent_orm = parent_orm
cls.col_ref = db.collection(collection)
key: Union[str, NoKeyType]
parent_key: Union[str, NoKeyType]
data: BaseModel
def __init__(self, key: Union[str, NoKeyType] = NoKey):
self.key = key
self.data = self.storage_model()
self.parent_key = NoKey
@property
def parent(self) -> Union[Orm, OrmNotFoundType]:
if self.parent_orm is not NoParent and self.parent_key is not NoKey:
return self.parent_orm(self.parent_key)
return OrmNotFound
...
하반부는 데이터베이스에서 데이터를 저장하고 불러오는 메커니즘과 관련이 있다.저는
__init_subclass__
에 설명했습니다. 본 ABC의 나머지 부분은 구체적으로 자신의 storage_model
를 제공해야 한다고 요구했습니다. Pydantic 모델이고 기본 클래스의 방법은 이 모델을 사용하여 데이터 저장소를 만들고 복구할 것입니다.이것은 parent
속성이라는 속성을 가지고 있으며, parent_key
속성으로 연결된 부모 대상의 실례를 되돌려줍니다.예를 들어, 인스턴스화된Game
객체가 있으면 다음과 같은 방법으로 사용자를 가져올 수 있습니다.game = Game(key)
game.load()
user = game.parent
Game
대상은 아버지 ORM 대상이User
인 것을 알고 Game
대상도 parent_key
속성을 저장하기 때문이다.이것은 사용자 대상을 되돌릴 수 있는 충분한 상세함이다.임무서
나는 앞의
Quest
대상에 대해 약간의 큰 재구축을 진행하였다.그것은 현재 두 부분으로 나뉜다. Quest
운행 임무와 관련된 모든 내용을 포함하고 단일 임무로 구체적으로 실현되는 기류로 한다.QuestPage
, 이것은 Orm
의 하위 클래스로 데이터베이스에 저장되고 불러오는 작업과 관련이 있습니다.나는 QuestPage
를 임무 로그의 한 페이지로 상상하는데 Quest
와 관련된'데이터 저장'을 포함하고 후자는 임무의 실제 실현이다.여기에 일부 합성이 있는데
QuestPage
에는 quest
의 실례가 속성으로 되어 있기 때문이다.
class QuestPage(Orm, collection="quest", parent_orm=Game):
data: QuestData
storage_model = QuestData
quest: Quest
@staticmethod
def make_key(game: Game, quest_name: str) -> str:
if not quest_name:
raise ValueError("quest_name must be valid")
return f"{game.key}:{quest_name}"
@classmethod
def from_game_get_first_quest(cls, game: Game) -> QuestPage:
return cls.from_game_get_quest(game, FIRST_QUEST_NAME)
@classmethod
def from_game_get_quest(cls, game: Game, quest_name: str) -> QuestPage:
key = cls.make_key(game, quest_name)
quest = cls(key, quest_name)
return quest
@classmethod
def iterate_all(cls) -> Generator[QuestPage, None, None]:
""" Iterate over all quests, the generator yields loaded quest_pages """
docs = cls.col_ref.where("complete", "!=", True).stream()
for doc in docs:
data = cls.storage_model.parse_obj(doc.to_dict())
quest_page = cls(doc.id, data.quest_name)
quest_page.data = data
quest_page.quest.load_raw(data.version, data.serialized_data)
yield quest_page
def __init__(self, key: str, quest_name):
super().__init__(key)
self.quest = Quest.from_name(quest_name, self)
self.data.quest_name = quest_name
def load(self) -> None:
""" Additionally parse the quest storage """
super().load()
if isinstance(self.quest, Quest):
self.quest.load_raw(self.data.version, self.data.serialized_data)
def save(self) -> None:
""" Additionally parse out the quest storage """
if isinstance(self.quest, Quest):
self.data.serialized_data = self.quest.save_raw()
self.data.version = str(self.quest.version)
super().save()
...
이것은 ORM 기본 클래스__init__()
, save()
와 load()
를 확장하여quest 페이지 데이터 모델과 다른quest 데이터 모델을 저장하고 불러올 수 있도록 합니다.이러한 분할의 원인은 임무 자체는 자신의 변수 공간을 가지고 임무와 관련된 데이터를 저장하고 모든 메타데이터인 버전 번호, 완성된 임무 목록 등은 단독으로 저장되기 때문이다.게다가 다른 변화까지 겹쳐 이 재구성은 새로운 기능을 추가하지 않고 코드를 다시 정리했다.이런 거 많이 안 했으면 좋겠어요.
Reference
이 문제에 관하여(LGTM Devlog 29:Firestore 및 init 하위 클래스 dunders 및 메타클래스의 ORM), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/meseta/lgtm-devlog-29-orm-for-firestore-and-initsubclass-dunders-and-metaclasses-5e1p텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)