Skip to content

Commit

Permalink
Merge pull request #193 from BelKed/cache-status
Browse files Browse the repository at this point in the history
Add `Cache.status` property
  • Loading branch information
FriedrichFroebel authored Aug 28, 2022
2 parents 1f81926 + cd51a3e commit 33179c3
Show file tree
Hide file tree
Showing 8 changed files with 887 additions and 16 deletions.
58 changes: 46 additions & 12 deletions pycaching/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from pycaching.log import Log
from pycaching.log import Type as LogType
from pycaching.trackable import Trackable
from pycaching.util import lazy_loaded, parse_date, rot13
from pycaching.util import deprecated, lazy_loaded, parse_date, rot13

# prefix _type() function to avoid collisions with cache type
_type = type
Expand Down Expand Up @@ -145,7 +145,6 @@ def _from_print_page(cls, geocaching, guid, soup):
content.find(class_="HalfRight AlignRight").p.text.strip().partition(":")[2].strip()
)
cache_info["location"] = Point.from_string(content.find(class_="LatLong").text.strip())
cache_info["state"] = None # not on the page
attributes = [
img["src"].split("/")[-1].partition(".")[0].rpartition("-")
for img in content.find(class_="sortables").find_all("img")
Expand All @@ -170,7 +169,7 @@ def _from_api_record(cls, geocaching, record):
wp=record["code"],
name=record["name"],
type=Type.from_number(record["geocacheType"]),
state=Status(record["cacheStatus"]) == Status.enabled,
status=Status(record["cacheStatus"]),
found=record["userFound"],
size=Size.from_number(record["containerType"]),
difficulty=record["difficulty"],
Expand Down Expand Up @@ -213,7 +212,7 @@ def __init__(self, geocaching, wp, **kwargs):
"type",
"location",
"original_location",
"state",
"status",
"found",
"size",
"difficulty",
Expand Down Expand Up @@ -405,18 +404,31 @@ def type(self, type):

@property
@lazy_loaded
@deprecated
def state(self):
"""The cache status.
:code:`True` if cache is enabled, :code:`False` if cache is disabled.
:code:`True` if cache is enabled, otherwise :code:`False`.
:type: :class:`bool`
"""
return self._state
return self._status == Status.enabled

@state.setter
def state(self, state):
self._state = bool(state)
@property
@lazy_loaded
def status(self):
"""The cache status (Enabled, Disabled, Archived, Unpublished, Locked).
:type: :class:`.cache.Status`
"""
return self._status

@status.setter
def status(self, status):
if isinstance(status, Status):
self._status = status
else:
raise errors.ValueError("Passed object is not Status instance.")

@property
@lazy_loaded
Expand Down Expand Up @@ -780,7 +792,7 @@ def load(self):

self.location = Point.from_string(root.find(id="uxLatLon").text)

self.state = root.find("ul", "OldWarning") is None
self.status = Status.from_cache_details(root)

log_image = root.find(id="ctl00_ContentBody_GeoNav_logTypeImage")
if log_image:
Expand Down Expand Up @@ -837,8 +849,11 @@ def load_quick(self):
"""Load basic cache details.
Use information from geocaching map tooltips. Therefore loading is very quick, but
the only loaded properties are: `name`, `type`, `state`, `size`, `difficulty`, `terrain`,
the only loaded properties are: `name`, `type`, `size`, `difficulty`, `terrain`,
`hidden`, `author`, `favorites` and `pm_only`.
It also loads `status`, but only for enabled caches. For other states, it can't be
completely determined (can't distinguish between archived and locked caches), so
lazy loading is used.
:raise .LoadError: If cache loading fails (probably because of not existing cache).
"""
Expand All @@ -853,7 +868,12 @@ def load_quick(self):
# prettify data
self.name = data["name"]
self.type = Type.from_string(data["type"]["text"])
self.state = data["available"]

# We can fill in status correctly only for enabled caches
# (locked caches are considered archived)
if data["available"]:
self.status = Status.enabled

self.size = Size.from_string(data["container"]["text"])
self.difficulty = data["difficulty"]["text"]
self.terrain = data["terrain"]["text"]
Expand Down Expand Up @@ -1412,3 +1432,17 @@ class Status(enum.IntEnum):
disabled = 1
archived = 2
unpublished = 3
locked = 4

@classmethod
def from_cache_details(cls, soup):
if soup.find(id="ctl00_ContentBody_disabledMessage"):
return Status.disabled
elif soup.find(id="ctl00_ContentBody_archivedMessage"):
return Status.archived
elif soup.find(id="unpublishedMessage") or soup.find(id="unpublishedReviewerNoteMessage"):
return Status.unpublished
elif soup.find(id="ctl00_ContentBody_lockedMessage"):
return Status.locked
else:
return Status.enabled
5 changes: 3 additions & 2 deletions pycaching/geocaching.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import requests
from bs4.element import Script

from pycaching.cache import Cache, Size
from pycaching.cache import Cache, Size, Status
from pycaching.errors import Error, LoginFailedException, NotLoggedInException, PMOnlyException, TooManyRequestsError
from pycaching.geo import Point, Rectangle
from pycaching.log import Log
Expand Down Expand Up @@ -289,7 +289,8 @@ def search(self, point, limit=float("inf")):
badge = row.find("svg", class_="badge")
c.found = "found" in str(badge) if badge is not None else False
c.favorites = row.find(attrs={"data-column": "FavoritePoint"}).text
c.state = not (row.get("class") and "disabled" in row.get("class"))
if not (row.get("class") and "disabled" in row.get("class")):
c.status = Status.enabled
c.pm_only = row.find("td", "pm-upsell") is not None

if c.pm_only:
Expand Down
168 changes: 168 additions & 0 deletions test/cassettes/cache_status_archived.json

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions test/cassettes/cache_status_disabled.json

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions test/cassettes/cache_status_enabled.json

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions test/cassettes/cache_status_enabled_load_quick.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{
"http_interactions": [
{
"recorded_at": "2022-08-25T19:41:18",
"request": {
"body": {
"encoding": "utf-8",
"string": ""
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Connection": [
"keep-alive"
],
"User-Agent": [
"python-requests/2.28.1"
]
},
"method": "GET",
"uri": "http://tiles01.geocaching.com/map.details?i=GC8CKQQ"
},
"response": {
"body": {
"encoding": null,
"string": ""
},
"headers": {
"Connection": [
"Keep-Alive"
],
"Content-Length": [
"0"
],
"Location": [
"https://tiles01.geocaching.com/map.details?i=GC8CKQQ"
],
"Server": [
"BigIP"
]
},
"status": {
"code": 302,
"message": "Moved Temporarily"
},
"url": "http://tiles01.geocaching.com/map.details?i=GC8CKQQ"
}
},
{
"recorded_at": "2022-08-25T19:41:19",
"request": {
"body": {
"encoding": "utf-8",
"string": ""
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Connection": [
"keep-alive"
],
"Cookie": [
"gspkauth=<AUTH COOKIE>"
],
"User-Agent": [
"python-requests/2.28.1"
]
},
"method": "GET",
"uri": "https://tiles01.geocaching.com/map.details?i=GC8CKQQ"
},
"response": {
"body": {
"encoding": "UTF-8",
"string": "{\"status\":\"success\",\"data\":[{\"name\":\"Kohinoorka\",\"gc\":\"GC8CKQQ\",\"g\":\"0a786802-1a56-4c25-b52e-51e064ada861\",\"available\":true,\"archived\":false,\"subrOnly\":false,\"li\":false,\"fp\":\"15\",\"difficulty\":{\"text\":2.0,\"value\":\"2\"},\"terrain\":{\"text\":2.0,\"value\":\"2\"},\"hidden\":\"8/24/2019\",\"container\":{\"text\":\"Small\",\"value\":\"small.gif\"},\"type\":{\"text\":\"Traditional Cache\",\"value\":2},\"owner\":{\"text\":\"haj\u00edci\",\"value\":\"d9b10451-f71d-4d5a-be1d-7864fa92fdf0\"}}]}"
},
"headers": {
"Cache-Control": [
"no-cache"
],
"Content-Length": [
"444"
],
"Content-Type": [
"application/json; charset=UTF-8"
],
"Date": [
"Thu, 25 Aug 2022 19:41:19 GMT"
],
"Expires": [
"-1"
],
"Pragma": [
"no-cache"
],
"Strict-Transport-Security": [
"max-age=31536000; includeSubDomains"
],
"Vary": [
"Accept-Encoding"
],
"X-Content-Type-Options": [
"nosniff"
],
"X-Frame-Options": [
"SAMEORIGIN"
],
"X-XSS-Protection": [
"1"
]
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://tiles01.geocaching.com/map.details?i=GC8CKQQ"
}
}
],
"recorded_with": "betamax/0.8.1"
}
168 changes: 168 additions & 0 deletions test/cassettes/cache_status_locked.json

