restiny 0.5.0__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.
restiny/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.5.0'
1
+ __version__ = '0.6.1'
restiny/__main__.py CHANGED
@@ -15,6 +15,7 @@ def run_app() -> None:
15
15
  from restiny.consts import CONF_DIR, DB_FILE
16
16
  from restiny.data.db import DBManager
17
17
  from restiny.data.repos import (
18
+ EnvironmentsSQLRepo,
18
19
  FoldersSQLRepo,
19
20
  RequestsSQLRepo,
20
21
  SettingsSQLRepo,
@@ -29,6 +30,7 @@ def run_app() -> None:
29
30
  folders_repo=FoldersSQLRepo(db_manager=db_manager),
30
31
  requests_repo=RequestsSQLRepo(db_manager=db_manager),
31
32
  settings_repo=SettingsSQLRepo(db_manager=db_manager),
33
+ environments_repo=EnvironmentsSQLRepo(db_manager=db_manager),
32
34
  ).run()
33
35
 
34
36
 
restiny/assets/style.tcss CHANGED
@@ -71,6 +71,10 @@ Input {
71
71
  padding: 0;
72
72
  }
73
73
 
74
+ .p-1 {
75
+ padding: 1;
76
+ }
77
+
74
78
  .pt-1 {
75
79
  padding-top: 1;
76
80
  }
@@ -92,10 +96,14 @@ Input {
92
96
  padding-right: 1;
93
97
  }
94
98
 
95
- .m {
99
+ .m-0 {
96
100
  margin: 0;
97
101
  }
98
102
 
103
+ .m-1 {
104
+ margin: 1;
105
+ }
106
+
99
107
  .mt-1 {
100
108
  margin-top: 1;
101
109
  }
restiny/data/models.py CHANGED
@@ -12,7 +12,7 @@ class SQLFolder(SQLModelBase):
12
12
  __tablename__ = 'folders'
13
13
 
14
14
  id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
15
- name: Mapped[str] = mapped_column()
15
+ name: Mapped[str] = mapped_column(nullable=False)
16
16
  parent_id: Mapped[int | None] = mapped_column(
17
17
  ForeignKey('folders.id'), nullable=True
18
18
  )
@@ -38,24 +38,24 @@ class SQLRequest(SQLModelBase):
38
38
  folder_id: Mapped[int] = mapped_column(
39
39
  ForeignKey('folders.id'), nullable=False
40
40
  )
41
- name: Mapped[str] = mapped_column()
41
+ name: Mapped[str] = mapped_column(nullable=False)
42
42
 
43
- method: Mapped[str] = mapped_column()
44
- url: Mapped[str | None] = mapped_column()
45
- headers: Mapped[str | None] = mapped_column()
46
- params: Mapped[str | None] = mapped_column()
43
+ method: Mapped[str] = mapped_column(nullable=False)
44
+ url: Mapped[str | None] = mapped_column(nullable=True)
45
+ headers: Mapped[str | None] = mapped_column(nullable=True)
46
+ params: Mapped[str | None] = mapped_column(nullable=True)
47
47
 
48
- body_enabled: Mapped[bool] = mapped_column()
49
- body_mode: Mapped[str] = mapped_column()
50
- body: Mapped[str | None] = mapped_column()
48
+ body_enabled: Mapped[bool] = mapped_column(nullable=False)
49
+ body_mode: Mapped[str] = mapped_column(nullable=False)
50
+ body: Mapped[str | None] = mapped_column(nullable=True)
51
51
 
52
- auth_enabled: Mapped[bool] = mapped_column()
53
- auth_mode: Mapped[str] = mapped_column()
54
- auth: Mapped[str | None] = mapped_column()
52
+ auth_enabled: Mapped[bool] = mapped_column(nullable=False)
53
+ auth_mode: Mapped[str] = mapped_column(nullable=False)
54
+ auth: Mapped[str | None] = mapped_column(nullable=True)
55
55
 
56
- option_timeout: Mapped[float | None] = mapped_column()
57
- option_follow_redirects: Mapped[bool] = mapped_column()
58
- option_verify_ssl: Mapped[bool] = mapped_column()
56
+ option_timeout: Mapped[float | None] = mapped_column(nullable=True)
57
+ option_follow_redirects: Mapped[bool] = mapped_column(nullable=False)
58
+ option_verify_ssl: Mapped[bool] = mapped_column(nullable=False)
59
59
 
60
60
  created_at: Mapped[datetime] = mapped_column(
61
61
  DateTime(),
@@ -88,3 +88,24 @@ class SQLSettings(SQLModelBase):
88
88
  onupdate=func.current_timestamp(),
89
89
  nullable=False,
90
90
  )
91
+
92
+
93
+ class SQLEnvironment(SQLModelBase):
94
+ __tablename__ = 'environments'
95
+
96
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
97
+
98
+ name: Mapped[str] = mapped_column(nullable=False, unique=True)
99
+ variables: Mapped[str] = mapped_column(nullable=False)
100
+
101
+ created_at: Mapped[datetime] = mapped_column(
102
+ DateTime(),
103
+ default=func.current_timestamp(),
104
+ nullable=False,
105
+ )
106
+ updated_at: Mapped[datetime] = mapped_column(
107
+ DateTime(),
108
+ default=func.current_timestamp(),
109
+ onupdate=func.current_timestamp(),
110
+ nullable=False,
111
+ )
restiny/data/repos.py CHANGED
@@ -8,12 +8,17 @@ from enum import StrEnum
8
8
  from functools import wraps
9
9
  from typing import Any
10
10
 
11
- from sqlalchemy import select
11
+ from sqlalchemy import case, select
12
12
  from sqlalchemy.exc import IntegrityError, InterfaceError, OperationalError
13
13
 
14
14
  from restiny.data.db import DBManager
15
- from restiny.data.models import SQLFolder, SQLRequest, SQLSettings
16
- from restiny.entities import Folder, Request, Settings
15
+ from restiny.data.models import (
16
+ SQLEnvironment,
17
+ SQLFolder,
18
+ SQLRequest,
19
+ SQLSettings,
20
+ )
21
+ from restiny.entities import Environment, Folder, Request, Settings
17
22
 
18
23
 
19
24
  def safe_repo(func):
@@ -349,3 +354,102 @@ class SettingsSQLRepo(SQLRepoBase):
349
354
  created_at=settings.created_at,
350
355
  updated_at=settings.updated_at,
351
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
+ )
restiny/entities.py CHANGED
@@ -124,6 +124,109 @@ class Request(BaseModel):
124
124
  created_at: datetime | None = None
