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.)