commit 79068e6f378c24c30b2e74fe6d53efa3a506db67 Author: norohind <60548839+norohind@users.noreply.github.com> Date: Wed Dec 1 01:24:11 2021 +0300 WIP diff --git a/config.py b/config.py new file mode 100644 index 0000000..6e79bc9 --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +import os + +postgres_username = os.getenv('DB_USERNAME') +postgres_password = os.getenv('DB_PASSWORD') +postgres_hostname = os.getenv('DB_HOSTNAME') +postgres_port = os.getenv('DB_PORT') +postgres_database_name = os.getenv('DB_DATABASE') diff --git a/get_livery_content.py b/get_livery_content.py new file mode 100644 index 0000000..b8e7d8d --- /dev/null +++ b/get_livery_content.py @@ -0,0 +1,36 @@ +from model import model +import requests +import os +import json +import datetime + +model.open_model() + + +def get_onlinestore_data() -> list[dict]: + items = list() + for item in requests.get("https://api.zaonce.net/3.0/store/product").json(): + items.append(dict(name=item["title"], cur_price=item["current_price"], orig_price=item["original_price"], + image=item["image"])) + print(f"Got {len(items)} items") + return items + + +def history_insert() -> None: + for file in sorted(os.listdir('history')): + with open('history\\' + file, 'r') as open_file: + content = json.load(open_file) + if 'image' not in content[0].keys(): + for item in content: + item['image'] = '' + + timestamp = datetime.datetime.utcfromtimestamp(int(file.split('.')[0])).strftime('%Y-%m-%dT%H:%M:%SZ') + for item in content: + item['timestamp'] = timestamp + + model.insert_livery_timestamp(content) + + +model.insert_livery(get_onlinestore_data()) + +model.close_model() diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..29aefb6 --- /dev/null +++ b/model/__init__.py @@ -0,0 +1,4 @@ +from model.postgres_model import PostgresModel + +model = PostgresModel() + diff --git a/model/abstract_model.py b/model/abstract_model.py new file mode 100644 index 0000000..5707ad2 --- /dev/null +++ b/model/abstract_model.py @@ -0,0 +1,25 @@ +import abc + + +class AbstractModel(abc.ABC): + + @abc.abstractmethod + def open_model(self) -> None: + raise NotImplemented + + @abc.abstractmethod + def close_model(self) -> None: + raise NotImplemented + + @abc.abstractmethod + def get_activity_changes(self, platform: str, leaderboard_type: str, limit: int, low_timestamp, high_timestamp)\ + -> list: + raise NotImplemented + + @abc.abstractmethod + def insert_livery(self, livery_list: list) -> None: + raise NotImplemented + + @abc.abstractmethod + def get_diff_action_id(self, action_id: int) -> list: + raise NotImplemented diff --git a/model/postgres_model.py b/model/postgres_model.py new file mode 100644 index 0000000..3626890 --- /dev/null +++ b/model/postgres_model.py @@ -0,0 +1,145 @@ +import json +import typing + +import psycopg2.extensions +import psycopg2.extras +import config + +from . import postgres_sql_requests +from .abstract_model import AbstractModel + + +def errors_catcher(func: callable) -> callable: + def decorated(*args, **kwargs): + try: + result = func(*args, **kwargs) + + except psycopg2.InterfaceError: + args[0].open_model() + result = func(*args, **kwargs) + + return result + + return decorated + + +class PostgresModel(AbstractModel): + db: psycopg2.extensions.connection + + def open_model(self): + self.db: psycopg2.extensions.connection = psycopg2.connect( + user=config.postgres_username, + password=config.postgres_password, + host=config.postgres_hostname, + port=config.postgres_port, + database=config.postgres_database_name, + cursor_factory=psycopg2.extras.DictCursor) + + print(f'Connected to {self.db.dsn}') + + with self.db: + with self.db.cursor() as cursor: + cursor.execute(postgres_sql_requests.schema_create) # schema creation + + def close_model(self): + self.db.close() + print(f'Connection to {self.db.dsn} closed successfully') + + @errors_catcher + def get_activity_changes(self, platform: str, leaderboard_type: str, limit: int, low_timestamp, high_timestamp)\ + -> list: + + with self.db: + with self.db.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + cursor.execute(postgres_sql_requests.select_activity_pretty_names, { + 'LB_type': utils.LeaderboardTypes(leaderboard_type.lower()).value, + 'platform': utils.Platform(platform.upper()).value, + 'limit': limit, + 'high_timestamp': high_timestamp, + 'low_timestamp': low_timestamp + }) + + result: list = cursor.fetchall() + + return result + + @errors_catcher + def insert_livery(self, livery_list: list) -> None: + """ + Takes livery content as list insert to DB + + :param livery_list: list from get_onlinestore_data + :return: + """ + + action_id: int # not last, current that we will use + + with self.db.cursor() as cursor: + cursor.execute(postgres_sql_requests.select_last_action_id) + action_id_fetch_one: typing.Union[None, dict[str, int]] = cursor.fetchone() + + if action_id_fetch_one is None: + # i.e. first launch + action_id = 1 # yep, not 0 + + else: + action_id = action_id_fetch_one['action_id'] + 1 + + # Patch for additional values + for squad in livery_list: + squad.update({'action_id': action_id}) + + with self.db: + with self.db.cursor() as cursor: + cursor.executemany( + postgres_sql_requests.insert_leader_board, + livery_list) + + @errors_catcher + def insert_livery_timestamp(self, livery_list: list) -> None: + """ + Takes livery content with timestamps as list insert to DB. Helpful for historical data + + :param livery_list: list from get_onlinestore_data + :return: + """ + + action_id: int # not last, current that we will use + + with self.db.cursor() as cursor: + cursor.execute(postgres_sql_requests.select_last_action_id) + action_id_fetch_one: typing.Union[None, dict[str, int]] = cursor.fetchone() + + if action_id_fetch_one is None: + # i.e. first launch + action_id = 1 # yep, not 0 + + else: + action_id = action_id_fetch_one['action_id'] + 1 + + # Patch for additional values + for squad in livery_list: + squad.update({'action_id': action_id}) + + with self.db: + with self.db.cursor() as cursor: + cursor.executemany( + postgres_sql_requests.insert_leader_board_timestamp, + livery_list) + + @errors_catcher + def get_diff_action_id(self, action_id: int) -> list: + """ + Takes action_id and returns which squadrons has been changed in leaderboard as in action_id and + experience they got in compassion to action_id - 1 for the same leaderboard and platform + + :param action_id: + :return: + """ + + with self.db: + with self.db.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor: + cursor.execute(postgres_sql_requests.select_diff_by_action_id, {'action_id': action_id}) + result: list = cursor.fetchall() + + return result diff --git a/model/postgres_sql_requests.py b/model/postgres_sql_requests.py new file mode 100644 index 0000000..80ce4bb --- /dev/null +++ b/model/postgres_sql_requests.py @@ -0,0 +1,69 @@ +schema_create = """create table if not exists livery ( +action_id integer, +name text, +cur_price integer, +orig_price integer, +image_url text, +timestamp timestamp default timezone('utc', now())); + +--create index if not exists idx_action_id_0 on squads_stats_states (action_id); +--create index if not exists idx_platform_leaderboard_type_1 on squads_stats_states(platform, leaderboard_type); +""" + +select_last_action_id = """select action_id +from livery +order by action_id desc +limit 1;""" + +insert_leader_board = """insert into livery (action_id, name, cur_price, orig_price, image_url) +values +(%(action_id)s, %(name)s, %(cur_price)s, %(orig_price)s, %(image)s);""" + +insert_leader_board_timestamp = """insert into livery (action_id, name, cur_price, orig_price, image_url, timestamp) +values +(%(action_id)s, %(name)s, %(cur_price)s, %(orig_price)s, %(image)s, %(timestamp)s);""" + +select_activity_pretty_names = """select +sum_score::bigint as "TotalExperience", +to_char(timestamp, 'YYYY-MM-DD HH24:MI:SS') as "Timestamp UTC", +action_id::bigint as "ActionId", +sum_score_old::bigint as "TotalExperienceOld", +(sum_score - sum_score_old)::bigint as "Diff" +from + ( + select + sum_score, + min(timestamp) as timestamp, + action_id, + lag (sum_score, 1) over (order by sum_score) sum_score_old + from ( + select sum(cur_price) as sum_score, min(timestamp) as timestamp, action_id + from livery + group by action_id + ) as foo + group by sum_score, action_id + order by timestamp desc + + ) as foo1 +where (sum_score - sum_score_old) <> 0 +limit %(limit)s;""" + +select_diff_by_action_id = """select + new_livery.name as new_name, + old_livery.name as old_name, + new_livery.orig_price as new_orig_price, + new_livery.cur_price as new_cur_price, + old_livery.orig_price as old_orig_price, + old_livery.cur_price as old_cur_price, + new_livery.cur_price - old_livery.cur_price +from ( + select * + from livery + where action_id = %(action_id)s) new_livery +full join + ( + select * + from livery + where action_id = %(action_id)s - 1 + ) old_livery +on new_livery.name = old_livery.name;""" \ No newline at end of file