From 71b02bbdc926044585cfe1eebe467df74d4621f7 Mon Sep 17 00:00:00 2001 From: sushichan044 Date: Fri, 1 Nov 2024 21:39:24 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E3=82=AF=E3=82=A8=E3=83=AA=E3=83=91?= =?UTF-8?q?=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF=E5=90=8D=E7=A7=B0=E3=82=92?= =?UTF-8?q?=E4=BB=96=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A8=E3=81=9D=E3=82=8D=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/birdxplorer_api/routers/data.py | 12 ++++++------ api/tests/routers/test_data.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/birdxplorer_api/routers/data.py b/api/birdxplorer_api/routers/data.py index 2955df7..57812a5 100644 --- a/api/birdxplorer_api/routers/data.py +++ b/api/birdxplorer_api/routers/data.py @@ -126,8 +126,8 @@ def get_notes( @router.get("/posts", response_model=PostListResponse) def get_posts( request: Request, - post_id: Union[List[PostId], None] = Query(default=None), - note_id: Union[List[NoteId], None] = Query(default=None), + post_ids: Union[List[PostId], None] = Query(default=None), + note_ids: Union[List[NoteId], None] = Query(default=None), created_at_from: Union[None, TwitterTimestamp, str] = Query(default=None), created_at_to: Union[None, TwitterTimestamp, str] = Query(default=None), offset: int = Query(default=0, ge=0), @@ -142,8 +142,8 @@ def get_posts( created_at_to = ensure_twitter_timestamp(created_at_to) posts = list( storage.get_posts( - post_ids=post_id, - note_ids=note_id, + post_ids=post_ids, + note_ids=note_ids, start=created_at_from, end=created_at_to, search_text=search_text, @@ -154,8 +154,8 @@ def get_posts( ) ) total_count = storage.get_number_of_posts( - post_ids=post_id, - note_ids=note_id, + post_ids=post_ids, + note_ids=note_ids, start=created_at_from, end=created_at_to, search_text=search_text, diff --git a/api/tests/routers/test_data.py b/api/tests/routers/test_data.py index c9dfbff..66cdb74 100644 --- a/api/tests/routers/test_data.py +++ b/api/tests/routers/test_data.py @@ -44,7 +44,7 @@ def test_posts_get_limit_and_offset(client: TestClient, post_samples: List[Post] def test_posts_get_has_post_id_filter(client: TestClient, post_samples: List[Post]) -> None: - response = client.get(f"/api/v1/data/posts/?postId={post_samples[0].post_id},{post_samples[2].post_id}") + response = client.get(f"/api/v1/data/posts/?postIds={post_samples[0].post_id},{post_samples[2].post_id}") assert response.status_code == 200 res_json = response.json() assert res_json == { @@ -57,7 +57,7 @@ def test_posts_get_has_post_id_filter(client: TestClient, post_samples: List[Pos def test_posts_get_has_note_id_filter(client: TestClient, post_samples: List[Post], note_samples: List[Note]) -> None: - response = client.get(f"/api/v1/data/posts/?noteId={','.join([n.note_id for n in note_samples])}") + response = client.get(f"/api/v1/data/posts/?noteIds={','.join([n.note_id for n in note_samples])}") assert response.status_code == 200 res_json = response.json() assert res_json == {"data": [json.loads(post_samples[0].model_dump_json())], "meta": {"next": None, "prev": None}} @@ -123,7 +123,7 @@ def test_posts_get_timestamp_out_of_range(client: TestClient, post_samples: List def test_posts_get_with_media_by_default(client: TestClient, post_samples: List[Post]) -> None: - response = client.get("/api/v1/data/posts/?postId=2234567890123456791") + response = client.get("/api/v1/data/posts/?postIds=2234567890123456791") assert response.status_code == 200 res_json_default = response.json() @@ -134,7 +134,7 @@ def test_posts_get_with_media_by_default(client: TestClient, post_samples: List[ def test_posts_get_with_media_true(client: TestClient, post_samples: List[Post]) -> None: - response = client.get("/api/v1/data/posts/?postId=2234567890123456791&media=true") + response = client.get("/api/v1/data/posts/?postIds=2234567890123456791&media=true") assert response.status_code == 200 res_json_default = response.json() @@ -146,7 +146,7 @@ def test_posts_get_with_media_true(client: TestClient, post_samples: List[Post]) def test_posts_get_with_media_false(client: TestClient, post_samples: List[Post]) -> None: expected_post = post_samples[1].model_copy(update={"media_details": []}) - response = client.get("/api/v1/data/posts/?postId=2234567890123456791&media=false") + response = client.get("/api/v1/data/posts/?postIds=2234567890123456791&media=false") assert response.status_code == 200 res_json_default = response.json() From 53535963969f9f3364ad8db130e068cae5cf03ed Mon Sep 17 00:00:00 2001 From: sushichan044 Date: Fri, 1 Nov 2024 21:53:21 +0900 Subject: [PATCH 2/3] docs: Add example usecase --- README.md | 4 ++++ docs/example.md | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docs/example.md diff --git a/README.md b/README.md index 97c0c4c..78fdee3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ BirdXplorer is software that helps users explore community notes data on X (formerly known as Twitter). +## Example Usecase + +See [example](./docs/example.md) + ## Development ### Requirements diff --git a/docs/example.md b/docs/example.md new file mode 100644 index 0000000..cf12150 --- /dev/null +++ b/docs/example.md @@ -0,0 +1,40 @@ +# BirdXplorer の 使用例 + +## 特定のトピックのコミュニティノートと、そのトピックに関連するツイートを取得する + +BirdXplorer では、コミュニティノートのトピックを AI で推定して分類しています。 +この分類の候補は、 `/api/v1/data/topics` で取得できます。 + +ここでは、トピック: テクノロジー (topicId: 51) について、そのコミュニティノート500件とコミュニティノートに関連するツイートを取得する例を示します。 + +```python +#!python3.10 +import json + +import requests + +# AI で推定 / 分類した際に 「テクノロジー」 と判定されたコミュニティノートを取得するための id +# その他の種類は `https://birdxplorer.onrender.com/api/v1/data/topics` で取得できます +TECHNOLOGY_TOPIC_ID = 51 + +offset = 0 +expected_data_amount = 500 # 最大で 1000 まで指定できます + +tech_notes_res = requests.get( + f"https://birdxplorer.onrender.com/api/v1/data/notes?offset={offset}&limit={expected_data_amount}&topic_ids={TECHNOLOGY_TOPIC_ID}&language=ja" +) +tech_notes = tech_notes_res.json()["data"] + +# コミュニティノート と X の Post は 1:1 で対応しています +tech_post_ids = list(map(lambda x: x["postId"], tech_notes)) +post_ids = ",".join(tech_post_ids) + +posts_res = requests.get( + f"https://birdxplorer.onrender.com/api/v1/data/posts?post_ids={post_ids}&limit={expected_data_amount}" +) +tech_posts = posts_res.json()["data"] + + +with open("tech_posts.json", "w") as f: + f.write(json.dumps(tech_posts, ensure_ascii=False, indent=2)) +``` From 35be03ead6d56c2b9a1a74ae14d8849513ea0dfe Mon Sep 17 00:00:00 2001 From: sushichan044 Date: Sat, 2 Nov 2024 01:50:58 +0900 Subject: [PATCH 3/3] docs: add api spec --- docs/example.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/example.md b/docs/example.md index cf12150..ad98304 100644 --- a/docs/example.md +++ b/docs/example.md @@ -1,5 +1,15 @@ # BirdXplorer の 使用例 +## API仕様の閲覧 + +API 仕様は、[Swagger UI](https://birdxplorer.onrender.com/docs) で閲覧できます。 + +また、[OpenAPI Spec](https://birdxplorer.onrender.com/openapi.json) も提供しています。 + +> [!TIP] +> OpenAPI Specification から API リクエスト用のコードを生成するライブラリを使用することで、 +> API の入出力をコード上で安全に扱えることがあります。 + ## 特定のトピックのコミュニティノートと、そのトピックに関連するツイートを取得する BirdXplorer では、コミュニティノートのトピックを AI で推定して分類しています。