Skip to content

Commit

Permalink
Fixed #12 Added abstration for pins
Browse files Browse the repository at this point in the history
  • Loading branch information
TheFriendlyCoder committed Apr 14, 2018
1 parent f4169de commit 1dd0a02
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 12 deletions.
6 changes: 3 additions & 3 deletions .pylintrc
Expand Up @@ -325,16 +325,16 @@ single-line-if-stmt=no
[SIMILARITIES]

# Ignore comments when computing similarities.
ignore-comments=yes
ignore-comments=no

# Ignore docstrings when computing similarities.
ignore-docstrings=yes
ignore-docstrings=no

# Ignore imports when computing similarities.
ignore-imports=no

# Minimum lines number of a similarity.
min-similarity-lines=4
min-similarity-lines=8


[BASIC]
Expand Down
7 changes: 7 additions & 0 deletions docs/friendlypins.pin.rst
@@ -0,0 +1,7 @@
friendlypins.pin module
=======================

.. automodule:: friendlypins.pin
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/friendlypins.rst
Expand Up @@ -9,6 +9,7 @@ Submodules
friendlypins.api
friendlypins.board
friendlypins.headers
friendlypins.pin
friendlypins.user

Module contents
Expand Down
53 changes: 50 additions & 3 deletions src/friendlypins/board.py
@@ -1,26 +1,34 @@
"""Primitives for interacting with Pinterest boards"""
import logging
import json
import requests
from friendlypins.headers import Headers
from friendlypins.pin import Pin


class Board(object):
"""Abstraction around a Pinterest board
:param dict data: Raw Pinterest API data describing the board
:param str root_url: URL of the Pinterest REST API
:param str token: Authentication token for the REST API
"""

def __init__(self, data):
def __init__(self, data, root_url, token):
self._log = logging.getLogger(__name__)
self._data = data
self._root_url = root_url
self._token = token

def __str__(self):
"""String representation of this object, for debugging purposes
"""String representation of this board, for debugging purposes
:rtype: :class:`str`
"""
return json.dumps(dict(self._data), sort_keys=True, indent=4)

def __repr__(self):
"""Object representation in string format
"""Board representation in string format
:rtype: :class:`str`
"""
Expand Down Expand Up @@ -50,5 +58,44 @@ def url(self):
"""
return self._data['url']

@property
def all_pins(self):
"""Gets a list of all pins from this board
NOTE: This process may take a long time to complete and require
a lot of memory for boards that contain large numbers of pins
:rtype: :class:`list` of :class:`friendlypins.pin.Pin`
"""
temp_url = '{0}/boards/{1}/pins/'.format(self._root_url, self.unique_id)
temp_url += "?access_token={0}".format(self._token)
temp_url += "&limit=100"
temp_url += "&fields=id,image,metadata,link,url,original_link,media"
response = requests.get(temp_url)
response.raise_for_status()
retval = []
header = Headers(response.headers)
self._log.debug("Pins query response header %s", header)

while True:
raw = response.json()
assert 'data' in raw

for cur_item in raw['data']:
retval.append(Pin(cur_item))

self._log.debug("Raw keys are %s", raw.keys())
self._log.debug("Paged info is %s", raw['page'])
if not raw['page']['cursor']:
break

paged_url = temp_url + "&cursor={0}".format(raw['page']['cursor'])
response = requests.get(paged_url)
response.raise_for_status()
header = Headers(response.headers)
self._log.debug("Pins query response header %s", header)

return retval

if __name__ == "__main__":
pass
4 changes: 2 additions & 2 deletions src/friendlypins/headers.py
Expand Up @@ -12,14 +12,14 @@ def __init__(self, data):
self._data = data

def __str__(self):
"""String representation of this object, for debugging purposes
"""String representation of this header, for debugging purposes
:rtype: :class:`str`
"""
return json.dumps(dict(self._data), sort_keys=True, indent=4)

