|
""" |
|
Rate Limits |
|
|
|
https://getpocket.com/developer/docs/rate-limits |
|
|
|
The Pocket API has two separate rate limits. These dictate how many calls can be made to the server within a given time. |
|
Enforcing rate limits prevents a single app or user from overwhelming the server. The response codes will tell you if |
|
you've hit your limit. Your application should be looking for these and if it encounters a rate limit status code, it |
|
should back off until it hits the reset time. Ignoring these codes may cause your access to be disabled. |
|
|
|
User Limit |
|
|
|
Each user is limited to 320 calls per hour. This should be very sufficient for most users as the average user only makes |
|
changes to their list periodically. To ensure the user stays within this limit, make use of the send method for batching |
|
requests. |
|
|
|
Consumer Key Limit |
|
|
|
Each application is limited to 10,000 calls per hour. (...) |
|
""" |
|
import time |
|
import typing |
|
|
|
import gpauthentication |
|
import gptagduration |
|
import requests |
|
|
|
|
|
class RateLimitResponseHeader(typing.NamedTuple): |
|
""" |
|
Response Headers |
|
|
|
https://getpocket.com/developer/docs/rate-limits |
|
|
|
The Pocket API responses include custom headers that provide information about the current status of rate limiting for |
|
both the current user and consumer key. |
|
|
|
- `'X-Limit-User-Limit'`: Current rate limit enforced per user |
|
- `'X-Limit-User-Remaining'`: Number of calls remaining before hitting user's rate limit |
|
- `'X-Limit-User-Reset'`: Seconds until user's rate limit resets |
|
- `'X-Limit-Key-Limit'`: Current rate limit enforced per consumer key |
|
- `'X-Limit-Key-Remaining'`: Number of calls remaining before hitting consumer key's rate limit |
|
- `'X-Limit-Key-Reset'`: Seconds until consumer key rate limit resets |
|
""" |
|
|
|
remaining_header_key: str |
|
reset_header_key: str |
|
|
|
def remaining(self, response: requests.Response): |
|
return int(response.headers.get(self.remaining_header_key, "1")) |
|
|
|
def reset(self, response: requests.Response): |
|
if self.remaining(response) > 0: |
|
return 0 |
|
return int(response.headers.get(self.reset_header, "0")) |
|
|
|
|
|
LIMIT_REMAINING_HEADERS = { |
|
RateLimitResponseHeader( |
|
remaining_header_key="X-Limit-User-Remaining", |
|
reset_header_key="X-Limit-User-Reset", |
|
), |
|
RateLimitResponseHeader( |
|
remaining_header_key="X-Limit-Key-Remaining", |
|
reset_header_key="X-Limit-Key-Reset", |
|
), |
|
} |
|
|
|
|
|
TAG_UNTAGGED = "_untagged_" |
|
|
|
|
|
class RetrieveParameters(typing.TypedDict, total=False): |
|
""" |
|
Pocket API: Retrieving a User's Pocket Data |
|
|
|
https://getpocket.com/developer/docs/v3/retrieve |
|
|
|
`state` |
|
- `'unread'` = only return unread items (default) |
|
- `'archive'` = only return archived items |
|
- `'all'` = return both unread and archived items |
|
|
|
`favorite` |
|
- `0` = only return un-favorited items |
|
- `1` = only return favorited items |
|
|
|
`tag` |
|
- `str` = only return items tagged with tag_name |
|
- `'_untagged_'` = only return untagged items |
|
|
|
`contentType` |
|
- `'article'` = only return articles |
|
- `'video'` = only return videos or articles with embedded videos |
|
- `'image'` = only return images |
|
|
|
`sort` |
|
- `'newest'` = return items in order of newest to oldest |
|
- `'oldest'` = return items in order of oldest to newest |
|
- `'title'` = return items in order of title alphabetically |
|
- `'site'` = return items in order of url alphabetically |
|
|
|
`detailType` |
|
- `'simple'` = return basic information about each item, including title, url, status, and more |
|
- `'complete'` = return all data about each item, including tags, images, authors, videos, and more |
|
|
|
`search` |
|
- `str` = Only return items whose title or url contain the search string |
|
|
|
`domain` |
|
- `str` = Only return items from a particular domain |
|
|
|
`since` |
|
- `int` = Only return items modified since the given since unix timestamp |
|
|
|
`count` |
|
- `int` = Only return count number of items |
|
|
|
`offset` |
|
- `int` = Used only with count; start returning from offset position of results |
|
""" |
|
|
|
state: typing.Literal[ |
|
"unread", # only return unread items (default) |
|
"archive", # only return archived items |
|
"all", # return both unread and archived items |
|
] |
|
favorite: typing.Literal[ |
|
0, 1, # only return un-favorited items # only return favorited items |
|
] |
|
tag: str # "${tag_name}" = only return items tagged with "${tag_name}", "_untagged_" = only return untagged items |
|
contentType: typing.Literal[ |
|
"article", # only return articles |
|
"video", # only return videos or articles with embedded videos |
|
"image", # only return images |
|
] |
|
sort: typing.Literal[ |
|
"newest", # return items in order of newest to oldest |
|
"oldest", # return items in order of oldest to newest |
|
"title", # return items in order of title alphabetically |
|
"site", # return items in order of url alphabetically |
|
] |
|
detailType: typing.Literal[ |
|
"simple", # return basic information about each item, including title, url, status, and more |
|
"complete", # return all data about each item, including tags, images, authors, videos, and more |
|
] |
|
search: str # Only return items whose title or url contain the search string |
|
domain: str # Only return items from a particular domain |
|
since: int # Only return items modified since the given since unix timestamp |
|
count: int # Only return count number of items |
|
offset: int # Used only with count; start returning from offset position of results |
|
|
|
|
|
def retrieve(data: RetrieveParameters): |
|
""" |
|
Pocket API: Retrieving a User's Pocket Data |
|
|
|
https://getpocket.com/developer/docs/v3/retrieve |
|
|
|
Pocket's /v3/get endpoint is a single call that is incredibly versatile. A few examples of the types of requests you can |
|
make: |
|
|
|
- Retrieve a user’s list of unread items |
|
- Sync data that has changed since the last time your app checked |
|
- Retrieve paged results sorted by the most recent saves |
|
- Retrieve just videos that the user has saved |
|
- Search for a given keyword in item’s title and url |
|
- Retrieve all items for a given domain |
|
and more |
|
|
|
Required Permissions |
|
|
|
In order to use the /v3/get endpoint, your consumer key must have the "Retrieve" permission. |
|
""" |
|
url = "https://getpocket.com/v3/get" |
|
response = requests.post( |
|
url=url, data={**gpauthentication.required_body_parameters(), **data}, |
|
) |
|
for header, wait_interval in { |
|
header: header.reset(response) for header in LIMIT_REMAINING_HEADERS |
|
}.items(): |
|
if wait_interval > 0: |
|
print(f"> Throttling; header: {header}, wait_interval: {wait_interval}.") |
|
time.sleep(wait_interval + 2) |
|
return retrieve(url, data) |
|
else: |
|
response.raise_for_status() |
|
return response |
|
|
|
|
|
def modify(actions: typing.Iterable[typing.Union[gptagduration.TagsAdd]]): |
|
""" |
|
Pocket API: Modifying a User's Pocket Data |
|
|
|
https://getpocket.com/developer/docs/v3/modify |
|
|
|
Pocket’s /v3/send endpoint allows you to make a change or batch several changes to a user’s list or Pocket data. |
|
""" |
|
response = requests.post( |
|
url="https://getpocket.com/v3/send", |
|
json={**gpauthentication.required_body_parameters(), "actions": actions}, |
|
) |
|
for header, wait_interval in { |
|
header: header.reset(response) for header in LIMIT_REMAINING_HEADERS |
|
}.items(): |
|
if wait_interval > 0: |
|
print(f"> Throttling; header: {header}, wait_interval: {wait_interval}.") |
|
time.sleep(wait_interval + 2) |
|
return modify(actions) |
|
else: |
|
response.raise_for_status() |
|
return response |