meerschaum 2.9.4__py3-none-any.whl → 3.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 (201) hide show
  1. meerschaum/__init__.py +5 -2
  2. meerschaum/_internal/__init__.py +1 -0
  3. meerschaum/_internal/arguments/_parse_arguments.py +4 -4
  4. meerschaum/_internal/arguments/_parser.py +33 -4
  5. meerschaum/_internal/cli/__init__.py +6 -0
  6. meerschaum/_internal/cli/daemons.py +103 -0
  7. meerschaum/_internal/cli/entry.py +220 -0
  8. meerschaum/_internal/cli/workers.py +435 -0
  9. meerschaum/_internal/docs/index.py +48 -2
  10. meerschaum/_internal/entry.py +50 -14
  11. meerschaum/_internal/shell/Shell.py +121 -29
  12. meerschaum/_internal/shell/__init__.py +4 -1
  13. meerschaum/_internal/static.py +359 -0
  14. meerschaum/_internal/term/TermPageHandler.py +1 -2
  15. meerschaum/_internal/term/__init__.py +40 -6
  16. meerschaum/_internal/term/tools.py +33 -8
  17. meerschaum/actions/__init__.py +6 -4
  18. meerschaum/actions/api.py +53 -13
  19. meerschaum/actions/attach.py +1 -0
  20. meerschaum/actions/bootstrap.py +8 -8
  21. meerschaum/actions/delete.py +4 -2
  22. meerschaum/actions/edit.py +171 -25
  23. meerschaum/actions/login.py +8 -8
  24. meerschaum/actions/register.py +143 -6
  25. meerschaum/actions/reload.py +22 -5
  26. meerschaum/actions/restart.py +14 -0
  27. meerschaum/actions/show.py +184 -31
  28. meerschaum/actions/start.py +166 -17
  29. meerschaum/actions/stop.py +38 -2
  30. meerschaum/actions/sync.py +7 -2
  31. meerschaum/actions/tag.py +9 -8
  32. meerschaum/actions/verify.py +5 -8
  33. meerschaum/api/__init__.py +45 -15
  34. meerschaum/api/_events.py +46 -4
  35. meerschaum/api/_oauth2.py +162 -9
  36. meerschaum/api/_tokens.py +102 -0
  37. meerschaum/api/dash/__init__.py +0 -3
  38. meerschaum/api/dash/callbacks/__init__.py +1 -0
  39. meerschaum/api/dash/callbacks/custom.py +4 -3
  40. meerschaum/api/dash/callbacks/dashboard.py +228 -117
  41. meerschaum/api/dash/callbacks/jobs.py +14 -7
  42. meerschaum/api/dash/callbacks/login.py +10 -1
  43. meerschaum/api/dash/callbacks/pipes.py +194 -14
  44. meerschaum/api/dash/callbacks/plugins.py +0 -1
  45. meerschaum/api/dash/callbacks/register.py +10 -3
  46. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  47. meerschaum/api/dash/callbacks/tokens.py +389 -0
  48. meerschaum/api/dash/components.py +36 -15
  49. meerschaum/api/dash/jobs.py +1 -1
  50. meerschaum/api/dash/keys.py +35 -93
  51. meerschaum/api/dash/pages/__init__.py +2 -1
  52. meerschaum/api/dash/pages/dashboard.py +1 -20
  53. meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
  54. meerschaum/api/dash/pages/login.py +2 -2
  55. meerschaum/api/dash/pages/pipes.py +16 -5
  56. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  57. meerschaum/api/dash/pages/tokens.py +53 -0
  58. meerschaum/api/dash/pipes.py +438 -88
  59. meerschaum/api/dash/sessions.py +12 -0
  60. meerschaum/api/dash/tokens.py +603 -0
  61. meerschaum/api/dash/websockets.py +1 -1
  62. meerschaum/api/dash/webterm.py +18 -6
  63. meerschaum/api/models/__init__.py +23 -3
  64. meerschaum/api/models/_actions.py +22 -0
  65. meerschaum/api/models/_pipes.py +91 -7
  66. meerschaum/api/models/_tokens.py +81 -0
  67. meerschaum/api/resources/static/css/dash.css +16 -0
  68. meerschaum/api/resources/static/js/terminado.js +3 -0
  69. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  70. meerschaum/api/resources/templates/termpage.html +13 -0
  71. meerschaum/api/routes/__init__.py +1 -0
  72. meerschaum/api/routes/_actions.py +3 -4
  73. meerschaum/api/routes/_connectors.py +3 -7
  74. meerschaum/api/routes/_jobs.py +26 -35
  75. meerschaum/api/routes/_login.py +120 -15
  76. meerschaum/api/routes/_misc.py +5 -10
  77. meerschaum/api/routes/_pipes.py +178 -143
  78. meerschaum/api/routes/_plugins.py +38 -28
  79. meerschaum/api/routes/_tokens.py +236 -0
  80. meerschaum/api/routes/_users.py +47 -35
  81. meerschaum/api/routes/_version.py +3 -3
  82. meerschaum/api/routes/_webterm.py +3 -3
  83. meerschaum/config/__init__.py +100 -30
  84. meerschaum/config/_default.py +132 -64
  85. meerschaum/config/_edit.py +38 -32
  86. meerschaum/config/_formatting.py +2 -0
  87. meerschaum/config/_patch.py +10 -8
  88. meerschaum/config/_paths.py +133 -13
  89. meerschaum/config/_read_config.py +87 -36
  90. meerschaum/config/_sync.py +6 -3
  91. meerschaum/config/_version.py +1 -1
  92. meerschaum/config/environment.py +262 -0
  93. meerschaum/config/stack/__init__.py +37 -15
  94. meerschaum/config/static.py +18 -0
  95. meerschaum/connectors/_Connector.py +11 -6
  96. meerschaum/connectors/__init__.py +41 -22
  97. meerschaum/connectors/api/_APIConnector.py +34 -6
  98. meerschaum/connectors/api/_actions.py +2 -2
  99. meerschaum/connectors/api/_jobs.py +12 -1
  100. meerschaum/connectors/api/_login.py +33 -7
  101. meerschaum/connectors/api/_misc.py +2 -2
  102. meerschaum/connectors/api/_pipes.py +23 -32
  103. meerschaum/connectors/api/_plugins.py +2 -2
  104. meerschaum/connectors/api/_request.py +1 -1
  105. meerschaum/connectors/api/_tokens.py +146 -0
  106. meerschaum/connectors/api/_users.py +70 -58
  107. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  108. meerschaum/connectors/instance/__init__.py +10 -0
  109. meerschaum/connectors/instance/_pipes.py +442 -0
  110. meerschaum/connectors/instance/_plugins.py +159 -0
  111. meerschaum/connectors/instance/_tokens.py +317 -0
  112. meerschaum/connectors/instance/_users.py +188 -0
  113. meerschaum/connectors/parse.py +5 -2
  114. meerschaum/connectors/sql/_SQLConnector.py +22 -5
  115. meerschaum/connectors/sql/_cli.py +12 -11
  116. meerschaum/connectors/sql/_create_engine.py +12 -168
  117. meerschaum/connectors/sql/_fetch.py +2 -18
  118. meerschaum/connectors/sql/_pipes.py +295 -278
  119. meerschaum/connectors/sql/_plugins.py +29 -0
  120. meerschaum/connectors/sql/_sql.py +47 -22
  121. meerschaum/connectors/sql/_users.py +36 -2
  122. meerschaum/connectors/sql/tables/__init__.py +254 -122
  123. meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
  124. meerschaum/connectors/valkey/_pipes.py +60 -31
  125. meerschaum/connectors/valkey/_plugins.py +2 -26
  126. meerschaum/core/Pipe/__init__.py +115 -85
  127. meerschaum/core/Pipe/_attributes.py +425 -124
  128. meerschaum/core/Pipe/_bootstrap.py +54 -24
  129. meerschaum/core/Pipe/_cache.py +555 -0
  130. meerschaum/core/Pipe/_clear.py +0 -11
  131. meerschaum/core/Pipe/_data.py +96 -68
  132. meerschaum/core/Pipe/_deduplicate.py +0 -13
  133. meerschaum/core/Pipe/_delete.py +12 -21
  134. meerschaum/core/Pipe/_drop.py +11 -23
  135. meerschaum/core/Pipe/_dtypes.py +49 -19
  136. meerschaum/core/Pipe/_edit.py +14 -4
  137. meerschaum/core/Pipe/_fetch.py +1 -1
  138. meerschaum/core/Pipe/_index.py +8 -14
  139. meerschaum/core/Pipe/_show.py +5 -5
  140. meerschaum/core/Pipe/_sync.py +123 -204
  141. meerschaum/core/Pipe/_verify.py +4 -4
  142. meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
  143. meerschaum/core/Plugin/__init__.py +1 -1
  144. meerschaum/core/Token/_Token.py +220 -0
  145. meerschaum/core/Token/__init__.py +12 -0
  146. meerschaum/core/User/_User.py +35 -10
  147. meerschaum/core/User/__init__.py +9 -1
  148. meerschaum/core/__init__.py +1 -0
  149. meerschaum/jobs/_Executor.py +88 -4
  150. meerschaum/jobs/_Job.py +149 -38
  151. meerschaum/jobs/__init__.py +3 -2
  152. meerschaum/jobs/systemd.py +8 -3
  153. meerschaum/models/__init__.py +35 -0
  154. meerschaum/models/pipes.py +247 -0
  155. meerschaum/models/tokens.py +38 -0
  156. meerschaum/models/users.py +26 -0
  157. meerschaum/plugins/__init__.py +301 -88
  158. meerschaum/plugins/bootstrap.py +510 -4
  159. meerschaum/utils/_get_pipes.py +97 -30
  160. meerschaum/utils/daemon/Daemon.py +199 -43
  161. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  162. meerschaum/utils/daemon/RotatingFile.py +63 -36
  163. meerschaum/utils/daemon/StdinFile.py +53 -13
  164. meerschaum/utils/daemon/__init__.py +47 -6
  165. meerschaum/utils/daemon/_names.py +6 -3
  166. meerschaum/utils/dataframe.py +480 -82
  167. meerschaum/utils/debug.py +49 -19
  168. meerschaum/utils/dtypes/__init__.py +478 -37
  169. meerschaum/utils/dtypes/sql.py +369 -29
  170. meerschaum/utils/formatting/__init__.py +5 -2
  171. meerschaum/utils/formatting/_jobs.py +1 -1
  172. meerschaum/utils/formatting/_pipes.py +52 -50
  173. meerschaum/utils/formatting/_pprint.py +1 -0
  174. meerschaum/utils/formatting/_shell.py +44 -18
  175. meerschaum/utils/misc.py +268 -186
  176. meerschaum/utils/packages/__init__.py +25 -40
  177. meerschaum/utils/packages/_packages.py +42 -34
  178. meerschaum/utils/pipes.py +213 -0
  179. meerschaum/utils/process.py +2 -2
  180. meerschaum/utils/prompt.py +175 -144
  181. meerschaum/utils/schedule.py +2 -1
  182. meerschaum/utils/sql.py +135 -49
  183. meerschaum/utils/threading.py +42 -0
  184. meerschaum/utils/typing.py +1 -4
  185. meerschaum/utils/venv/_Venv.py +2 -2
  186. meerschaum/utils/venv/__init__.py +7 -7
  187. meerschaum/utils/warnings.py +19 -13
  188. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
  189. meerschaum-3.0.0.dist-info/RECORD +289 -0
  190. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
  191. meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
  192. meerschaum/api/models/_interfaces.py +0 -15
  193. meerschaum/api/models/_locations.py +0 -15
  194. meerschaum/api/models/_metrics.py +0 -15
  195. meerschaum/config/_environment.py +0 -145
  196. meerschaum/config/static/__init__.py +0 -186
  197. meerschaum-2.9.4.dist-info/RECORD +0 -263
  198. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
  199. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
  200. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
  201. {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
@@ -6,4 +6,4 @@
6
6
  Import the Plugin class.
7
7
  """
8
8
 
9
- from meerschaum.plugins import Plugin
9
+ from meerschaum.core.Plugin._Plugin import Plugin
@@ -0,0 +1,220 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define the properties of a long-lived access token.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import uuid
12
+ from random import randint
13
+ from typing import Optional, Union, List, Tuple
14
+ from datetime import datetime, timedelta, timezone
15
+
16
+ import meerschaum as mrsm
17
+
18
+ _PLACEHOLDER_EXPIRATION = datetime(2000, 1, 1)
19
+
20
+ class Token:
21
+ """
22
+ Tokens (long lived access tokens) may be registered and revoked to provide easier authentication (e.g. IoT devices).
23
+ Tokens must be tied to a Meerschaum user account.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ label: Optional[str] = None,
29
+ creation: Optional[datetime] = None,
30
+ expiration: Optional[datetime] = _PLACEHOLDER_EXPIRATION,
31
+ instance: Optional[str] = None,
32
+ user: Optional[mrsm.core.User] = None,
33
+ user_id: Union[int, str, uuid.UUID, None] = None,
34
+ scopes: Optional[List[str]] = None,
35
+ is_valid: bool = True,
36
+ id: Optional[uuid.UUID] = None,
37
+ secret: Optional[str] = None,
38
+ secret_hash: Optional[str] = None,
39
+ ):
40
+ from meerschaum.utils.dtypes import coerce_timezone, round_time
41
+ from meerschaum.utils.daemon import get_new_daemon_name
42
+ from meerschaum._internal.static import STATIC_CONFIG
43
+ now = datetime.now(timezone.utc)
44
+ default_expiration_days = mrsm.get_config(
45
+ 'api', 'tokens', 'default_expiration_days',
46
+ ) or 366
47
+ default_expiration = round_time(
48
+ now + timedelta(days=default_expiration_days),
49
+ timedelta(days=1),
50
+ )
51
+ if expiration == _PLACEHOLDER_EXPIRATION:
52
+ expiration = default_expiration
53
+ self.creation = coerce_timezone(creation) if creation is not None else None
54
+ self.expiration = coerce_timezone(expiration) if expiration is not None else None
55
+ self._instance_keys = str(instance) if instance is not None else None
56
+ self.label = label or get_new_daemon_name()
57
+ self._user = user
58
+ self._user_id = user_id
59
+ self.scopes = scopes or list(STATIC_CONFIG['tokens']['scopes'])
60
+ self.is_valid = is_valid
61
+ self.id = id
62
+ self.secret = secret
63
+ self.secret_hash = secret_hash
64
+
65
+ def generate_credentials(self) -> Tuple[uuid.UUID, str]:
66
+ """
67
+ Generate and return the client ID and secret values for this token.
68
+ """
69
+ if self.id and self.secret:
70
+ return self.id, self.secret
71
+
72
+ from meerschaum.utils.misc import generate_password
73
+ from meerschaum._internal.static import STATIC_CONFIG
74
+ min_len = STATIC_CONFIG['tokens']['minimum_length']
75
+ max_len = STATIC_CONFIG['tokens']['maximum_length']
76
+
77
+ secret_len = randint(min_len, max_len + 1)
78
+ self.secret = generate_password(secret_len)
79
+ self.id = uuid.uuid4()
80
+ return self.id, self.secret
81
+
82
+ def get_api_key(self) -> str:
83
+ """
84
+ Return the API key to be sent in the `Authorization` header.
85
+ """
86
+ return 'mrsm-key:' + base64.b64encode(f"{self.id}:{self.secret}".encode('utf-8')).decode('utf-8')
87
+
88
+ @property
89
+ def instance_connector(self) -> mrsm.connectors.InstanceConnector:
90
+ """
91
+ Return the instance connector to use for this token.
92
+ """
93
+ from meerschaum.connectors.parse import parse_instance_keys
94
+ return parse_instance_keys(self._instance_keys)
95
+
96
+ @property
97
+ def user(self) -> Union[mrsm.core.User, None]:
98
+ """
99
+ Return the `User` for this token.
100
+ """
101
+ if self._user is not None:
102
+ return self._user
103
+
104
+ if self._user_id is not None:
105
+ username = self.instance_connector.get_username(self._user_id)
106
+ if not username:
107
+ return None
108
+ _user = mrsm.core.User(
109
+ username,
110
+ user_id=self._user_id,
111
+ instance=str(self.instance_connector),
112
+ )
113
+ self._user = _user
114
+ return _user
115
+
116
+ return None
117
+
118
+ def register(self, debug: bool = False) -> mrsm.SuccessTuple:
119
+ """
120
+ Register the new token to the configured instance.
121
+ """
122
+ if self.user is None:
123
+ return False, "Cannot register a token without a user."
124
+
125
+ return self.instance_connector.register_token(self, debug=debug)
126
+
127
+ def edit(self, debug: bool = False) -> mrsm.SuccessTuple:
128
+ """
129
+ Edit some of the token's attributes (expiration, scopes).
130
+ """
131
+ return self.instance_connector.edit_token(self, debug=debug)
132
+
133
+ def invalidate(self, debug: bool = False) -> mrsm.SuccessTuple:
134
+ """
135
+ Set `is_valid` to False for this token.
136
+ """
137
+ self.is_valid = False
138
+ return self.instance_connector.invalidate_token(self, debug=debug)
139
+
140
+ def delete(self, debug: bool = False) -> mrsm.SuccessTuple:
141
+ """
142
+ Delete this token from the instance connector.
143
+ """
144
+ return self.instance_connector.delete_token(self, debug=debug)
145
+
146
+ def exists(self, debug: bool = False) -> bool:
147
+ """
148
+ Return `True` if a token's ID exists in the tokens pipe.
149
+ """
150
+ if not self.id:
151
+ return False
152
+ return self.instance_connector.token_exists(self.id, debug=debug)
153
+
154
+ def to_model(self, refresh: bool = False, debug: bool = False) -> 'TokenModel':
155
+ """
156
+ Export the current state to a `TokenModel`.
157
+ """
158
+ from meerschaum.models import TokenModel
159
+ in_memory_doc = {
160
+ 'id': self.id,
161
+ 'label': self.label,
162
+ 'creation': self.creation,
163
+ 'expiration': self.expiration,
164
+ 'is_valid': self.is_valid,
165
+ 'user_id': self._user_id,
166
+ 'scopes': self.scopes,
167
+ }
168
+ if not refresh:
169
+ return TokenModel(**in_memory_doc)
170
+
171
+ if not self.id:
172
+ raise ValueError(f"ID is not set for {self}.")
173
+
174
+ token_model = self.instance_connector.get_token_model(self.id, debug=debug)
175
+ if token_model is None:
176
+ raise ValueError(f"{self} does not exist on instance '{self.instance_connector}'.")
177
+
178
+ return token_model
179
+
180
+ def get_scopes(self, refresh: bool = False, debug: bool = False) -> List[str]:
181
+ """
182
+ Return the scopes for this `Token`.
183
+ """
184
+ if not refresh:
185
+ return self.scopes
186
+
187
+ self.scopes = self.instance_connector.get_token_scopes(self, debug=debug)
188
+ return self.scopes
189
+
190
+ def get_expiration_status(self, debug: bool = False) -> bool:
191
+ """
192
+ Check the token's expiration against the current timestamp.
193
+ If it's expired, invalidate the token.
194
+
195
+ Returns
196
+ -------
197
+ A bool to indication whether the token has expired.
198
+ A value of `True` means the token is invalid,
199
+ and `False` indicates a valid token.
200
+ """
201
+ expiration = self.expiration
202
+ if expiration is None:
203
+ return False
204
+
205
+ now = datetime.now(timezone.utc)
206
+ is_expired = expiration <= now
207
+ if is_expired:
208
+ self.is_valid = False
209
+ invalidate_success, invalidate_msg = self.invalidate(debug=debug)
210
+ if not invalidate_success:
211
+ from meerschaum.utils.warnings import warn
212
+ warn(f"Failed to invalidate {self}:\n{invalidate_msg}")
213
+
214
+ return is_expired
215
+
216
+ def __str__(self):
217
+ return self.label
218
+
219
+ def __repr__(self):
220
+ return self.to_model(refresh=False).__repr__().replace('TokenModel(', 'Token(')
@@ -0,0 +1,12 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define the components of long-lived access tokens.
6
+ """
7
+
8
+ from meerschaum.core.Token._Token import Token
9
+
10
+ __all__ = (
11
+ 'Token',
12
+ )
@@ -7,14 +7,15 @@ User class definition
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
10
11
  import os
11
12
  import hashlib
12
13
  import hmac
14
+ import uuid
13
15
  from binascii import b2a_base64, a2b_base64, Error as _BinAsciiError
14
16
 
15
17
  import meerschaum as mrsm
16
- from meerschaum.utils.typing import Optional, Dict, Any, Union
17
- from meerschaum.config.static import STATIC_CONFIG
18
+ from meerschaum.utils.typing import Optional, Dict, Any, Union, List
18
19
  from meerschaum.utils.warnings import warn
19
20
 
20
21
 
@@ -40,7 +41,7 @@ def hash_password(
40
41
 
41
42
  rounds: Optional[int], default None
42
43
  If provided, use this number of rounds to generate the hash.
43
- Defaults to 3,000,000.
44
+ Defaults to 1,000,000.
44
45
 
45
46
  Returns
46
47
  -------
@@ -48,6 +49,7 @@ def hash_password(
48
49
  See the `passlib` documentation on the string format:
49
50
  https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html#format-algorithm
50
51
  """
