restiny 0.2.1__py3-none-any.whl → 0.6.1__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 (38) hide show
  1. restiny/__about__.py +1 -1
  2. restiny/__main__.py +28 -14
  3. restiny/assets/style.tcss +56 -2
  4. restiny/consts.py +236 -0
  5. restiny/data/db.py +60 -0
  6. restiny/data/models.py +111 -0
  7. restiny/data/repos.py +455 -0
  8. restiny/data/sql/__init__.py +3 -0
  9. restiny/entities.py +438 -0
  10. restiny/enums.py +14 -5
  11. restiny/httpx_auths.py +52 -0
  12. restiny/ui/__init__.py +17 -0
  13. restiny/ui/app.py +586 -0
  14. restiny/ui/collections_area.py +594 -0
  15. restiny/ui/environments_screen.py +270 -0
  16. restiny/ui/request_area.py +602 -0
  17. restiny/{core → ui}/response_area.py +4 -1
  18. restiny/ui/settings_screen.py +73 -0
  19. restiny/ui/top_bar_area.py +60 -0
  20. restiny/{core → ui}/url_area.py +54 -38
  21. restiny/utils.py +52 -15
  22. restiny/widgets/__init__.py +15 -1
  23. restiny/widgets/collections_tree.py +74 -0
  24. restiny/widgets/confirm_prompt.py +76 -0
  25. restiny/widgets/custom_input.py +20 -0
  26. restiny/widgets/dynamic_fields.py +65 -70
  27. restiny/widgets/password_input.py +161 -0
  28. restiny/widgets/path_chooser.py +12 -12
  29. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/METADATA +7 -5
  30. restiny-0.6.1.dist-info/RECORD +38 -0
  31. restiny/core/__init__.py +0 -15
  32. restiny/core/app.py +0 -348
  33. restiny/core/request_area.py +0 -337
  34. restiny-0.2.1.dist-info/RECORD +0 -24
  35. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/WHEEL +0 -0
  36. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/entry_points.txt +0 -0
  37. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/licenses/LICENSE +0 -0
  38. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/top_level.txt +0 -0
