A03ki / uecbbs

@uec_bbsを支えるPythonパッケージTwissifyの管理
https://twitter.com/uec_bbs
MIT License
0 stars 0 forks source link

`since_id`と`max_id`を保存するデータベースを作成する #19

Closed A03ki closed 4 years ago

A03ki commented 4 years ago

前提

max_idは取得したツイートのうち最も小さなツイートのID、 since_idは取得したツイートのうち最も大きなツイートのIDである。

max_idsince_idを使用するAPIは以下の4つ。

なぜ

どちらもツイートを取得する際に、過去に取得したツイートを重複して取得しないために用いる。 max_idsince_idを変数に入れて保持してもいいが、プログラムが終了した後まで値を維持できない。そのため、なんらかの方法でこれらの値を保存しておく必要がある。考えられる保存形式としてはtxtやcsv、データベースだろう。

home_timelineとmentions_timelineとretweets_of_meは保存する値がmax_idsince_idの2つだけなので、保存形式はtxtやcsvでも良かった。しかし、 user_timelineでは各ユーザーに対してmax_idsince_idが必要になる。加えて、標準モジュールのcsvだと行の更新が面倒であり、pandasであれば行の更新は簡単だが、毎回csvファイルを全て書き換えることになる。また、user_timeline は15分に最大180ものユーザータイムラインを取得できることから行数は多くなると思われる。 したがって、max_idsince_idをデータベースで保存したほうが良いと判断した。

csvではなくデータベースを使う理由

データベース概論Ⅰ_1.データベースシステムの基本概念 (1) - YouTube この解説で納得できるはず。

参考文献

GET statuses/user_timeline - Twitter 開発者ドキュメント 日本語訳

A03ki commented 4 years ago

since_idmax_idをわかりやすく容易に保持できる入れ物が欲しい。

前提

それぞれのタイムラインでsince_idmax_idを使う。これらの値はタイムラインを取得する度に更新される。そのため、ただの変数だと扱いにくいと考える。例えば、タプル だとどちらがsince_idmax_idかの判断がしづらい。 辞書でも良いが、since_idmax_id以外にキーの追加は必要なく、値の書き換えも容易にできてしまう。 since_idmax_idをプロパティとした新しいクラスを作成してもいいが、同様の機能を持つ既存の関数があるならばそれを使いたい。

どのように

namedtupleを使用する。

namedtupleはタプル に名前をつけただけあって書き換えは不可能。since_idmax_idの2つの名前のみで維持できる。

from typing import NamedTuple, Optional

class TimelineIndex(NamedTuple):
    since_id: Optional[int] = None
    max_id: Optional[int] = None

namedtupleを使うと以下のようにわかりやすくなる。

timelineindex = TimelineIndex()
timelineindex

出力:

TimelineIndex(since_id=None, max_id=None)

Python 3.5も考慮するなら下記を使った方が良さそう。

from collections import namedtuple

class TimelineIndex(namedtuple("TimelineIndex",
                               ["since_id", "max_id"],
                               defaults=[None, None])):
    __slots__ = ()

参考文献

collections --- コンテナデータ型 — Python 3.8.2 ドキュメント typing --- 型ヒントのサポート — Python 3.8.2 ドキュメント

A03ki commented 4 years ago

SQLAlchemyを使えば、取り出した行はそのままでもわかりやすく扱うことができる。

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class TimelineIndex(Base):
    __tablename__ = "TimelineIndex"

    name = Column(String, primary_key=True)
    since_id = Column(Integer)
    max_id = Column(Integer)

    def __repr__(self):
        return ((self.__class__.__name__
                + "(name={name}, since_id={since_id}, since_id={max_id})")
                .format(name=self.name,
                        since_id=self.since_id,
                        max_id=self.max_id))
timelineindex = TimelineIndex()
timelineindex

出力:

TimelineIndex(name=None, since_id=None, max_id=None)

しかしながらnamedtupleと違って普通に代入できてしまう。

timelineindex.since_id = 100
timelineindex

出力:

TimelineIndex(name=None, since_id=100, max_id=None)

上述した通り、since_idmax_idは4つのAPIでのみ使用する。これらは引数として渡され、返ってきたツイートのIDから更新を行うことになる。namedtupleであれば、更新までの間にsince_idmax_idが書き換わることは絶対にない。

そのため、 データベース→タイムラインのsince_idmax_id→データベース→... という流れを データベース→namedtuple→タイムラインのsince_idmax_id→データベース→... のように仲介してもいいのではないだろうか。

実際、データベースからnamedtupleに正しくsince_idmax_idが受け渡せているかのテストを行うのは簡単だが、受け取ったsince_idmax_idがどこで書き換えられたかというデバッグは困難だと思われる。

イミュータブルという安心感を得るためにnamedtupleを間に挟むのはありだと考えた。

A03ki commented 4 years ago

安全にやり取りするために since_idmax_idnamedtupleに入れておこうしていた。 しかし、これらはタイムラインAPIを呼ぶときにしか使わない。 そのためデータベースからIDを取得して、次の行でIDを使うなら、わざわざnamedtupleに格納する必要はないのかもしれない。

A03ki commented 4 years ago

namedtupleを経由せずに、SQLAlchemyを使うことにした。

使い方

from twissify.tables import TimelineIndex

timelineindex = TimelineIndex(since_id=10, max_id=200)
timelineindex

出力:

TimelineIndex(name=None, since_id=10, max_id=200)

timelineindex.since_id

出力:

10