-
Notifications
You must be signed in to change notification settings - Fork 0
/
benone.py
executable file
·172 lines (137 loc) · 5.66 KB
/
benone.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
#!/usr/bin/env python3
import argparse
import os
import sys
from pathlib import Path
from flask import Flask, render_template, request
from werkzeug.utils import secure_filename
from src.analysis import DigitCounterAnalysis, WrongFile, Reader
from src.config import AppConfig, DEF_CONFIG_FILENAME
from src.database import Database
from src.utils import Term
class WrongRequest(Exception):
pass
class WrongEnvironment(Exception):
"""Exception raised when wrong environment is set.
Attributes:
env -- input environment value which cased exception
message -- explanation of the error
"""
def __init__(self, env: str, message: str = 'Wrong environment, options = ["production", "development"]'):
self.env = env
self.message = message
super().__init__(self.message)
def add_api(app, database: Database):
"""Adds API for performing analyses and communication with database"""
@app.route('/', methods=['GET'])
def index():
return render_template(
'index.html',
help=database.get_app_help()
)
@app.route('/api/files', methods=['GET'])
def files_list():
"""Get available files uploaded by users."""
return {
'files': database.get_filenames(),
}
@app.route('/api/extensions', methods=['GET'])
def extensions_list():
"""Get available extensions to use when parsing file."""
return {
'extensions': Reader.get_supported_extensions(),
}
@app.route('/api/analyze', methods=['POST'])
def analyze_file():
"""Analyze file for Benford's Law.
First check if analysis not in database, if True - just load
it and show to user, if not - do analysis.
"""
try:
data = request.get_json()
ext = data['ext']
filename = data['filename']
filename = secure_filename(filename)
filename = os.path.join(app.config['UPLOAD_FOLDER'], filename)
# analysis id is file hash, if done once, do not do it again
analysis_id = Reader.file_id(Path(filename), ext)
# try to get analysis from database using file hash as id
if not (analysis := database.get_analysis(analysis_id)):
analysis = DigitCounterAnalysis(filename, ext=ext)
database.add_analysis(analysis)
except WrongFile as e:
Term.error(str(e))
return {'success': False, 'error': str(e)}, 400
except (KeyError, TypeError) as e:
Term.error(str(e))
return {'success': False, 'error': str(e)}, 400
except Exception as e:
Term.error(str(e))
return {'success': False, 'error': str(e)}, 400
return {
'success': True,
'stats': analysis.get_stats(),
'lead_frequenters': analysis.get_frequenters('lead'),
}, 200
@app.route('/api/upload', methods=['POST'])
def get_user_file():
"""Get file from user.
If file with same name exists - check if content is the
same, if yes - do not save file, if not - return error
that names must be unique (for now).
"""
# check if the post request has the file part
if 'file' not in request.files:
return {'success': False, 'error': 'No file'}, 400
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
return {'success': False, 'error': 'File not selected'}, 400
if file:
filename = secure_filename(file.filename)
filename = os.path.join(app.config['UPLOAD_FOLDER'], filename)
# if file exists and content is same do not save again
if (stored_file := Path(filename)).exists():
if Reader.same_files(stored_file, file):
return {'success': True}, 200
# same filename but different content - not allowed, sorry
else:
return {'success': False, 'error': 'File with the same name already exists, '
'unfortunately it is not allowed yet'}, 400
# everything's ok, save file under UPLOAD_FOLDER
file.save(filename)
return {'success': True}, 200
def create_app(app_config: AppConfig = AppConfig(DEF_CONFIG_FILENAME)):
app = Flask(__name__)
if app_config.ENV == 'development':
# refreshing application
app.config = {
**app.config,
'SEND_FILE_MAX_AGE_DEFAULT': 0,
'TEMPLATES_AUTO_RELOAD': True
}
elif app_config.ENV == 'production':
pass
else:
raise WrongEnvironment(app_config.ENV)
app.config = {**app.config, **app_config.__dict__}
database = Database('data/users.pickle', 'data/analyses.pickle', app_config.UPLOAD_FOLDER)
@app.context_processor
def inject_globals():
return {
'home': f'http://{app_config.HOST}:{app_config.PORT}',
}
add_api(app, database)
return app
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='BeNone is an Service for benning your data!')
parser.add_argument('-c', '--config', type=str, default=DEF_CONFIG_FILENAME, help='config filename')
args = parser.parse_args()
try:
main_app_config = AppConfig(args.config)
except ValueError:
Term.error(f'Invalid configuration file = {args.config}')
sys.exit(1)
bpp = create_app(main_app_config)
bpp.run(main_app_config.HOST, main_app_config.PORT)