125
125
  updated_at: datetime | None = None
126
126
 
127
+ def resolve_variables(
128
+ self, variables: list[Environment.Variable]
129
+ ) -> 'Request':
130
+ def _resolve_variables(value: str) -> str:
131
+ new_value = value
132
+ for variable in variables:
133
+ if not variable.enabled:
134
+ continue
135
+
136
+ new_value = new_value.replace(
137
+ '{{' + variable.key + '}}', variable.value
138
+ )
139
+ new_value = new_value.replace(
140
+ '${' + variable.key + '}', variable.value
141
+ )
142
+ return new_value
143
+
144
+ resolved_url = _resolve_variables(self.url)
145
+
146
+ resolved_headers = [
147
+ self.Header(
148
+ enabled=header.enabled,
149
+ key=_resolve_variables(header.key),
150
+ value=_resolve_variables(header.value),
151
+ )
152
+ for header in self.headers
153
+ ]
154
+
155
+ resolved_params = [
156
+ self.Param(
157
+ enabled=param.enabled,
158
+ key=_resolve_variables(param.key),
159
+ value=_resolve_variables(param.value),
160
+ )
161
+ for param in self.params
162
+ ]
163
+
164
+ resolved_auth = self.auth
165
+ if self.auth_enabled:
166
+ if self.auth_mode == AuthMode.BASIC:
167
+ resolved_auth = self.BasicAuth(
168
+ username=_resolve_variables(self.auth.username),
169
+ password=_resolve_variables(self.auth.password),
170
+ )
171
+ elif self.auth_mode == AuthMode.BEARER:
172
+ resolved_auth = self.BearerAuth(
173
+ token=_resolve_variables(self.auth.token)
174
+ )
175
+ elif self.auth_mode == AuthMode.API_KEY:
176
+ resolved_auth = self.ApiKeyAuth(
177
+ key=_resolve_variables(self.auth.key),
178
+ value=_resolve_variables(self.auth.value),
179
+ where=self.auth.where,
180
+ )
181
+ elif self.auth_mode == AuthMode.DIGEST:
182
+ resolved_auth = self.DigestAuth(
183
+ username=_resolve_variables(self.auth.username),
184
+ password=_resolve_variables(self.auth.password),
185
+ )
186
+
187
+ resolved_body = self.body
188
+ if self.body_enabled:
189
+ if self.body_mode == BodyMode.RAW:
190
+ resolved_body = self.RawBody(
191
+ language=self.body.language,
192
+ value=_resolve_variables(self.body.value),
193
+ )
194
+ elif self.body_mode == BodyMode.FILE:
195
+ pass
196
+ elif self.body_mode == BodyMode.FORM_URLENCODED:
197
+ resolved_body = self.UrlEncodedFormBody(
198
+ fields=[
199
+ self.UrlEncodedFormBody.Field(
200
+ enabled=field.enabled,
201
+ key=_resolve_variables(field.key),
202
+ value=_resolve_variables(field.value),
203
+ )
204
+ for field in self.body.fields
205
+ ]
206
+ )
207
+ elif self.body_mode == BodyMode.FORM_MULTIPART:
208
+ resolved_body = self.MultipartFormBody(
209
+ fields=[
210
+ self.MultipartFormBody.Field(
211
+ value_kind=field.value_kind,
212
+ enabled=field.enabled,
213
+ key=_resolve_variables(field.key),
214
+ value=_resolve_variables(field.value),
215
+ )
216
+ for field in self.body.fields
217
+ ]
218
+ )
219
+
220
+ return self.model_copy(
221
+ update=dict(
222
+ url=resolved_url,
223
+ headers=resolved_headers,
224
+ params=resolved_params,
225
+ body=resolved_body,
226
+ auth=resolved_auth,
227
+ )
228
+ )
229
+
127
230
  def to_httpx_req(self) -> httpx.Request:
