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.
Files changed (138) hide show
  1. automation/__init__.py +46 -0
  2. automation/alarms/__init__.py +563 -0
  3. automation/alarms/states.py +192 -0
  4. automation/alarms/trigger.py +64 -0
  5. automation/buffer.py +132 -0
  6. automation/core.py +1775 -0
  7. automation/dbmodels/__init__.py +23 -0
  8. automation/dbmodels/alarms.py +524 -0
  9. automation/dbmodels/core.py +86 -0
  10. automation/dbmodels/events.py +153 -0
  11. automation/dbmodels/logs.py +155 -0
  12. automation/dbmodels/machines.py +181 -0
  13. automation/dbmodels/opcua.py +81 -0
  14. automation/dbmodels/opcua_server.py +174 -0
  15. automation/dbmodels/tags.py +921 -0
  16. automation/dbmodels/users.py +259 -0
  17. automation/extensions/__init__.py +15 -0
  18. automation/extensions/api.py +149 -0
  19. automation/extensions/cors.py +18 -0
  20. automation/filter/__init__.py +19 -0
  21. automation/iad/__init__.py +3 -0
  22. automation/iad/frozen_data.py +54 -0
  23. automation/iad/out_of_range.py +51 -0
  24. automation/iad/outliers.py +51 -0
  25. automation/logger/__init__.py +0 -0
  26. automation/logger/alarms.py +426 -0
  27. automation/logger/core.py +265 -0
  28. automation/logger/datalogger.py +646 -0
  29. automation/logger/events.py +194 -0
  30. automation/logger/logdict.py +53 -0
  31. automation/logger/logs.py +203 -0
  32. automation/logger/machines.py +248 -0
  33. automation/logger/opcua_server.py +130 -0
  34. automation/logger/users.py +96 -0
  35. automation/managers/__init__.py +4 -0
  36. automation/managers/alarms.py +455 -0
  37. automation/managers/db.py +328 -0
  38. automation/managers/opcua_client.py +186 -0
  39. automation/managers/state_machine.py +183 -0
  40. automation/models.py +174 -0
  41. automation/modules/__init__.py +14 -0
  42. automation/modules/alarms/__init__.py +0 -0
  43. automation/modules/alarms/resources/__init__.py +10 -0
  44. automation/modules/alarms/resources/alarms.py +280 -0
  45. automation/modules/alarms/resources/summary.py +79 -0
  46. automation/modules/events/__init__.py +0 -0
  47. automation/modules/events/resources/__init__.py +10 -0
  48. automation/modules/events/resources/events.py +83 -0
  49. automation/modules/events/resources/logs.py +109 -0
  50. automation/modules/tags/__init__.py +0 -0
  51. automation/modules/tags/resources/__init__.py +8 -0
  52. automation/modules/tags/resources/tags.py +201 -0
  53. automation/modules/users/__init__.py +2 -0
  54. automation/modules/users/resources/__init__.py +10 -0
  55. automation/modules/users/resources/models/__init__.py +2 -0
  56. automation/modules/users/resources/models/roles.py +5 -0
  57. automation/modules/users/resources/models/users.py +14 -0
  58. automation/modules/users/resources/roles.py +38 -0
  59. automation/modules/users/resources/users.py +113 -0
  60. automation/modules/users/roles.py +121 -0
  61. automation/modules/users/users.py +335 -0
  62. automation/opcua/__init__.py +1 -0
  63. automation/opcua/models.py +541 -0
  64. automation/opcua/subscription.py +259 -0
  65. automation/pages/__init__.py +0 -0
  66. automation/pages/alarms.py +34 -0
  67. automation/pages/alarms_history.py +21 -0
  68. automation/pages/assets/styles.css +7 -0
  69. automation/pages/callbacks/__init__.py +28 -0
  70. automation/pages/callbacks/alarms.py +218 -0
  71. automation/pages/callbacks/alarms_summary.py +20 -0
  72. automation/pages/callbacks/db.py +222 -0
  73. automation/pages/callbacks/filter.py +238 -0
  74. automation/pages/callbacks/machines.py +29 -0
  75. automation/pages/callbacks/machines_detailed.py +581 -0
  76. automation/pages/callbacks/opcua.py +266 -0
  77. automation/pages/callbacks/opcua_server.py +244 -0
  78. automation/pages/callbacks/tags.py +495 -0
  79. automation/pages/callbacks/trends.py +119 -0
  80. automation/pages/communications.py +129 -0
  81. automation/pages/components/__init__.py +123 -0
  82. automation/pages/components/alarms.py +151 -0
  83. automation/pages/components/alarms_summary.py +45 -0
  84. automation/pages/components/database.py +128 -0
  85. automation/pages/components/gaussian_filter.py +69 -0
  86. automation/pages/components/machines.py +396 -0
  87. automation/pages/components/opcua.py +384 -0
  88. automation/pages/components/opcua_server.py +53 -0
  89. automation/pages/components/tags.py +253 -0
  90. automation/pages/components/trends.py +66 -0
  91. automation/pages/database.py +26 -0
  92. automation/pages/filter.py +55 -0
  93. automation/pages/machines.py +20 -0
  94. automation/pages/machines_detailed.py +41 -0
  95. automation/pages/main.py +63 -0
  96. automation/pages/opcua_server.py +28 -0
  97. automation/pages/tags.py +40 -0
  98. automation/pages/trends.py +35 -0
  99. automation/singleton.py +30 -0
  100. automation/state_machine.py +1672 -0
  101. automation/tags/__init__.py +2 -0
  102. automation/tags/cvt.py +1198 -0
  103. automation/tags/filter.py +55 -0
  104. automation/tags/tag.py +418 -0
  105. automation/tests/__init__.py +10 -0
  106. automation/tests/test_alarms.py +110 -0
  107. automation/tests/test_core.py +257 -0
  108. automation/tests/test_unit.py +21 -0
  109. automation/tests/test_user.py +155 -0
  110. automation/utils/__init__.py +164 -0
  111. automation/utils/decorators.py +222 -0
  112. automation/utils/npw.py +294 -0
  113. automation/utils/observer.py +21 -0
  114. automation/utils/units.py +118 -0
  115. automation/variables/__init__.py +55 -0
  116. automation/variables/adimentional.py +30 -0
  117. automation/variables/current.py +71 -0
  118. automation/variables/density.py +115 -0
  119. automation/variables/eng_time.py +68 -0
  120. automation/variables/force.py +90 -0
  121. automation/variables/length.py +104 -0
  122. automation/variables/mass.py +80 -0
  123. automation/variables/mass_flow.py +101 -0
  124. automation/variables/percentage.py +30 -0
  125. automation/variables/power.py +113 -0
  126. automation/variables/pressure.py +93 -0
  127. automation/variables/temperature.py +168 -0
  128. automation/variables/volume.py +70 -0
  129. automation/variables/volumetric_flow.py +100 -0
  130. automation/workers/__init__.py +2 -0
  131. automation/workers/logger.py +164 -0
  132. automation/workers/state_machine.py +207 -0
  133. automation/workers/worker.py +36 -0
  134. pyautomationio-0.0.0.dist-info/METADATA +198 -0
  135. pyautomationio-0.0.0.dist-info/RECORD +138 -0
  136. pyautomationio-0.0.0.dist-info/WHEEL +5 -0
  137. pyautomationio-0.0.0.dist-info/licenses/LICENSE +21 -0
  138. 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,8 @@
1
+ from ....extensions.api import api
2
+
3
+
4
+ def init_app():
5
+
6
+ from .tags import ns as ns_tags
7
+
8
+ api.add_namespace(ns_tags, path="/tags")
@@ -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,2 @@
1
+ from .roles import Roles, Role
2
+ from .users import Users
@@ -0,0 +1,10 @@
1
+ from ....extensions.api import api
2
+
3
+
4
+ def init_app():
5
+
6
+ from .users import ns as ns_users
7
+ from .roles import ns as ns_roles
8
+
9
+ api.add_namespace(ns_users, path="/users")
10
+ api.add_namespace(ns_roles, path="/users/roles")
@@ -0,0 +1,2 @@
1
+ from .roles import create_role_parser
2
+ from .users import login_parser, signup_parser
@@ -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()