52
+ from meerschaum._internal.static import STATIC_CONFIG
51
53
  hash_config = STATIC_CONFIG['users']['password_hash']
52
54
  if password is None:
53
55
  password = ''
@@ -64,7 +66,7 @@ def hash_password(
64
66
  )
65
67
  return (
66
68
  f"$pbkdf2-{hash_config['algorithm_name']}"
67
- + f"${hash_config['pbkdf2_sha256__default_rounds']}"
69
+ + f"${rounds}"
68
70
  + '$' + ab64_encode(salt).decode('utf-8')
69
71
  + '$' + ab64_encode(pw_hash).decode('utf-8')
70
72
  )
@@ -89,16 +91,16 @@ def verify_password(
89
91
  -------
90
92
  A `bool` indicating whether `password` matches `password_hash`.
91
93
  """
94
+ from meerschaum._internal.static import STATIC_CONFIG
92
95
  if password is None or password_hash is None:
93
96
  return False
94
- hash_config = STATIC_CONFIG['users']['password_hash']
95
97
  try:
96
98
  digest, rounds_str, encoded_salt, encoded_checksum = password_hash.split('$')[1:]
97
99
  algorithm_name = digest.split('-')[-1]
98
100
  salt = ab64_decode(encoded_salt)
99
101
  checksum = ab64_decode(encoded_checksum)
100
102
  rounds = int(rounds_str)
101
- except Exception as e:
103
+ except Exception:
102
104
  warn(f"Failed to extract context from password hash '{password_hash}'. Is it corrupted?")
103
105
  return False
104
106
 
@@ -177,7 +179,7 @@ class User:
177
179
  type: Optional[str] = None,
178
180
  email: Optional[str] = None,
179
181
  attributes: Optional[Dict[str, Any]] = None,
180
- user_id: Optional[int] = None,
182
+ user_id: Union[int, str, uuid.UUID, None] = None,
181
183
  instance: Optional[str] = None
182
184
  ):
183
185
  if password is None:
@@ -188,7 +190,7 @@ class User:
188
190
  self.type = type
189
191
  self._attributes = attributes
190
192
  self._user_id = user_id
191
- self._instance_keys = str(instance)
193
+ self._instance_keys = str(instance) if instance is not None else None
192
194
 
193
195
  def __repr__(self):
194
196
  return str(self)
@@ -203,14 +205,14 @@ class User:
203
205
  return self._attributes
204
206
 
205
207
  @property
206
- def instance_connector(self) -> 'mrsm.connectors.Connector':
208
+ def instance_connector(self) -> mrsm.connectors.InstanceConnector:
207
209
  from meerschaum.connectors.parse import parse_instance_keys
208
210
  if '_instance_connector' not in self.__dict__:
209
211
  self._instance_connector = parse_instance_keys(self._instance_keys)
210
212
  return self._instance_connector
211
213
 
212
214
  @property
213
- def user_id(self) -> Union[int, str, None]:
215
+ def user_id(self) -> Union[int, str, uuid.UUID, None]:
214
216
  """NOTE: This causes recursion with the API,
215
217
  so don't try to get fancy with read-only attributes.
216
218
  """
@@ -231,3 +233,26 @@ class User:
231
233
 
232
234
  self._password_hash = hash_password(self.password)
233
235
  return self._password_hash
236
+
237
+ def get_attributes(self, refresh: bool = False, debug: bool = False) -> Dict[str, Any]:
238
+ """
239
+ Return the user's attributes.
240
+ """
241
+ if not refresh:
242
+ return self.attributes
243
+ self._attributes = self.instance_connector.get_user_attributes(self, debug=debug) or {}
244
+ return self._attributes
245
+
246
+ def get_scopes(self, refresh: bool = False, debug: bool = False) -> List[str]:
247
+ """
248
+ Return the scopes for this user.
249
+ """
250
+ from meerschaum._internal.static import STATIC_CONFIG
251
+ _attributes = self.get_attributes(refresh=refresh, debug=debug)
252
+ return _attributes.get('scopes', None) or list(STATIC_CONFIG['tokens']['scopes'])
253
+
254
+ def register(self, debug: bool = False, **kwargs: Any) -> mrsm.SuccessTuple:
255
+ """
256
+ Register a user to the instance connector.
257
+ """
258
+ return self.instance_connector.register_user(self, debug=debug, **kwargs)
@@ -9,7 +9,15 @@ Manager users' metadata via the User class
9
9
  from typing import Optional
10
10
 
11
11
  import meerschaum as mrsm
12
- from meerschaum.core.User._User import User
12
+ from meerschaum.core.User._User import User, hash_password, verify_password
13
+
14
+
15
+ __all__ = (
16
+ 'User',
17
+ 'hash_password',
18
+ 'verify_password',
19
+ 'is_user_allowed_to_execute',
20
+ )
13
21
 
14
22
 
15
23
  def is_user_allowed_to_execute(
@@ -9,3 +9,4 @@ Import the core class definitions into the parent module.
9
9
  from meerschaum.core.Pipe import Pipe
10
10
  from meerschaum.core.Plugin import Plugin
11
11
  from meerschaum.core.User import User
12
+ from meerschaum.core.Token import Token
@@ -10,28 +10,72 @@ from __future__ import annotations
10
10
  from abc import abstractmethod
11
11
 
12
12
  from meerschaum.connectors import Connector
13
- from meerschaum.utils.typing import List, Dict, SuccessTuple, TYPE_CHECKING, Optional, Any
13
+ from meerschaum.utils.typing import (
14
+ List, Dict, SuccessTuple, TYPE_CHECKING, Optional, Any, Union, Callable,
15
+ )
14
16
 
15
17
  if TYPE_CHECKING:
16
18
  from meerschaum.jobs import Job
19
+ from datetime import datetime
17
20
 
18
21
  class Executor(Connector):
19
22
  """
20
23
  Define the methods for managing jobs.
21
24
  """
22
25
 
26
+ @abstractmethod
27
+ def get_job_names(self, debug: bool = False) -> List[str]:
28
+ """
29
+ Return a list of existing jobs, including hidden ones.
30
+ """
31
+
23
32
  @abstractmethod
24
33
  def get_job_exists(self, name: str, debug: bool = False) -> bool:
25
34
  """
26
35
  Return whether a job exists.
27
36
  """
28
-
37
+
38
+ @abstractmethod
39
+ def get_jobs(self, debug: bool = False) -> Dict[str, Job]:
40
+ """
41
+ Return a dictionary of existing jobs.
42
+ """
43
+
44
+ @abstractmethod
45
+ def get_job_metadata(self, name: str, debug: bool = False) -> Dict[str, Any]:
46
+ """
47
+ Return a job's metadata.
48
+ """
49
+
50
+ @abstractmethod
51
+ def get_job_properties(self, name: str, debug: bool = False) -> Dict[str, Any]:
52
+ """
53
+ Return the underlying daemon's properties.
54
+ """
29
55
  @abstractmethod
30
- def get_jobs(self) -> Dict[str, Job]:
56
+ def get_job_status(self, name: str, debug: bool = False) -> str:
31
57
  """
32
- Return a dictionary of names -> Jobs.
58
+ Return the job's status.
33
59
  """
34
60
 
61
+ @abstractmethod
62
+ def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
63
+ """
64
+ Return when a job began running.
65
+ """
66
+
67
+ @abstractmethod
68
+ def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
69
+ """
70
+ Return when a job stopped running.
71
+ """
72
+
73
+ @abstractmethod
74
+ def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
75
+ """
76
+ Return a job's `paused` timestamp, if it exists.
77
+ """
78
+
35
79
  @abstractmethod
36
80
  def create_job(
37
81
  self,
@@ -73,3 +117,43 @@ class Executor(Connector):
73
117
  """
74
118
  Return a job's log output.
75
119
  """
120
+
121
+ @abstractmethod
122
+ def get_job_stop_time(self, name: str, debug: bool = False) -> Union[datetime, None]:
123
+ """
124
+ Return the job's manual stop time.
125
+ """
126
+
127
+ @abstractmethod
128
+ async def monitor_logs_async(
129
+ self,
130
+ name: str,
131
+ callback_function: Callable[[Any], Any],
132
+ input_callback_function: Callable[[], str],
133
+ stop_callback_function: Callable[[SuccessTuple], str],
134
+ stop_on_exit: bool = False,
135
+ strip_timestamps: bool = False,
136
+ accept_input: bool = True,
137
+ debug: bool = False,
138
+ ):
139
+ """
140
+ Monitor a job's log files and await a callback with the changes.
141
+ """
142
+
143
+ @abstractmethod
144
+ def monitor_logs(self, *args, **kwargs):
145
+ """
146
+ Monitor a job's log files.
147
+ """
148
+
149
+ @abstractmethod
150
+ def get_job_is_blocking_on_stdin(self, name: str, debug: bool = False) -> bool:
151
+ """
152
+ Return whether a job is blocking on stdin.
153
+ """
154
+
155
+ @abstractmethod
156
+ def get_job_prompt_kwargs(self, name: str, debug: bool = False) -> Dict[str, Any]:
157
+ """
158
+ Return the kwargs to the blocking prompt.
159
+ """