Skip to content

Commit

Permalink
Merge 3e59f9f into 1786e88
Browse files Browse the repository at this point in the history
  • Loading branch information
TheFriendlyCoder committed Jul 26, 2020
2 parents 1786e88 + 3e59f9f commit d7dcb20
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 15 deletions.
18 changes: 18 additions & 0 deletions src/friendlypins/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
from friendlypins.user import User
from friendlypins.board import Board
from friendlypins.section import Section
from friendlypins.pin import Pin
from friendlypins.utils.rest_io import RestIO

Expand All @@ -19,6 +20,23 @@ def __init__(self, personal_access_token):
self._log = logging.getLogger(__name__)
self._io = RestIO(personal_access_token)

def get_section_by_id(self, section_id):
"""Locates a specific Pinterest board subsection given it's internal
identifier
NOTE: this API assumes that the ID provided references a valid section.
If it does not, the object returned will be invalid and any attempts
to access data from the board will result in an error.
Args:
section_id (int):
the unique identifier for the section
Returns:
Section: reference to the Pinterest section
"""
return Section(Section.default_url(section_id), self._io)

def get_board_by_id(self, board_id):
"""Locates a specific Pinterest board given it's internal identifier
Expand Down
15 changes: 14 additions & 1 deletion src/friendlypins/board.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Primitives for interacting with Pinterest boards"""
from datetime import datetime
from dateutil import tz
from friendlypins.pin import Pin
from friendlypins.utils.base_object import BaseObject
from friendlypins.pin import Pin
from friendlypins.section import Section


class Board(BaseObject):
Expand Down Expand Up @@ -105,6 +106,18 @@ def pins(self):
for cur_item in cur_page['data']:
yield Pin.from_json(cur_item, self._io)

@property
def sections(self):
"""list (Section): subsections on board used for sorting pins"""
self._log.debug("Loading board sections for board %s...",
self._relative_url)
path = "board/{0}/sections".format(self.unique_id)
for cur_page in self._io.get_pages(path):
assert "data" in cur_page

for cur_item in cur_page["data"]:
yield Section.from_json(cur_item, self._io)

def delete(self):
"""Removes this board and all pins attached to it"""
self._log.debug('Deleting board %s', self._relative_url)
Expand Down
44 changes: 44 additions & 0 deletions src/friendlypins/section.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Primitives for manipulation Pinterest board subsections"""
from friendlypins.utils.base_object import BaseObject
from friendlypins.pin import Pin


class Section(BaseObject):
"""Abstraction around a Pinterest board subsection"""

@staticmethod
def default_url(unique_id):
"""Generates a URL for the REST API endpoint for a section with a given identification number
Args:
unique_id (int): unique ID for the board
Returns:
str: URL for the API endpoint
"""
return "board/sections/{0}".format(unique_id)

@staticmethod
def default_fields():
"""list (str): list of fields we pre-populate when loading section data"""
return list()

@property
def title(self):
"""str: descriptive text associated with this subsection"""
return self._data["title"]

@property
def pins(self):
"""list (Pin): pins associated with this subsection"""
self._log.debug("Loading pins for board subsection %s...",
self._relative_url)
properties = {
"fields": ','.join(Pin.default_fields())
}
path = "{0}/pins".format(self._relative_url)
for cur_page in self._io.get_pages(path, properties):
assert "data" in cur_page

for cur_item in cur_page["data"]:
yield Pin.from_json(cur_item, self._io)
15 changes: 11 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import json
from copy import deepcopy
from pathlib import Path
import pytest

Expand Down Expand Up @@ -94,7 +95,7 @@ def vcr_config(request):
if request.config.getoption("--record-mode") == "none":
retval["record_mode"] = "once"

return retval
return deepcopy(retval)


@pytest.fixture(scope="function")
Expand All @@ -104,19 +105,25 @@ def test_env(request):
str(REFERECE_DATA.name))

key_file = request.config.getoption("--key-file")
key = "DUMMY"
key = None
if key_file:
if not os.path.exists(key_file):
raise Exception("API authentication token file not found")

with open(key_file) as fh:
key = fh.read().strip()
elif request.config.getoption("--record-mode") == "rewrite":
else:
# If no explicit auth token can be found, lets try loading a default one
if DEFAULT_KEY_FILE.exists():
key = DEFAULT_KEY_FILE.read_text().strip()

if request.config.getoption("--record-mode") == "rewrite" and not key:
raise Exception("Rewrite mode can only work with a valid auth key. "
"Use --key-file to run the tests.")

retval = json.loads(REFERECE_DATA.read_text())
retval["key"] = key
retval["key"] = key or "DUMMY" # If we have no auth token, provide a default value

yield retval


Expand Down
14 changes: 10 additions & 4 deletions tests/reference_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"description": "Board used for unit testing",
"privacy": "public",
"pins": [
485262928605818444
485262928605818444,
485262928605821260
]
},
"test_pin": {
Expand All @@ -28,7 +29,12 @@
"thumbnail_width": 735,
"thumbnail_height": 907
},
"section_pins": [
485262928605821260
]
"test_section": {
"id": 1234,
"title": "Sample Section",
"pins": [
485262928605821260
]
}

}
10 changes: 4 additions & 6 deletions tests/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,18 @@ def test_board_properties(test_env):
assert isinstance(board.num_followers, int)
assert isinstance(board.num_collaborators, int)
assert isinstance(board.num_pins, int)
all_pins = test_env["test_board"]["pins"] + test_env["section_pins"]
assert board.num_pins == len(all_pins)
assert board.num_pins == len(test_env["test_board"]["pins"])


@pytest.mark.vcr()
def test_get_pins(test_env):
obj = API(test_env["key"])
board = obj.get_board_by_id(test_env["test_board"]["id"])
pins = list(board.pins)
all_pins = test_env["test_board"]["pins"] + test_env["section_pins"]
assert len(pins) == len(all_pins)
assert len(pins) == len(test_env["test_board"]["pins"])
for cur_pin in pins:
assert cur_pin.unique_id in all_pins
all_pins.remove(cur_pin.unique_id)
assert cur_pin.unique_id in test_env["test_board"]["pins"]
test_env["test_board"]["pins"].remove(cur_pin.unique_id)


def test_cache_refresh():
Expand Down
30 changes: 30 additions & 0 deletions tests/test_board_section.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest
from friendlypins.api import API
from friendlypins.section import Section
from friendlypins.pin import Pin


@pytest.mark.vcr()
def test_board_sections(test_env):
obj = API(test_env["key"])
board = obj.get_board_by_id(test_env["test_board"]["id"])
sections = list(board.sections)

assert len(sections) == 1
assert isinstance(sections[0], Section)
assert sections[0].id == test_env["test_section"]["id"]
assert sections[0].title == test_env["test_section"]["title"]


@pytest.mark.vcr()
def test_section_pins(test_env):
obj = API(test_env["key"])
section = obj.get_section_by_id(test_env["test_section"]["id"])
assert section is not None
pins = list(section.pins)

assert len(pins) == len(test_env["test_section"]["pins"])
for cur_pin in pins:
assert isinstance(cur_pin, Pin)
assert cur_pin.unique_id in test_env["test_section"]["pins"]
test_env["test_section"]["pins"].remove(cur_pin.unique_id)

0 comments on commit d7dcb20

Please sign in to comment.