Large diffs are not rendered by default.

39 changes: 37 additions & 2 deletions test/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import date
from unittest import mock

from pycaching.cache import Cache, Size, Type, Waypoint
from pycaching.cache import Cache, Size, Status, Type, Waypoint
from pycaching.errors import LoadError, PMOnlyException
from pycaching.errors import ValueError as PycachingValueError
from pycaching.geo import Point
Expand All @@ -24,7 +24,7 @@ def setUp(self):
name="Testing",
type=Type.traditional,
location=Point(),
state=True,
status=Status.enabled,
found=False,
size=Size.micro,
difficulty=1.5,
Expand Down Expand Up @@ -115,6 +115,12 @@ def test_waypoints(self):
def test_state(self):
self.assertEqual(self.c.state, True)

def test_status(self):
self.assertEqual(self.c.status, Status.enabled)

def test_status_name(self):
self.assertEqual(self.c.status.name, "enabled")

def test_found(self):
self.assertEqual(self.c.found, False)

Expand Down Expand Up @@ -463,3 +469,32 @@ def test_note(self):

def test_str(self):
self.assertEqual(str(self.w), "id")


class TestCacheStatus(LoggedInTest):
def test_cache_status(self):
with self.subTest("Enabled"):
cache = Cache(self.gc, "GC8CKQQ")
with self.recorder.use_cassette("cache_status_enabled"):
self.assertEqual(Status.enabled, cache.status)

with self.subTest("Disabled"):
cache = Cache(self.gc, "GC7Y77T")
with self.recorder.use_cassette("cache_status_disabled"):
self.assertEqual(Status.disabled, cache.status)

with self.subTest("Archived"):
cache = Cache(self.gc, "GC1PAR2")
with self.recorder.use_cassette("cache_status_archived"):
self.assertEqual(Status.archived, cache.status)

with self.subTest("Locked"):
cache = Cache(self.gc, "GC10")
with self.recorder.use_cassette("cache_status_locked"):
self.assertEqual(Status.locked, cache.status)

with self.subTest("Enabled > Quick load"):
cache = Cache(self.gc, "GC8CKQQ")
with self.recorder.use_cassette("cache_status_enabled_load_quick"):
cache.load_quick()
self.assertEqual(Status.enabled, cache.status)

0 comments on commit 33179c3

Please sign in to comment.