arpakitlib 1.7.250__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.
Files changed (37) hide show
  1. arpakitlib/_arpakit_project_template/alembic/README +1 -0
  2. arpakitlib/_arpakit_project_template/alembic/env.py +83 -0
  3. arpakitlib/_arpakit_project_template/alembic/script.py.mako +26 -0
  4. arpakitlib/_arpakit_project_template/alembic.ini +119 -0
  5. arpakitlib/_arpakit_project_template/example.env +5 -14
  6. arpakitlib/_arpakit_project_template/manage/docker_run_postgres.sh +4 -3
  7. arpakitlib/_arpakit_project_template/manage/docker_start_postgres.sh +2 -1
  8. arpakitlib/_arpakit_project_template/manage/docker_stop_postgres.sh +2 -1
  9. arpakitlib/_arpakit_project_template/manage/git_set_arpakit_company_origin.sh +4 -2
  10. arpakitlib/_arpakit_project_template/manage/git_set_arpakit_origin.sh +4 -2
  11. arpakitlib/_arpakit_project_template/src/additional_model/additional_model.py +0 -7
  12. arpakitlib/_arpakit_project_template/src/api/auth.py +52 -0
  13. arpakitlib/_arpakit_project_template/src/api/create_api_app.py +21 -14
  14. arpakitlib/_arpakit_project_template/src/api/create_handle_exception_.py +13 -13
  15. arpakitlib/_arpakit_project_template/src/api/event.py +24 -2
  16. arpakitlib/_arpakit_project_template/src/api/transmitted_api_data.py +3 -11
  17. arpakitlib/_arpakit_project_template/src/core/settings.py +3 -111
  18. arpakitlib/_arpakit_project_template/src/db/util.py +1 -3
  19. arpakitlib/_arpakit_project_template/src/just_script/__init__.py +0 -0
  20. arpakitlib/_arpakit_project_template/src/just_script/example.py +16 -0
  21. arpakitlib/ar_arpakit_project_template_util.py +8 -18
  22. arpakitlib/ar_arpakit_schedule_uust_api_client_util.py +5 -4
  23. arpakitlib/ar_arpakitlib_cli_util.py +10 -22
  24. arpakitlib/ar_class_util.py +0 -1
  25. arpakitlib/ar_cryptomus_api_client_util.py +21 -0
  26. arpakitlib/ar_fastapi_util.py +101 -70
  27. arpakitlib/ar_schedule_uust_api_client_util.py +24 -24
  28. arpakitlib/ar_settings_util.py +166 -14
  29. arpakitlib/ar_sqlalchemy_model_util.py +1 -1
  30. arpakitlib/ar_steam_payment_api_client_util.py +21 -0
  31. arpakitlib/ar_wata_api_client.py +21 -0
  32. {arpakitlib-1.7.250.dist-info → arpakitlib-1.7.252.dist-info}/METADATA +1 -1
  33. {arpakitlib-1.7.250.dist-info → arpakitlib-1.7.252.dist-info}/RECORD +37 -28
  34. /arpakitlib/_arpakit_project_template/src/core/{_check_settings.py → _show_settings.py} +0 -0
  35. {arpakitlib-1.7.250.dist-info → arpakitlib-1.7.252.dist-info}/LICENSE +0 -0
  36. {arpakitlib-1.7.250.dist-info → arpakitlib-1.7.252.dist-info}/WHEEL +0 -0
  37. {arpakitlib-1.7.250.dist-info → arpakitlib-1.7.252.dist-info}/entry_points.txt +0 -0
@@ -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.ar_logging_util import setup_normal_logging
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
@@ -212,19 +215,20 @@ class APIException(fastapi.exceptions.HTTPException):
212
215
 
213
216
  def create_handle_exception(
214
217
  *,
215
- funcs_before_response: list[Callable | None] | None = None,
216
- async_funcs_after_response: list[Callable | None] | None = None,
218
+ funcs_before: list[Callable | None] | None = None,
219
+ async_funcs_after: list[Callable | None] | None = None,
217
220
  ) -> Callable:
218
- if funcs_before_response is None:
219
- funcs_before_response = []
220
- funcs_before_response = [v for v in funcs_before_response if v is not None]
221
+ if funcs_before is None:
222
+ funcs_before = []
223
+ funcs_before = [v for v in funcs_before if v is not None]
221
224
 
222
- if async_funcs_after_response is None:
223
- async_funcs_after_response = []
224
- async_funcs_after_response = [v for v in async_funcs_after_response if v is not None]
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]
225
228
 
