From 5ec99200aa3623af4f14f1e73fbad57528d3ac54 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Wed, 20 Nov 2019 13:12:50 -0700 Subject: [PATCH 1/2] Added support for multiple houses --- tests/resources/README.md | 1 + .../character/tibiacom_multiple_houses.txt | 87 +++++++++++++++++++ tests/tests_character.py | 15 ++++ tibiapy/character.py | 38 +++++--- tibiapy/utils.py | 19 ++++ 5 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 tests/resources/character/tibiacom_multiple_houses.txt diff --git a/tests/resources/README.md b/tests/resources/README.md index 0e6b9c77..74c8e102 100644 --- a/tests/resources/README.md +++ b/tests/resources/README.md @@ -11,6 +11,7 @@ deaths by summons, players, assisted deaths, etcetera. - [tibiacom_not_found.txt](character/tibiacom_not_found.txt) - A character not found page. - [tibiacom_title_badges.txt](character/tibiacom_title_badges.txt) - A character with unlocked titles and badges. - [tibiacom_no_badges_selected.txt](character/tibiacom_no_badges_selected.txt) - A character with no selected badges. +- [tibiacom_multiple_houses.txt](character/tibiacom_multiple_houses.txt) - A character with two houses. - [tibiadata.json](character/tibiadata.json) - A character on TibiaData, also showing Pvp deaths with assists. - [tibiadata_deleted.json](character/tibiadata_deleted.json) - A character scheduled for deletion on TibiaData. - [tibiadata_not_found.json](character/tibiadata_not_found.json) - The response of a character not found on TibiaData. diff --git a/tests/resources/character/tibiacom_multiple_houses.txt b/tests/resources/character/tibiacom_multiple_houses.txt new file mode 100644 index 00000000..0bedf8f4 --- /dev/null +++ b/tests/resources/character/tibiacom_multiple_houses.txt @@ -0,0 +1,87 @@ +
+
Character Information
Name:Sayuri Nowan
Title:Silencer (16 titles unlocked)
Sex:female
Vocation:Elder Druid
Level:535
Achievement Points:425
World:Menera
Residence:Thais
Married To:Comandante Dako
House: Cormaya 10 (Edron) is paid until Dec 06 2019
House: Old Heritage Estate (Rathleton) is paid until Dec 04 2019
Guild Membership:Baiabaia of the Baia Baia
Last Login:Nov 19 2019, 23:08:03 CET
Comment:TibiaLottery-848B3
Account Status:Premium Account
+

Account Achievements
Tibia AchievementTibia AchievementExemplary Citizen
Tibia AchievementTibia AchievementRuthless
Tibia AchievementTibia AchievementTrue LightbearerTibia Secret Achievement
Tibia AchievementTibia AchievementTurncoatTibia Secret Achievement


Account Information
Loyalty Title:Warrior of Tibia
Created:Mar 21 2006, 08:07:09 CET


Characters
NameWorldStatus 
1. Holy HenriettaAstera
+ + +
+
2. Jello HousekeeperAstera
+ + +
+
3. MalunitaAstera
+ + +
+
4. Miss WeasleyAstera
+ + +
+
5. Sa yuMitigera
+ + +
+
6. Santa SayuGarnera
+ + +
+
7. SashuriZunera
+ + +
+
8. SashurinaTournament - restricted Store
+ + +
+
9. SashuritaTournament - regular
+ + +
+
10. Sayu con HatJonera
+ + +
+
11. Sayu en GladeraGladeradeleted
+ + +
+
12. Sayu GaheFunera
+ + +
+
13. Sayu in RookAstera
+ + +
+
14. Sayu riSecura
+ + +
+
15. SayunabraInabradeleted
+ + +
+
16. Sayuri Nowan (Main Character)Menera
+ + +
+
17. SayurinaMenera
+ + +
+
18. Sayurita en GentebritaGentebra
+ + +
+
+