restiny/data/repos.py ADDED
@@ -0,0 +1,455 @@
1
+ import json
2
+ import sys
3
+ import traceback
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass
6
+ from datetime import UTC
7
+ from enum import StrEnum
8
+ from functools import wraps
9
+ from typing import Any
10
+
11
+ from sqlalchemy import case, select
12
+ from sqlalchemy.exc import IntegrityError, InterfaceError, OperationalError
13
+
14
+ from restiny.data.db import DBManager
15
+ from restiny.data.models import (
16
+ SQLEnvironment,
17
+ SQLFolder,
18
+ SQLRequest,
19
+ SQLSettings,
20
+ )
21
+ from restiny.entities import Environment, Folder, Request, Settings
22
+
23
+
24
+ def safe_repo(func):
25
+ @wraps(func)
26
+ def wrapper(*args, **kwargs):
27
+ try:
28
+ return func(*args, **kwargs)
29
+
30
+ except (
31
+ InterfaceError,
32
+ OperationalError,
33
+ ):
34
+ traceback.print_exc(file=sys.stderr)
35
+ return RepoResp(status=RepoStatus.DB_ERROR)
36
+
37
+ except IntegrityError as error:
38
+ error_msg = str(error)
39
+ if 'UNIQUE' in str(error_msg):
40
+ return RepoResp(status=RepoStatus.DUPLICATED)
41
+
42
+ traceback.print_exc(file=sys.stderr)
43
+ return RepoResp(status=RepoStatus.DB_ERROR)
44
+
45
+ return wrapper
46
+
47
+
48
+ class RepoStatus(StrEnum):
49
+ OK = 'ok'
50
+ NOT_FOUND = 'not_found'
51
+ DUPLICATED = 'duplicated'
52
+ DB_ERROR = 'db_error'
53
+
54
+
55
+ @dataclass
56
+ class RepoResp:
57
+ status: RepoStatus = RepoStatus.OK
58
+ data: Any = None
59
+
60
+ @property
61
+ def ok(self) -> bool:
62
+ return self.status == RepoStatus.OK
63
+
64
+
65
+ class SQLRepoBase(ABC):
66
+ def __init__(self, db_manager: DBManager):
67
+ self.db_manager = db_manager
68
+
69
+ @property
70
+ @abstractmethod
71
+ def _updatable_sql_fields(self) -> list[str]:
72
+ pass
73
+
74
+
75
+ class FoldersSQLRepo(SQLRepoBase):
76
+ @property
77
+ def _updatable_sql_fields(self) -> list[str]:
78
+ return [SQLFolder.parent_id.key, SQLFolder.name.key]
79
+
80
+ @safe_repo
81
+ def list_by_parent_id(self, parent_id: int) -> RepoResp:
82
+ with self.db_manager.session_scope() as session:
83
+ sql_folders = session.scalars(
84
+ select(SQLFolder)
85
+ .where(SQLFolder.parent_id == parent_id)
86
+ .order_by(SQLFolder.name.asc())
87
+ ).all()
88
+ folders = [
89
+ self._sql_to_folder(sql_folder) for sql_folder in sql_folders
90
+ ]
91
+ return RepoResp(data=folders)
92
+
93
+ @safe_repo
94
+ def list_roots(self) -> RepoResp:
95
+ with self.db_manager.session_scope() as session:
96
+ sql_folders = session.scalars(
97
+ select(SQLFolder)
98
+ .where(SQLFolder.parent_id.is_(None))
99
+ .order_by(SQLFolder.name.asc())
100
+ ).all()
101
+ folders = [
102
+ self._sql_to_folder(sql_folder) for sql_folder in sql_folders
103
+ ]
104
+ return RepoResp(data=folders)
105
+
106
+ @safe_repo
107
+ def get_by_id(self, id: int) -> RepoResp:
108
+ with self.db_manager.session_scope() as session:
109
+ sql_folder = session.get(SQLFolder, id)
110
+ if not sql_folder:
111
+ return RepoResp(status=RepoStatus.NOT_FOUND)
112
+
113
+ folder = self._sql_to_folder(sql_folder)
114
+ return RepoResp(data=folder)
115
+
116
+ @safe_repo
117
+ def create(self, folder: Folder) -> RepoResp:
118
+ with self.db_manager.session_scope() as session:
119
+ sql_folder = self._folder_to_sql(folder)
120
+ session.add(sql_folder)
121
+ session.flush()
122
+ new_folder = self._sql_to_folder(sql_folder)
123
+ return RepoResp(data=new_folder)
124
+
125
+ @safe_repo
126
+ def update(self, folder: Folder) -> RepoResp:
127
+ with self.db_manager.session_scope() as session:
128
+ sql_folder = session.get(SQLFolder, folder.id)
129
+ if not sql_folder:
130
+ return RepoResp(status=RepoStatus.NOT_FOUND)
131
+
132
+ new_data = self._folder_to_sql(folder)
133
+ for field in self._updatable_sql_fields:
134
+ setattr(sql_folder, field, getattr(new_data, field))
135
+
136
+ session.flush()
137
+
138
+ new_folder = self._sql_to_folder(sql_folder)
139
+ return RepoResp(data=new_folder)
140
+
141
+ @safe_repo
142
+ def delete_by_id(self, id: int) -> RepoResp:
143
+ with self.db_manager.session_scope() as session:
144
+ sql_folder = session.get(SQLFolder, id)
145
+ if not sql_folder:
146
+ return RepoResp(status=RepoStatus.NOT_FOUND)
147
+
148
+ session.delete(sql_folder)
149
+ return RepoResp()
150
+
151
+ def _sql_to_folder(self, sql_folder: SQLFolder) -> Folder:
152
+ return Folder(
153
+ id=sql_folder.id,
154
+ parent_id=sql_folder.parent_id,
155
+ name=sql_folder.name,
156
+ created_at=sql_folder.created_at.replace(tzinfo=UTC),
157
+ updated_at=sql_folder.updated_at.replace(tzinfo=UTC),
158
+ )
159
+
160
+ def _folder_to_sql(self, folder: Folder) -> SQLFolder:
161
+ return SQLFolder(
162
+ id=folder.id,
163
+ parent_id=folder.parent_id,
164
+ name=folder.name,
165
+ created_at=folder.created_at,
166
+ updated_at=folder.updated_at,
167
+ )
168
+
169
+
170
+ class RequestsSQLRepo(SQLRepoBase):
171
+ @safe_repo
172
+ def list_by_folder_id(self, folder_id: int) -> RepoResp:
173
+ with self.db_manager.session_scope() as session:
174
+ sql_requests = session.scalars(
175
+ select(SQLRequest)
176
+ .where(SQLRequest.folder_id == folder_id)
177
+ .order_by(SQLRequest.name.asc())
178
+ ).all()
179
+ requests = [
180
+ self._sql_to_request(sql_folder) for sql_folder in sql_requests
181
+ ]
182
+ return RepoResp(data=requests)
183
+
184
+ @safe_repo
185
+ def get_by_id(self, id: int) -> RepoResp:
186
+ with self.db_manager.session_scope() as session:
187
+ sql_request = session.get(SQLRequest, id)
188
+
189
+ if not sql_request:
190
+ return RepoResp(status=RepoStatus.NOT_FOUND)
191
+
192
+ request = self._sql_to_request(sql_request)
193
+ return RepoResp(data=request)
194
+
195
+ @safe_repo
196
+ def create(self, request: Request) -> RepoResp:
197
+ with self.db_manager.session_scope() as session:
198
+ sql_request = self._request_to_sql(request)
199
+ session.add(sql_request)
200
+ session.flush()
201
+ new_request = self._sql_to_request(sql_request)
202
+ return RepoResp(data=new_request)
203
+
204
+ @safe_repo
205
+ def update(self, request: Request) -> RepoResp:
206
+ with self.db_manager.session_scope() as session:
207
+ sql_request = session.get(SQLRequest, request.id)
208
+ if not sql_request:
209
+ return RepoResp(status=RepoStatus.NOT_FOUND)
210
+
211
+ new_data = self._request_to_sql(request)
212
+ for field in self._updatable_sql_fields:
213
+ setattr(sql_request, field, getattr(new_data, field))
214
+
215
+ session.flush()
216
+
217
+ new_request = self._sql_to_request(sql_request)
218
+ return RepoResp(data=new_request)
219
+
220
+ @safe_repo
221
+ def delete_by_id(self, id: int) -> RepoResp:
222
+ with self.db_manager.session_scope() as session:
223
+ sql_request = session.get(SQLRequest, id)
224
+ if not sql_request:
225
+ return RepoResp(status=RepoStatus.NOT_FOUND)
226
+
227
+ session.delete(sql_request)
228
+ return RepoResp()
229
+
230
+ @property
231
+ def _updatable_sql_fields(self) -> list[str]:
232
+ return [
233
+ SQLRequest.folder_id.key,
234
+ SQLRequest.name.key,
235
+ SQLRequest.method.key,
236
+ SQLRequest.url.key,
237
+ SQLRequest.headers.key,
238
+ SQLRequest.params.key,
239
+ SQLRequest.body_enabled.key,
240
+ SQLRequest.body_mode.key,
241
+ SQLRequest.body.key,
242
+ SQLRequest.auth_enabled.key,
243
+ SQLRequest.auth_mode.key,
244
+ SQLRequest.auth.key,
245
+ SQLRequest.option_timeout.key,
246
+ SQLRequest.option_follow_redirects.key,
247
+ SQLRequest.option_verify_ssl.key,
248
+ ]
249
+
250
+ def _sql_to_request(self, sql_request: SQLRequest) -> Request:
251
+ return Request(
252
+ id=sql_request.id,
253
+ folder_id=sql_request.folder_id,
254
+ name=sql_request.name,
255
+ method=sql_request.method,
256
+ url=sql_request.url,
257
+ headers=json.loads(sql_request.headers),
258
+ params=json.loads(sql_request.params),
259
+ body_enabled=sql_request.body_enabled,
260
+ body_mode=sql_request.body_mode,
261
+ body=json.loads(sql_request.body) if sql_request.body else None,
262
+ auth_enabled=sql_request.auth_enabled,
263
+ auth_mode=sql_request.auth_mode,
264
+ auth=json.loads(sql_request.auth) if sql_request.auth else None,
265
+ options=Request.Options(
266
+ timeout=sql_request.option_timeout,
267
+ follow_redirects=sql_request.option_follow_redirects,
268
+ verify_ssl=sql_request.option_verify_ssl,
269
+ ),
270
+ created_at=sql_request.created_at.replace(tzinfo=UTC),
271
+ updated_at=sql_request.updated_at.replace(tzinfo=UTC),
272
+ )
273
+
274
+ def _request_to_sql(self, request: Request) -> SQLRequest:
275
+ return SQLRequest(
276
+ id=request.id,
277
+ folder_id=request.folder_id,
278
+ name=request.name,
279
+ method=request.method,
280
+ url=request.url,
281
+ headers=json.dumps(
282
+ [header.model_dump() for header in request.headers]
283
+ ),
284
+ params=json.dumps(
285
+ [param.model_dump() for param in request.params]
286
+ ),
287
+ body_enabled=request.body_enabled,
288
+ body_mode=request.body_mode,
289
+ body=json.dumps(request.body.model_dump(), default=str)
290
+ if request.body
291
+ else None,
292
+ auth_enabled=request.auth_enabled,
293
+ auth_mode=request.auth_mode,
294
+ auth=json.dumps(request.auth.model_dump(), default=str)
295
+ if request.auth
296
+ else None,
297
+ option_timeout=request.options.timeout,
298
+ option_follow_redirects=request.options.follow_redirects,
299
+ option_verify_ssl=request.options.verify_ssl,
300
+ created_at=request.created_at,
301
+ updated_at=request.updated_at,
302
+ )
303
+
304
+
305
+ class SettingsSQLRepo(SQLRepoBase):
306
+ @safe_repo
307
+ def get(self) -> RepoResp:
308
+ with self.db_manager.session_scope() as session:
309
+ sql_settings = session.scalar(select(SQLSettings).limit(1))
310
+
311
+ if not sql_settings:
312
+ return RepoResp(data=Settings())
313
+
314
+ settings = self._sql_to_settings(sql_settings)
315
+ return RepoResp(data=settings)
316
+
317
+ @safe_repo
318
+ def set(self, settings: Settings) -> RepoResp:
319
+ with self.db_manager.session_scope() as session:
320
+ sql_settings = session.scalar(select(SQLSettings).limit(1))
321
+
322
+ if not sql_settings:
323
+ # create
324
+ sql_settings = self._settings_to_sql(settings=settings)
325
+ session.add(sql_settings)
326
+ session.flush()
327
+ new_settings = self._sql_to_settings(sql_settings=sql_settings)
328
+ return RepoResp(data=new_settings)
329
+ else:
330
+ # update
331
+ new_data = self._settings_to_sql(settings=settings)
332
+ for field in self._updatable_sql_fields:
333
+ setattr(sql_settings, field, getattr(new_data, field))
334
+ session.flush()
335
+ new_settings = self._sql_to_settings(sql_settings=sql_settings)
336
+ return RepoResp(data=new_settings)
337
+
338
+ @property
339
+ def _updatable_sql_fields(self) -> list[str]:
340
+ return [SQLSettings.theme.key]
341
+
342
+ def _sql_to_settings(self, sql_settings: SQLSettings) -> Settings:
343
+ return Settings(
344
+ id=sql_settings.id,
345
+ theme=sql_settings.theme,
346
+ created_at=sql_settings.created_at.replace(tzinfo=UTC),
347
+ updated_at=sql_settings.updated_at.replace(tzinfo=UTC),
348
+ )
349
+
350
+ def _settings_to_sql(self, settings: Settings) -> SQLSettings:
351
+ return SQLSettings(
352
+ id=settings.id,
353
+ theme=settings.theme,
354
+ created_at=settings.created_at,
355
+ updated_at=settings.updated_at,
356
+ )
357
+
358
+
359
+ class EnvironmentsSQLRepo(SQLRepoBase):
360
+ @safe_repo
361
+ def get_by_id(self, id: int) -> RepoResp:
362
+ with self.db_manager.session_scope() as session:
363
+ sql_environment = session.get(SQLEnvironment, id)
364
+
365
+ if not sql_environment:
366
+ return RepoResp(status=RepoStatus.NOT_FOUND)
367
+
368
+ environment = self._sql_to_environment(sql_environment)
369
+ return RepoResp(data=environment)
370
+
371
+ @safe_repo
372
+ def get_by_name(self, name: str) -> RepoResp:
373
+ with self.db_manager.session_scope() as session:
374
+ sql_environment = session.scalar(
375
+ select(SQLEnvironment).where(SQLEnvironment.name == name)
376
+ )
377
+
378
+ if not sql_environment:
379
+ return RepoResp(status=RepoStatus.NOT_FOUND)
380
+
381
+ environment = self._sql_to_environment(sql_environment)
382
+ return RepoResp(data=environment)
383
+
384
+ @safe_repo
385
+ def list(self) -> RepoResp:
386
+ with self.db_manager.session_scope() as session:
387
+ sql_envs = session.scalars(
388
+ select(SQLEnvironment).order_by(
389
+ case((SQLEnvironment.name == 'global', 0), else_=1),
390
+ SQLEnvironment.name.asc(),
391
+ )
392
+ )
393
+ envs = [self._sql_to_environment(sql_env) for sql_env in sql_envs]
394
+ return RepoResp(data=envs)
395
+
396
+ @safe_repo
397
+ def create(self, environment: Environment) -> RepoResp:
398
+ with self.db_manager.session_scope() as session:
399
+ sql_env = self._environment_to_sql(environment)
400
+ session.add(sql_env)
401
+ session.flush()
402
+ new_env = self._sql_to_environment(sql_env)
403
+ return RepoResp(data=new_env)
404
+
405
+ @safe_repo
406
+ def update(self, environment: Environment) -> RepoResp:
407
+ with self.db_manager.session_scope() as session:
408
+ sql_environment = session.get(SQLEnvironment, environment.id)
409
+ if not sql_environment:
410
+ return RepoResp(status=RepoStatus.NOT_FOUND)
411
+
412
+ new_data = self._environment_to_sql(environment)
413
+ for field in self._updatable_sql_fields:
414
+ setattr(sql_environment, field, getattr(new_data, field))
415
+
416
+ session.flush()
417
+
418
+ new_environment = self._sql_to_environment(sql_environment)
419
+ return RepoResp(data=new_environment)
420
+
421
+ @safe_repo
422
+ def delete_by_id(self, id: int) -> RepoResp:
423
+ with self.db_manager.session_scope() as session:
424
+ sql_environment = session.get(SQLEnvironment, id)
425
+ if not sql_environment:
426
+ return RepoResp(status=RepoStatus.NOT_FOUND)
427
+
428
+ session.delete(sql_environment)
429
+ return RepoResp()
430
+
431
+ @property
432
+ def _updatable_sql_fields(self) -> list[str]:
433
+ return [SQLEnvironment.name.key, SQLEnvironment.variables.key]
434
+
435
+ def _sql_to_environment(
436
+ self, sql_environment: SQLEnvironment
437
+ ) -> Environment:
438
+ return Environment(
439
+ id=sql_environment.id,
440
+ name=sql_environment.name,
441
+ variables=json.loads(sql_environment.variables),
442
+ created_at=sql_environment.created_at.replace(tzinfo=UTC),
443
+ updated_at=sql_environment.updated_at.replace(tzinfo=UTC),
444
+ )
445
+
446
+ def _environment_to_sql(self, environment: Environment) -> SQLEnvironment:
447
+ return SQLEnvironment(
448
+ id=environment.id,
449
+ name=environment.name,
450
+ variables=json.dumps(
451
+ [variable.model_dump() for variable in environment.variables]
452
+ ),
453
+ created_at=environment.created_at,
454
+ updated_at=environment.updated_at,
455
+ )
@@ -0,0 +1,3 @@
1
+ from restiny.consts import MODULE_DIR
2
+
3
+ SQL_DIR = MODULE_DIR.joinpath('data/sql')