226
- async def handle_exception(
227
- request: starlette.requests.Request, exception: Exception
229
+ async def func(
230
+ request: starlette.requests.Request,
231
+ exception: Exception
228
232
  ) -> APIJSONResponse:
229
233
  status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
230
234
 
@@ -266,10 +270,10 @@ def create_handle_exception(
266
270
  status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
267
271
  error_so.error_code = BaseAPIErrorCodes.unknown_error
268
272
 
269
- if error_so.error_code:
273
+ if error_so.error_code is not None:
270
274
  error_so.error_code = error_so.error_code.upper().replace(" ", "_").strip()
271
275
 
272
- if error_so.error_specification_code:
276
+ if error_so.error_specification_code is not None:
273
277
  error_so.error_specification_code = (
274
278
  error_so.error_specification_code.upper().replace(" ", "_").strip()
275
279
  )
@@ -280,23 +284,29 @@ def create_handle_exception(
280
284
  if error_so.error_code == BaseAPIErrorCodes.cannot_authorize:
281
285
  status_code = status.HTTP_401_UNAUTHORIZED
282
286
 
283
- _kwargs = {}
284
- for func in funcs_before_response:
285
- _func_data = func(
286
- status_code=status_code, error_so=error_so, request=request, exception=exception, **_kwargs
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
287
296
  )
288
297
  if is_async_object(_func_data):
289
298
  _func_data = await _func_data
290
299
  if _func_data is not None:
291
- status_code, error_so, _kwargs = _func_data[0], _func_data[1], _func_data[2]
292
- raise_for_type(status_code, int)
300
+ error_so, _transmitted_kwargs = _func_data[0], _func_data[1]
293
301
  raise_for_type(error_so, ErrorSO)
294
- raise_for_type(_kwargs, dict)
302
+ raise_for_type(_transmitted_kwargs, dict)
303
+
304
+ # async_funcs_after
295
305
 
296
- for async_func_after_response in async_funcs_after_response:
297
- raise_if_not_async_func(async_func_after_response)
298
- _ = asyncio.create_task(async_func_after_response(
299
- error_so=error_so, status_code=status_code, request=request, exception=exception
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
300
310
  ))
301
311
 
302
312
  return APIJSONResponse(
@@ -304,43 +314,46 @@ def create_handle_exception(
304
314
  status_code=status_code
305
315
  )
306
316
 
307
- return handle_exception
317
+ return func
308
318
 
309
319
 
310
- def logging_func_before_response(
320
+ def logging__api_func_before_in_handle_exception(
311
321
  *,
312
322
  ignore_api_error_codes: list[str] | None = None,
313
323
  ignore_status_codes: list[int] | None = None,
314
324
  ignore_exception_types: list[type[Exception]] | None = None,
315
325
  need_exc_info: bool = False
316
- ):
326
+ ) -> Callable:
327
+ current_func_name = inspect.currentframe().f_code.co_name
328
+
317
329
  def func(
318
330
  *,
331
+ request: starlette.requests.Request,
319
332
  status_code: int,
320
333
  error_so: ErrorSO,
321
- request: starlette.requests.Request,
322
334
  exception: Exception,
335
+ transmitted_kwargs: dict[str, Any],
323
336
  **kwargs
324
- ) -> (int, ErrorSO, dict[str, Any]):
325
- kwargs["logging_before_response_in_handle_exception"] = True
337
+ ) -> (ErrorSO, dict[str, Any]):
338
+ transmitted_kwargs[current_func_name] = now_utc_dt()
326
339
 
327
340
  if ignore_api_error_codes and error_so.error_code in ignore_api_error_codes:
328
- return status_code, error_so, kwargs
341
+ return error_so, transmitted_kwargs
329
342
 
330
343
  if ignore_status_codes and status_code in ignore_status_codes:
331
- return status_code, error_so, kwargs
344
+ return error_so, transmitted_kwargs
332
345
 
333
346
  if ignore_exception_types and (
334
347
  exception in ignore_exception_types or type(exception) in ignore_exception_types
335
348
  ):
336
- return status_code, error_so, kwargs
349
+ return error_so, transmitted_kwargs
337
350
 
338
351
  _logger.error(safely_transfer_obj_to_json_str(error_so.model_dump()), exc_info=need_exc_info)
339
352
 
340
353
  return func
341
354
 
342
355
 
343
- def story_log_func_before_response(
356
+ def story_log__api_func_before_in_handle_exception(
344
357
  *,
345
358
  sqlalchemy_db: SQLAlchemyDB,
346
359
  ignore_api_error_codes: list[str] | None = None,
@@ -349,26 +362,29 @@ def story_log_func_before_response(
349
362
  ) -> Callable:
350
363
  raise_for_type(sqlalchemy_db, SQLAlchemyDB)
351
364
 
365
+ current_func_name = inspect.currentframe().f_code.co_name
366
+
352
367
  async def async_func(
353
368
  *,
369
+ request: starlette.requests.Request,
354
370
  status_code: int,
355
371
  error_so: ErrorSO,
356
- request: starlette.requests.Request,
357
372
  exception: Exception,
373
+ transmitted_kwargs: dict[str, Any],
358
374
  **kwargs
359
- ) -> (int, ErrorSO, dict[str, Any]):
360
- kwargs["create_story_log_before_response_in_handle_exception"] = True
375
+ ) -> (ErrorSO, dict[str, Any]):
376
+ transmitted_kwargs[current_func_name] = now_utc_dt()
361
377
 
362
378
  if ignore_api_error_codes and error_so.error_code in ignore_api_error_codes:
363
- return status_code, error_so, kwargs
379
+ return error_so, transmitted_kwargs
364
380
 
365
381
  if ignore_status_codes and status_code in ignore_status_codes:
366
- return status_code, error_so, kwargs
382
+ return error_so, transmitted_kwargs
367
383
 
368
384
  if ignore_exception_types and (
369
385
  exception in ignore_exception_types or type(exception) in ignore_exception_types
370
386
  ):
371
- return status_code, error_so, kwargs
387
+ return error_so, transmitted_kwargs
372
388
 
373
389
  async with sqlalchemy_db.new_async_session() as session:
374
390
  story_log_dbm = StoryLogDBM(
@@ -384,9 +400,9 @@ def story_log_func_before_response(
384
400
  await session.refresh(story_log_dbm)
385
401
 
386
402
  error_so.error_data.update({"story_log_long_id": story_log_dbm.long_id})
387
- kwargs["story_log_id"] = story_log_dbm.id
403
+ transmitted_kwargs["story_log_id"] = story_log_dbm.id
388
404
 
389
- return status_code, error_so, kwargs
405
+ return error_so, transmitted_kwargs
390
406
 
391
407
  return async_func
392
408
 
@@ -463,7 +479,7 @@ class ARPAKITLibSO(BaseSO):
463
479
  arpakitlib: bool = True
464
480
 
465
481
 
466
- def add_needed_api_router_to_app(*, app: FastAPI):
482
+ def create_needed_api_router():
467
483
  api_router = APIRouter()
468
484
 
469
485
  @api_router.get(
@@ -490,9 +506,7 @@ def add_needed_api_router_to_app(*, app: FastAPI):
490
506
  content=ARPAKITLibSO(arpakitlib=True)
491
507
  )
492
508
 
493
- app.include_router(router=api_router, prefix="")
494
-
495
- return app
509
+ return api_router
496
510
 
497
511
 
498
512
  class BaseStartupAPIEvent:
@@ -517,6 +531,17 @@ class BaseTransmittedAPIData(BaseModel):
517
531
  model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
518
532
 
519
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
+
520
545
  def get_transmitted_api_data(request: starlette.requests.Request) -> BaseTransmittedAPIData:
521
546
  return request.app.state.transmitted_api_data
522
547
 
@@ -574,7 +599,9 @@ def base_api_auth(
574
599
  ac: fastapi.security.HTTPAuthorizationCredentials | None = fastapi.Security(
575
600
  fastapi.security.HTTPBearer(auto_error=False)
576
601
  ),
577
- api_key_string: str | None = Security(APIKeyHeader(name="apikey", auto_error=False)),
602
+ api_key_string: str | None = Security(
603
+ APIKeyHeader(name="apikey", auto_error=False)
604
+ ),
578
605
  request: starlette.requests.Request,
579
606
  transmitted_api_data: BaseTransmittedAPIData = Depends(get_transmitted_api_data)
580
607
  ) -> BaseAPIAuthData:
@@ -586,7 +613,7 @@ def base_api_auth(
586
613
  require_correct_token=require_correct_token
587
614
  )
588
615
 
589
- # api_key
616
+ # parse api_key
590
617
 
591
618
  api_auth_data.api_key_string = api_key_string
592
619
 
@@ -604,7 +631,7 @@ def base_api_auth(
604
631
  if not api_auth_data.api_key_string and "apikey" in request.query_params.keys():
605
632
  api_auth_data.api_key_string = request.query_params["apikey"]
606
633
 
607
- # token
634
+ # parse token
608
635
 
609
636
  api_auth_data.token_string = ac.credentials if ac and ac.credentials and ac.credentials.strip() else None
610
637
 
@@ -633,7 +660,7 @@ def base_api_auth(
633
660
  if not api_auth_data.token_string:
634
661
  api_auth_data.token_string = None
635
662
 
636
- # api_key
663
+ # require_api_key_string
637
664
 
638
665
  if require_api_key_string and not api_auth_data.api_key_string:
639
666
  raise APIException(
@@ -642,7 +669,7 @@ def base_api_auth(
642
669
  error_data=safely_transfer_obj_to_json_str_to_json_obj(api_auth_data.model_dump())
643
670
  )
644
671
 
645
- # token
672
+ # require_token_string
646
673
 
647
674
  if require_token_string and not api_auth_data.token_string:
648
675
  raise APIException(
@@ -651,10 +678,10 @@ def base_api_auth(
651
678
  error_data=safely_transfer_obj_to_json_str_to_json_obj(api_auth_data.model_dump())
652
679
  )
653
680
 
654
- # api_key
681
+ # validate_api_key_func
655
682
 
656
683
  if validate_api_key_func is not None:
657
- validate_api_key_func_data = validate_api_key_func(
684
+ validate_api_key_func_res = validate_api_key_func(
658
685
  api_key_string=api_auth_data.api_key_string,
659
686
  token_string=api_auth_data.token_string,
660
687
  base_api_auth_data=api_auth_data,
@@ -662,14 +689,14 @@ def base_api_auth(
662
689
  request=request,
663
690
  **kwargs
664
691
  )
665
- if is_async_object(validate_api_key_func_data):
666
- validate_api_key_func_data = await validate_api_key_func_data
667
- api_auth_data.is_api_key_correct = validate_api_key_func_data
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
668
695
 
669
- # token
696
+ # validate_token_func
670
697
 
671
698
  if validate_token_func is not None:
672
- validate_token_func_data = validate_token_func(
699
+ validate_token_func_res = validate_token_func(
673
700
  api_key_string=api_auth_data.api_key_string,
674
701
  token_string=api_auth_data.token_string,
675
702
  base_api_auth_data=api_auth_data,
@@ -677,11 +704,11 @@ def base_api_auth(
677
704
  request=request,
678
705
  **kwargs
679
706
  )
680
- if is_async_object(validate_token_func_data):
681
- validate_token_func_data_data = await validate_token_func_data
682
- api_auth_data.is_token_correct = validate_token_func_data_data
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
683
710
 
684
- # api_key
711
+ # require_correct_api_key
685
712
 
686
713
  if require_correct_api_key:
687
714
  if not api_auth_data.is_api_key_correct:
@@ -692,7 +719,7 @@ def base_api_auth(
692
719
  error_data=safely_transfer_obj_to_json_str_to_json_obj(api_auth_data.model_dump()),
693
720
  )
694
721
 
695
- # token
722
+ # require_correct_token
696
723
 
697
724
  if require_correct_token:
698
725
  if not api_auth_data.is_token_correct:
@@ -750,7 +777,6 @@ def create_fastapi_app(
750
777
  *,
751
778
  title: str = "arpakitlib FastAPI",
752
779
  description: str | None = "arpakitlib FastAPI",
753
- log_filepath: str | None = "./story.log",
754
780
  handle_exception_: Callable | None = None,
755
781
  startup_api_events: list[BaseStartupAPIEvent | None] | None = None,
756
782
  shutdown_api_events: list[BaseShutdownAPIEvent | None] | None = None,
@@ -760,12 +786,14 @@ def create_fastapi_app(
760
786
  media_dirpath: str | None = None,
761
787
  static_dirpath: str | None = None
762
788
  ):
763
- _logger.info("start create_fastapi_app")
764
-
765
- setup_normal_logging(log_filepath=log_filepath)
789
+ _logger.info("start")
766
790
 
767
791
  if handle_exception_ is None:
768
- handle_exception_ = create_handle_exception()
792
+ handle_exception_ = create_handle_exception(
793
+ funcs_before=[
794
+ logging__api_func_before_in_handle_exception()
795
+ ]
796
+ )
769
797
 
770
798
  if not startup_api_events:
771
799
  startup_api_events = [BaseStartupAPIEvent()]
@@ -807,11 +835,14 @@ def create_fastapi_app(
807
835
  handle_exception=handle_exception_
808
836
  )
809
837
 
810
- add_needed_api_router_to_app(app=app)
838
+ app.include_router(
839
+ router=create_needed_api_router(),
840
+ prefix=""
841
+ )
811
842
 
812
843
  app.include_router(router=main_api_router)
813
844
 
814
- _logger.info("finish create_fastapi_app")
845
+ _logger.info("finish")
815
846
 
816
847
  return app
817
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
@@ -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 SimpleSettings(BaseSettings):
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
- @classmethod
69
- def save_env_example_to_file(cls, filepath: str) -> str:
70
- env_example = cls.generate_env_example()
71
- with open(filepath, mode="w") as f:
72
- f.write(env_example)
73
- return env_example
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
- pass
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__':
@@ -67,7 +67,7 @@ class BaseDBM(DeclarativeBase):
67
67
 
68
68
  return res
69
69
 
70
- def simple_dict_with_sd_properties(self) -> dict[str ,Any]:
70
+ def simple_dict_with_sd_properties(self) -> dict[str, Any]:
71
71
  return self.simple_dict(include_sd_properties=True)
72
72
 
73
73
  def simple_json(self, *, include_sd_properties: bool = True) -> str: