Skip to content

Commit e1cb2f7

Browse files
authored
Tumblr method for fetching primary blog ID (used as user ID) (#79)
Closes #77 New Tumblr method fetches the primary blog id. This is useful for identifying the Tumblr user in their posts, comments, likes, etc. when fetching other verticals
1 parent 2c3dfb9 commit e1cb2f7

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

src/pardner/services/tumblr.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from typing import Any, Iterable, Optional, override
23

34
from pardner.exceptions import UnsupportedRequestException
@@ -12,6 +13,7 @@ class TumblrTransferService(BaseTransferService):
1213
See API documentation: https://www.tumblr.com/docs/en/api/v2
1314
"""
1415

16+
primary_blog_id: str | None = None
1517
_authorization_url = 'https://www.tumblr.com/oauth2/authorize'
1618
_base_url = 'https://api.tumblr.com/v2/'
1719
_token_url = 'https://api.tumblr.com/v2/oauth2/token'
@@ -23,7 +25,20 @@ def __init__(
2325
redirect_uri: str,
2426
state: Optional[str] = None,
2527
verticals: set[Vertical] = set(),
28+
primary_blog_id: str | None = None,
2629
) -> None:
30+
"""
31+
Creates an instance of ``TumblrTransferService``.
32+
33+
:param client_id: Client identifier given by the OAuth provider upon registration.
34+
:param client_secret: The ``client_secret`` paired to the ``client_id``.
35+
:param redirect_uri: The registered callback URI.
36+
:param state: State string used to prevent CSRF and identify flow.
37+
:param verticals: The :class:`Vertical`s for which the transfer service has
38+
appropriate scope to fetch.
39+
:param primary_blog_id: Optionally, the primary blog ID of the data owner (the
40+
user being authorized).
41+
"""
2742
super().__init__(
2843
service_name='Tumblr',
2944
client_id=client_id,
@@ -33,6 +48,7 @@ def __init__(
3348
supported_verticals={SocialPostingVertical},
3449
verticals=verticals,
3550
)
51+
self.primary_blog_id = primary_blog_id
3652

3753
@override
3854
def scope_for_verticals(self, verticals: Iterable[Vertical]) -> set[str]:
@@ -48,6 +64,40 @@ def fetch_token(
4864
) -> dict[str, Any]:
4965
return super().fetch_token(code, authorization_response, include_client_id)
5066

67+
def fetch_primary_blog_id(self) -> str:
68+
"""
69+
Fetches the primary blog ID from the data owner, which will be used as the
70+
``data_owner_id`` in the vertical model objects. If the ``primary_blog_id``
71+
attribute on this class is already set, the method does not make a new request.
72+
73+
Note: "PrimaryBlogId" is not a vertical. This is used purely as a unique
74+
identifier for the user, since Tumblr doesn't provide one by default.
75+
76+
:returns: the primary blog id.
77+
78+
:raises: :class:`ValueError`: if the primary blog ID could not be extracted from
79+
the response.
80+
"""
81+
if self.primary_blog_id:
82+
return self.primary_blog_id
83+
user_info = self._get_resource_from_path('user/info').json().get('response', {})
84+
for blog_info in user_info.get('user', {}).get('blogs', []):
85+
if (
86+
isinstance(blog_info, dict)
87+
and blog_info.get('primary')
88+
and 'uuid' in blog_info
89+
and isinstance(blog_info['uuid'], str)
90+
):
91+
self.primary_blog_id = blog_info['uuid']
92+
return blog_info['uuid']
93+
94+
raise ValueError(
95+
'Failed to fetch primary blog id. Either manually set the _primary_blog_id '
96+
'attribute or verify all the client credentials '
97+
'and permissions are correct. Response from Tumblr: '
98+
f'{json.dumps(user_info, indent=2)}'
99+
)
100+
51101
def fetch_social_posting_vertical(
52102
self,
53103
request_params: dict[str, Any] = {},

tests/test_transfer_services/test_tumblr.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,35 @@ def test_fetch_social_posting_vertical(mocker, tumblr_transfer_service):
4040
oauth2_session_get.call_args.args[1]
4141
== 'https://api.tumblr.com/v2/user/dashboard'
4242
)
43+
44+
45+
def test_fetch_primary_blog_id_already_set(tumblr_transfer_service):
46+
tumblr_transfer_service.primary_blog_id = 'existing-blog-id'
47+
assert tumblr_transfer_service.fetch_primary_blog_id() == 'existing-blog-id'
48+
49+
50+
def test_fetch_primary_blog_id_success(mocker, tumblr_transfer_service):
51+
response_object = mocker.MagicMock()
52+
response_object.json.return_value = {
53+
'response': {
54+
'user': {
55+
'blogs': [
56+
{'primary': False, 'uuid': 'secondary-blog-id'},
57+
{'primary': True, 'uuid': 'primary-blog-id', 'name': 'my-blog'},
58+
{'primary': False, 'uuid': 'another-secondary-id'},
59+
]
60+
}
61+
}
62+
}
63+
oauth2_session_get = mock_oauth2_session_get(mocker, response_object)
64+
65+
assert tumblr_transfer_service.fetch_primary_blog_id() == 'primary-blog-id'
66+
assert tumblr_transfer_service.primary_blog_id == 'primary-blog-id'
67+
assert oauth2_session_get.call_args.args[1] == 'https://api.tumblr.com/v2/user/info'
68+
69+
70+
def test_fetch_primary_blog_id_raises_exception(
71+
tumblr_transfer_service, mock_oauth2_session_get_bad_response
72+
):
73+
with pytest.raises(HTTPError):
74+
tumblr_transfer_service.fetch_primary_blog_id()

0 commit comments

Comments
 (0)