def __repr__(self):
"""Object representation in string format
"""Header representation in string format
:rtype: :class:`str`
"""
Expand Down
73 changes: 73 additions & 0 deletions src/friendlypins/pin.py
@@ -0,0 +1,73 @@
"""Primitives for operating on Pinterest pins"""
import logging
import json


class Pin(object):
"""Abstraction around a Pinterest pin
:param dict data: Raw Pinterest API data describing a pin"""

def __init__(self, data):
self._log = logging.getLogger(__name__)
self._data = data

def __str__(self):
"""String representation of this pin, for debugging purposes
:rtype: :class:`str`
"""
return json.dumps(dict(self._data), sort_keys=True, indent=4)

def __repr__(self):
"""Pin representation in string format
:rtype: :class:`str`
"""
return "<{0} ({1})>".format(self.__class__.__name__, self.note)

@property
def url(self):
"""Web address for the UI associated with the pin
:rtype: :class:`str`
"""
return self._data['url']

@property
def note(self):
"""Descriptive text associated with pin
:rtype: :class:`str`
"""
return self._data['note']

@property
def link(self):
"""Source URL containing the original data for the pin
:rtype: :class:`str`
"""
return self._data['link']

@property
def unique_id(self):
"""The unique identifier associated with this pin
:rtype: :class:`int`
"""
return int(self._data['id'])

@property
def media_type(self):
"""Gets descriptor for the type of data stored in the pin's link
:rtype: :class:`str`
"""
if 'media' not in self._data:
return None

return self._data['media']['type']

if __name__ == "__main__":
pass
6 changes: 3 additions & 3 deletions src/friendlypins/user.py
Expand Up @@ -21,14 +21,14 @@ def __init__(self, data, root_url, token):
self._token = token

def __str__(self):
"""String representation of this object, for debugging purposes
"""String representation of this user, for debugging purposes
:rtype: :class:`str`
"""
return json.dumps(dict(self._data), sort_keys=True, indent=4)

def __repr__(self):
"""Object representation in string format
"""User representation in string format
:rtype: :class:`str`
"""
Expand Down Expand Up @@ -88,7 +88,7 @@ def boards(self):

retval = []
for cur_item in raw['data']:
retval.append(Board(cur_item))
retval.append(Board(cur_item, self._root_url, self._token))
return retval

if __name__ == "__main__":
Expand Down
42 changes: 41 additions & 1 deletion unit_tests/test_board.py
@@ -1,4 +1,5 @@
import pytest
import mock
from friendlypins.board import Board

def test_board_properties():
Expand All @@ -11,10 +12,49 @@ def test_board_properties():
"url": expected_url
}

obj = Board(sample_data)
obj = Board(sample_data, 'http://pinterest_url', '1234abcd')
assert obj.unique_id == expected_id
assert obj.name == expected_name
assert obj.url == expected_url

def test_get_all_pins():
data = {
'id': '987654321'
}
api_url = "https://pinterest_url/v1"
token = "1234abcd"
obj = Board(data, api_url, token)

expected_id = 1234
expected_url = "https://www.pinterest.ca/MyName/MyPin/"
expected_note = "My Pin descriptive text"
expected_link ="http://www.mysite.com/target"
expected_mediatype = "image"
expected_data = {
"data": [{
"id": str(expected_id),
"url": expected_url,
"note": expected_note,
"link": expected_link,
"media": {
"type": expected_mediatype
}
}],
"page": {
"cursor": None
}
}

with mock.patch("friendlypins.board.requests") as mock_requests:
mock_response = mock.MagicMock()
mock_response.json.return_value = expected_data
mock_requests.get.return_value = mock_response
result = obj.all_pins

assert len(result) == 1
assert expected_url == result[0].url
assert expected_note == result[0].note
assert expected_id == result[0].unique_id
assert expected_mediatype == result[0].media_type
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])
51 changes: 51 additions & 0 deletions unit_tests/test_pin.py
@@ -0,0 +1,51 @@
import pytest
import mock
from friendlypins.pin import Pin

def test_pin_properties():
expected_id = 1234
expected_note = "Here's my note"
expected_url = "https://www.pinterest.ca/MyName/MyPin/"
expected_link = "http://www.google.ca"
expected_media_type = "video"
sample_data = {
"id": str(expected_id),
"note": expected_note,
"link": expected_link,
"url": expected_url,
"media": {
"type": expected_media_type
}
}

obj = Pin(sample_data)

assert obj.unique_id == expected_id
assert obj.note == expected_note
assert obj.url == expected_url
assert obj.link == expected_link
assert obj.media_type == expected_media_type

def test_pin_missing_media_type():
expected_id = 1234
expected_note = "Here's my note"
expected_url = "https://www.pinterest.ca/MyName/MyPin/"
expected_link = "http://www.google.ca"
expected_media_type = "video"
sample_data = {
"id": str(expected_id),
"note": expected_note,
"link": expected_link,
"url": expected_url,
}

obj = Pin(sample_data)

assert obj.unique_id == expected_id
assert obj.note == expected_note
assert obj.url == expected_url
assert obj.link == expected_link
assert obj.media_type is None

if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])

0 comments on commit 1dd0a02

Please sign in to comment.