zrb 1.0.0b9__py3-none-any.whl → 1.1.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 (88) hide show
  1. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
  2. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
  3. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +99 -55
  4. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_util.py +301 -0
  5. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
  6. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +131 -2
  7. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +128 -5
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/gateway/view/content/my-module/my-entity.html +297 -0
  9. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_create_my_entity.py +53 -0
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_delete_my_entity.py +62 -0
  11. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_read_my_entity.py +65 -0
  12. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_update_my_entity.py +61 -0
  13. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +81 -13
  14. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/navigation_config_file.py +8 -0
  15. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +8 -0
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -3
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +8 -1
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/navigation_config_file.py +6 -0
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +56 -12
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +10 -4
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +136 -52
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +3 -3
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +1 -1
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +19 -8
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +46 -43
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +5 -2
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +16 -21
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/config/navigation.py +39 -0
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +52 -11
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/schema/navigation.py +95 -0
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +277 -44
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +66 -0
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +33 -8
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/permission.html +311 -0
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/role.html +0 -0
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/user.html +0 -0
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +4 -1
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/login.html +67 -0
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/logout.html +49 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/common/util.js +160 -0
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/style.css +14 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/util.js +94 -0
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/pico-style.css +23 -0
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +44 -0
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/style.css +102 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +73 -18
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +6 -1
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +1 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +9 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
  56. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
  57. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
  58. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
  59. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +15 -0
  60. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
  61. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
  62. zrb/runner/web_route/refresh_token_api_route.py +1 -1
  63. zrb/runner/web_route/static/refresh-token.template.js +9 -0
  64. zrb/runner/web_route/static/static_route.py +1 -1
  65. zrb/task/base_task.py +10 -10
  66. zrb/util/codemod/modification_mode.py +3 -0
  67. zrb/util/codemod/modify_class.py +58 -0
  68. zrb/util/codemod/modify_class_parent.py +68 -0
  69. zrb/util/codemod/modify_class_property.py +128 -0
  70. zrb/util/codemod/modify_dict.py +75 -0
  71. zrb/util/codemod/modify_function.py +65 -0
  72. zrb/util/codemod/modify_function_call.py +68 -0
  73. zrb/util/codemod/modify_method.py +88 -0
  74. zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
  75. zrb/util/file.py +3 -2
  76. zrb/util/load.py +13 -7
  77. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/METADATA +2 -2
  78. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/RECORD +80 -46
  79. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
  80. zrb/util/codemod/append_code_to_class.py +0 -35
  81. zrb/util/codemod/append_code_to_function.py +0 -38
  82. zrb/util/codemod/append_code_to_method.py +0 -55
  83. zrb/util/codemod/append_key_to_dict.py +0 -51
  84. zrb/util/codemod/append_param_to_function_call.py +0 -39
  85. zrb/util/codemod/prepend_parent_to_class.py +0 -38
  86. zrb/util/codemod/prepend_property_to_class.py +0 -55
  87. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/WHEEL +0 -0
  88. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,66 @@
