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.
Files changed (39) 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/router/v1/get_api_error_info.py +1 -2
  17. arpakitlib/_arpakit_project_template/src/api/schema/v1/out.py +0 -7
  18. arpakitlib/_arpakit_project_template/src/api/transmitted_api_data.py +3 -11
  19. arpakitlib/_arpakit_project_template/src/core/settings.py +3 -111
  20. arpakitlib/_arpakit_project_template/src/db/util.py +1 -3
  21. arpakitlib/_arpakit_project_template/src/just_script/__init__.py +0 -0
  22. arpakitlib/_arpakit_project_template/src/just_script/example.py +16 -0
  23. arpakitlib/ar_arpakit_project_template_util.py +8 -18
  24. arpakitlib/ar_arpakit_schedule_uust_api_client_util.py +5 -4
  25. arpakitlib/ar_arpakitlib_cli_util.py +10 -22
  26. arpakitlib/ar_class_util.py +0 -1
  27. arpakitlib/ar_cryptomus_api_client_util.py +21 -0
  28. arpakitlib/ar_fastapi_util.py +106 -70
  29. arpakitlib/ar_schedule_uust_api_client_util.py +24 -24
  30. arpakitlib/ar_settings_util.py +166 -14
  31. arpakitlib/ar_sqlalchemy_model_util.py +1 -1
  32. arpakitlib/ar_steam_payment_api_client_util.py +21 -0
  33. arpakitlib/ar_wata_api_client.py +21 -0
  34. {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/METADATA +1 -1
  35. {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/RECORD +39 -30
  36. /arpakitlib/_arpakit_project_template/src/core/{_check_settings.py → _show_settings.py} +0 -0
  37. {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/LICENSE +0 -0
  38. {arpakitlib-1.7.249.dist-info → arpakitlib-1.7.252.dist-info}/WHEEL +0 -0
  39. {arpakitlib-1.7.249.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
@@ -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
- funcs_before_response: list[Callable | None] | None = None,
211
- 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,
212
220
  ) -> Callable:
213
- if funcs_before_response is None:
214
- funcs_before_response = []
215
- 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]
216
224
 
217
- if async_funcs_after_response is None:
218
- async_funcs_after_response = []
219
- 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]
220
228
 
221
- async def handle_exception(
222
- request: starlette.requests.Request, exception: Exception
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
- _kwargs = {}
279
- for func in funcs_before_response:
280
- _func_data = func(
281
- 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
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
- status_code, error_so, _kwargs = _func_data[0], _func_data[1], _func_data[2]
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(_kwargs, dict)
302
+ raise_for_type(_transmitted_kwargs, dict)
303
+
304
+ # async_funcs_after
290
305
 
291
- for async_func_after_response in async_funcs_after_response:
292
- raise_if_not_async_func(async_func_after_response)
293
- _ = asyncio.create_task(async_func_after_response(
294
- 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
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 handle_exception
317
+ return func
303
318
 
304
319
 
305
- def logging_func_before_response(
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
- ) -> (int, ErrorSO, dict[str, Any]):
320
- kwargs["logging_before_response_in_handle_exception"] = True
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 status_code, error_so, kwargs
341
+ return error_so, transmitted_kwargs
324
342
 
325
343
  if ignore_status_codes and status_code in ignore_status_codes:
326
- return status_code, error_so, kwargs
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 status_code, error_so, kwargs
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 story_log_func_before_response(
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
- ) -> (int, ErrorSO, dict[str, Any]):
355
- kwargs["create_story_log_before_response_in_handle_exception"] = True
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 status_code, error_so, kwargs
379
+ return error_so, transmitted_kwargs
359
380
 
360
381
  if ignore_status_codes and status_code in ignore_status_codes:
361
- return status_code, error_so, kwargs
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 status_code, error_so, kwargs
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
- kwargs["story_log_id"] = story_log_dbm.id
403
+ transmitted_kwargs["story_log_id"] = story_log_dbm.id
383
404
 
384
- return status_code, error_so, kwargs
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 add_needed_api_router_to_app(*, app: FastAPI):
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
- app.include_router(router=api_router, prefix="")
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(APIKeyHeader(name="apikey", auto_error=False)),
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
- # api_key
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
- # token
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
- # api_key
681
+ # validate_api_key_func
650
682
 
651
683
  if validate_api_key_func is not None:
652
- validate_api_key_func_data = validate_api_key_func(
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(validate_api_key_func_data):
661
- validate_api_key_func_data = await validate_api_key_func_data
662
- 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
663
695
 
664
- # token
696
+ # validate_token_func
665
697
 
666
698
  if validate_token_func is not None:
667
- validate_token_func_data = validate_token_func(
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(validate_token_func_data):
676
- validate_token_func_data_data = await validate_token_func_data
677
- 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
678
710
 
679
- # api_key
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
- # token
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 create_fastapi_app")
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
- add_needed_api_router_to_app(app=app)
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 create_fastapi_app")
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
@@ -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__':