Skip to content

Commit 46a61aa

Browse files
authored
Added protobuf stats. (#329)
* Added protobuf stats. * Update ChartData.ts * Added tests for proto change * Fixed tests. Added mocks * fix bug. * fix imports * Moved everything to use one class for pathing * Address comments Fix import error * Fixed issues with tests * imports * Fixed all imports! * fixed bug with tests.
1 parent 8a24e3b commit 46a61aa

30 files changed

Lines changed: 399 additions & 177 deletions

backend/blueprints/api.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import os
33
from functools import wraps
44

5-
from carball.analysis.utils import proto_manager
65
from flask import render_template, url_for, redirect, request, jsonify, send_from_directory, Blueprint, current_app, \
76
Response
87
from google.protobuf.json_format import MessageToJson
@@ -12,9 +11,8 @@
1211
from backend.blueprints.spa_api.service_layers.utils import with_session
1312
from backend.database.objects import Game, PlayerGame
1413
from backend.database.wrapper.query_filter_builder import QueryFilterBuilder
15-
from backend.utils.cloud_handler import download_proto
1614
from backend.utils.psyonix_api_handler import get_rank, tier_div_to_string
17-
from backend.database.utils.file_manager import parsed_directory, get_proto_path, get_replay_path
15+
from backend.utils.file_manager import FileManager
1816

1917
bp = Blueprint('apiv1', __name__, url_prefix='/api/v1')
2018

@@ -153,52 +151,44 @@ def api_v1_get_ranks():
153151
def api_v1_get_stats(session=None):
154152
# TODO: stats?
155153
ct = session.query(Game).count()
156-
dct = len([f for f in os.listdir(current_app.config[parsed_directory]) if f.endswith('pts')])
154+
dct = len([f for f in os.listdir(FileManager.get_default_parse_folder()) if f.endswith('pts')])
157155
return jsonify({'db_count': ct, 'count': dct})
158156

159157

160158
@bp.route('/replay/<id_>')
161159
@key_required
162160
def api_v1_get_replay_info(id_):
163-
pickle_path = get_proto_path(current_app, id_)
164-
if not os.path.isfile(pickle_path):
165-
g = download_proto(id_)
166-
else:
167-
try:
168-
with open(pickle_path, 'rb') as f:
169-
g = proto_manager.ProtobufManager.read_proto_out_from_file(f)
170-
except Exception as e:
171-
return jsonify({'error': 'Error opening game: ' + str(e)})
161+
proto = FileManager.get_proto(id_)
172162

173163
response = Response(
174-
response=convert_proto_to_json(g),
164+
response=convert_proto_to_json(proto),
175165
status=200,
176166
mimetype='application/json'
177167
)
178168

179169
return response
180170

181171
game = session.query(Game).filter(Game.hash == id_).first()
182-
data = {'datetime': g.datetime, 'map': g.map, 'mmrs': game.mmrs, 'ranks': game.ranks, 'name': g.name,
183-
'hash': game.hash, 'version': g.replay_version, 'id': g.id, 'frames': len(g.frames)}
184-
data = player_interface(data, g, game.mmrs, game.ranks)
185-
data = team_interface(data, g)
186-
data = score_interface(data, g)
187-
data = goals_interface(data, g)
172+
data = {'datetime': proto.datetime, 'map': proto.map, 'mmrs': game.mmrs, 'ranks': game.ranks, 'name': proto.name,
173+
'hash': game.hash, 'version': proto.replay_version, 'id': proto.id, 'frames': len(proto.frames)}
174+
data = player_interface(data, proto, game.mmrs, game.ranks)
175+
data = team_interface(data, proto)
176+
data = score_interface(data, proto)
177+
data = goals_interface(data, proto)
188178
return jsonify(data)
189179

190180

191181
@bp.route('/parsed/list')
192182
@key_required
193183
def api_v1_list_parsed_replays():
194-
fs = os.listdir(current_app.config[parsed_directory])
184+
fs = os.listdir(FileManager.get_default_parse_folder())
195185
return jsonify(fs)
196186

197187

198188
@bp.route('/parsed/<path:fn>')
199189
@key_required
200190
def api_v1_download_parsed(fn):
201-
return send_from_directory(current_app.config[parsed_directory], fn, as_attachment=True)
191+
return send_from_directory(FileManager.get_default_parse_folder(), fn, as_attachment=True)
202192

203193

204194
@bp.route('/rank/<id_>')

backend/blueprints/spa_api/service_layers/homepage/twitch.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ def __init__(self, streams: List[Stream]):
3939

4040
@classmethod
4141
def create(cls):
42-
streams = cls.get_streams()
42+
try:
43+
streams = cls.get_streams()
44+
except KeyError as e:
45+
return cls([])
4346
objs = []
4447
for stream in streams:
4548
objs.append(Stream(stream['user_name'], game='Rocket League', title=stream['title'],
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from typing import List
22

3-
from flask import current_app
4-
5-
from backend.database.wrapper.chart.player_chart_metadata import player_stats_metadata
3+
from backend.database.wrapper.chart.player_chart_metadata import player_group_stats_metadata
64
from backend.database.wrapper.chart.stat_point import OutputChartData
75
from backend.database.wrapper.chart.team_chart_metadata import team_stats_metadata
86
from backend.database.wrapper.stats.chart_stats_wrapper import ChartStatsWrapper
7+
from backend.database.wrapper.stats.shared_stats_wrapper import SharedStatsWrapper
98

109
wrapper = ChartStatsWrapper()
1110

@@ -14,11 +13,12 @@ class PlayerStatsChart:
1413
@staticmethod
1514
def create_from_id(id_: str) -> List[OutputChartData]:
1615
wrapped_player_games = wrapper.get_chart_stats_for_player(id_)
17-
return wrapper.wrap_chart_stats(wrapped_player_games, player_stats_metadata)
16+
protobuf_stats = wrapper.get_protobuf_stats(id_)
17+
all_basic_stats = SharedStatsWrapper.merge_stats(wrapped_player_games, protobuf_stats)
18+
return wrapper.wrap_chart_stats(all_basic_stats, player_group_stats_metadata)
1819

1920

2021
class TeamStatsChart:
2122
@staticmethod
2223
def create_from_id(id_: str) -> List[OutputChartData]:
23-
wrapped_team_games = wrapper.get_chart_stats_for_team(id_)
24-
return wrapper.wrap_chart_stats(wrapped_team_games, team_stats_metadata)
24+
return wrapper.wrap_chart_stats(wrapper.get_chart_stats_for_team(id_), team_stats_metadata)

backend/blueprints/spa_api/service_layers/replay/groups.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
from typing import List
44

5-
from flask import current_app
6-
75
from backend.database.wrapper import player_wrapper
86
from backend.database.wrapper.chart.chart_data import ChartData, ChartDataPoint
97
from backend.database.wrapper.chart.player_chart_metadata import player_group_stats_metadata

backend/blueprints/spa_api/service_layers/replay/heatmaps.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22

33
import numpy as np
44
from carball.generated.api.game_pb2 import Game
5-
from flask import current_app
65

7-
from backend.database.utils.file_manager import get_pandas, get_proto
6+
from backend.utils.file_manager import FileManager
87

98

109
class ReplayHeatmaps:
1110

1211
@staticmethod
1312
def create_from_id(id_: str, type_="positioning") -> 'ReplayHeatmaps':
14-
data_frame = get_pandas(current_app, id_)
15-
protobuf_game = get_proto(current_app, id_)
13+
data_frame = FileManager.get_pandas(id_)
14+
protobuf_game = FileManager.get_proto(id_)
1615
# output = generate_heatmaps(data_frame, protobuf_game, type="hits")
1716
width = 400 / 500
1817
step = 350.0

backend/blueprints/spa_api/service_layers/replay/replay_positions.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from typing import List
22

3-
from flask import current_app
4-
5-
from backend.database.utils.file_manager import get_pandas, get_proto
3+
from backend.utils.file_manager import FileManager
64
from backend.blueprints.spa_api.service_layers.replay.replay_player import ReplayPlayer
75

86

@@ -20,8 +18,8 @@ def __init__(self, id_: str,
2018

2119
@staticmethod
2220
def create_from_id(id_: str) -> 'ReplayPositions':
23-
data_frame = get_pandas(current_app, id_)
24-
protobuf_game = get_proto(current_app, id_)
21+
data_frame = FileManager.get_pandas(id_)
22+
protobuf_game = FileManager.get_proto(id_)
2523

2624
cs = ['pos_x', 'pos_y', 'pos_z']
2725
rot_cs = ['rot_x', 'rot_y', 'rot_z']

backend/blueprints/spa_api/spa_api.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,15 @@
1919
from backend.blueprints.spa_api.service_layers.replay.heatmaps import ReplayHeatmaps
2020
from backend.blueprints.spa_api.service_layers.replay.predicted_ranks import PredictedRank
2121
from backend.blueprints.spa_api.service_layers.replay.visibility import ReplayVisibility
22-
from backend.blueprints.spa_api.service_layers.replay.visualizations import Visualizations
2322
from backend.blueprints.spa_api.utils.query_param_definitions import upload_file_query_params, \
2423
replay_search_query_params, progression_query_params, playstyle_query_params, visibility_params, convert_to_enum
2524
from backend.database.startup import lazy_get_redis
26-
from backend.database.utils.file_manager import get_replay_path
2725
from backend.tasks.add_replay import create_replay_task, parsed_replay_processing
2826
from backend.utils.checks import log_error
2927
from backend.utils.global_functions import get_current_user_id
3028
from backend.blueprints.spa_api.service_layers.replay.visualizations import Visualizations
31-
from backend.database.utils.file_manager import get_replay_path
3229
from backend.tasks.update import update_self
33-
30+
from backend.utils.file_manager import FileManager
3431

3532
try:
3633
import config
@@ -306,7 +303,7 @@ def api_download_group():
306303
@bp.route('/replay/<id_>/download')
307304
def download_replay(id_):
308305
filename = id_ + ".replay"
309-
path = get_replay_path(current_app, id_)
306+
path = FileManager.get_replay_path(id_)
310307
if os.path.isfile(path):
311308
return send_from_directory(current_app.config['REPLAY_DIR'], filename, as_attachment=True)
312309
elif config is not None and hasattr(config, 'GCP_BUCKET_URL'):

backend/database/utils/dynamic_field_manager.py

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@
77

88

99
class DynamicFieldResult:
10-
def __init__(self, field_name):
10+
def __init__(self, field_name: str):
1111
self.field_name = field_name
1212

1313

1414
class ProtoFieldResult(DynamicFieldResult):
15-
def __init__(self, nested_parents, field_name, field_descriptor, nested_names=None):
15+
def __init__(self, nested_parents: List[str], field_name: str, field_descriptor, nested_names: List[str] = None):
1616
super().__init__(field_name)
1717
self.field_descriptor = field_descriptor
1818
self.nested_parents = nested_parents
1919
self.nested_names = nested_names
2020

2121

22-
def get_proto_fields_as_flatten_list(proto_message: message, nested_parents=None, nested_fields=None) -> List[ProtoFieldResult]:
22+
def get_proto_fields_as_flatten_list(proto_message: message,
23+
nested_parents: List[str] = None,
24+
nested_fields: List[str] = None) -> List[ProtoFieldResult]:
2325
"""
2426
Gets the result of a flatten version of all fields in the protobuf object.
2527
:param proto_message: A class representing what we want to get the fields from. It is not an instance.
@@ -53,12 +55,14 @@ def filter_proto_fields(proto_field_list: List[ProtoFieldResult], blacklist_fiel
5355
result_list = []
5456
for item in proto_field_list:
5557
allowed = True
56-
for name in blacklist_field_names:
57-
if name == item.field_name:
58-
allowed = False
59-
for message in blacklist_message_types:
60-
if message in item.nested_parents:
61-
allowed = False
58+
if blacklist_field_names is not None:
59+
for name in blacklist_field_names:
60+
if name == item.field_name:
61+
allowed = False
62+
if blacklist_message_types is not None:
63+
for message in blacklist_message_types:
64+
if message in item.nested_parents:
65+
allowed = False
6266
if allowed:
6367
result_list.append(item)
6468
return result_list
@@ -73,22 +77,58 @@ def get_db_proto_union(proto_field_list: List[ProtoFieldResult], db_object: DBOb
7377
return result_list
7478

7579

76-
def create_and_filter_proto_field(proto_message: message, blacklist_field_names: List[str],
77-
blacklist_message_types: List[str], db_object: DBObjectBase) -> List[ProtoFieldResult]:
80+
def get_whitelist_proto_union(proto_field_list: List[ProtoFieldResult],
81+
whitelist_field_name: [str],
82+
whitelist_message_type: [str]) -> List[ProtoFieldResult]:
83+
result_list = []
84+
allowed = False
85+
for item in proto_field_list:
86+
if whitelist_field_name is not None and item.field_name in whitelist_field_name:
87+
allowed = True
88+
if whitelist_message_type is not None:
89+
for field_type in whitelist_message_type:
90+
if field_type in item.nested_parents:
91+
allowed = True
92+
if allowed:
93+
result_list.append(item)
94+
return result_list
95+
96+
97+
def create_and_filter_proto_field(proto_message: message,
98+
blacklist_field_names: List[str]=None,
99+
blacklist_message_types: List[str]=None,
100+
db_object: DBObjectBase=None,
101+
whitelist_field_names: List[str]=None,
102+
whitelist_message_types: List[str]=None) -> List[ProtoFieldResult]:
78103
"""
79104
Creates a flatten list of the union of protobuf and db objects.
105+
106+
First this limits to just the whitelist if it exists
107+
Then filters out the blacklist
108+
Then limits to what exists in the db if that exists.
80109
:param proto_message: The protobuf class not an object we are grabbing fields from.
81110
:param blacklist_field_names: Fields we do not want included.
82111
:param blacklist_message_types: Message types we do not want included.
83112
:param db_object: The database object that is being unioned with the protobuf
113+
:param whitelist_field_names A list of field names that are the only ones allowed.
114+
:param whitelist_message_types A list of field types that are the only ones allowed.
84115
:return: A list of fields that can be used to go between protobuf and the database.
85116
"""
86-
list = get_proto_fields_as_flatten_list(proto_message)
87-
list = filter_proto_fields(list, blacklist_field_names, blacklist_message_types)
88-
return get_db_proto_union(list, db_object)
117+
field_list = get_proto_fields_as_flatten_list(proto_message)
118+
119+
if whitelist_field_names is not None or whitelist_message_types is not None:
120+
field_list = get_whitelist_proto_union(field_list, whitelist_field_names, whitelist_message_types)
121+
122+
if blacklist_field_names is not None or blacklist_message_types is not None:
123+
field_list = filter_proto_fields(field_list, blacklist_field_names, blacklist_message_types)
124+
125+
if db_object is not None:
126+
field_list = get_db_proto_union(field_list, db_object)
127+
128+
return field_list
89129

90130

91-
def get_proto_values(proto_object, fields: List[ProtoFieldResult]):
131+
def get_proto_values(proto_object, fields: List[ProtoFieldResult]) -> List[any]:
92132
"""
93133
Returns all proto values specified by a list of ProtoFieldResult
94134
:param proto_object:

backend/database/utils/file_manager.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

backend/database/wrapper/chart/chart_data.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class ChartSubcatagory(Enum):
3131

3232

3333
class ChartStatsMetadata:
34-
def __init__(self, stat_name: str, type_: ChartType, subcategory: ChartSubcatagory):
34+
def __init__(self, stat_name: str, type_: ChartType, subcategory: ChartSubcatagory, is_protobuf=False):
35+
self.is_protobuf = is_protobuf
3536
self.stat_name = stat_name
3637
self.type = type_.name
3738
self.subcategory = subcategory.name.replace('_', ' ')

0 commit comments

Comments
 (0)