This repository has been archived by the owner on Feb 21, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add archive manager makers and managers (#68)
* Add archive manager makers and managers - Manager makers available as "items_archive" and "sections_archive" attributes of API object. - Managers returned by methods "for_item", "for_section", "for_project" of these makers. - Items and sections available as elements, returned by methods items() or sections() of those managers. * Add mypy to type hints * Fix type hints for archive manager - Don't subclass ArchiveManager from Manager. Subclassing doesn't provide any extra goodies, and is only confusing (mainly because we don't follow the "implicit protocol" of Manager where state_name and object_type must be defined) - Rename internal property object_type to element_type to keep it consistent with elements * Add typing to setup.py (for python2.7 compatibility) * Add tests for items and sections archive manager * tox.ini: replace py.test with pytest The name py.test is deprecated * Add a CHANGELOG record
- Loading branch information
Showing
8 changed files
with
271 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[mypy] | ||
python_version = 2.7 | ||
follow_imports = silent | ||
scripts_are_modules = true | ||
|
||
# We had to ignore missing imports, because of third-party libraries installed | ||
# inside the virtualenv, and apparently there's no easy way for mypy to respect | ||
# packages inside the virtualenv. That's the option pre-commit-config runs with | ||
# by default, but we add it here as well for the sake of uniformity of the | ||
# output | ||
ignore_missing_imports = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
""" | ||
Managers to get the list of archived items and sections. | ||
Manager makers available as "items_archive" and "sections_archive" attributes of | ||
API object. | ||
Usage example (for items). | ||
```python | ||
# Create an API object | ||
import todoist | ||
api = todoist.TodoistAPI(...) | ||
# Get project ID (take inbox) | ||
project_id = api.user.get()['inbox_project'] | ||
# Initiate ItemsArchiveManager | ||
archive = api.items_archive.for_project(project_id) | ||
# Iterate over the list of completed items for the archive | ||
for item in archive.items(): | ||
print(item["date_completed"], item["content"]) | ||
``` | ||
""" | ||
from typing import TYPE_CHECKING, Dict, Iterator, Optional | ||
|
||
from ..models import Item, Model, Section | ||
|
||
if TYPE_CHECKING: | ||
from ..api import TodoistAPI | ||
|
||
|
||
class ArchiveManager(object): | ||
|
||
object_model = Model | ||
|
||
def __init__(self, api, element_type): | ||
# type: (TodoistAPI, str) -> None | ||
assert element_type in {"sections", "items"} | ||
self.api = api | ||
self.element_type = element_type | ||
|
||
def next_page(self, cursor): | ||
# type: (Optional[str]) -> Dict | ||
"""Return response for the next page of the archive.""" | ||
resp = self.api.session.get( | ||
self._next_url(), | ||
params=self._next_query_params(cursor), | ||
headers=self._request_headers(), | ||
) | ||
resp.raise_for_status() | ||
return resp.json() | ||
|
||
def _next_url(self): | ||
return "{0}/sync/{1}/archive/{2}".format( | ||
self.api.api_endpoint, self.api.api_version, self.element_type | ||
) | ||
|
||
def _next_query_params(self, cursor): | ||
# type: (Optional[str]) -> Dict | ||
ret = {} | ||
if cursor: | ||
ret["cursor"] = cursor | ||
return ret | ||
|
||
def _request_headers(self): | ||
return {"Authorization": "Bearer {}".format(self.api.token)} | ||
|
||
def _iterate(self): | ||
has_more = True | ||
cursor = None | ||
|
||
while True: | ||
if not has_more: | ||
break | ||
|
||
resp = self.next_page(cursor) | ||
|
||
elements = [self._make_element(data) for data in resp[self.element_type]] | ||
has_more = resp["has_more"] | ||
cursor = resp.get("next_cursor") | ||
for el in elements: | ||
yield el | ||
|
||
def _make_element(self, data): | ||
return self.object_model(data, self.api) | ||
|
||
|
||
class SectionsArchiveManagerMaker(object): | ||
def __init__(self, api): | ||
self.api = api | ||
|
||
def __repr__(self): | ||
return "{}()".format(self.__class__.__name__) | ||
|
||
def for_project(self, project_id): | ||
"""Get manager to iterate over all archived sections for project.""" | ||
return SectionsArchiveManager(api=self.api, project_id=project_id) | ||
|
||
|
||
class SectionsArchiveManager(ArchiveManager): | ||
|
||
object_model = Section | ||
|
||
def __init__(self, api, project_id): | ||
super(SectionsArchiveManager, self).__init__(api, "sections") | ||
self.project_id = project_id | ||
|
||
def __repr__(self): | ||
return "SectionsArchiveManager(project_id={})".format(self.project_id) | ||
|
||
def sections(self): | ||
# type: () -> Iterator[Section] | ||
"""Iterate over all archived sections.""" | ||
for obj in self._iterate(): | ||
yield obj | ||
|
||
def _next_query_params(self, cursor): | ||
ret = super(SectionsArchiveManager, self)._next_query_params(cursor) | ||
ret["project_id"] = self.project_id | ||
return ret | ||
|
||
|
||
class ItemsArchiveManagerMaker(object): | ||
def __init__(self, api): | ||
self.api = api | ||
|
||
def __repr__(self): | ||
return "{}()".format(self.__class__.__name__) | ||
|
||
def for_project(self, project_id): | ||
"""Get manager to iterate over all top-level archived items for project.""" | ||
return ItemsArchiveManager(api=self.api, project_id=project_id) | ||
|
||
def for_section(self, section_id): | ||
"""Get manager to iterate over all top-level archived items for section.""" | ||
return ItemsArchiveManager(api=self.api, section_id=section_id) | ||
|
||
def for_parent(self, parent_id): | ||
"""Get manager to iterate over all archived sub-tasks for an item.""" | ||
return ItemsArchiveManager(api=self.api, parent_id=parent_id) | ||
|
||
|
||
class ItemsArchiveManager(ArchiveManager): | ||
|
||
object_model = Item | ||
|
||
def __init__(self, api, project_id=None, section_id=None, parent_id=None): | ||
super(ItemsArchiveManager, self).__init__(api, "items") | ||
assert sum([bool(project_id), bool(section_id), bool(parent_id)]) == 1 | ||
self.project_id = project_id | ||
self.section_id = section_id | ||
self.parent_id = parent_id | ||
|
||
def __repr__(self): | ||
k, v = self._key_value() | ||
return "ItemsArchiveManager({}={})".format(k, v) | ||
|
||
def items(self): | ||
# type: () -> Iterator[Item] | ||
"""Iterate over all archived items.""" | ||
for obj in self._iterate(): | ||
yield obj | ||
|
||
def _next_query_params(self, cursor): | ||
ret = super(ItemsArchiveManager, self)._next_query_params(cursor) | ||
k, v = self._key_value() | ||
ret[k] = v | ||
return ret | ||
|
||
def _key_value(self): | ||
if self.project_id: | ||
return "project_id", self.project_id | ||
elif self.section_id: | ||
return "section_id", self.section_id | ||
else: # if self.parent_id: | ||
return "parent_id", self.parent_id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,4 @@ | |
envlist = py27,py37 | ||
[testenv] | ||
deps = pytest | ||
commands = py.test {posargs} | ||
commands = pytest {posargs} |