This repository has been archived by the owner on Apr 24, 2024. It is now read-only.
/
__init__.py
346 lines (292 loc) · 12.5 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
from __future__ import absolute_import
from decimal import Decimal
import locale
import os
import requests
import sys
import warnings
from flask import current_app, g
import flask_sqlalchemy
from flask_babel import Babel, get_locale
from flask_wtf.csrf import CsrfProtect
import six
from sqlalchemy.engine import Engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import MetaData
from werkzeug.utils import import_string
from .transformers import Transformer
from .versioned_static import static_file, VersionedStaticFlask
try:
import raven.contrib.flask as raven_flask
sentry = raven_flask.Sentry()
except ImportError:
sentry = None
db = flask_sqlalchemy.SQLAlchemy()
# Patch Flask-SQLAlchemy to use a custom Metadata instance with a naming scheme
# for constraints.
def _patch_metadata():
naming_convention = {
'fk': ('fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s'
'_%(referred_column_0_name)s'),
'pk': 'pk_%(table_name)s',
'ix': 'ix_%(table_name)s_%(column_0_name)s',
'ck': 'ck_%(table_name)s_%(constraint_name)s',
'uq': 'uq_%(table_name)s_%(column_0_name)s',
}
metadata = MetaData(naming_convention=naming_convention)
base = declarative_base(cls=flask_sqlalchemy.Model, name='Model',
metaclass=flask_sqlalchemy._BoundDeclarativeMeta,
metadata=metadata)
base.query = flask_sqlalchemy._QueryProperty(db)
db.Model = base
_patch_metadata()
# Work around buggy HTTP servers sending out 0 length chunked responses
def _patch_httplib():
import six.moves.http_client
def patch_http_response_read(func):
def inner(*args):
try:
return func(*args)
except six.moves.http_client.IncompleteRead as e:
return e.partial
return inner
six.moves.http_client.HTTPResponse.read = patch_http_response_read(
six.moves.http_client.HTTPResponse.read)
_patch_httplib()
from .util import DB_STATS, AcceptRequest, WeakCiphersAdapter
__version__ = u'0.12.12.dev'
requests_session = requests.Session()
# Set default locale
locale.setlocale(locale.LC_ALL, '')
csrf = CsrfProtect()
babel = Babel()
# Ensure models are declared
from . import models
from .auth import models as auth_models
from .auth import AuthMethod
def create_app(config=None, **kwargs):
"""Create the WSGI application that is this app.
In addition to the arguments documented below, this function accepts as a
keyword agument all agruments to :py:class:`flask.Flask` except
`import_name`, which is set to 'evesrp'. Additionally, the value of
`instance_relative_config` has a default value of `True`.
If the `config` argument isn't specified, the app will attempt to use the
file 'config.py' in the instance folder if it exists, and will then fall
back to using the value of the EVESRP_SETTINGS environment variable as a
path to a config file.
:param config: The app configuration file. Can be a Python
:py:class:`dict`, a path to a configuration file, or an importable
module name containing the configuration.
:type config: str, dict
"""
# Default instance_relative_config to True to let the config fallback work
kwargs.setdefault('instance_relative_config', True)
app = VersionedStaticFlask('evesrp', **kwargs)
app.request_class = AcceptRequest
app.config.from_object('evesrp.default_config')
# Push the instance folder path onto sys.path to allow importing from there
sys.path.insert(0, app.instance_path)
# Check in config is a dict, python config file, or importable object name,
# in that order. Finally, check the EVESRP_SETTINGS environment variable
# as a last resort.
if isinstance(config, dict):
app.config.update(config)
elif isinstance(config, six.string_types):
if config.endswith(('.txt', '.py', '.cfg')):
app.config.from_pyfile(config)
else:
app.config.from_object(config)
elif config is None:
try:
app.config.from_pyfile('config.py')
except OSError:
app.config.from_envvar('EVESRP_SETTINGS')
# Configure Sentry
if 'SENTRY_DSN' in app.config or 'SENTRY_DSN' in os.environ:
if sentry is not None:
app.config['SENTRY_RELEASE'] = __version__
sentry.init_app(app=app)
else:
app.logger.warning("SENTRY_DSN is defined but Sentry is not"
" installed.")
# Register SQLAlchemy monitoring before the DB is connected
app.before_request(sqlalchemy_before)
db.init_app(app)
from .views.login import login_manager
login_manager.init_app(app)
before_csrf = list(app.before_request_funcs[None])
csrf.init_app(app)
# Remove the context processor that checks CSRF values. All it is used for
# is the template function.
app.before_request_funcs[None] = before_csrf
# Connect views
from .views import index, error_page, update_navbar, divisions, login,\
requests, api, detect_language, locale_selector
app.add_url_rule(rule=u'/', view_func=index)
for error_code in (400, 403, 404, 500):
app.register_error_handler(error_code, error_page)
app.after_request(update_navbar)
app.register_blueprint(divisions.blueprint, url_prefix='/division')
app.register_blueprint(login.blueprint)
app.register_blueprint(requests.blueprint, url_prefix='/request')
app.register_blueprint(api.api, url_prefix='/api')
app.register_blueprint(api.filters, url_prefix='/api/filter')
from .views import request_count
app.add_template_global(request_count)
from .json import SRPEncoder
app.json_encoder=SRPEncoder
# Hook up Babel and associated callbacks
babel.init_app(app)
app.before_request(detect_language)
# localeselector can be set only once per Babel instance. Really, this will
# only throw an exception when we're running tests. This also only became
# an issue when they changed when Babel.locale_selector_func was changed
# for version 0.10.
try:
babel.localeselector(locale_selector)
except AssertionError:
pass
# Configure the Jinja context
# Inject variables into the context
from .auth import PermissionType
from .util import locale as jinja_locale
@app.context_processor
def inject_enums():
return {
'ActionType': models.ActionType,
'PermissionType': PermissionType,
'app_version': __version__,
'site_name': app.config['SRP_SITE_NAME'],
'url_for_page': requests.url_for_page,
'static_file': static_file,
'locales': jinja_locale.enabled_locales,
'get_locale': get_locale,
}
app.template_filter('currencyfmt')(jinja_locale.currencyfmt)
app.template_filter('percentfmt')(jinja_locale.percentfmt)
app.template_filter('numberfmt')(jinja_locale.numberfmt)
# Auto-trim whitespace
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
init_app(app)
return app
def init_app(app):
_config_requests_session(app)
_config_url_converters(app)
_config_authmethods(app)
_config_killmails(app)
# SQLAlchemy performance logging
def sqlalchemy_before():
if DB_STATS.total_queries > 0:
current_app.logger.debug(u"{} queries in {} ms.".format(
DB_STATS.total_queries,
round(DB_STATS.total_time * 1000, 3)))
DB_STATS.clear()
g.DB_STATS = DB_STATS
# Utility function for creating instances from dicts
def _instance_from_dict(instance_descriptor):
type_name = instance_descriptor.pop('type')
Type = import_string(type_name)
return Type(**instance_descriptor)
# Utility function for raising config deprecation warnings
def _deprecated_object_instance(key, value):
warnings.warn(u"Non-basic data types in configuration values are deprecated"
u"({}: {})".format(key, value), DeprecationWarning,
stacklevel=2)
# Auth setup
def _config_authmethods(app):
auth_methods = []
# Once the deprecated config value support is removed, this can be
# rewritten as a dict comprehension
with app.app_context():
for method in app.config['SRP_AUTH_METHODS']:
if isinstance(method, dict):
auth_methods.append(_instance_from_dict(method))
elif isinstance(method, AuthMethod):
_deprecated_object_instance('SRP_AUTH_METHODS', method)
auth_methods.append(method)
app.auth_methods = auth_methods
# Request detail URL setup
def _config_url_converters(app):
url_transformers = {}
for config_key, config_value in app.config.items():
# Skip null config values
if config_value is None:
continue
# Look for config keys in the format 'SRP_*_URL_TRANSFORMERS'
if not config_key.endswith('_URL_TRANSFORMERS')\
or not config_key.startswith('SRP_'):
continue
attribute = config_key.replace('SRP_', '', 1)
attribute = attribute.replace('_URL_TRANSFORMERS', '', 1)
attribute = attribute.lower()
# Create Transformer instances for this attribute
transformers = {}
for transformer_config in config_value:
if isinstance(transformer_config, tuple):
# Special case for Transformers: A 2-tuple in the form:
# (u'Transformer Name',
# 'http://example.com/transformer/slug/{}')
transformer = Transformer(*transformer_config)
elif isinstance(transformer_config, dict):
# Standard instance dictionary format
# Provide a default type value
transformer_config.setdefault('type',
'evesrp.transformers.Transformer')
transformer = _instance_from_dict(transformer_config)
elif isinstance(transformer_config, Transformer):
# DEPRECATED: raw Transformer instance
_deprecated_object_instance(config_key, transformer_config)
transformer = transformer_config
transformers[transformer.name] = transformer
url_transformers[attribute] = transformers
app.url_transformers = url_transformers
# Requests session setup
def _config_requests_session(app):
try:
ua_string = app.config['SRP_USER_AGENT_STRING']
except KeyError as outer_exc:
try:
ua_string = 'EVE-SRP/{version} ({email})'.format(
email=app.config['SRP_USER_AGENT_EMAIL'],
version=__version__)
except KeyError as inner_exc:
raise inner_exc
requests_session = requests.Session()
requests_session.headers.update({'User-Agent': ua_string})
requests_session.mount('https://crest-tq.eveonline.com',
WeakCiphersAdapter())
app.requests_session = requests_session
# Killmail verification
def _config_killmails(app):
killmail_sources = []
# For now, use a loop with checks. After removing the depecated config
# method it can be rewritten as a list comprehension
for source in app.config['SRP_KILLMAIL_SOURCES']:
if isinstance(source, six.string_types):
killmail_sources.append(import_string(source))
elif isinstance(source, type):
_deprecated_object_instance('SRP_KILLMAIL_SOURCES', source)
killmail_sources.append(source)
app.killmail_sources = killmail_sources
# Work around DBAPI-specific issues with Decimal subclasses.
# Specifically, almost everything besides pysqlite and psycopg2 raise
# exceptions if an instance of a Decimal subclass as opposed to an instance of
# Decimal itself is passed in as a value for a Numeric column.
@db.event.listens_for(Engine, 'before_execute', retval=True)
def _workaround_decimal_subclasses(conn, clauseelement, multiparams, params):
def filter_decimal_subclasses(parameters):
for key in six.iterkeys(parameters):
value = parameters[key]
# Only get instances of subclasses of Decimal, not direct
# Decimal instances
if type(value) != Decimal and isinstance(value, Decimal):
parameters[key] = Decimal(value)
if multiparams:
for mp in multiparams:
if isinstance(mp, dict):
filter_decimal_subclasses(mp)
elif isinstance(mp, list):
for parameters in mp:
filter_decimal_subclasses(parameters)
return clauseelement, multiparams, params