1
+ from fastapi.testclient import TestClient
2
+ from my_app_name.main import app
3
+ from my_app_name.test._util.access_token import get_admin_access_token
4
+
5
+
6
+ def test_update_permission():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_permission_data = {
10
+ "name": "to-be-updated-permission",
11
+ "description": "to-be-updated-permission-description",
12
+ }
13
+ insert_response = client.post(
14
+ "/api/v1/permissions",
15
+ json=new_permission_data,
16
+ headers={"Authorization": f"Bearer {access_token}"},
17
+ )
18
+ assert insert_response.status_code == 200
19
+ id = insert_response.json().get("id")
20
+ # updating
21
+ updated_permission_data = {
22
+ "name": "updated-permission",
23
+ "description": "updated-permission-description",
24
+ }
25
+ response = client.put(
26
+ f"/api/v1/permissions/{id}",
27
+ json=updated_permission_data,
28
+ headers={"Authorization": f"Bearer {access_token}"},
29
+ )
30
+ assert response.status_code == 200
31
+ response_data = response.json()
32
+ assert response_data.get("id") == id
33
+ assert response_data.get("name") == "updated-permission"
34
+ assert response_data.get("description") == "updated-permission-description"
35
+
36
+
37
+ def test_update_permission_bulk():
38
+ client = TestClient(app, base_url="http://localhost")
39
+ access_token = get_admin_access_token()
40
+ new_first_permission_data = {
41
+ "name": "to-be-updated-permission-bulk-1",
42
+ "description": "to-be-updated-permission-bulk-description-1",
43
+ }
44
+ insert_response = client.post(
45
+ "/api/v1/permissions/bulk",
46
+ json=[new_first_permission_data],
47
+ headers={"Authorization": f"Bearer {access_token}"},
48
+ )
49
+ assert insert_response.status_code == 200
50
+ ids = [row["id"] for row in insert_response.json()]
51
+ # updating (we only test with one data)
52
+ updated_permission_data = {"description": "updated-permission-description"}
53
+ response = client.put(
54
+ f"/api/v1/permissions/bulk",
55
+ json={
56
+ "permission_ids": ids,
57
+ "data": updated_permission_data,
58
+ },
59
+ headers={"Authorization": f"Bearer {access_token}"},
60
+ )
61
+ assert response.status_code == 200
62
+ response_data = response.json()
63
+ assert len(response_data) == 1
64
+ response_data[0].get("id") == ids[0]
65
+ response_data[0].get("name") == new_first_permission_data["name"]
66
+ response_data[0].get("description") == new_first_permission_data["description"]
@@ -0,0 +1,195 @@
1
+ import time
2
+ from typing import Annotated
3
+
4
+ from fastapi import Depends
5
+ from fastapi.testclient import TestClient
6
+ from my_app_name.config import (
7
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME,
8
+ APP_AUTH_GUEST_USER,
9
+ APP_AUTH_GUEST_USER_PERMISSIONS,
10
+ APP_AUTH_REFRESH_TOKEN_COOKIE_NAME,
11
+ APP_AUTH_SUPER_USER,
12
+ APP_AUTH_SUPER_USER_PASSWORD,
13
+ )
14
+ from my_app_name.main import app
15
+ from my_app_name.module.gateway.util.auth import get_current_user
16
+ from my_app_name.schema.user import AuthUserResponse
17
+
18
+
19
+ @app.get("/test/current-user", response_model=AuthUserResponse)
20
+ async def serve_get_current_user(
21
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
22
+ ) -> AuthUserResponse:
23
+ return current_user
24
+
25
+
26
+ def test_create_invalid_user_session():
27
+ client = TestClient(app, base_url="http://localhost")
28
+ response = client.post(
29
+ "/api/v1/user-sessions",
30
+ data={
31
+ "username": "nonExistingUserFromSevenKingdom",
32
+ "password": "nonExistingSecurity",
33
+ },
34
+ )
35
+ assert response.status_code == 401
36
+
37
+
38
+ def test_get_guest_user():
39
+ client = TestClient(app, base_url="http://localhost")
40
+ # Fetch current user
41
+ response = client.get("/test/current-user")
42
+ assert response.status_code == 200
43
+ user_data = response.json()
44
+ assert user_data.get("username") == APP_AUTH_GUEST_USER
45
+ assert not user_data.get("is_super_user")
46
+ assert user_data.get("is_guest")
47
+ assert user_data.get("permission_names") == APP_AUTH_GUEST_USER_PERMISSIONS
48
+
49
+
50
+ def test_create_admin_user_session():
51
+ client = TestClient(app, base_url="http://localhost")
52
+ # Create new admin user session and check the response
53
+ session_response = client.post(
54
+ "/api/v1/user-sessions",
55
+ data={
56
+ "username": APP_AUTH_SUPER_USER,
57
+ "password": APP_AUTH_SUPER_USER_PASSWORD,
58
+ },
59
+ )
60
+ assert session_response.status_code == 200
61
+ session_data = session_response.json()
62
+ assert session_data.get("user_id") == APP_AUTH_SUPER_USER
63
+ assert session_data.get("token_type") == "bearer"
64
+ assert "access_token" in session_data
65
+ assert "access_token_expired_at" in session_data
66
+ assert "refresh_token" in session_data
67
+ assert "refresh_token_expired_at" in session_data
68
+ assert session_response.cookies.get(
69
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
70
+ ) == session_data.get("access_token")
71
+ assert session_response.cookies.get(
72
+ APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
73
+ ) == session_data.get("refresh_token")
74
+
75
+
76
+ def test_get_admin_user_with_bearer():
77
+ client = TestClient(app, base_url="http://localhost")
78
+ session_response = client.post(
79
+ "/api/v1/user-sessions",
80
+ data={
81
+ "username": APP_AUTH_SUPER_USER,
82
+ "password": APP_AUTH_SUPER_USER_PASSWORD,
83
+ },
84
+ )
85
+ assert session_response.status_code == 200
86
+ access_token = session_response.json()["access_token"]
87
+ # Fetch current user with bearer token
88
+ response = client.get(
89
+ "/test/current-user", headers={"Authorization": f"Bearer {access_token}"}
90
+ )
91
+ assert response.status_code == 200
92
+ user_data = response.json()
93
+ assert user_data.get("username") == APP_AUTH_SUPER_USER
94
+ assert user_data.get("is_super_user")
95
+ assert not user_data.get("is_guest")
96
+
97
+
98
+ def test_get_admin_user_with_cookie():
99
+ login_client = TestClient(app, base_url="http://localhost")
100
+ session_response = login_client.post(
101
+ "/api/v1/user-sessions",
102
+ data={
103
+ "username": APP_AUTH_SUPER_USER,
104
+ "password": APP_AUTH_SUPER_USER_PASSWORD,
105
+ },
106
+ )
107
+ assert session_response.status_code == 200
108
+ # Fetch current user with cookies
109
+ cookies = {
110
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME: session_response.cookies.get(
111
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
112
+ )
113
+ }
114
+ # re-initiate client with cookies
115
+ client = TestClient(app, base_url="http://localhost", cookies=cookies)
116
+ response = client.get("/test/current-user")
117
+ assert response.status_code == 200
118
+ user_data = response.json()
119
+ assert user_data.get("username") == APP_AUTH_SUPER_USER
120
+ assert user_data.get("is_super_user")
121
+ assert not user_data.get("is_guest")
122
+
123
+
124
+ def test_update_user_session():
125
+ login_client = TestClient(app, base_url="http://localhost")
126
+ old_session_response = login_client.post(
127
+ "/api/v1/user-sessions",
128
+ data={
129
+ "username": APP_AUTH_SUPER_USER,
130
+ "password": APP_AUTH_SUPER_USER_PASSWORD,
131
+ },
132
+ )
133
+ assert old_session_response.status_code == 200
134
+ old_session_data = old_session_response.json()
135
+ # re-initiate client with cookies and delete user session
136
+ client = TestClient(app, base_url="http://localhost")
137
+ time.sleep(1)
138
+ new_session_response = client.put(
139
+ "/api/v1/user-sessions",
140
+ params={"refresh_token": old_session_data.get("refresh_token")},
141
+ )
142
+ assert new_session_response.status_code == 200
143
+ new_session_data = new_session_response.json()
144
+ # Cookies and response should match
145
+ assert new_session_response.cookies.get(
146
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
147
+ ) == new_session_data.get("access_token")
148
+ assert new_session_response.cookies.get(
149
+ APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
150
+ ) == new_session_data.get("refresh_token")
151
+ # New session should has longer TTL than old session
152
+ assert old_session_data.get("access_token") != new_session_data.get("access_token")
153
+ assert old_session_data.get("access_token_expired_at") < new_session_data.get(
154
+ "access_token_expired_at"
155
+ )
156
+ assert old_session_data.get("refresh_token") != new_session_data.get(
157
+ "refresh_token"
158
+ )
159
+ assert old_session_data.get("refresh_token_expired_at") < new_session_data.get(
160
+ "refresh_token_expired_at"
161
+ )
162
+
163
+
164
+ def test_delete_user_session():
165
+ login_client = TestClient(app, base_url="http://localhost")
166
+ session_response = login_client.post(
167
+ "/api/v1/user-sessions",
168
+ data={
169
+ "username": APP_AUTH_SUPER_USER,
170
+ "password": APP_AUTH_SUPER_USER_PASSWORD,
171
+ },
172
+ )
173
+ assert session_response.status_code == 200
174
+ # Initiate cookies that should be deleted when user session is deleted.
175
+ cookies = {
176
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME: session_response.cookies.get(
177
+ APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
178
+ ),
179
+ APP_AUTH_REFRESH_TOKEN_COOKIE_NAME: session_response.cookies.get(
180
+ APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
181
+ ),
182
+ }
183
+ # re-initiate client with cookies and delete user session
184
+ client = TestClient(app, base_url="http://localhost", cookies=cookies)
185
+ response = client.delete(
186
+ "/api/v1/user-sessions",
187
+ params={
188
+ "refresh_token": session_response.cookies.get(
189
+ APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
190
+ ),
191
+ },
192
+ )
193
+ assert response.status_code == 200
194
+ assert response.cookies.get(APP_AUTH_ACCESS_TOKEN_COOKIE_NAME, None) is None
195
+ assert response.cookies.get(APP_AUTH_REFRESH_TOKEN_COOKIE_NAME, None) is None
@@ -0,0 +1,28 @@
1
+ from fastapi.testclient import TestClient
2
+ from my_app_name.main import app
3
+
4
+
5
+ def test_get_health():
6
+ client = TestClient(app, base_url="http://localhost")
7
+ response = client.get("/health")
8
+ assert response.status_code == 200
9
+ assert response.json() == {"message": "ok"}
10
+
11
+
12
+ def test_head_health():
13
+ client = TestClient(app, base_url="http://localhost")
14
+ response = client.head("/health")
15
+ assert response.status_code == 200
16
+
17
+
18
+ def test_get_readiness():
19
+ client = TestClient(app, base_url="http://localhost")
20
+ response = client.get("/readiness")
21
+ assert response.status_code == 200
22
+ assert response.json() == {"message": "ok"}
23
+
24
+
25
+ def test_head_readiness():
26
+ client = TestClient(app, base_url="http://localhost")
27
+ response = client.head("/readiness")
28
+ assert response.status_code == 200
@@ -0,0 +1,15 @@
1
+ import os
2
+
3
+ from fastapi.testclient import TestClient
4
+ from my_app_name.main import app
5
+ from my_app_name.module.gateway.util.view import render_content
6
+
7
+
8
+ def test_homepage():
9
+ client = TestClient(app, base_url="http://localhost")
10
+ response = client.get("/")
11
+ assert response.status_code == 200
12
+ view_path = os.path.join(
13
+ os.path.dirname(os.path.dirname(__file__)), "module", "gateway", "view"
14
+ )
15
+ assert response.text == render_content("homepage.html").body.decode("utf-8")
@@ -0,0 +1,16 @@
1
+ import os
2
+
3
+ from fastapi.testclient import TestClient
4
+ from my_app_name.main import app
5
+ from my_app_name.module.gateway.util.view import render_error
6
+
7
+
8
+ def test_not_found_error():
9
+ client = TestClient(app, base_url="http://localhost")
10
+ response = client.get(
11
+ "/holly-grail/not-found/philosopher-stone/not-found/hope/inexist"
12
+ )
13
+ assert response.status_code == 404
14
+ assert response.text == render_error(
15
+ error_message="Not found", status_code=404
16
+ ).body.decode("utf-8")
@@ -0,0 +1,7 @@
1
+ pytest -vv \
2
+ --cov=my_app_name \
3
+ --cov-config=.coveragerc \
4
+ --cov-report=html \
5
+ --cov-report=term \
6
+ --cov-report=term-missing \
7
+ --ignore=_zrb
@@ -30,7 +30,7 @@ def serve_refresh_token_api(app: "FastAPI", web_config: WebConfig) -> None:
30
30
  # If we still don't have a refresh token, raise an exception
