Compare commits

..

12 Commits

Author SHA1 Message Date
995b45464f Bump version: 0.23.4.11 → 0.23.4.12 2021-01-20 16:12:51 +02:00
ebe28c948a requirement update 2021-01-20 16:12:40 +02:00
34d7230faf Response serialization update to include status code. Json encoder and decoder updates 2021-01-20 16:07:19 +02:00
3235991cce User agent updates 2021-01-20 15:15:31 +02:00
3fba1d6b3d Bump version: 0.23.4.10 → 0.23.4.11 2021-01-18 16:35:35 +02:00
88c8d5a9a0 Unified all event setting 2021-01-18 16:35:31 +02:00
382749a8d8 Bump version: 0.23.4.9 → 0.23.4.10 2021-01-18 16:12:31 +02:00
0c877e315b as_dict updates 2021-01-18 16:12:26 +02:00
c95f642fee Citizen.as_dict rewrite 2021-01-18 16:00:29 +02:00
da0276f9a6 Removed unused CitizenMilitary.boosters property
Explicitly updated all Citizen*.as_dict properties
2021-01-18 15:22:53 +02:00
f89f91e969 Concurrency checks placed directly in Citizen class
Added concurrency checks also for citizen updates

Sentry: EREPUBLIK-BOT-77
2021-01-18 15:11:40 +02:00
56c2ca1b6e House renewal price check bugfix 2021-01-12 08:05:57 +02:00
8 changed files with 274 additions and 65 deletions

View File

@ -4,7 +4,7 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.23.4.9'
__version__ = '0.23.4.12'
from erepublik import classes, constants, utils
from erepublik.citizen import Citizen

View File

