PyAutomationIO 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- automation/__init__.py +46 -0
- automation/alarms/__init__.py +563 -0
- automation/alarms/states.py +192 -0
- automation/alarms/trigger.py +64 -0
- automation/buffer.py +132 -0
- automation/core.py +1775 -0
- automation/dbmodels/__init__.py +23 -0
- automation/dbmodels/alarms.py +524 -0
- automation/dbmodels/core.py +86 -0
- automation/dbmodels/events.py +153 -0
- automation/dbmodels/logs.py +155 -0
- automation/dbmodels/machines.py +181 -0
- automation/dbmodels/opcua.py +81 -0
- automation/dbmodels/opcua_server.py +174 -0
- automation/dbmodels/tags.py +921 -0
- automation/dbmodels/users.py +259 -0
- automation/extensions/__init__.py +15 -0
- automation/extensions/api.py +149 -0
- automation/extensions/cors.py +18 -0
- automation/filter/__init__.py +19 -0
- automation/iad/__init__.py +3 -0
- automation/iad/frozen_data.py +54 -0
- automation/iad/out_of_range.py +51 -0
- automation/iad/outliers.py +51 -0
- automation/logger/__init__.py +0 -0
- automation/logger/alarms.py +426 -0
- automation/logger/core.py +265 -0
- automation/logger/datalogger.py +646 -0
- automation/logger/events.py +194 -0
- automation/logger/logdict.py +53 -0
- automation/logger/logs.py +203 -0
- automation/logger/machines.py +248 -0
- automation/logger/opcua_server.py +130 -0
- automation/logger/users.py +96 -0
- automation/managers/__init__.py +4 -0
- automation/managers/alarms.py +455 -0
- automation/managers/db.py +328 -0
- automation/managers/opcua_client.py +186 -0
- automation/managers/state_machine.py +183 -0
- automation/models.py +174 -0
- automation/modules/__init__.py +14 -0
- automation/modules/alarms/__init__.py +0 -0
- automation/modules/alarms/resources/__init__.py +10 -0
- automation/modules/alarms/resources/alarms.py +280 -0
- automation/modules/alarms/resources/summary.py +79 -0
- automation/modules/events/__init__.py +0 -0
- automation/modules/events/resources/__init__.py +10 -0
- automation/modules/events/resources/events.py +83 -0
- automation/modules/events/resources/logs.py +109 -0
- automation/modules/tags/__init__.py +0 -0
- automation/modules/tags/resources/__init__.py +8 -0
- automation/modules/tags/resources/tags.py +201 -0
- automation/modules/users/__init__.py +2 -0
- automation/modules/users/resources/__init__.py +10 -0
- automation/modules/users/resources/models/__init__.py +2 -0
- automation/modules/users/resources/models/roles.py +5 -0
- automation/modules/users/resources/models/users.py +14 -0
- automation/modules/users/resources/roles.py +38 -0
- automation/modules/users/resources/users.py +113 -0
- automation/modules/users/roles.py +121 -0
- automation/modules/users/users.py +335 -0
- automation/opcua/__init__.py +1 -0
- automation/opcua/models.py +541 -0
- automation/opcua/subscription.py +259 -0
- automation/pages/__init__.py +0 -0
- automation/pages/alarms.py +34 -0
- automation/pages/alarms_history.py +21 -0
- automation/pages/assets/styles.css +7 -0
- automation/pages/callbacks/__init__.py +28 -0
- automation/pages/callbacks/alarms.py +218 -0
- automation/pages/callbacks/alarms_summary.py +20 -0
- automation/pages/callbacks/db.py +222 -0
- automation/pages/callbacks/filter.py +238 -0
- automation/pages/callbacks/machines.py +29 -0
- automation/pages/callbacks/machines_detailed.py +581 -0
- automation/pages/callbacks/opcua.py +266 -0
- automation/pages/callbacks/opcua_server.py +244 -0
- automation/pages/callbacks/tags.py +495 -0
- automation/pages/callbacks/trends.py +119 -0
- automation/pages/communications.py +129 -0
- automation/pages/components/__init__.py +123 -0
- automation/pages/components/alarms.py +151 -0
- automation/pages/components/alarms_summary.py +45 -0
- automation/pages/components/database.py +128 -0
- automation/pages/components/gaussian_filter.py +69 -0
- automation/pages/components/machines.py +396 -0
- automation/pages/components/opcua.py +384 -0
- automation/pages/components/opcua_server.py +53 -0
- automation/pages/components/tags.py +253 -0
- automation/pages/components/trends.py +66 -0
- automation/pages/database.py +26 -0
- automation/pages/filter.py +55 -0
- automation/pages/machines.py +20 -0
- automation/pages/machines_detailed.py +41 -0
- automation/pages/main.py +63 -0
- automation/pages/opcua_server.py +28 -0
- automation/pages/tags.py +40 -0
- automation/pages/trends.py +35 -0
- automation/singleton.py +30 -0
- automation/state_machine.py +1672 -0
- automation/tags/__init__.py +2 -0
- automation/tags/cvt.py +1198 -0
- automation/tags/filter.py +55 -0
- automation/tags/tag.py +418 -0
- automation/tests/__init__.py +10 -0
- automation/tests/test_alarms.py +110 -0
- automation/tests/test_core.py +257 -0
- automation/tests/test_unit.py +21 -0
- automation/tests/test_user.py +155 -0
- automation/utils/__init__.py +164 -0
- automation/utils/decorators.py +222 -0
- automation/utils/npw.py +294 -0
- automation/utils/observer.py +21 -0
- automation/utils/units.py +118 -0
- automation/variables/__init__.py +55 -0
- automation/variables/adimentional.py +30 -0
- automation/variables/current.py +71 -0
- automation/variables/density.py +115 -0
- automation/variables/eng_time.py +68 -0
- automation/variables/force.py +90 -0
- automation/variables/length.py +104 -0
- automation/variables/mass.py +80 -0
- automation/variables/mass_flow.py +101 -0
- automation/variables/percentage.py +30 -0
- automation/variables/power.py +113 -0
- automation/variables/pressure.py +93 -0
- automation/variables/temperature.py +168 -0
- automation/variables/volume.py +70 -0
- automation/variables/volumetric_flow.py +100 -0
- automation/workers/__init__.py +2 -0
- automation/workers/logger.py +164 -0
- automation/workers/state_machine.py +207 -0
- automation/workers/worker.py +36 -0
- pyautomationio-0.0.0.dist-info/METADATA +198 -0
- pyautomationio-0.0.0.dist-info/RECORD +138 -0
- pyautomationio-0.0.0.dist-info/WHEEL +5 -0
- pyautomationio-0.0.0.dist-info/licenses/LICENSE +21 -0
- pyautomationio-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import pytz
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from flask_restx import Namespace, Resource, fields
|
|
4
|
+
from .... import PyAutomation
|
|
5
|
+
from ....extensions.api import api
|
|
6
|
+
from ....extensions import _api as Api
|
|
7
|
+
from .... import _TIMEZONE, TIMEZONE
|
|
8
|
+
|
|
9
|
+
ns = Namespace('Operation Logs', description='Operation Logs')
|
|
10
|
+
app = PyAutomation()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logs_filter_model = api.model("logs_filter_model",{
|
|
14
|
+
'usernames': fields.List(fields.String(), required=False),
|
|
15
|
+
'alarm_names': fields.List(fields.String(), required=False),
|
|
16
|
+
'event_ids': fields.List(fields.Integer(), required=False),
|
|
17
|
+
'classification': fields.String(required=False),
|
|
18
|
+
'message': fields.String(required=False),
|
|
19
|
+
'description': fields.String(required=False),
|
|
20
|
+
'greater_than_timestamp': fields.DateTime(required=False, default=datetime.now(pytz.utc).astimezone(TIMEZONE) - timedelta(minutes=30), description=f'Greater than timestamp - DateTime Format: {app.cvt.DATETIME_FORMAT}'),
|
|
21
|
+
'less_than_timestamp': fields.DateTime(required=False, default=datetime.now(pytz.utc).astimezone(TIMEZONE), description=f'Less than timestamp - DateTime Format: {app.cvt.DATETIME_FORMAT}',),
|
|
22
|
+
'timezone': fields.String(required=False, default=_TIMEZONE)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
logs_model = api.model("logs_model",{
|
|
26
|
+
'message': fields.String(required=True, description="Log message"),
|
|
27
|
+
'alarm_summary_id': fields.Integer(required=False, description="Alarm summary id comment"),
|
|
28
|
+
'event_id': fields.Integer(required=False, description="Event id comment"),
|
|
29
|
+
'description': fields.String(required=False, description="Log description")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
@ns.route('/add')
|
|
33
|
+
class AddLogsByResource(Resource):
|
|
34
|
+
|
|
35
|
+
@api.doc(security='apikey')
|
|
36
|
+
@Api.token_required(auth=True)
|
|
37
|
+
@ns.expect(logs_model)
|
|
38
|
+
def post(self):
|
|
39
|
+
r"""
|
|
40
|
+
Create Log
|
|
41
|
+
"""
|
|
42
|
+
user = Api.get_current_user()
|
|
43
|
+
api.payload.update({
|
|
44
|
+
"user": user
|
|
45
|
+
})
|
|
46
|
+
if "event_id" in api.payload:
|
|
47
|
+
api.payload.update({
|
|
48
|
+
"classification": "Event"
|
|
49
|
+
})
|
|
50
|
+
elif "alarm_summary_id" in api.payload:
|
|
51
|
+
api.payload.update({
|
|
52
|
+
"classification": "Alarm"
|
|
53
|
+
})
|
|
54
|
+
else:
|
|
55
|
+
api.payload.update({
|
|
56
|
+
"classification": "General"
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
log, message = app.create_log(**api.payload)
|
|
60
|
+
if log:
|
|
61
|
+
|
|
62
|
+
return log.serialize(), 200
|
|
63
|
+
|
|
64
|
+
return message, 400
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@ns.route('/filter_by')
|
|
68
|
+
class LogsFilterByResource(Resource):
|
|
69
|
+
|
|
70
|
+
@api.doc(security='apikey')
|
|
71
|
+
@Api.token_required(auth=True)
|
|
72
|
+
@ns.expect(logs_filter_model)
|
|
73
|
+
def post(self):
|
|
74
|
+
r"""
|
|
75
|
+
Logs Filter By
|
|
76
|
+
"""
|
|
77
|
+
timezone = _TIMEZONE
|
|
78
|
+
if "timezone" in api.payload:
|
|
79
|
+
|
|
80
|
+
timezone = api.payload["timezone"]
|
|
81
|
+
|
|
82
|
+
if timezone not in pytz.all_timezones:
|
|
83
|
+
|
|
84
|
+
return f"Invalid Timezone", 400
|
|
85
|
+
|
|
86
|
+
separator = '.'
|
|
87
|
+
if 'greater_than_timestamp' in api.payload:
|
|
88
|
+
|
|
89
|
+
greater_than_timestamp = api.payload['greater_than_timestamp']
|
|
90
|
+
api.payload['greater_than_timestamp'] = greater_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
|
|
91
|
+
|
|
92
|
+
if "less_than_timestamp" in api.payload:
|
|
93
|
+
|
|
94
|
+
less_than_timestamp = api.payload['less_than_timestamp']
|
|
95
|
+
api.payload['less_than_timestamp'] = less_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
|
|
96
|
+
return app.filter_logs_by(**api.payload), 200
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@ns.route('/lasts/<lasts>')
|
|
100
|
+
class LastsEventsResource(Resource):
|
|
101
|
+
|
|
102
|
+
@api.doc(security='apikey')
|
|
103
|
+
@Api.token_required(auth=True)
|
|
104
|
+
def get(self, lasts:int=10):
|
|
105
|
+
r"""
|
|
106
|
+
Get lasts events
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
return app.get_lasts_logs(lasts=int(lasts)), 200
|
|
File without changes
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import pytz
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from flask_restx import Namespace, Resource, fields, reqparse
|
|
4
|
+
from .... import PyAutomation
|
|
5
|
+
from ....extensions.api import api
|
|
6
|
+
from ....extensions import _api as Api
|
|
7
|
+
from .... import _TIMEZONE, TIMEZONE
|
|
8
|
+
|
|
9
|
+
ns = Namespace('Tags', description='Tags')
|
|
10
|
+
app = PyAutomation()
|
|
11
|
+
|
|
12
|
+
query_trends_model = api.model("query_trends_model",{
|
|
13
|
+
'tags': fields.List(fields.String(), required=True),
|
|
14
|
+
'greater_than_timestamp': fields.DateTime(required=True, default=datetime.now(pytz.utc).astimezone(TIMEZONE) - timedelta(minutes=30), description='Greater than DateTime'),
|
|
15
|
+
'less_than_timestamp': fields.DateTime(required=True, default=datetime.now(pytz.utc).astimezone(TIMEZONE), description='Less than DateTime'),
|
|
16
|
+
'timezone': fields.String(required=True, default=_TIMEZONE)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
query_table_model = api.model("query_table_model",{
|
|
20
|
+
'tags': fields.List(fields.String(), required=True),
|
|
21
|
+
'greater_than_timestamp': fields.DateTime(required=True, default=datetime.now(pytz.utc).astimezone(TIMEZONE) - timedelta(minutes=30), description='Greater than DateTime'),
|
|
22
|
+
'less_than_timestamp': fields.DateTime(required=True, default=datetime.now(pytz.utc).astimezone(TIMEZONE), description='Less than DateTime'),
|
|
23
|
+
'timezone': fields.String(required=True, default=_TIMEZONE),
|
|
24
|
+
'page': fields.Integer(required=False, default=1, description='Page number'),
|
|
25
|
+
'limit': fields.Integer(required=False, default=20, description='Items per page')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
write_value_model = api.model("write_value_model", {
|
|
29
|
+
'tag_name': fields.String(required=True, description='Nombre del tag'),
|
|
30
|
+
'value': fields.Raw(required=True, description='Valor a escribir (float, int, bool, str)')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@ns.route('/')
|
|
35
|
+
class TagsCollection(Resource):
|
|
36
|
+
|
|
37
|
+
@api.doc(security='apikey')
|
|
38
|
+
@Api.token_required(auth=True)
|
|
39
|
+
def get(self):
|
|
40
|
+
"""
|
|
41
|
+
Get Tags
|
|
42
|
+
"""
|
|
43
|
+
return app.get_tags(), 200
|
|
44
|
+
|
|
45
|
+
@ns.route('/names')
|
|
46
|
+
class TagsNamesCollection(Resource):
|
|
47
|
+
|
|
48
|
+
parser = reqparse.RequestParser()
|
|
49
|
+
parser.add_argument('names', type=str, action='append', location='args', help='Tags names to get')
|
|
50
|
+
|
|
51
|
+
@api.doc(security='apikey', parser=parser)
|
|
52
|
+
@Api.token_required(auth=True)
|
|
53
|
+
def get(self):
|
|
54
|
+
"""
|
|
55
|
+
Get Tags Names
|
|
56
|
+
"""
|
|
57
|
+
args = self.parser.parse_args()
|
|
58
|
+
names = args.get('names')
|
|
59
|
+
return app.get_tags_by_names(names=names or []), 200
|
|
60
|
+
|
|
61
|
+
@ns.route('/query_trends')
|
|
62
|
+
class QueryTrendsResource(Resource):
|
|
63
|
+
|
|
64
|
+
@api.doc(security='apikey')
|
|
65
|
+
@Api.token_required(auth=True)
|
|
66
|
+
@ns.expect(query_trends_model)
|
|
67
|
+
def post(self):
|
|
68
|
+
"""
|
|
69
|
+
Query tag value filtering by timestamp
|
|
70
|
+
|
|
71
|
+
Authorized Roles: {0}
|
|
72
|
+
"""
|
|
73
|
+
timezone = _TIMEZONE
|
|
74
|
+
tags = api.payload['tags']
|
|
75
|
+
if "timezone" in api.payload:
|
|
76
|
+
|
|
77
|
+
timezone = api.payload["timezone"]
|
|
78
|
+
|
|
79
|
+
if timezone not in pytz.all_timezones:
|
|
80
|
+
|
|
81
|
+
return f"Invalid Timezone", 400
|
|
82
|
+
|
|
83
|
+
for tag in tags:
|
|
84
|
+
|
|
85
|
+
if not app.get_tag_by_name(name=tag):
|
|
86
|
+
|
|
87
|
+
return f"{tag} not exist into db", 404
|
|
88
|
+
|
|
89
|
+
separator = '.'
|
|
90
|
+
greater_than_timestamp = api.payload['greater_than_timestamp']
|
|
91
|
+
start = greater_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
|
|
92
|
+
less_than_timestamp = api.payload['less_than_timestamp']
|
|
93
|
+
stop = less_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
|
|
94
|
+
result = app.get_trends(start, stop, timezone, *tags)
|
|
95
|
+
|
|
96
|
+
return result, 200
|
|
97
|
+
|
|
98
|
+
@ns.route('/query_table')
|
|
99
|
+
class QueryTableResource(Resource):
|
|
100
|
+
|
|
101
|
+
@api.doc(security='apikey')
|
|
102
|
+
@Api.token_required(auth=True)
|
|
103
|
+
@ns.expect(query_table_model)
|
|
104
|
+
def post(self):
|
|
105
|
+
"""
|
|
106
|
+
Query tag values in table format with pagination
|
|
107
|
+
|
|
108
|
+
Authorized Roles: {0}
|
|
109
|
+
"""
|
|
110
|
+
timezone = _TIMEZONE
|
|
111
|
+
tags = api.payload['tags']
|
|
112
|
+
page = api.payload.get('page', 1)
|
|
113
|
+
limit = api.payload.get('limit', 20)
|
|
114
|
+
|
|
115
|
+
if "timezone" in api.payload:
|
|
116
|
+
timezone = api.payload["timezone"]
|
|
117
|
+
|
|
118
|
+
if timezone not in pytz.all_timezones:
|
|
119
|
+
return f"Invalid Timezone", 400
|
|
120
|
+
|
|
121
|
+
for tag in tags:
|
|
122
|
+
if not app.get_tag_by_name(name=tag):
|
|
123
|
+
return f"{tag} not exist into db", 404
|
|
124
|
+
|
|
125
|
+
separator = '.'
|
|
126
|
+
greater_than_timestamp = api.payload['greater_than_timestamp']
|
|
127
|
+
# Ensure timestamp format is consistent
|
|
128
|
+
start = greater_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
|
|
129
|
+
|
|
130
|
+
less_than_timestamp = api.payload['less_than_timestamp']
|
|
131
|
+
stop = less_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
|
|
132
|
+
|
|
133
|
+
result = app.get_tags_tables(start, stop, timezone, tags, page, limit)
|
|
134
|
+
|
|
135
|
+
return result, 200
|
|
136
|
+
|
|
137
|
+
@ns.route('/write_value')
|
|
138
|
+
class WriteValueResource(Resource):
|
|
139
|
+
|
|
140
|
+
@api.doc(security='apikey')
|
|
141
|
+
@Api.token_required(auth=True)
|
|
142
|
+
@ns.expect(write_value_model)
|
|
143
|
+
def post(self):
|
|
144
|
+
"""
|
|
145
|
+
Escribe un valor a un tag en CVT y en el servidor OPC UA si está configurado
|
|
146
|
+
|
|
147
|
+
Authorized Roles: {0}
|
|
148
|
+
"""
|
|
149
|
+
tag_name = api.payload['tag_name']
|
|
150
|
+
value = api.payload['value']
|
|
151
|
+
|
|
152
|
+
# Buscar el tag en CVT
|
|
153
|
+
tag = app.cvt.get_tag_by_name(name=tag_name)
|
|
154
|
+
if not tag:
|
|
155
|
+
return {'message': f'Tag {tag_name} no existe', 'success': False}, 404
|
|
156
|
+
|
|
157
|
+
# Escribir en CVT
|
|
158
|
+
try:
|
|
159
|
+
timestamp = datetime.now(pytz.utc).astimezone(TIMEZONE)
|
|
160
|
+
app.cvt.set_value(id=tag.id, value=value, timestamp=timestamp)
|
|
161
|
+
except Exception as err:
|
|
162
|
+
return {
|
|
163
|
+
'message': f'Error escribiendo en CVT: {str(err)}',
|
|
164
|
+
'tag': tag_name,
|
|
165
|
+
'success': False
|
|
166
|
+
}, 500
|
|
167
|
+
|
|
168
|
+
# Si tiene node_namespace, escribir en OPC UA Server usando el método de core
|
|
169
|
+
opcua_result = None
|
|
170
|
+
opcua_status = None
|
|
171
|
+
if tag.node_namespace and tag.opcua_address:
|
|
172
|
+
opcua_result, opcua_status = app.write_opcua_value(
|
|
173
|
+
opcua_address=tag.opcua_address,
|
|
174
|
+
node_namespace=tag.node_namespace,
|
|
175
|
+
value=value
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Resultado consolidado
|
|
179
|
+
result = {
|
|
180
|
+
'message': 'Valor escrito en CVT' + (' y OPC UA' if opcua_status == 200 else ''),
|
|
181
|
+
'tag': tag_name,
|
|
182
|
+
'value': value,
|
|
183
|
+
'cvt_success': True,
|
|
184
|
+
'opcua_success': opcua_status == 200 if opcua_status else None,
|
|
185
|
+
'opcua_detail': opcua_result if opcua_result else None
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Status: 200 si CVT OK, aunque OPC UA falle (parcial success)
|
|
189
|
+
final_status = 200 if opcua_status in (200, None) else 207 # 207 = Multi-Status
|
|
190
|
+
return result, final_status
|
|
191
|
+
|
|
192
|
+
@ns.route('/timezones')
|
|
193
|
+
class TimezonesCollection(Resource):
|
|
194
|
+
|
|
195
|
+
@api.doc(security='apikey')
|
|
196
|
+
@Api.token_required(auth=True)
|
|
197
|
+
def get(self):
|
|
198
|
+
"""
|
|
199
|
+
Get Available Timezones
|
|
200
|
+
"""
|
|
201
|
+
return pytz.all_timezones, 200
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from flask_restx import reqparse
|
|
2
|
+
|
|
3
|
+
create_role_parser = reqparse.RequestParser(bundle_errors=True)
|
|
4
|
+
create_role_parser.add_argument("name", type=str, required=True, help='Role name')
|
|
5
|
+
create_role_parser.add_argument("level", type=int, required=True, help='Role level, 0 maximum level', default=0)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from flask_restx import reqparse
|
|
2
|
+
|
|
3
|
+
login_parser = reqparse.RequestParser(bundle_errors=True)
|
|
4
|
+
login_parser.add_argument("username", type=str, required=False, help='Username')
|
|
5
|
+
login_parser.add_argument("email", type=str, required=False, help='User email')
|
|
6
|
+
login_parser.add_argument("password", type=str, required=True, help='User passqord')
|
|
7
|
+
|
|
8
|
+
signup_parser = reqparse.RequestParser(bundle_errors=True)
|
|
9
|
+
signup_parser.add_argument("username", type=str, required=True, help='Username')
|
|
10
|
+
signup_parser.add_argument("role_name", type=str, required=True, help='Role Name')
|
|
11
|
+
signup_parser.add_argument("email", type=str, required=True, help="Email address")
|
|
12
|
+
signup_parser.add_argument("password", type=str, required=True, help="Password")
|
|
13
|
+
signup_parser.add_argument("name", type=str, required=False, help="User's name")
|
|
14
|
+
signup_parser.add_argument("lastname", type=str, required=False, help="User's lastname")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from flask_restx import Namespace, Resource
|
|
2
|
+
from .... import PyAutomation
|
|
3
|
+
from ....modules.users.roles import roles
|
|
4
|
+
from ....extensions.api import api
|
|
5
|
+
from ....extensions import _api as Api
|
|
6
|
+
from .models.roles import create_role_parser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ns = Namespace('Roles', description='Roles')
|
|
10
|
+
app = PyAutomation()
|
|
11
|
+
|
|
12
|
+
@ns.route('/')
|
|
13
|
+
class UsersByRoleResource(Resource):
|
|
14
|
+
|
|
15
|
+
@api.doc(security='apikey')
|
|
16
|
+
@Api.token_required(auth=True)
|
|
17
|
+
def get(self):
|
|
18
|
+
"""View all tecnogiros's role"""
|
|
19
|
+
|
|
20
|
+
return roles.serialize(), 200
|
|
21
|
+
|
|
22
|
+
@ns.route('/add')
|
|
23
|
+
class CreateRoleResource(Resource):
|
|
24
|
+
|
|
25
|
+
@Api.validate_reqparser(reqparser=create_role_parser)
|
|
26
|
+
@api.doc(security='apikey')
|
|
27
|
+
@Api.token_required(auth=True)
|
|
28
|
+
@ns.expect(create_role_parser)
|
|
29
|
+
def post(self):
|
|
30
|
+
"""Add Role"""
|
|
31
|
+
args = create_role_parser.parse_args()
|
|
32
|
+
role, message = app.set_role(**args)
|
|
33
|
+
|
|
34
|
+
if role:
|
|
35
|
+
|
|
36
|
+
return role.serialize(), 200
|
|
37
|
+
|
|
38
|
+
return message, 400
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from flask import request
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from flask_restx import Namespace, Resource
|
|
4
|
+
from .models.users import signup_parser, login_parser
|
|
5
|
+
from .... import PyAutomation, TIMEZONE, _TIMEZONE
|
|
6
|
+
from ....extensions.api import api
|
|
7
|
+
from ....extensions import _api as Api
|
|
8
|
+
from ....modules.users.users import Users as CVTUsers
|
|
9
|
+
from ....dbmodels.users import Users
|
|
10
|
+
|
|
11
|
+
DATETIME_FORMAT = "%m/%d/%Y, %H:%M:%S"
|
|
12
|
+
ns = Namespace('Users', description='Users')
|
|
13
|
+
app = PyAutomation()
|
|
14
|
+
users = CVTUsers()
|
|
15
|
+
|
|
16
|
+
@ns.route('/')
|
|
17
|
+
class UsersResource(Resource):
|
|
18
|
+
|
|
19
|
+
@api.doc(security='apikey')
|
|
20
|
+
@Api.token_required(auth=True)
|
|
21
|
+
def get(self):
|
|
22
|
+
"""Get all usernames"""
|
|
23
|
+
|
|
24
|
+
return users.serialize(), 200
|
|
25
|
+
|
|
26
|
+
@ns.route('/signup')
|
|
27
|
+
class SignUpResource(Resource):
|
|
28
|
+
|
|
29
|
+
@Api.validate_reqparser(reqparser=signup_parser)
|
|
30
|
+
@ns.expect(signup_parser)
|
|
31
|
+
def post(self):
|
|
32
|
+
"""User signup"""
|
|
33
|
+
args = signup_parser.parse_args()
|
|
34
|
+
user, message = app.signup(**args)
|
|
35
|
+
|
|
36
|
+
if user:
|
|
37
|
+
|
|
38
|
+
return user.serialize(), 200
|
|
39
|
+
|
|
40
|
+
return message, 400
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@ns.route('/login')
|
|
44
|
+
class LoginResource(Resource):
|
|
45
|
+
|
|
46
|
+
@Api.validate_reqparser(reqparser=login_parser)
|
|
47
|
+
@ns.expect(login_parser)
|
|
48
|
+
def post(self):
|
|
49
|
+
"""User login"""
|
|
50
|
+
args = login_parser.parse_args()
|
|
51
|
+
user, message = app.login(**args)
|
|
52
|
+
|
|
53
|
+
if user:
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
"apiKey": user.token,
|
|
57
|
+
"role": user.role.name,
|
|
58
|
+
"role_level": user.role.level,
|
|
59
|
+
"datetime": datetime.now(TIMEZONE).strftime(DATETIME_FORMAT),
|
|
60
|
+
"timezone": _TIMEZONE
|
|
61
|
+
}, 200
|
|
62
|
+
|
|
63
|
+
return message, 403
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@ns.route('/credentials_are_valid')
|
|
67
|
+
class VerifyCredentialsResource(Resource):
|
|
68
|
+
|
|
69
|
+
@api.doc(security='apikey')
|
|
70
|
+
@Api.token_required(auth=True)
|
|
71
|
+
@Api.validate_reqparser(reqparser=login_parser)
|
|
72
|
+
@ns.expect(login_parser)
|
|
73
|
+
def post(self):
|
|
74
|
+
"""Verify user credentials"""
|
|
75
|
+
args = login_parser.parse_args()
|
|
76
|
+
credentials_valid, _ = users.verify_credentials(**args)
|
|
77
|
+
return credentials_valid, 200
|
|
78
|
+
|
|
79
|
+
@ns.route('/<username>')
|
|
80
|
+
class UserResource(Resource):
|
|
81
|
+
|
|
82
|
+
@api.doc(security='apikey')
|
|
83
|
+
@Api.token_required(auth=True)
|
|
84
|
+
def get(self, username):
|
|
85
|
+
"""Get user information"""
|
|
86
|
+
|
|
87
|
+
user = users.get_by_username(username=username)
|
|
88
|
+
|
|
89
|
+
if user:
|
|
90
|
+
|
|
91
|
+
return user.serialize(), 200
|
|
92
|
+
|
|
93
|
+
return f"{username} is not a valid username", 400
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@ns.route('/logout')
|
|
97
|
+
class LogoutResource(Resource):
|
|
98
|
+
|
|
99
|
+
@api.doc(security='apikey')
|
|
100
|
+
@Api.token_required(auth=True)
|
|
101
|
+
def post(self):
|
|
102
|
+
"""User logout"""
|
|
103
|
+
if 'X-API-KEY' in request.headers:
|
|
104
|
+
|
|
105
|
+
token = request.headers['X-API-KEY']
|
|
106
|
+
|
|
107
|
+
elif 'Authorization' in request.headers:
|
|
108
|
+
|
|
109
|
+
token = request.headers['Authorization'].split('Token ')[-1]
|
|
110
|
+
|
|
111
|
+
_, message = Users.logout(token=token)
|
|
112
|
+
|
|
113
|
+
return message, 200
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
from ...singleton import Singleton
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Role:
|
|
6
|
+
r"""
|
|
7
|
+
Documentation here
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, name:str, level:int, identifier:str=None):
|
|
11
|
+
|
|
12
|
+
_identifier = secrets.token_hex(4)
|
|
13
|
+
|
|
14
|
+
if identifier:
|
|
15
|
+
|
|
16
|
+
_identifier = identifier
|
|
17
|
+
|
|
18
|
+
self.identifier = _identifier
|
|
19
|
+
self.name:str = name
|
|
20
|
+
self.level:int = level
|
|
21
|
+
|
|
22
|
+
def serialize(self):
|
|
23
|
+
r"""
|
|
24
|
+
Documentation here
|
|
25
|
+
"""
|
|
26
|
+
return {
|
|
27
|
+
"identifier": self.identifier,
|
|
28
|
+
"name": self.name,
|
|
29
|
+
"level": self.level
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Roles(Singleton):
|
|
34
|
+
r"""
|
|
35
|
+
Documentation here
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
|
|
40
|
+
self.roles = dict()
|
|
41
|
+
|
|
42
|
+
def add(self, role:Role)->str:
|
|
43
|
+
r"""
|
|
44
|
+
Documentation here
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(role, Role):
|
|
47
|
+
|
|
48
|
+
if not self.check_role_name(name=role.name):
|
|
49
|
+
|
|
50
|
+
self.roles[role.identifier] = role
|
|
51
|
+
|
|
52
|
+
return role.identifier, f"role creation successful"
|
|
53
|
+
|
|
54
|
+
return None, f"role {role.name} is already used"
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
|
|
58
|
+
raise TypeError(f"{role} must be a Role instale")
|
|
59
|
+
|
|
60
|
+
def get(self, id:str)->Role:
|
|
61
|
+
r"""
|
|
62
|
+
Documentation here
|
|
63
|
+
"""
|
|
64
|
+
if id in self.roles:
|
|
65
|
+
|
|
66
|
+
return self.roles[id]
|
|
67
|
+
|
|
68
|
+
def get_by_name(self, name:str)->Role:
|
|
69
|
+
r"""
|
|
70
|
+
Documentation here
|
|
71
|
+
"""
|
|
72
|
+
for _, role in self.roles.items():
|
|
73
|
+
|
|
74
|
+
if name.lower()==role.name.lower():
|
|
75
|
+
|
|
76
|
+
return role
|
|
77
|
+
|
|
78
|
+
def get_names(self)->list:
|
|
79
|
+
r"""
|
|
80
|
+
Documentation here
|
|
81
|
+
"""
|
|
82
|
+
return [role.name for _, role in self.roles.items()]
|
|
83
|
+
|
|
84
|
+
def put(self, id:str, **kwargs):
|
|
85
|
+
r"""
|
|
86
|
+
Documentation here
|
|
87
|
+
"""
|
|
88
|
+
if id in self.roles:
|
|
89
|
+
role = self.roles[id]
|
|
90
|
+
fields = {key: value for key, value in kwargs.items() if key in role.serialize()}
|
|
91
|
+
self.roles[id].__dict__.update(fields)
|
|
92
|
+
|
|
93
|
+
def delete(self, id:str)->dict:
|
|
94
|
+
r"""
|
|
95
|
+
Documentation here
|
|
96
|
+
"""
|
|
97
|
+
if id in self.roles:
|
|
98
|
+
|
|
99
|
+
return self.roles.pop(id)
|
|
100
|
+
|
|
101
|
+
def _delete_all(self):
|
|
102
|
+
|
|
103
|
+
self.roles = dict()
|
|
104
|
+
|
|
105
|
+
def check_role_name(self, name:str):
|
|
106
|
+
r"""
|
|
107
|
+
Documentation here
|
|
108
|
+
"""
|
|
109
|
+
if self.get_by_name(name=name):
|
|
110
|
+
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def serialize(self):
|
|
116
|
+
r"""
|
|
117
|
+
Documentation here
|
|
118
|
+
"""
|
|
119
|
+
return [role.serialize() for _, role in self.roles.items() if role.name.lower()!="sudo"]
|
|
120
|
+
|
|
121
|
+
roles = Roles()
|