+ + + +
Search Character
+ + +
Name:
+
+
+ + \ No newline at end of file diff --git a/tests/tests_character.py b/tests/tests_character.py index 8bd541c0..6b93b4f9 100644 --- a/tests/tests_character.py +++ b/tests/tests_character.py @@ -15,6 +15,7 @@ FILE_CHARACTER_DEATHS_COMPLEX = "character/tibiacom_deaths_complex.txt" FILE_CHARACTER_TITLE_BADGES = "character/tibiacom_title_badges.txt" FILE_CHARACTER_NO_BADGES_SELECTED = "character/tibiacom_no_badges_selected.txt" +FILE_CHARACTER_MULTIPLE_HOUSES = "character/tibiacom_multiple_houses.txt" FILE_CHARACTER_TIBIADATA = "character/tibiadata.json" FILE_CHARACTER_TIBIADATA_UNHIDDEN = "character/tibiadata_unhidden.json" @@ -128,6 +129,20 @@ def test_character_from_content_no_selected_badges(self): self.assertEqual(0, len(char.account_badges)) self.assertEqual(0, len(char.former_names)) + def test_character_from_content_multiple_houses(self): + """Testing parsing a character with multiple houses.""" + content = self._load_resource(FILE_CHARACTER_MULTIPLE_HOUSES) + char = Character.from_content(content) + self.assertEqual("Sayuri Nowan", char.name) + self.assertEqual(2, len(char.houses)) + self.assertEqual(char.house.name, char.houses[0].name) + first_house = char.houses[0] + second_house = char.houses[1] + self.assertEqual("Cormaya 10", first_house.name) + self.assertEqual("Old Heritage Estate", second_house.name) + self.assertEqual("Edron", first_house.town) + self.assertEqual("Rathleton", second_house.town) + def test_character_from_content_unrelated(self): """Testing parsing an unrelated tibia.com section""" content = self._load_resource(self.FILE_UNRELATED_SECTION) diff --git a/tibiapy/character.py b/tibiapy/character.py index 14cd0cf0..1e94f17e 100644 --- a/tibiapy/character.py +++ b/tibiapy/character.py @@ -1,6 +1,7 @@ import datetime import re import urllib.parse +import warnings from collections import OrderedDict from typing import List, Optional @@ -12,7 +13,7 @@ from tibiapy.guild import Guild from tibiapy.house import CharacterHouse from tibiapy.utils import parse_json, parse_tibia_date, parse_tibia_datetime, parse_tibiacom_content, \ - parse_tibiadata_date, parse_tibiadata_datetime, try_datetime, try_enum + parse_tibiadata_date, parse_tibiadata_datetime, try_datetime, try_enum, deprecated # Extracts the scheduled deletion date of a character.""" deleted_regexp = re.compile(r'([^,]+), will be deleted at (.*)') @@ -159,8 +160,8 @@ class Character(abc.BaseCharacter): The current hometown of the character. married_to: :class:`str`, optional The name of the character's spouse. - house: :class:`CharacterHouse`, optional - The house currently owned by the character. + houses: :class:`list` of :class:`CharacterHouse` + The houses currently owned by the character. guild_membership: :class:`GuildMembership`, optional The guild the character is a member of. last_login: :class:`datetime.datetime`, optional @@ -195,7 +196,7 @@ class Character(abc.BaseCharacter): "former_world", "residence", "married_to", - "house", + "houses", "guild_membership", "last_login", "account_status", @@ -222,7 +223,7 @@ def __init__(self, name=None, world=None, vocation=None, level=0, sex=None, **kw self.former_world = kwargs.get("former_world") # type: Optional[str] self.residence = kwargs.get("residence") # type: str self.married_to = kwargs.get("married_to") # type: Optional[str] - self.house = kwargs.get("house") # type: Optional[CharacterHouse] + self.houses = kwargs.get("house", []) # type: List[CharacterHouse] self.guild_membership = kwargs.get("guild_membership") # type: Optional[GuildMembership] self.last_login = try_datetime(kwargs.get("last_login")) self.account_status = try_enum(AccountStatus, kwargs.get("account_status")) @@ -265,6 +266,18 @@ def hidden(self): def married_to_url(self): """:class:`str`: The URL to the husband/spouse information page on Tibia.com, if applicable.""" return self.get_url(self.married_to) if self.married_to else None + + @property + @deprecated("houses") + def house(self): + """:class:`CharacterHouse`: The house currently owned by the character. + + .. deprecated:: 2.4.0 + Characters may have more than one house. This will only return the first if any. Use :attr:`houses` instead. + + Only kept for backwards compatibility and may be removed in the next major update. + """ + return self.houses[0] if self.houses else None # endregion # region Public methods @@ -350,8 +363,8 @@ def from_tibiadata(cls, content): if "house" in character_data: house = character_data["house"] paid_until_date = parse_tibiadata_date(house["paid"]) - char.house = CharacterHouse(house["houseid"], house["name"], char.world, house["town"], char.name, - paid_until_date) + char.houses.append(CharacterHouse(house["houseid"], house["name"], char.world, house["town"], char.name, + paid_until_date)) char.comment = character_data.get("comment") if len(character_data["last_login"]) > 0: char.last_login = parse_tibiadata_datetime(character_data["last_login"][0]) @@ -460,7 +473,7 @@ def _parse_character_information(self, rows): """ int_rows = ["level", "achievement_points"] char = {} - house = {} + houses = [] for row in rows: cols_raw = row.find_all('td') cols = [ele.text.strip() for ele in cols_raw] @@ -475,8 +488,8 @@ def _parse_character_information(self, rows): house_link = cols_raw[1].find('a') url = urllib.parse.urlparse(house_link["href"]) query = urllib.parse.parse_qs(url.query) - house = {"id": int(query["houseid"][0]), "name": house_link.text.strip(), - "town": query["town"][0], "paid_until": paid_until_date} + houses.append({"id": int(query["houseid"][0]), "name": house_link.text.strip(), + "town": query["town"][0], "paid_until": paid_until_date}) continue if field in int_rows: value = int(value) @@ -520,9 +533,8 @@ def _parse_character_information(self, rows): # This means that there is a attribute in the character's information table that does not have a # corresponding class attribute. pass - if house: - self.house = CharacterHouse(house["id"], house["name"], self.world, house["town"], self.name, - house["paid_until"]) + self.houses = [CharacterHouse(h["id"], h["name"], self.world, h["town"], self.name, h["paid_until"]) + for h in houses] def _parse_deaths(self, rows): """ diff --git a/tibiapy/utils.py b/tibiapy/utils.py index 4198f93a..488dad94 100644 --- a/tibiapy/utils.py +++ b/tibiapy/utils.py @@ -1,6 +1,8 @@ import datetime +import functools import json import re +import warnings from typing import Optional, Type, TypeVar, Union import bs4 @@ -394,3 +396,20 @@ def _recursive_strip(value): if isinstance(value, str): return value.strip() return value + + +def deprecated(instead=None): + def actual_decorator(func): + @functools.wraps(func) + def decorated(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + if instead: + fmt = "{0.__name__} is deprecated, use {1} instead." + else: + fmt = '{0.__name__} is deprecated.' + + warnings.warn(fmt.format(func, instead), stacklevel=3, category=DeprecationWarning) + warnings.simplefilter('default', DeprecationWarning) + return func(*args, **kwargs) + return decorated + return actual_decorator From 5c79f903cbf95bd86be200c5e532e58cb2fece1b Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Wed, 20 Nov 2019 13:32:33 -0700 Subject: [PATCH 2/2] Updated version and changelog --- CHANGELOG.rst | 6 ++++++ tibiapy/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 656e8869..b79791ac 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,12 @@ Changelog Due to this library relying on external content, older versions are not guaranteed to work. Try to always use the latest version. +.. _v2.4.0: +2.4.0 (2019-11-20) +================== +- Added support for multiple houses per character. Accessible on ``Character.houses`` field. +- ``Character.house`` is now deprecated. It will contain the character's first house or ``None``. + .. _v2.3.4: 2.3.4 (2019-11-14) ================== diff --git a/tibiapy/__init__.py b/tibiapy/__init__.py index a6427044..e8ff04f4 100644 --- a/tibiapy/__init__.py +++ b/tibiapy/__init__.py @@ -13,7 +13,7 @@ from tibiapy.creature import * from tibiapy.client import * -__version__ = '2.3.4' +__version__ = '2.4.0' from logging import NullHandler