arpakitlib 1.7.249__py3-none-any.whl → 1.7.252__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.
- arpakitlib/_arpakit_project_template/alembic/README +1 -0
- arpakitlib/_arpakit_project_template/alembic/env.py +83 -0
- arpakitlib/_arpakit_project_template/alembic/script.py.mako +26 -0
- arpakitlib/_arpakit_project_template/alembic.ini +119 -0
- arpakitlib/_arpakit_project_template/example.env +5 -14
- arpakitlib/_arpakit_project_template/manage/docker_run_postgres.sh +4 -3
- arpakitlib/_arpakit_project_template/manage/docker_start_postgres.sh +2 -1
- arpakitlib/_arpakit_project_template/manage/docker_stop_postgres.sh +2 -1
- arpakitlib/_arpakit_project_template/manage/git_set_arpakit_company_origin.sh +4 -2
- arpakitlib/_arpakit_project_template/manage/git_set_arpakit_origin.sh +4 -2
- arpakitlib/_arpakit_project_template/src/additional_model/additional_model.py +0 -7
- arpakitlib/_arpakit_project_template/src/api/auth.py +52 -0
- arpakitlib/_arpakit_project_template/src/api/create_api_app.py +21 -14
- arpakitlib/_arpakit_project_template/src/api/create_handle_exception_.py +13 -13
- arpakitlib/_arpakit_project_template/src/api/event.py +24 -2
- arpakitlib/_arpakit_project_template/src/api/router/v1/get_api_error_info.py +1 -2
- arpakitlib/_arpakit_project_template/src/api/schema/v1/out.py +0 -7
- arpakitlib/_arpakit_project_template/src/api/transmitted_api_data.py +3 -11
- arpakitlib/_arpakit_project_template/src/core/settings.py +3 -111
- arpakitlib/_arpakit_project_template/src/db/util.py +1 -3
- arpakitlib/_arpakit_project_template/src/just_script/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/just_script/example.py +16 -0
- arpakitlib/ar_arpakit_project_template_util.py +8 -18
- arpakitlib/ar_arpakit_schedule_uust_api_client_util.py +5 -4
- arpakitlib/ar_arpakitlib_cli_util.py +10 -22
- arpakitlib/ar_class_util.py +0 -1
- arpakitlib/ar_cryptomus_api_client_util.py +21 -0
- arpakitlib/ar_fastapi_util.py +106 -70
- arpakitlib/ar_schedule_uust_api_client_util.py +24 -24
- arpakitlib/ar_settings_util.py +166 -14
- arpakitlib/ar_sqlalchemy_model_util.py +1 -1
- arpakitlib/ar_steam_payment_api_client_util.py +21 -0
- arpakitlib/ar_wata_api_client.py +21 -0
- {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/METADATA +1 -1
- {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/RECORD +39 -30
- /arpakitlib/_arpakit_project_template/src/core/{_check_settings.py → _show_settings.py} +0 -0
- {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/LICENSE +0 -0
- {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/WHEEL +0 -0
- {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/entry_points.txt +0 -0
arpakitlib/ar_fastapi_util.py
CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
import datetime as dt
|
7
|
+
import inspect
|
7
8
|
import logging
|
8
9
|
import os.path
|
9
10
|
import pathlib
|
@@ -24,12 +25,14 @@ from starlette import status
|
|
24
25
|
from starlette.middleware.cors import CORSMiddleware
|
25
26
|
from starlette.staticfiles import StaticFiles
|
26
27
|
|
28
|
+
from arpakitlib.ar_datetime_util import now_utc_dt
|
27
29
|
from arpakitlib.ar_dict_util import combine_dicts
|
28
30
|
from arpakitlib.ar_enumeration_util import Enumeration
|
29
31
|
from arpakitlib.ar_exception_util import exception_to_traceback_str
|
32
|
+
from arpakitlib.ar_file_storage_in_dir_util import FileStorageInDir
|
30
33
|
from arpakitlib.ar_func_util import raise_if_not_async_func, is_async_object
|
31
34
|
from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str_to_json_obj, safely_transfer_obj_to_json_str
|
32
|
-
from arpakitlib.
|
35
|
+
from arpakitlib.ar_settings_util import SimpleSettings
|
33
36
|
from arpakitlib.ar_sqlalchemy_model_util import StoryLogDBM, OperationDBM
|
34
37
|
from arpakitlib.ar_sqlalchemy_util import SQLAlchemyDB
|
35
38
|
from arpakitlib.ar_type_util import raise_for_type, raise_if_none
|
@@ -151,6 +154,11 @@ class OperationSO(SimpleSO):
|
|
151
154
|
return cls.model_validate(operation_dbm.simple_dict(include_sd_properties=True))
|
152
155
|
|
153
156
|
|
157
|
+
class APIErrorInfoSO(BaseSO):
|
158
|
+
api_error_codes: list[str] = []
|
159
|
+
api_error_specification_codes: list[str] = []
|
160
|
+
|
161
|
+
|
154
162
|
class APIJSONResponse(fastapi.responses.JSONResponse):
|
155
163
|
def __init__(self, *, content: dict | list | BaseSO | None, status_code: int = starlette.status.HTTP_200_OK):
|
156
164
|
if isinstance(content, dict):
|
@@ -207,19 +215,20 @@ class APIException(fastapi.exceptions.HTTPException):
|
|
207
215
|
|
208
216
|
def create_handle_exception(
|
209
217
|
*,
|
210
|
-
|
211
|
-
|
218
|
+
funcs_before: list[Callable | None] | None = None,
|
219
|
+
async_funcs_after: list[Callable | None] | None = None,
|
212
220
|
) -> Callable:
|
213
|
-
if
|
214
|
-
|
215
|
-
|
221
|
+
if funcs_before is None:
|
222
|
+
funcs_before = []
|
223
|
+
funcs_before = [v for v in funcs_before if v is not None]
|
216
224
|
|
217
|
-
if
|
218
|
-
|
219
|
-
|
225
|
+
if async_funcs_after is None:
|
226
|
+
async_funcs_after = []
|
227
|
+
async_funcs_after = [v for v in async_funcs_after if v is not None]
|
220
228
|
|
221
|
-
async def
|
222
|
-
request: starlette.requests.Request,
|
229
|
+
async def func(
|
230
|
+
request: starlette.requests.Request,
|
231
|
+
exception: Exception
|
223
232
|
) -> APIJSONResponse:
|
224
233
|
status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
|
225
234
|
|
@@ -261,10 +270,10 @@ def create_handle_exception(
|
|
261
270
|
status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
|
262
271
|
error_so.error_code = BaseAPIErrorCodes.unknown_error
|
263
272
|
|
264
|
-
if error_so.error_code:
|
273
|
+
if error_so.error_code is not None:
|
265
274
|
error_so.error_code = error_so.error_code.upper().replace(" ", "_").strip()
|
266
275
|
|
267
|
-
if error_so.error_specification_code:
|
276
|
+
if error_so.error_specification_code is not None:
|
268
277
|
error_so.error_specification_code = (
|
269
278
|
error_so.error_specification_code.upper().replace(" ", "_").strip()
|
270
279
|
)
|
@@ -275,23 +284,29 @@ def create_handle_exception(
|
|
275
284
|
if error_so.error_code == BaseAPIErrorCodes.cannot_authorize:
|
276
285
|
status_code = status.HTTP_401_UNAUTHORIZED
|
277
286
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
287
|
+
error_so.error_data["status_code"] = status_code
|
288
|
+
|
289
|
+
# funcs_before
|
290
|
+
|
291
|
+
_transmitted_kwargs = {}
|
292
|
+
for func_before in funcs_before:
|
293
|
+
_func_data = func_before(
|
294
|
+
request=request, status_code=status_code, error_so=error_so, exception=exception,
|
295
|
+
transmitted_kwargs=_transmitted_kwargs
|
282
296
|
)
|
283
297
|
if is_async_object(_func_data):
|
284
298
|
_func_data = await _func_data
|
285
299
|
if _func_data is not None:
|
286
|
-
|
287
|
-
raise_for_type(status_code, int)
|
300
|
+
error_so, _transmitted_kwargs = _func_data[0], _func_data[1]
|
288
301
|
raise_for_type(error_so, ErrorSO)
|
289
|
-
raise_for_type(
|
302
|
+
raise_for_type(_transmitted_kwargs, dict)
|
303
|
+
|
304
|
+
# async_funcs_after
|
290
305
|
|
291
|
-
for
|
292
|
-
raise_if_not_async_func(
|
293
|
-
_ = asyncio.create_task(
|
294
|
-
|
306
|
+
for async_func_after in async_funcs_after:
|
307
|
+
raise_if_not_async_func(async_func_after)
|
308
|
+
_ = asyncio.create_task(async_func_after(
|
309
|
+
request=request, status_code=status_code, error_so=error_so, exception=exception
|
295
310
|
))
|
296
311
|
|
297
312
|
return APIJSONResponse(
|
@@ -299,43 +314,46 @@ def create_handle_exception(
|
|
299
314
|
status_code=status_code
|
300
315
|
)
|
301
316
|
|
302
|
-
return
|
317
|
+
return func
|
303
318
|
|
304
319
|
|
305
|
-
def
|
320
|
+
def logging__api_func_before_in_handle_exception(
|
306
321
|
*,
|
307
322
|
ignore_api_error_codes: list[str] | None = None,
|
308
323
|
ignore_status_codes: list[int] | None = None,
|
309
324
|
ignore_exception_types: list[type[Exception]] | None = None,
|
310
325
|
need_exc_info: bool = False
|
311
|
-
):
|
326
|
+
) -> Callable:
|
327
|
+
current_func_name = inspect.currentframe().f_code.co_name
|
328
|
+
|
312
329
|
def func(
|
313
330
|
*,
|
331
|
+
request: starlette.requests.Request,
|
314
332
|
status_code: int,
|
315
333
|
error_so: ErrorSO,
|
316
|
-
request: starlette.requests.Request,
|
317
334
|
exception: Exception,
|
335
|
+
transmitted_kwargs: dict[str, Any],
|
318
336
|
**kwargs
|
319
|
-
) -> (
|
320
|
-
|
337
|
+
) -> (ErrorSO, dict[str, Any]):
|
338
|
+
transmitted_kwargs[current_func_name] = now_utc_dt()
|
321
339
|
|
322
340
|
if ignore_api_error_codes and error_so.error_code in ignore_api_error_codes:
|
323
|
-
return
|
341
|
+
return error_so, transmitted_kwargs
|
324
342
|
|
325
343
|
if ignore_status_codes and status_code in ignore_status_codes:
|
326
|
-
return
|
344
|
+
return error_so, transmitted_kwargs
|
327
345
|
|
328
346
|
if ignore_exception_types and (
|
329
347
|
exception in ignore_exception_types or type(exception) in ignore_exception_types
|
330
348
|
):
|
331
|
-
return
|
349
|
+
return error_so, transmitted_kwargs
|
332
350
|
|
333
351
|
_logger.error(safely_transfer_obj_to_json_str(error_so.model_dump()), exc_info=need_exc_info)
|
334
352
|
|
335
353
|
return func
|
336
354
|
|
337
355
|
|
338
|
-
def
|
356
|
+
def story_log__api_func_before_in_handle_exception(
|
339
357
|
*,
|
340
358
|
sqlalchemy_db: SQLAlchemyDB,
|
341
359
|
ignore_api_error_codes: list[str] | None = None,
|
@@ -344,26 +362,29 @@ def story_log_func_before_response(
|
|
344
362
|
) -> Callable:
|
345
363
|
raise_for_type(sqlalchemy_db, SQLAlchemyDB)
|
346
364
|
|
365
|
+
current_func_name = inspect.currentframe().f_code.co_name
|
366
|
+
|
347
367
|
async def async_func(
|
348
368
|
*,
|
369
|
+
request: starlette.requests.Request,
|
349
370
|
status_code: int,
|
350
371
|
error_so: ErrorSO,
|
351
|
-
request: starlette.requests.Request,
|
352
372
|
exception: Exception,
|
373
|
+
transmitted_kwargs: dict[str, Any],
|
353
374
|
**kwargs
|
354
|
-
) -> (
|
355
|
-
|
375
|
+
) -> (ErrorSO, dict[str, Any]):
|
376
|
+
transmitted_kwargs[current_func_name] = now_utc_dt()
|
356
377
|
|
357
378
|
if ignore_api_error_codes and error_so.error_code in ignore_api_error_codes:
|
358
|
-
return
|
379
|
+
return error_so, transmitted_kwargs
|
359
380
|
|
360
381
|
if ignore_status_codes and status_code in ignore_status_codes:
|
361
|
-
return
|
382
|
+
return error_so, transmitted_kwargs
|
362
383
|
|
363
384
|
if ignore_exception_types and (
|
364
385
|
exception in ignore_exception_types or type(exception) in ignore_exception_types
|
365
386
|
):
|
366
|
-
return
|
387
|
+
return error_so, transmitted_kwargs
|
367
388
|
|
368
389
|
async with sqlalchemy_db.new_async_session() as session:
|
369
390
|
story_log_dbm = StoryLogDBM(
|
@@ -379,9 +400,9 @@ def story_log_func_before_response(
|
|
379
400
|
await session.refresh(story_log_dbm)
|
380
401
|
|
381
402
|
error_so.error_data.update({"story_log_long_id": story_log_dbm.long_id})
|
382
|
-
|
403
|
+
transmitted_kwargs["story_log_id"] = story_log_dbm.id
|
383
404
|
|
384
|
-
return
|
405
|
+
return error_so, transmitted_kwargs
|
385
406
|
|
386
407
|
return async_func
|
387
408
|
|
@@ -458,7 +479,7 @@ class ARPAKITLibSO(BaseSO):
|
|
458
479
|
arpakitlib: bool = True
|
459
480
|
|
460
481
|
|
461
|
-
def
|
482
|
+
def create_needed_api_router():
|
462
483
|
api_router = APIRouter()
|
463
484
|
|
464
485
|
@api_router.get(
|
@@ -485,9 +506,7 @@ def add_needed_api_router_to_app(*, app: FastAPI):
|
|
485
506
|
content=ARPAKITLibSO(arpakitlib=True)
|
486
507
|
)
|
487
508
|
|
488
|
-
|
489
|
-
|
490
|
-
return app
|
509
|
+
return api_router
|
491
510
|
|
492
511
|
|
493
512
|
class BaseStartupAPIEvent:
|
@@ -512,6 +531,17 @@ class BaseTransmittedAPIData(BaseModel):
|
|
512
531
|
model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
|
513
532
|
|
514
533
|
|
534
|
+
class SimpleTransmittedAPIData(BaseTransmittedAPIData):
|
535
|
+
settings: SimpleSettings | None = None
|
536
|
+
|
537
|
+
|
538
|
+
class AdvancedTransmittedAPIData(SimpleTransmittedAPIData):
|
539
|
+
sqlalchemy_db: SQLAlchemyDB | None = None
|
540
|
+
media_file_storage_in_dir: FileStorageInDir | None = None
|
541
|
+
cache_file_storage_in_dir: FileStorageInDir | None = None
|
542
|
+
dump_file_storage_in_dir: FileStorageInDir | None = None
|
543
|
+
|
544
|
+
|
515
545
|
def get_transmitted_api_data(request: starlette.requests.Request) -> BaseTransmittedAPIData:
|
516
546
|
return request.app.state.transmitted_api_data
|
517
547
|
|
@@ -569,7 +599,9 @@ def base_api_auth(
|
|
569
599
|
ac: fastapi.security.HTTPAuthorizationCredentials | None = fastapi.Security(
|
570
600
|
fastapi.security.HTTPBearer(auto_error=False)
|
571
601
|
),
|
572
|
-
api_key_string: str | None = Security(
|
602
|
+
api_key_string: str | None = Security(
|
603
|
+
APIKeyHeader(name="apikey", auto_error=False)
|
604
|
+
),
|
573
605
|
request: starlette.requests.Request,
|
574
606
|
transmitted_api_data: BaseTransmittedAPIData = Depends(get_transmitted_api_data)
|
575
607
|
) -> BaseAPIAuthData:
|
@@ -581,7 +613,7 @@ def base_api_auth(
|
|
581
613
|
require_correct_token=require_correct_token
|
582
614
|
)
|
583
615
|
|
584
|
-
# api_key
|
616
|
+
# parse api_key
|
585
617
|
|
586
618
|
api_auth_data.api_key_string = api_key_string
|
587
619
|
|
@@ -599,7 +631,7 @@ def base_api_auth(
|
|
599
631
|
if not api_auth_data.api_key_string and "apikey" in request.query_params.keys():
|
600
632
|
api_auth_data.api_key_string = request.query_params["apikey"]
|
601
633
|
|
602
|
-
# token
|
634
|
+
# parse token
|
603
635
|
|
604
636
|
api_auth_data.token_string = ac.credentials if ac and ac.credentials and ac.credentials.strip() else None
|
605
637
|
|
@@ -628,7 +660,7 @@ def base_api_auth(
|
|
628
660
|
if not api_auth_data.token_string:
|
629
661
|
api_auth_data.token_string = None
|
630
662
|
|
631
|
-
#
|
663
|
+
# require_api_key_string
|
632
664
|
|
633
665
|
if require_api_key_string and not api_auth_data.api_key_string:
|
634
666
|
raise APIException(
|
@@ -637,7 +669,7 @@ def base_api_auth(
|
|
637
669
|
error_data=safely_transfer_obj_to_json_str_to_json_obj(api_auth_data.model_dump())
|
638
670
|
)
|
639
671
|
|
640
|
-
#
|
672
|
+
# require_token_string
|
641
673
|
|
642
674
|
if require_token_string and not api_auth_data.token_string:
|
643
675
|
raise APIException(
|
@@ -646,10 +678,10 @@ def base_api_auth(
|
|
646
678
|
error_data=safely_transfer_obj_to_json_str_to_json_obj(api_auth_data.model_dump())
|
647
679
|
)
|
648
680
|
|
649
|
-
#
|
681
|
+
# validate_api_key_func
|
650
682
|
|
651
683
|
if validate_api_key_func is not None:
|
652
|
-
|
684
|
+
validate_api_key_func_res = validate_api_key_func(
|
653
685
|
api_key_string=api_auth_data.api_key_string,
|
654
686
|
token_string=api_auth_data.token_string,
|
655
687
|
base_api_auth_data=api_auth_data,
|
@@ -657,14 +689,14 @@ def base_api_auth(
|
|
657
689
|
request=request,
|
658
690
|
**kwargs
|
659
691
|
)
|
660
|
-
if is_async_object(
|
661
|
-
|
662
|
-
api_auth_data.is_api_key_correct =
|
692
|
+
if is_async_object(validate_api_key_func_res):
|
693
|
+
validate_api_key_func_res = await validate_api_key_func_res
|
694
|
+
api_auth_data.is_api_key_correct = validate_api_key_func_res
|
663
695
|
|
664
|
-
#
|
696
|
+
# validate_token_func
|
665
697
|
|
666
698
|
if validate_token_func is not None:
|
667
|
-
|
699
|
+
validate_token_func_res = validate_token_func(
|
668
700
|
api_key_string=api_auth_data.api_key_string,
|
669
701
|
token_string=api_auth_data.token_string,
|
670
702
|
base_api_auth_data=api_auth_data,
|
@@ -672,11 +704,11 @@ def base_api_auth(
|
|
672
704
|
request=request,
|
673
705
|
**kwargs
|
674
706
|
)
|
675
|
-
if is_async_object(
|
676
|
-
|
677
|
-
api_auth_data.is_token_correct =
|
707
|
+
if is_async_object(validate_token_func_res):
|
708
|
+
validate_token_func_res = await validate_token_func_res
|
709
|
+
api_auth_data.is_token_correct = validate_token_func_res
|
678
710
|
|
679
|
-
#
|
711
|
+
# require_correct_api_key
|
680
712
|
|
681
713
|
if require_correct_api_key:
|
682
714
|
if not api_auth_data.is_api_key_correct:
|
@@ -687,7 +719,7 @@ def base_api_auth(
|
|
687
719
|
error_data=safely_transfer_obj_to_json_str_to_json_obj(api_auth_data.model_dump()),
|
688
720
|
)
|
689
721
|
|
690
|
-
#
|
722
|
+
# require_correct_token
|
691
723
|
|
692
724
|
if require_correct_token:
|
693
725
|
if not api_auth_data.is_token_correct:
|
@@ -745,7 +777,6 @@ def create_fastapi_app(
|
|
745
777
|
*,
|
746
778
|
title: str = "arpakitlib FastAPI",
|
747
779
|
description: str | None = "arpakitlib FastAPI",
|
748
|
-
log_filepath: str | None = "./story.log",
|
749
780
|
handle_exception_: Callable | None = None,
|
750
781
|
startup_api_events: list[BaseStartupAPIEvent | None] | None = None,
|
751
782
|
shutdown_api_events: list[BaseShutdownAPIEvent | None] | None = None,
|
@@ -755,12 +786,14 @@ def create_fastapi_app(
|
|
755
786
|
media_dirpath: str | None = None,
|
756
787
|
static_dirpath: str | None = None
|
757
788
|
):
|
758
|
-
_logger.info("start
|
759
|
-
|
760
|
-
setup_normal_logging(log_filepath=log_filepath)
|
789
|
+
_logger.info("start")
|
761
790
|
|
762
791
|
if handle_exception_ is None:
|
763
|
-
handle_exception_ = create_handle_exception(
|
792
|
+
handle_exception_ = create_handle_exception(
|
793
|
+
funcs_before=[
|
794
|
+
logging__api_func_before_in_handle_exception()
|
795
|
+
]
|
796
|
+
)
|
764
797
|
|
765
798
|
if not startup_api_events:
|
766
799
|
startup_api_events = [BaseStartupAPIEvent()]
|
@@ -802,11 +835,14 @@ def create_fastapi_app(
|
|
802
835
|
handle_exception=handle_exception_
|
803
836
|
)
|
804
837
|
|
805
|
-
|
838
|
+
app.include_router(
|
839
|
+
router=create_needed_api_router(),
|
840
|
+
prefix=""
|
841
|
+
)
|
806
842
|
|
807
843
|
app.include_router(router=main_api_router)
|
808
844
|
|
809
|
-
_logger.info("finish
|
845
|
+
_logger.info("finish")
|
810
846
|
|
811
847
|
return app
|
812
848
|
|
@@ -43,6 +43,30 @@ class ScheduleUUSTAPIClient:
|
|
43
43
|
)
|
44
44
|
}
|
45
45
|
|
46
|
+
async def _async_make_http_request(
|
47
|
+
self,
|
48
|
+
*,
|
49
|
+
method: str = "GET",
|
50
|
+
url: str,
|
51
|
+
params: dict[str, Any] | None = None,
|
52
|
+
**kwargs
|
53
|
+
) -> ClientResponse:
|
54
|
+
response = await async_make_http_request(
|
55
|
+
method=method,
|
56
|
+
url=url,
|
57
|
+
headers=self.headers,
|
58
|
+
params=combine_dicts(params, self.auth_params()),
|
59
|
+
max_tries_=9,
|
60
|
+
proxy_url_=self.api_proxy_url,
|
61
|
+
raise_for_status_=True,
|
62
|
+
timeout_=timedelta(seconds=15),
|
63
|
+
**kwargs
|
64
|
+
)
|
65
|
+
json_data = await response.json()
|
66
|
+
if "error" in json_data.keys():
|
67
|
+
raise Exception(f"error in json_data, {json_data}")
|
68
|
+
return response
|
69
|
+
|
46
70
|
def auth_params(self) -> dict[str, Any]:
|
47
71
|
if self.api_password:
|
48
72
|
return {
|
@@ -72,30 +96,6 @@ class ScheduleUUSTAPIClient:
|
|
72
96
|
def generate_v2_token(self) -> str:
|
73
97
|
return self.generate_new_v2_token(password_first_part=self.api_password_first_part)
|
74
98
|
|
75
|
-
async def _async_make_http_request(
|
76
|
-
self,
|
77
|
-
*,
|
78
|
-
method: str = "GET",
|
79
|
-
url: str,
|
80
|
-
params: dict[str, Any] | None = None,
|
81
|
-
**kwargs
|
82
|
-
) -> ClientResponse:
|
83
|
-
response = await async_make_http_request(
|
84
|
-
method=method,
|
85
|
-
url=url,
|
86
|
-
headers=self.headers,
|
87
|
-
params=combine_dicts(params, self.auth_params()),
|
88
|
-
max_tries_=9,
|
89
|
-
proxy_url_=self.api_proxy_url,
|
90
|
-
raise_for_status_=True,
|
91
|
-
timeout_=timedelta(seconds=15),
|
92
|
-
**kwargs
|
93
|
-
)
|
94
|
-
json_data = await response.json()
|
95
|
-
if "error" in json_data.keys():
|
96
|
-
raise Exception(f"error in json_data, {json_data}")
|
97
|
-
return response
|
98
|
-
|
99
99
|
async def get_current_week(self) -> int:
|
100
100
|
"""
|
101
101
|
response.json example
|
arpakitlib/ar_settings_util.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
# arpakit
|
2
|
-
|
2
|
+
import os
|
3
3
|
from typing import Union, Any
|
4
4
|
|
5
|
+
import pytz
|
5
6
|
from pydantic import ConfigDict, field_validator, model_validator
|
6
7
|
from pydantic_core import PydanticUndefined
|
8
|
+
from pydantic_core.core_schema import ValidationInfo
|
7
9
|
from pydantic_settings import BaseSettings
|
8
10
|
|
9
11
|
from arpakitlib.ar_enumeration_util import Enumeration
|
12
|
+
from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str
|
13
|
+
from arpakitlib.ar_sqlalchemy_util import generate_sqlalchemy_url
|
10
14
|
|
11
15
|
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
12
16
|
|
@@ -26,11 +30,9 @@ class ModeTypes(Enumeration):
|
|
26
30
|
prod: str = "prod"
|
27
31
|
|
28
32
|
|
29
|
-
class
|
33
|
+
class BaseSettings2(BaseSettings):
|
30
34
|
model_config = ConfigDict(extra="ignore")
|
31
35
|
|
32
|
-
mode_type: str = ModeTypes.not_prod
|
33
|
-
|
34
36
|
@model_validator(mode="before")
|
35
37
|
@classmethod
|
36
38
|
def validate_all_fields(cls, values: dict[str, Any]) -> dict[str, Any]:
|
@@ -39,6 +41,21 @@ class SimpleSettings(BaseSettings):
|
|
39
41
|
values[key] = None
|
40
42
|
return values
|
41
43
|
|
44
|
+
@classmethod
|
45
|
+
def generate_env_example(cls) -> str:
|
46
|
+
return generate_env_example(settings_class=cls)
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def save_env_example_to_file(cls, filepath: str) -> str:
|
50
|
+
env_example = cls.generate_env_example()
|
51
|
+
with open(filepath, mode="w") as f:
|
52
|
+
f.write(env_example)
|
53
|
+
return env_example
|
54
|
+
|
55
|
+
|
56
|
+
class SimpleSettings(BaseSettings2):
|
57
|
+
mode_type: str = ModeTypes.not_prod
|
58
|
+
|
42
59
|
@field_validator("mode_type")
|
43
60
|
@classmethod
|
44
61
|
def validate_mode_type(cls, v: str):
|
@@ -61,20 +78,155 @@ class SimpleSettings(BaseSettings):
|
|
61
78
|
if self.is_mode_type_prod:
|
62
79
|
raise ValueError(f"mode type = {self.mode_type}")
|
63
80
|
|
64
|
-
@classmethod
|
65
|
-
def generate_env_example(cls) -> str:
|
66
|
-
return generate_env_example(settings_class=cls)
|
67
81
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
82
|
+
class AdvancedSettings(SimpleSettings):
|
83
|
+
project_name: str | None = None
|
84
|
+
|
85
|
+
sql_db_user: str | None = None
|
86
|
+
|
87
|
+
@field_validator("sql_db_user", mode="after")
|
88
|
+
def validate_sql_db_user(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
89
|
+
if v is not None:
|
90
|
+
return v
|
91
|
+
res = validation_info.data.get("project_name")
|
92
|
+
if res is not None:
|
93
|
+
return res
|
94
|
+
return res
|
95
|
+
|
96
|
+
sql_db_password: str | None = None
|
97
|
+
|
98
|
+
@field_validator("sql_db_password", mode="after")
|
99
|
+
def validate_sql_db_password(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
100
|
+
if v is not None:
|
101
|
+
return v
|
102
|
+
res = validation_info.data.get("project_name")
|
103
|
+
if res is not None:
|
104
|
+
return res
|
105
|
+
return res
|
106
|
+
|
107
|
+
sql_db_port: int | None = int("50506") if "50506".strip().isdigit() else None
|
108
|
+
|
109
|
+
sql_db_database: str | None = None
|
110
|
+
|
111
|
+
@field_validator("sql_db_database", mode="after")
|
112
|
+
def validate_sql_db_database(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
113
|
+
if v is not None:
|
114
|
+
return v
|
115
|
+
res = validation_info.data.get("project_name")
|
116
|
+
if res is not None:
|
117
|
+
return res
|
118
|
+
return res
|
119
|
+
|
120
|
+
sync_sql_db_url: str | None = None
|
121
|
+
|
122
|
+
@field_validator("sync_sql_db_url", mode="after")
|
123
|
+
def validate_sync_sql_db_url(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
124
|
+
if v is not None:
|
125
|
+
return v
|
126
|
+
|
127
|
+
return generate_sqlalchemy_url(
|
128
|
+
base="postgresql+asyncpg",
|
129
|
+
user=validation_info.data.get("sql_db_user"),
|
130
|
+
password=validation_info.data.get("sql_db_password"),
|
131
|
+
port=validation_info.data.get("sql_db_port"),
|
132
|
+
database=validation_info.data.get("sql_db_database")
|
133
|
+
)
|
134
|
+
|
135
|
+
async_sql_db_url: str | None = None
|
136
|
+
|
137
|
+
@field_validator("async_sql_db_url", mode="after")
|
138
|
+
def validate_async_sql_db_url(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
139
|
+
if v is not None:
|
140
|
+
return v
|
141
|
+
|
142
|
+
return generate_sqlalchemy_url(
|
143
|
+
base="postgresql+asyncpg",
|
144
|
+
user=validation_info.data.get("sql_db_user"),
|
145
|
+
password=validation_info.data.get("sql_db_password"),
|
146
|
+
port=validation_info.data.get("sql_db_port"),
|
147
|
+
database=validation_info.data.get("sql_db_database")
|
148
|
+
)
|
149
|
+
|
150
|
+
sql_db_echo: bool = False
|
151
|
+
|
152
|
+
api_port: int | None = None
|
153
|
+
|
154
|
+
@field_validator("api_port", mode="before")
|
155
|
+
def validate_api_port(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> int | None:
|
156
|
+
if isinstance(v, str):
|
157
|
+
if v.isdigit():
|
158
|
+
return int(v)
|
159
|
+
return None
|
160
|
+
|
161
|
+
api_init_sql_db_at_start: bool = True
|
162
|
+
|
163
|
+
api_logging__api_func_before_in_handle_exception: bool = True
|
164
|
+
|
165
|
+
api_story_log__api_func_before_in_handle_exception: bool = True
|
166
|
+
|
167
|
+
api_correct_api_key: str | None = "1"
|
168
|
+
|
169
|
+
api_correct_token: str | None = "1"
|
170
|
+
|
171
|
+
api_enable_admin1: bool = True
|
172
|
+
|
173
|
+
admin1_secret_key: str | None = "85a9583cb91c4de7a78d7eb1e5306a04418c9c43014c447ea8ec8dd5deb4cf71"
|
174
|
+
|
175
|
+
var_dirpath: str | None = "./var"
|
176
|
+
|
177
|
+
log_filepath: str | None = None
|
178
|
+
|
179
|
+
@field_validator("log_filepath", mode="after")
|
180
|
+
def validate_log_filepath(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
181
|
+
if v is not None:
|
182
|
+
return v
|
183
|
+
var_dirpath = validation_info.data.get("var_dirpath")
|
184
|
+
if var_dirpath is None:
|
185
|
+
return None
|
186
|
+
return os.path.join(var_dirpath, "story.log")
|
187
|
+
|
188
|
+
cache_dirpath: str | None = None
|
189
|
+
|
190
|
+
@field_validator("cache_dirpath", mode="after")
|
191
|
+
def validate_cache_dirpath(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
192
|
+
if v is not None:
|
193
|
+
return v
|
194
|
+
var_dirpath = validation_info.data.get("var_dirpath")
|
195
|
+
if var_dirpath is None:
|
196
|
+
return None
|
197
|
+
return os.path.join(var_dirpath, "cache")
|
198
|
+
|
199
|
+
media_dirpath: str | None = None
|
200
|
+
|
201
|
+
@field_validator("media_dirpath", mode="after")
|
202
|
+
def validate_media_dirpath(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
203
|
+
if v is not None:
|
204
|
+
return v
|
205
|
+
var_dirpath = validation_info.data.get("var_dirpath")
|
206
|
+
if var_dirpath is None:
|
207
|
+
return None
|
208
|
+
return os.path.join(var_dirpath, "media")
|
209
|
+
|
210
|
+
dump_dirpath: str | None = None
|
211
|
+
|
212
|
+
@field_validator("dump_dirpath", mode="after")
|
213
|
+
def validate_dump_dirpath(cls, v: Any, validation_info: ValidationInfo, **kwargs) -> str | None:
|
214
|
+
if v is not None:
|
215
|
+
return v
|
216
|
+
var_dirpath = validation_info.data.get("var_dirpath")
|
217
|
+
if var_dirpath is None:
|
218
|
+
return None
|
219
|
+
return os.path.join(var_dirpath, "dump")
|
220
|
+
|
221
|
+
local_timezone: str | None = None
|
222
|
+
|
223
|
+
@property
|
224
|
+
def local_timezone_as_pytz(self) -> Any:
|
225
|
+
return pytz.timezone(self.local_timezone)
|
74
226
|
|
75
227
|
|
76
228
|
def __example():
|
77
|
-
|
229
|
+
print(safely_transfer_obj_to_json_str(AdvancedSettings(var_dirpath="./var").model_dump(mode="json")))
|
78
230
|
|
79
231
|
|
80
232
|
if __name__ == '__main__':
|