128
231
  headers: dict[str, str] = {
129
232
  header.key: header.value
@@ -260,7 +363,7 @@ class Request(BaseModel):
260
363
  body_files = None
261
364
  if self.body_enabled:
262
365
  if self.body_mode == BodyMode.RAW:
263
- body_raw = self.body
366
+ body_raw = self.body.value
264
367
  elif self.body_mode == BodyMode.FORM_URLENCODED:
265
368
  body_form_urlencoded = {
266
369
  form_field.key: form_field.value
@@ -318,3 +421,18 @@ class Settings(BaseModel):
318
421
 
319
422
  created_at: datetime | None = None
320
423
  updated_at: datetime | None = None
424
+
425
+
426
+ class Environment(BaseModel):
427
+ class Variable(BaseModel):
428
+ enabled: bool
429
+ key: str
430
+ value: str
431
+
432
+ id: int | None = None
433
+
434
+ name: str
435
+ variables: list[Variable] = _Field(default_factory=list)
436
+
437
+ created_at: datetime | None = None
438
+ updated_at: datetime | None = None
restiny/ui/__init__.py CHANGED
@@ -5,6 +5,7 @@ This module contains the specific sections of the DataFox user interface (UI).
5
5
  from restiny.ui.collections_area import CollectionsArea
6
6
  from restiny.ui.request_area import RequestArea
7
7
  from restiny.ui.response_area import ResponseArea
8
+ from restiny.ui.top_bar_area import TopBarArea
8
9
  from restiny.ui.url_area import URLArea
9
10
 
10
11
  __all__ = [
@@ -12,4 +13,5 @@ __all__ = [
12
13
  'ResponseArea',
13
14
  'URLArea',
14
15
  'CollectionsArea',
16
+ 'TopBarArea',
15
17
  ]