@ -13,43 +13,43 @@ __all__ = ['SlowRequests', 'CitizenAPI']
class SlowRequests(Session):
last_time: datetime.datetime
timeout: datetime.timedelta = datetime.timedelta(milliseconds=500)
uas: List[str] = [
_uas: List[str] = [
# Chrome
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36',
# FireFox
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0',
'Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',
]
debug: bool = False
def __init__(self, proxies: Dict[str, str] = None):
def __init__(self, proxies: Dict[str, str] = None, user_agent: str = None):
super().__init__()
if proxies:
self.proxies = proxies
if user_agent is None:
user_agent = random.choice(self._uas)
self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log"))
self.last_time = utils.now()
self.headers.update({
'User-Agent': random.choice(self.uas)
})
self.headers.update({'User-Agent': user_agent})
@property
def as_dict(self):
return dict(last_time=self.last_time, timeout=self.timeout, user_agent=self.headers['User-Agent'],
request_log_name=self.request_log_name, debug=self.debug)
return dict(last_time=self.last_time, timeout=self.timeout, cookies=self.cookies.get_dict(), debug=self.debug,
user_agent=self.headers['User-Agent'], request_log_name=self.request_log_name, proxies=self.proxies)
def request(self, method, url, *args, **kwargs):
self._slow_down_requests()
@ -120,6 +120,10 @@ class CitizenBaseAPI:
self._req = SlowRequests()
self.token = ""
@property
def as_dict(self):
return dict(url=self.url, request=self._req.as_dict, token=self.token)
def post(self, url: str, data=None, json=None, **kwargs) -> Response:
return self._req.post(url, data, json, **kwargs)

View File

@ -38,9 +38,9 @@ class BaseCitizen(access_points.CitizenAPI):
energy: classes.Energy = None
details: classes.Details = None
politics: classes.Politics = None
my_companies: classes.MyCompanies = None
reporter: classes.Reporter = None
stop_threads: Event = None
concurrency_available: Event = None
telegram: classes.TelegramReporter = None
r: Response = None
@ -57,8 +57,6 @@ class BaseCitizen(access_points.CitizenAPI):
self.my_companies = classes.MyCompanies(self)
self.reporter = classes.Reporter(self)
self.stop_threads = Event()
self.concurrency_available = Event()
self.concurrency_available.set()
self.telegram = classes.TelegramReporter(stop_event=self.stop_threads)
self.config.email = email
@ -470,7 +468,7 @@ class BaseCitizen(access_points.CitizenAPI):
self._req.debug = bool(debug)
def to_json(self, indent: bool = False) -> str:
return utils.json.dumps(self, cls=classes.MyJSONEncoder, indent=4 if indent else None)
return utils.json.dumps(self, cls=classes.ErepublikJSONEncoder, indent=4 if indent else None, sort_keys=True)
def get_countries_with_regions(self) -> Set[constants.Country]:
r_json = self._post_main_travel_data().json()
@ -484,13 +482,13 @@ class BaseCitizen(access_points.CitizenAPI):
filename = f"{self.__class__.__name__}__dump.json"
with open(filename, 'w') as f:
utils.json.dump(dict(config=self.config, cookies=self._req.cookies.get_dict(),
user_agent=self._req.headers.get("User-Agent")), f, cls=classes.MyJSONEncoder)
user_agent=self._req.headers.get("User-Agent")), f, cls=classes.ErepublikJSONEncoder)
self.write_log(f"Session saved to: '{filename}'")
@classmethod
def load_from_dump(cls, dump_name: str):
with open(dump_name) as f:
data = utils.json.load(f)
data = utils.json.load(f, object_hook=utils.json_decode_object_hook)
player = cls(data['config']['email'], "")
player._req.cookies.update(data['cookies'])
player._req.headers.update({"User-Agent": data['user_agent']})
@ -525,18 +523,29 @@ class BaseCitizen(access_points.CitizenAPI):
@property
def as_dict(self):
ret = self.__dict__.copy()
ret.pop('stop_threads', None)
ret.pop('_CitizenMilitary__last_war_update_data', None)
ret.update(_properties=dict(
ret = super().as_dict
ret.update(
name=self.name, __str__=self.__str__(),
ebs=dict(normal=self.eb_normal, double=self.eb_double, small=self.eb_small),
promos=self.promos, inventory=self._inventory.as_dict, ot_points=self.ot_points, food=self.food,
division=self.division, maveric=self.maverick, eday=self.eday, wheel_of_fortune=self.wheel_of_fortune,
debug=self.debug,
logged_in=self.logged_in, restricted_ip=self.restricted_ip, _properties=dict(
now=self.now, should_do_levelup=self.should_do_levelup, is_levelup_reachable=self.is_levelup_reachable,
max_time_till_full_ff=self.max_time_till_full_ff, is_levelup_close=self.is_levelup_close,
time_till_full_ff=self.time_till_full_ff, time_till_week_change=self.time_till_week_change,
next_wc_start=self.next_wc_start, next_reachable_energy=self.next_reachable_energy,
health_info=self.health_info))
health_info=self.health_info),
_last_full_update=self._last_full_update, _last_inventory_update=self._last_inventory_update,
config=self.config.as_dict, energy=self.energy.as_dict, details=self.details.as_dict,
politics=self.politics.as_dict, my_companies=self.my_companies.as_dict, reporter=self.reporter.as_dict,
telegram=self.telegram.as_dict, stop_threads=self.stop_threads.is_set(), response=self.r,
)
return ret
def set_locks(self):
self.stop_threads.set()
@property
def health_info(self):
ret = f"{self.energy.recovered}/{self.energy.limit} + {self.energy.recoverable}, " \
@ -769,7 +778,7 @@ class BaseCitizen(access_points.CitizenAPI):
:param msg: Message about the action
:param kwargs: Extra information regarding action
"""
kwargs = utils.json.loads(utils.json.dumps(kwargs or {}, cls=classes.MyJSONEncoder))
kwargs = utils.json.loads(utils.json.dumps(kwargs or {}, cls=classes.ErepublikJSONEncoder))
action = action[:32]
self.write_log(msg)
if self.reporter.allowed:
@ -980,7 +989,6 @@ class CitizenCompanies(BaseCitizen):
def work_as_manager_in_holding(self, holding: classes.Holding) -> Optional[Dict[str, Any]]:
return self._work_as_manager(holding)
@utils.wait_for_lock
def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]:
if self.restricted_ip:
return None
@ -1050,7 +1058,7 @@ class CitizenCompanies(BaseCitizen):
class CitizenEconomy(CitizenTravel):
def update_money(self, page: int = 0, currency: int = 62) -> Dict[str, Any]:
def update_money(self, page: int = 0, currency: int = 62):
"""
Gets monetary market offers to get exact amount of CC and Gold available
"""
@ -1060,7 +1068,6 @@ class CitizenEconomy(CitizenTravel):
resp_data = resp.json()
self.details.cc = float(resp_data.get("ecash").get("value"))
self.details.gold = float(resp_data.get("gold").get("value"))
return resp_data
def check_house_durability(self) -> Dict[int, datetime]:
ret = {}
@ -1082,7 +1089,7 @@ class CitizenEconomy(CitizenTravel):
local_cheapest = sorted(offers, key=lambda o: o.price)[0]
global_cheapest = self.get_market_offers("House", q)[f"q{q}"]
if global_cheapest.price + 200 < local_cheapest.price:
if global_cheapest.price + 2000 < local_cheapest.price:
if global_cheapest.price + 2000 < self.details.cc:
if self.travel_to_country(global_cheapest.country):
buy = self.buy_market_offer(global_cheapest, 1)
@ -1244,7 +1251,6 @@ class CitizenEconomy(CitizenTravel):
self._report_action("BOUGHT_PRODUCTS", json_ret.get('message'), kwargs=json_ret)
return json_ret
@utils.wait_for_lock
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if amount is None or amount > offer.amount:
amount = offer.amount
@ -1548,7 +1554,12 @@ class CitizenMilitary(CitizenTravel):
__last_war_update_data = None
active_fs: bool = False
boosters: Dict[int, Dict[int, int]] = {100: {}, 50: {}}
@property
def as_dict(self):
d = super().as_dict
d.update(active_fs=self.active_fs, all_battles=self.all_battles)
return d
def update_war_info(self):
if self.__last_war_update_data and self.__last_war_update_data.get('last_updated',
@ -1810,7 +1821,6 @@ class CitizenMilitary(CitizenTravel):
self.travel_to_residence()
break
@utils.wait_for_lock
def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None,
count: int = None, use_ebs: bool = False) -> Optional[int]:
"""Fight in a battle.
@ -1927,7 +1937,6 @@ class CitizenMilitary(CitizenTravel):
return hits, err, damage
@utils.wait_for_lock
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool,
count: int = 1) -> Optional[int]:
"""Deploy bombs in a battle for given side.
@ -2385,6 +2394,12 @@ class CitizenTasks(CitizenEconomy):
ot_points: int = 0
next_ot_time: datetime = None
@property
def as_dict(self):
d = super().as_dict
d.update(tg_contract=self.tg_contract, ot_points=self.ot_points, next_ot_time=self.next_ot_time)
return d
def eat(self):
""" Eat food """
self._eat("blue")
@ -2512,7 +2527,7 @@ class CitizenTasks(CitizenEconomy):
self.ot_points = ot.get("points", 0)
class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
class _Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
CitizenMedia, CitizenPolitics, CitizenSocial, CitizenMilitary, CitizenTasks):
def __init__(self, email: str = "", password: str = "", auto_login: bool = False):
super().__init__(email, password)
@ -2524,10 +2539,18 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
@classmethod
def load_from_dump(cls, dump_name: str = ""):
filename = dump_name if dump_name else f"{cls.__name__}__dump.json"
player: Citizen = super().load_from_dump(filename) # noqa
player: _Citizen = super().load_from_dump(filename) # noqa
player.login()
return player
def _eat(self, colour: str = "blue") -> Response:
resp = super()._eat(colour)
if not any([resp.json().get("units_consumed").values()]):
if colour == 'orange' and resp.json().get('food_remaining'):
self.eat()
return self._eat(colour)
return resp
def config_setup(self, **kwargs):
self.config.reset()
for key, value in kwargs.items():
@ -2885,3 +2908,161 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenLeaderBoard,
self.stop_threads.wait(90)
except: # noqa
self.report_error('Command central is broken')
class Citizen(_Citizen):
_concurrency_lock: Event
_update_lock: Event
_update_timeout: int = 10
_concurrency_timeout: int = 600
def __init__(self, *args, **kwargs):
self._concurrency_lock = Event()
self._concurrency_lock.set()
self._update_lock = Event()
self._update_lock.set()
super().__init__(*args, **kwargs)
def update_weekly_challenge(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_weekly_challenge()
finally:
self._update_lock.set()
def update_companies(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_companies()
finally:
self._update_lock.set()
def update_war_info(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_war_info()
finally:
self._update_lock.set()
def update_job_info(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_job_info()
finally:
self._update_lock.set()
def update_money(self, page: int = 0, currency: int = 62):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_money(page, currency)
finally:
self._update_lock.set()
def update_inventory(self):
if not self._update_lock.wait(self._update_timeout):
e = f'Update concurrency not freed in {self._update_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._update_lock.clear()
super().update_inventory()
finally:
self._update_lock.set()
def _work_as_manager(self, wam_holding: classes.Holding) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super()._work_as_manager(wam_holding)
finally:
self._concurrency_lock.set()
def fight(self, battle: classes.Battle, division: classes.BattleDivision, side: classes.BattleSide = None,
count: int = None, use_ebs: bool = False) -> Optional[int]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super().fight(battle, division, side, count, use_ebs)
finally:
self._concurrency_lock.set()
def deploy_bomb(self, battle: classes.Battle, division: classes.BattleDivision, bomb_id: int, inv_side: bool,
count: int = 1) -> Optional[int]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super().deploy_bomb(battle, division, bomb_id, inv_side, count)
finally:
self._concurrency_lock.set()
def buy_market_offer(self, offer: OfferItem, amount: int = None) -> Optional[Dict[str, Any]]:
if not self._concurrency_lock.wait(self._concurrency_timeout):
e = f'Concurrency not freed in {self._concurrency_timeout}sec!'
self.write_log(e)
if self.debug:
self.report_error(e)
return None
try:
self._concurrency_lock.clear()
return super().buy_market_offer(offer, amount)
finally:
self._concurrency_lock.set()
@property
def as_dict(self):
d = super().as_dict
d.update(locks=dict(concurrency_lock=self._concurrency_lock.is_set(), update_lock=self._update_lock.is_set(),
concurrency_timeout=self._concurrency_timeout, update_timeout=self._update_timeout))
return d
def set_locks(self):
super().set_locks()
self._concurrency_lock.set()
self._update_lock.set()

View File

@ -10,9 +10,8 @@ from requests import Response, Session, post
from . import constants, types, utils
__all__ = ['Battle', 'BattleDivision', 'BattleSide', 'Company', 'Config', 'Details', 'Energy', 'ErepublikException',
'ErepublikNetworkException', 'EnergyToFight',
'Holding', 'MyCompanies', 'MyJSONEncoder', 'OfferItem', 'Politics', 'Reporter', 'TelegramReporter',
'Inventory']
'ErepublikJSONEncoder', 'ErepublikNetworkException', 'EnergyToFight', 'Holding', 'Inventory', 'MyCompanies',
'OfferItem', 'Politics', 'Reporter', 'TelegramReporter', ]
class ErepublikException(Exception):
@ -599,10 +598,10 @@ class Reporter:
if self.__to_update:
for unreported_data in self.__to_update:
unreported_data.update(player_id=self.citizen_id, key=self.key)
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=MyJSONEncoder))
unreported_data = utils.json.loads(utils.json.dumps(unreported_data, cls=ErepublikJSONEncoder))
self._req.post(f"{self.url}/bot/update", json=unreported_data)
self.__to_update.clear()
data = utils.json.loads(utils.json.dumps(data, cls=MyJSONEncoder))
data = utils.json.loads(utils.json.dumps(data, cls=ErepublikJSONEncoder))
r = self._req.post(f"{self.url}/bot/update", json=data)
return r
@ -680,7 +679,7 @@ class Reporter:
return []
class MyJSONEncoder(utils.json.JSONEncoder):
class ErepublikJSONEncoder(utils.json.JSONEncoder):
def default(self, o):
from erepublik.citizen import Citizen
if isinstance(o, Decimal):
@ -694,7 +693,7 @@ class MyJSONEncoder(utils.json.JSONEncoder):
return dict(__type__='timedelta', days=o.days, seconds=o.seconds,
microseconds=o.microseconds, total_seconds=o.total_seconds())
elif isinstance(o, Response):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text)
return dict(headers=dict(o.__dict__['headers']), url=o.url, text=o.text, status_code=o.status_code)
elif hasattr(o, 'as_dict'):
return o.as_dict
elif isinstance(o, set):

View File

@ -12,6 +12,7 @@ from decimal import Decimal
from pathlib import Path
from typing import Any, Dict, List, Union
import pytz
import requests
from . import __version__, constants
@ -25,11 +26,8 @@ __all__ = ['VERSION', 'calculate_hit', 'caught_error', 'date_from_eday', 'eday_f
'get_air_hit_dmg_value', 'get_file', 'get_ground_hit_dmg_value', 'get_sleep_seconds', 'good_timedelta',
'interactive_sleep', 'json', 'localize_dt', 'localize_timestamp', 'normalize_html_json', 'now',
'process_error', 'process_warning', 'send_email', 'silent_sleep', 'slugify', 'write_file', 'write_request',
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock']
if not sys.version_info >= (3, 6):
raise AssertionError('This script requires Python version 3.6 and higher\n'
'But Your version is v{}.{}.{}'.format(*sys.version_info))
'write_interactive_log', 'write_silent_log', 'get_final_hit_dmg', 'wait_for_lock',
'json_decode_object_hook', 'json_load', 'json_loads']
VERSION: str = __version__
@ -234,8 +232,8 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Dict[str,
if isinstance(local_vars.get('citizen'), Citizen):
local_vars['citizen'] = repr(local_vars['citizen'])
from erepublik.classes import MyJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=MyJSONEncoder),
from erepublik.classes import ErepublikJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=ErepublikJSONEncoder),
"application/json")))
if isinstance(player, Citizen):
files.append(('file', ("instance.json", player.to_json(indent=True), "application/json")))
@ -380,12 +378,6 @@ def get_final_hit_dmg(base_dmg: Union[Decimal, float, str], rang: int,
return Decimal(dmg)
# def _clear_up_battle_memory(battle):
# del battle.invader._battle, battle.defender._battle
# for div_id, division in battle.div.items():
# del division._battle
def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2)
@ -409,3 +401,36 @@ def wait_for_lock(function):
return ret
return wrapper
def json_decode_object_hook(
o: Union[Dict[str, Any], List[Any], int, float, str]
) -> Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]:
""" Convert classes.ErepublikJSONEncoder datetime, date and timedelta to their python objects
:param o:
:return: Union[Dict[str, Any], List[Any], int, float, str, datetime.date, datetime.datetime, datetime.timedelta]
"""
if o.get('__type__'):
_type = o.get('__type__')
if _type == 'datetime':
dt = datetime.datetime.strptime(f"{o['date']} {o['time']}", "%Y-%m-%d %H:%M:%S")
if o.get('tzinfo'):
dt = pytz.timezone(o['tzinfo']).localize(dt)
return dt
elif _type == 'date':
dt = datetime.datetime.strptime(f"{o['date']}", "%Y-%m-%d")
return dt.date()
elif _type == 'timedelta':
return datetime.timedelta(seconds=o['total_seconds'])
return o
def json_load(f, **kwargs):
kwargs.update(object_hook=json_decode_object_hook)
return json.load(f, **kwargs)
def json_loads(s: str, **kwargs):
kwargs.update(object_hook=json_decode_object_hook)
return json.loads(s, **kwargs)

View File

@ -1,19 +1,19 @@
bump2version==1.0.1
coverage==5.3.1
edx-sphinx-theme==1.6.0
edx-sphinx-theme==1.6.1
flake8==3.8.4
ipython>=7.19.0
isort==5.7.0
pip==20.3.3
pre-commit==2.9.3
pur==5.3.0
PyInstaller==4.1
PyInstaller==4.2
PySocks==1.7.1
pytest==6.2.1
pytz>=2020.5
requests>=2.25.1
responses==0.12.1
setuptools==51.1.2
setuptools==51.3.3
Sphinx==3.4.3
twine==3.3.0
wheel==0.36.2

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.23.4.9
current_version = 0.23.4.12
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?

View File

@ -50,6 +50,6 @@ setup(
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.23.4.9',
version='0.23.4.12',
zip_safe=False,
)