31
31
  if not refresh_token:
32
32
  return JSONResponse(
33
- content={"detail": "Refresh token not provided"}, status_code=400
33
+ content={"detail": "Refresh token not provided"}, status_code=401
34
34
  )
35
35
  # Get token
36
36
  new_token = regenerate_tokens(web_config, refresh_token)
@@ -1,6 +1,10 @@
1
1
  function refreshAuthToken(){
2
2
  const refreshUrl = "/api/v1/refresh-token";
3
+ let shouldRefresh = true;
3
4
  async function refresh() {
5
+ if (!shouldRefresh) {
6
+ return;
7
+ }
4
8
  try {
5
9
  const response = await fetch(refreshUrl, {
6
10
  method: "POST",
@@ -9,6 +13,11 @@ function refreshAuthToken(){
9
13
  });
10
14
 
11
15
  if (!response.ok) {
16
+ if (response.status === 401 || response.status === 403) {
17
+ console.warn("Skipping token refresh, authentication required.");
18
+ shouldRefresh = false;
19
+ return;
20
+ }
12
21
  throw new Error(`HTTP error! Status: ${response.status}`);
13
22
  }
14
23
  console.log("Token refreshed successfully");
@@ -30,7 +30,7 @@ def serve_static_resources(app: "FastAPI", web_config: WebConfig) -> None:
30
30
  async def refresh_token_js():
31
31
  return PlainTextResponse(
32
32
  content=_get_refresh_token_js(
33
- 60 * web_config.refresh_token_expire_minutes / 3
33
+ 60 * web_config.access_token_expire_minutes / 3
34
34
  ),
35
35
  media_type="application/javascript",
36
36
  )
zrb/task/base_task.py CHANGED
@@ -295,16 +295,16 @@ class BaseTask(AnyTask):
295
295
  async def exec_root_tasks(self, session: AnySession):
296
296
  session.set_main_task(self)
297
297
  session.state_logger.write(session.as_state_log())
298
- log_state = asyncio.create_task(self._log_session_state(session))
299
- root_tasks = [
300
- task
301
- for task in session.get_root_tasks(self)
302
- if session.is_allowed_to_run(task)
303
- ]
304
- root_task_coros = [
305
- run_async(root_task.exec_chain(session)) for root_task in root_tasks
306
- ]
307
298
  try:
299
+ log_state = asyncio.create_task(self._log_session_state(session))
300
+ root_tasks = [
301
+ task
302
+ for task in session.get_root_tasks(self)
303
+ if session.is_allowed_to_run(task)
304
+ ]
305
+ root_task_coros = [
306
+ run_async(root_task.exec_chain(session)) for root_task in root_tasks
307
+ ]
308
308
  await asyncio.gather(*root_task_coros)
309
309
  await session.wait_deferred()
310
310
  session.terminate()
@@ -312,7 +312,7 @@ class BaseTask(AnyTask):
312
312
  return session.final_result
313
313
  except IndexError:
314
314
  return None
315
- except asyncio.CancelledError:
315
+ except (asyncio.CancelledError, KeyboardInterrupt):
316
316
  ctx = self.get_ctx(session)
317
317
  ctx.log_info("Session terminated")
318
318
  finally:
@@ -0,0 +1,3 @@
1
+ PREPEND = 0
2
+ APPEND = 1
3
+ REPLACE = 2
@@ -0,0 +1,58 @@
1
+ import libcst as cst
2
+
3
+ from zrb.util.codemod.modification_mode import APPEND, PREPEND, REPLACE
4
+
5
+
6
+ def replace_class_code(original_code: str, class_name: str, new_code: str) -> str:
7
+ return _modify_code(original_code, class_name, new_code, REPLACE)
8
+
9
+
10
+ def prepend_code_to_class(original_code: str, class_name: str, new_code: str) -> str:
11
+ return _modify_code(original_code, class_name, new_code, PREPEND)
12
+
13
+
14
+ def append_code_to_class(original_code: str, class_name: str, new_code: str) -> str:
15
+ return _modify_code(original_code, class_name, new_code, APPEND)
16
+
17
+
18
+ def _modify_code(original_code: str, class_name: str, new_code: str, mode: int) -> str:
19
+ # Parse the original code into a module
20
+ module = cst.parse_module(original_code)
21
+ # Initialize transformer with the class name and method code
22
+ transformer = _ClassCodeModifier(class_name, new_code, mode)
23
+ # Apply the transformation
24
+ modified_module = module.visit(transformer)
25
+ # Check if the class was found
26
+ if not transformer.class_found:
27
+ raise ValueError(f"Class {class_name} not found in the provided code.")
28
+ # Return the modified code
29
+ return modified_module.code
30
+
31
+
32
+ class _ClassCodeModifier(cst.CSTTransformer):
33
+ def __init__(self, class_name: str, new_code: str, mode: int):
34
+ self.class_name = class_name
35
+ self.new_code = cst.parse_module(new_code).body
36
+ self.class_found = False
37
+ self.mode = mode
38
+
39
+ def leave_ClassDef(
40
+ self, original_node: cst.ClassDef, updated_node: cst.ClassDef
41
+ ) -> cst.ClassDef:
42
+ # Check if this is the target class
43
+ if original_node.name.value == self.class_name:
44
+ self.class_found = True
45
+ if self.mode == REPLACE:
46
+ new_body = updated_node.body.with_changes(body=tuple(self.new_code))
47
+ return updated_node.with_changes(body=new_body)
48
+ if self.mode == PREPEND:
49
+ new_body = updated_node.body.with_changes(
50
+ body=tuple(self.new_code) + updated_node.body.body
51
+ )
52
+ return updated_node.with_changes(body=new_body)
53
+ if self.mode == APPEND:
54
+ new_body = updated_node.body.with_changes(
55
+ body=updated_node.body.body + tuple(self.new_code)
56
+ )
57
+ return updated_node.with_changes(body=new_body)
58
+ return updated_node
@@ -0,0 +1,68 @@
1
+ import libcst as cst
2
+
3
+ from zrb.util.codemod.modification_mode import APPEND, PREPEND, REPLACE
4
+
5
+
6
+ def replace_parent_class(
7
+ original_code: str, class_name: str, parent_class_name: str
8
+ ) -> str:
9
+ return _modify_parent_class(original_code, class_name, parent_class_name, REPLACE)
10
+
11
+
12
+ def append_parent_class(
13
+ original_code: str, class_name: str, parent_class_name: str
14
+ ) -> str:
15
+ return _modify_parent_class(original_code, class_name, parent_class_name, APPEND)
16
+
17
+
18
+ def prepend_parent_class(
19
+ original_code: str, class_name: str, parent_class_name: str
20
+ ) -> str:
21
+ return _modify_parent_class(original_code, class_name, parent_class_name, PREPEND)
22
+
23
+
24
+ def _modify_parent_class(
25
+ original_code: str, class_name: str, parent_class_name: str, mode: int
26
+ ) -> str:
27
+ # Parse the original code into a module
28
+ module = cst.parse_module(original_code)
29
+ # Initialize transformer with the class name and parent class name
30
+ transformer = _ParentClassAdder(class_name, parent_class_name, mode)
31
+ # Apply the transformation
32
+ modified_module = module.visit(transformer)
33
+ # Check if the class was found
34
+ if not transformer.class_found:
35
+ raise ValueError(f"Class {class_name} not found in the provided code.")
36
+ # Return the modified code
37
+ return modified_module.code
38
+
39
+
40
+ class _ParentClassAdder(cst.CSTTransformer):
41
+ def __init__(self, class_name: str, parent_class_name: str, mode: int):
42
+ self.class_name = class_name
43
+ self.parent_class_name = parent_class_name
44
+ self.class_found = False
45
+ self.mode = mode
46
+
47
+ def leave_ClassDef(
48
+ self, original_node: cst.ClassDef, updated_node: cst.ClassDef
49
+ ) -> cst.ClassDef:
50
+ # Check if this is the target class
51
+ if original_node.name.value == self.class_name:
52
+ self.class_found = True
53
+ if self.mode == REPLACE:
54
+ new_bases = (cst.Arg(value=cst.Name(self.parent_class_name)),)
55
+ return updated_node.with_changes(bases=new_bases)
56
+ if self.mode == PREPEND:
57
+ new_bases = (
58
+ cst.Arg(value=cst.Name(self.parent_class_name)),
59
+ *updated_node.bases,
60
+ )
61
+ return updated_node.with_changes(bases=new_bases)
62
+ if self.mode == APPEND:
63
+ new_bases = (
64
+ *updated_node.bases,
65
+ cst.Arg(value=cst.Name(self.parent_class_name)),
66
+ )
67
+ return updated_node.with_changes(bases=new_bases)
68
+ return updated_node
@@ -0,0 +1,128 @@
1
+ import libcst as cst
2
+
3
+ from zrb.util.codemod.modification_mode import APPEND, PREPEND
4
+
5
+
6
+ def append_property_to_class(
7
+ original_code: str,
8
+ class_name: str,
9
+ property_name: str,
10
+ annotation: str,
11
+ default_value: str,
12
+ ) -> str:
13
+ return _modify_class_property(
14
+ original_code, class_name, property_name, annotation, default_value, APPEND
15
+ )
16
+
17
+
18
+ def prepend_property_to_class(
19
+ original_code: str,
20
+ class_name: str,
21
+ property_name: str,
22
+ annotation: str,
23
+ default_value: str,
24
+ ) -> str:
25
+ return _modify_class_property(
26
+ original_code, class_name, property_name, annotation, default_value, PREPEND
27
+ )
28
+
29
+
30
+ def _modify_class_property(
31
+ original_code: str,
32
+ class_name: str,
33
+ property_name: str,
34
+ annotation: str,
35
+ default_value: str,
36
+ mode: int,
37
+ ) -> str:
38
+ # Parse the original code into a module
39
+ module = cst.parse_module(original_code)
40
+ # Initialize transformer with the class name, property name, annotation, and default value
41
+ transformer = _ClassPropertyModifier(
42
+ class_name, property_name, annotation, default_value, mode
43
+ )
44
+ # Apply the transformation
45
+ modified_module = module.visit(transformer)
46
+ # Check if the class was found
47
+ if not transformer.class_found:
48
+ raise ValueError(f"Class {class_name} not found in the provided code.")
49
+ # Return the modified code
50
+ return modified_module.code
51
+
52
+
53
+ class _ClassPropertyModifier(cst.CSTTransformer):
54
+ def __init__(
55
+ self,
56
+ class_name: str,
57
+ property_name: str,
58
+ annotation: str,
59
+ default_value: str,
60
+ mode: int,
61
+ ):
62
+ self.class_name = class_name
63
+ self.property_name = property_name
64
+ self.annotation = cst.Annotation(cst.parse_expression(annotation))
65
+ self.default_value = cst.parse_expression(default_value)
66
+ self.class_found = False
67
+ self.mode = mode
68
+
69
+ def leave_ClassDef(
70
+ self, original_node: cst.ClassDef, updated_node: cst.ClassDef
71
+ ) -> cst.ClassDef:
72
+ # Check if this is the target class
73
+ if original_node.name.value == self.class_name:
74
+ self.class_found = True
75
+ # Create the annotated property with a default value
76
+ new_property = cst.SimpleStatementLine(
77
+ body=[
78
+ cst.AnnAssign(
79
+ target=cst.Name(self.property_name),
80
+ annotation=self.annotation,
81
+ value=self.default_value,
82
+ )
83
+ ]
84
+ )
85
+ if self.mode == PREPEND:
86
+ new_body = cst.IndentedBlock(
87
+ body=(new_property,) + updated_node.body.body
88
+ )
89
+ return updated_node.with_changes(body=new_body)
90
+ if self.mode == APPEND:
91
+ # Identify properties and methods
92
+ properties = []
93
+ methods = []
94
+ for stmt in updated_node.body.body:
95
+ if isinstance(stmt, cst.SimpleStatementLine) and isinstance(
96
+ stmt.body[0], (cst.AnnAssign, cst.Assign)
97
+ ):
98
+ properties.append(stmt)
99
+ elif isinstance(stmt, cst.FunctionDef):
100
+ methods.append(stmt)
101
+ if properties:
102
+ # Class has properties
103
+ last_property_index = updated_node.body.body.index(properties[-1])
104
+ new_body = cst.IndentedBlock(
105
+ body=(
106
+ updated_node.body.body[: last_property_index + 1]
107
+ + (new_property,)
108
+ + updated_node.body.body[last_property_index + 1 :]
109
+ )
110
+ )
111
+ return updated_node.with_changes(body=new_body)
112
+ if methods:
113
+ # Class doesn't have properties but has methods
114
+ first_method_index = updated_node.body.body.index(methods[0])
115
+ new_body = cst.IndentedBlock(
116
+ body=(
117
+ updated_node.body.body[:first_method_index]
118
+ + (new_property,)
119
+ + updated_node.body.body[first_method_index:]
120
+ )
121
+ )
122
+ return updated_node.with_changes(body=new_body)
123
+ # Class is empty, add add the bottom
124
+ new_body = cst.IndentedBlock(
125
+ body=updated_node.body.body + (new_property,)
126
+ )
127
+ return updated_node.with_changes(body=new_body)
128
+ return updated_node