def attributes(self, attributes): if not isinstance(attributes, dict): raise errors.ValueError("Attribues is not dict.") self._attributes = {} for name, allowed in attributes.items(): name = name.strip().lower() if name in self._possible_attributes: self._attributes[name] = allowed else: logging.warning("Unknown attribute {}, ignoring.".format(name))
def from_string(cls, name): """Return a cache size from its human readable name. :raise .ValueError: If cache size cannot be determined. """ name = name.strip().lower() try: return cls(name) except ValueError as e: raise errors.ValueError("Unknown cache size '{}'.".format(name)) from e
def post_log(self, log): """Post a log for this cache. :param .Log log: Previously created :class:`Log` filled with data. """ if not log.text: raise errors.ValueError("Log text is empty") valid_types, hidden_inputs, date_format = self._load_log_page() if log.type.value not in valid_types: raise errors.ValueError("The Cache does not accept this type of log") # assemble post data post = hidden_inputs formatted_date = format_date(log.visited, date_format) post["ctl00$ContentBody$LogBookPanel1$btnSubmitLog"] = "Submit Log Entry" post["ctl00$ContentBody$LogBookPanel1$ddLogType"] = valid_types[log.type.value] post["ctl00$ContentBody$LogBookPanel1$uxDateVisited"] = formatted_date post["ctl00$ContentBody$LogBookPanel1$uxLogInfo"] = log.text self.geocaching._request(self._log_page_url, method="POST", data=post)
def post_log(self, log): """Post a log for this cache. :param .Log log: Previously created :class:`Log` filled with data. """ if not log.text: raise errors.ValueError("Log text is empty") valid_types, hidden_inputs = self._load_log_page() if log.type.value not in valid_types: raise errors.ValueError("The cache does not accept this type of log") # assemble post data post = hidden_inputs post["LogTypeId"] = log.type.value post["LogDate"] = log.visited.strftime("%Y-%m-%d") post["LogText"] = log.text self.geocaching._request(self._get_log_page_url(), method="POST", data=post) self.found_status = log
def parse_date(raw): """Return a parsed date.""" raw = raw.strip() patterns = ("%Y-%m-%d", "%Y/%m/%d", "%m/%d/%Y", "%d/%m/%Y", "%d.%m.%Y", "%d/%b/%Y", "%d.%b.%Y", "%b/%d/%Y", "%d %b %y") for pattern in patterns: try: return datetime.strptime(raw, pattern).date() except ValueError: pass raise errors.ValueError("Unknown date format - '{}'.".format(raw))
def from_filename(cls, filename): """Return a log type from its image filename.""" if filename == "1003": # 2 different IDs for publish_listing return cls.publish_listing elif filename == "1001": # 2 different IDs for visit return cls.visit try: return cls(filename) except ValueError as e: raise errors.ValueError("Unknown log type '{}'.".format(filename)) from e
def from_number(cls, number): """Return a cache size from its numeric id. :raise .ValueError: If cache size cannot be determined. """ number = int(number) number_mapping = { 2: cls.micro, 8: cls.small, 3: cls.regular, 4: cls.large, 6: cls.other } try: return number_mapping[number] except KeyError as e: raise errors.ValueError("Unknown cache size numeric id '{}'.".format(number)) from e
def from_string(cls, string): """Parses the coords in Degree Minutes format. Expecting: S 36 51.918 E 174 46.725 or N 6 52.861 W174 43.327 Spaces do not matter. Neither does having the degree symbol. Returns a geopy.Point instance.""" # Make it uppercase for consistency coords = string.upper().replace("N", " ").replace("S", " ") \ .replace("E", " ").replace("W", " ").replace("+", " ") try: m = re.match( r"\s*(-?\s*\d+)\D+(\d+[\.,]\d+)\D?\s*(-?\s*\d+)\D+(\d+[\.,]\d+)", coords) latDeg, latMin, lonDeg, lonMin = [ float(part.replace(" ", "").replace(",", ".")) for part in m.groups() ] if "S" in string: latDeg *= -1 if "W" in string: lonDeg *= -1 return Point(Util.to_decimal(latDeg, latMin), Util.to_decimal(lonDeg, lonMin)) except AttributeError: pass # fallback try: return super(Point, cls).from_string(string) except ValueError as e: # wrap possible error to pycaching.errors.ValueError raise errors.ValueError() from e
def from_string(cls, name): """Return a cache type from its human readable name. :raise .ValueError: If cache type cannot be determined. """ name = name.replace(" Geocache", "") # with space! name = name.replace(" Cache", "") # with space! name = name.lower().strip() name_mapping = { "traditional": cls.traditional, "multi-cache": cls.multicache, "mystery": cls.mystery, "unknown": cls.unknown, "letterbox hybrid": cls.letterbox, "event": cls.event, "mega-event": cls.mega_event, "giga-event": cls.giga_event, "earthcache": cls.earthcache, "cito": cls.cito, "cache in trash out event": cls.cache_in_trash_out_event, "webcam": cls.webcam, "virtual": cls.virtual, "wherigo": cls.wherigo, "lost and found event": cls.community_celebration, "project ape": cls.project_ape, "geocaching hq": cls.geocaching_hq, "groundspeak hq": cls.geocaching_hq, "gps adventures exhibit": cls.gps_adventures_exhibit, "groundspeak block party": cls.groundspeak_block_party, "locationless (reverse)": cls.locationless, "geocaching hq celebration": cls.hq_celebration, "community celebration event": cls.community_celebration } try: return name_mapping[name] except KeyError as e: raise errors.ValueError( "Unknown cache type '{}'.".format(name)) from e
def _get_log_counts_from_cache_details(soup): """Return a dictionary of all log counts found in the page representation, based on the cache details page. :param bs4.BeautifulSoup soup: Parsed html document of the cache details page. """ lbl_find_counts = soup.find("span", {"id": "ctl00_ContentBody_lblFindCounts"}) log_totals = lbl_find_counts.find("p", "LogTotals") # Text gives numbers separated by a lot of spaces, splitting retrieves the numbers. # The values might contain thousand separators, which we have to remove before converting # them to real numbers. values = log_totals.text.split() values = [ int(value.replace(",", "").replace(".", "")) for value in values ] # Retrieve the list of image sources. images = log_totals.find_all("img") types = [] for image in images: type = image["src"] # "../images/logtypes/2.png" type = type.split("/")[-1].split(".")[0] # "2" type = LogType.from_filename(type) types.append(type) # Prevent possible wrong assignments when the list sizes differ for some unknown reason. if not len(values) == len(types): raise errors.ValueError( "Different list sizes getting log counts: {} types and {} counts." .format(len(types), len(values))) # Finally create the mapping. log_counts = dict(zip(types, values)) return log_counts
def terrain(self, terrain): terrain = float(terrain) if terrain < 1 or terrain > 5 or terrain * 10 % 5 != 0: # X.0 or X.5 raise errors.ValueError("Terrain must be from 1 to 5 and divisible by 0.5.") self._terrain = terrain
def difficulty(self, difficulty): difficulty = float(difficulty) if difficulty < 1 or difficulty > 5 or difficulty * 10 % 5 != 0: # X.0 or X.5 raise errors.ValueError("Difficulty must be from 1 to 5 and divisible by 0.5.") self._difficulty = difficulty
def geocaching(self, geocaching): if not hasattr(geocaching, "_request"): raise errors.ValueError("Passed object (type: '{}')" "doesn't contain '_request' method.".format(_type(geocaching))) self._geocaching = geocaching
def wp(self, wp): wp = str(wp).upper().strip() if not wp.startswith("GC"): raise errors.ValueError("Waypoint '{}' doesn't start with 'GC'.".format(wp)) self._wp = wp
def guid(self, guid): guid = guid.strip() guid_regex = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" if not re.match(guid_regex, guid): raise errors.ValueError("GUID not well formatted: {}".format(guid)) self._guid = guid
def tid(self, tid): tid = str(tid).upper().strip() if not tid.startswith("TB"): raise errors.ValueError( "Trackable ID '{}' doesn't start with 'TB'.".format(tid)) self._tid = tid