fastapi-rtk 1.0.5__tar.gz → 1.0.6__tar.gz

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 (134) hide show
  1. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/PKG-INFO +1 -1
  2. fastapi_rtk-1.0.6/fastapi_rtk/_version.py +1 -0
  3. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/interface.py +0 -54
  4. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/model.py +14 -1
  5. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/async_task_runner.py +115 -29
  6. fastapi_rtk-1.0.5/fastapi_rtk/_version.py +0 -1
  7. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/.gitignore +0 -0
  8. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/LICENSE +0 -0
  9. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/README.md +0 -0
  10. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/__init__.py +0 -0
  11. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/api/__init__.py +0 -0
  12. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/api/base_api.py +0 -0
  13. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/api/model_rest_api.py +0 -0
  14. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/apis.py +0 -0
  15. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/__init__.py +0 -0
  16. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/auth.py +0 -0
  17. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  18. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  19. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  20. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/hashers/utils.py +0 -0
  21. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  22. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  23. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  24. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/strategies/config.py +0 -0
  25. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/strategies/db.py +0 -0
  26. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  27. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/__init__.py +0 -0
  28. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/__init__.py +0 -0
  29. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/column.py +0 -0
  30. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/db.py +0 -0
  31. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  32. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/filters.py +0 -0
  33. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/interface.py +0 -0
  34. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/model.py +0 -0
  35. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/generic/session.py +0 -0
  36. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  37. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/column.py +0 -0
  38. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/db.py +0 -0
  39. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  40. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  41. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  42. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  43. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  44. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  45. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  46. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  47. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/filters.py +0 -0
  48. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/backends/sqla/session.py +0 -0
  49. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/__init__.py +0 -0
  50. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/db.py +0 -0
  51. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/file_manager.py +0 -0
  52. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/filter.py +0 -0
  53. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/interface.py +0 -0
  54. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/model.py +0 -0
  55. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/bases/session.py +0 -0
  56. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/__init__.py +0 -0
  57. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/cli.py +0 -0
  58. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/__init__.py +0 -0
  59. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  60. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  61. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  62. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  63. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  64. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  65. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  66. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  67. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  68. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/export.py +0 -0
  69. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/security.py +0 -0
  70. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/commands/translate.py +0 -0
  71. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/const.py +0 -0
  72. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/decorators.py +0 -0
  73. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/types.py +0 -0
  74. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/cli/utils.py +0 -0
  75. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/config.py +0 -0
  76. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/const.py +0 -0
  77. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/db.py +0 -0
  78. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/decorators.py +0 -0
  79. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/dependencies.py +0 -0
  80. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/exceptions.py +0 -0
  81. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  82. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/file_managers/__init__.py +0 -0
  83. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/file_managers/file_manager.py +0 -0
  84. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/file_managers/image_manager.py +0 -0
  85. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  86. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  87. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/filters.py +0 -0
  88. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/globals.py +0 -0
  89. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/__init__.py +0 -0
  90. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/babel/__init__.py +0 -0
  91. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/babel/cli.py +0 -0
  92. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/babel/config.py +0 -0
  93. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/babel.cfg +0 -0
  94. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/lazy_text.py +0 -0
  95. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/messages.pot +0 -0
  96. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  97. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  98. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  99. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  100. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/manager.py +0 -0
  101. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/middlewares.py +0 -0
  102. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/mixins.py +0 -0
  103. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/models.py +0 -0
  104. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/routers.py +0 -0
  105. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/schemas.py +0 -0
  106. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/security/__init__.py +0 -0
  107. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/security/sqla/__init__.py +0 -0
  108. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/security/sqla/apis.py +0 -0
  109. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/security/sqla/models.py +0 -0
  110. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/security/sqla/security_manager.py +0 -0
  111. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/setting.py +0 -0
  112. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/types.py +0 -0
  113. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/__init__.py +0 -0
  114. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/class_factory.py +0 -0
  115. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  116. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/deep_merge.py +0 -0
  117. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/extender_mixin.py +0 -0
  118. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  119. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/hooks.py +0 -0
  120. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/lazy.py +0 -0
  121. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/merge_schema.py +0 -0
  122. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  123. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/prettify_dict.py +0 -0
  124. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/pydantic.py +0 -0
  125. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/run_utils.py +0 -0
  126. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/self_dependencies.py +0 -0
  127. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  128. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/sqla.py +0 -0
  129. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/timezone.py +0 -0
  130. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/update_signature.py +0 -0
  131. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  132. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/utils/werkzeug.py +0 -0
  133. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/fastapi_rtk/version.py +0 -0
  134. {fastapi_rtk-1.0.5 → fastapi_rtk-1.0.6}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-rtk
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
5
5
  Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
6
6
  Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
@@ -0,0 +1 @@
1
+ __version__ = "1.0.6"
@@ -7,9 +7,7 @@ from typing import Annotated, Literal, Type
7
7
 
8
8
  import fastapi
9
9
  import marshmallow_sqlalchemy
10
- import pydantic
11
10
  import sqlalchemy
12
- import sqlalchemy.exc
13
11
  from pydantic import (
14
12
  AfterValidator,
15
13
  BeforeValidator,
@@ -45,7 +43,6 @@ from ...utils import (
45
43
  lazy_import,
46
44
  lazy_self,
47
45
  safe_call,
48
- safe_call_sync,
49
46
  smart_run,
50
47
  )
51
48
  from .db import SQLAQueryBuilder
@@ -682,57 +679,6 @@ class SQLAInterface(AbstractInterface[ModelType, Session | AsyncSession, Column]
682
679
 
683
680
  return result
684
681
 
685
- def _generate_schema_from_dict(self, schema_dict):
686
- # Allow await for deferrable columns
687
- deferrable_columns = [
688
- key for key in schema_dict.keys() if key in self.list_properties
689
- ]
690
- model_name = schema_dict.get("__name__", "UnknownModel")
691
- if deferrable_columns:
692
-
693
- async def fill_deferrable(model: Model, col: str):
694
- try:
695
- return getattr(model, col)
696
- except sqlalchemy.exc.MissingGreenlet:
697
- logger.warning(
698
- f"'MissingGreenlet' error when accessing {model.__class__.__name__}.{col} to create pydantic model {model_name}, trying to await it instead."
699
- )
700
- try:
701
- await getattr(model.awaitable_attrs, col)
702
- except Exception as e:
703
- return_value = (
704
- []
705
- if self.is_relation_one_to_many(col)
706
- or self.is_relation_many_to_many(col)
707
- else None
708
- )
709
- logger.error(
710
- f"Error when awaiting {model.__class__.__name__}.{col} to create pydantic model {model_name}, returning {return_value} instead: {e}"
711
- )
712
- return return_value
713
- return getattr(model, col)
714
-
715
- async def fill_deferables(data: Model):
716
- async with AsyncTaskRunner():
717
- for col in deferrable_columns:
718
- AsyncTaskRunner.add_task(
719
- lambda col=col: fill_deferrable(data, col)
720
- )
721
-
722
- def fill_deferrables_validator(data):
723
- if isinstance(data, Model):
724
- safe_call_sync(fill_deferables(data))
725
- return data
726
-
727
- decorated_func = pydantic.model_validator(mode="before")(
728
- fill_deferrables_validator
729
- )
730
- schema_dict["__validators__"] = schema_dict.get("__validators__", {})
731
- schema_dict["__validators__"][fill_deferrables_validator.__name__] = (
732
- decorated_func
733
- )
734
- return super()._generate_schema_from_dict(schema_dict)
735
-
736
682
  """
737
683
  --------------------------------------------------------------------------------------------------------
738
684
  SQLA RELATED METHODS - ONLY IN SQLAInterface
@@ -13,7 +13,7 @@ from sqlalchemy.util.typing import Literal
13
13
  from ...bases.model import BasicModel
14
14
  from ...const import DEFAULT_METADATA_KEY
15
15
  from ...exceptions import FastAPIReactToolkitException
16
- from ...utils import smart_run_sync
16
+ from ...utils import AsyncTaskRunner, smart_run_sync
17
17
 
18
18
  __all__ = ["Model", "metadata", "metadatas", "Base"]
19
19
 
@@ -125,6 +125,19 @@ class Model(sqlalchemy.ext.asyncio.AsyncAttrs, BasicModel, DeclarativeBase):
125
125
  def get_pk_attrs(cls):
126
126
  return [col.name for col in cls.__mapper__.primary_key]
127
127
 
128
+ async def load(self, cols: list[str] | str):
129
+ """
130
+ Asynchronously load the specified columns for the current instance.
131
+
132
+ Args:
133
+ cols (list[str] | str): A list of column names to load or a single column name.
134
+ """
135
+ async with AsyncTaskRunner() as runner:
136
+ if isinstance(cols, str):
137
+ cols = [cols]
138
+ for col in cols:
139
+ runner.add_task(getattr(self.awaitable_attrs, col))
140
+
128
141
 
129
142
  class Table(SA_Table):
130
143
  """
@@ -9,6 +9,8 @@ from .prettify_dict import prettify_dict
9
9
 
10
10
  __all__ = ["AsyncTaskRunner"]
11
11
 
12
+ T = typing.TypeVar("T")
13
+
12
14
 
13
15
  class CallerInfo(typing.TypedDict):
14
16
  """
@@ -36,26 +38,37 @@ class AsyncTaskException(AsyncTaskRunnerException):
36
38
  self.caller = caller
37
39
 
38
40
 
39
- def wrap_in_async_task_exception(
40
- task: typing.Callable[[], typing.Coroutine | None] | typing.Coroutine,
41
- /,
42
- caller: CallerInfo | None = None,
43
- ):
44
- async def wrapper():
45
- try:
46
- tsk = task
47
- if callable(tsk):
48
- tsk = tsk()
49
- if tsk is None:
50
- return None
51
- return await tsk
52
- except Exception as e:
53
- raise AsyncTaskException(str(e), original_exception=e, caller=caller) from e
41
+ class ResultAccessor(typing.Generic[T]):
42
+ """Helper class that can be awaited OR accessed directly."""
43
+
44
+ def __init__(self, task: "AsyncTask[T]"):
45
+ self._task = task
46
+
47
+ def __await__(self):
48
+ """Allow awaiting: await task.result"""
49
+ return self._task.__await__()
50
+
51
+ def __repr__(self) -> str:
52
+ """Direct access without await - returns value or raises error."""
53
+ return repr(self._ensure_result())
54
54
 
55
- return wrapper
55
+ def __str__(self) -> str:
56
+ """Direct access without await - returns value or raises error."""
57
+ return str(self._ensure_result())
56
58
 
59
+ # Make it behave like the actual value when accessed
60
+ def __getattr__(self, name):
61
+ return getattr(self._ensure_result(), name)
57
62
 
58
- class AsyncTask:
63
+ def _ensure_result(self):
64
+ if not hasattr(self._task, "_result"):
65
+ raise RuntimeError(
66
+ "Task has not been executed yet. Await the task to get the result."
67
+ )
68
+ return self._task._result
69
+
70
+
71
+ class AsyncTask(typing.Generic[T]):
59
72
  """
60
73
  Represents a task to be run asynchronously.
61
74
 
@@ -64,19 +77,51 @@ class AsyncTask:
64
77
 
65
78
  def __init__(
66
79
  self,
67
- task: typing.Callable[[], typing.Coroutine | None] | typing.Coroutine,
80
+ task: typing.Callable[..., typing.Awaitable[T]] | typing.Awaitable[T],
68
81
  /,
69
82
  caller: CallerInfo | None = None,
70
83
  tags: list[str] | None = None,
71
84
  ):
72
- self.task = wrap_in_async_task_exception(task, caller=caller)
85
+ self.task = task
73
86
  self.caller = caller
74
87
  self.tags = tags or []
75
88
 
89
+ @property
90
+ def result(self):
91
+ """
92
+ Provides access to the result of the task.
93
+
94
+ Returns:
95
+ ResultAccessor[T]: An accessor that can be awaited or accessed directly.
96
+ """
97
+ return ResultAccessor(self)
98
+
76
99
  def __call__(self):
77
- if callable(self.task):
78
- return self.task()
79
- return self.task
100
+ return self._run()
101
+
102
+ def __await__(self):
103
+ return self._run().__await__()
104
+
105
+ async def _run(self):
106
+ """
107
+ Runs the task and caches the result.
108
+
109
+ Returns:
110
+ T: The result of the task.
111
+ """
112
+ if hasattr(self, "_result"):
113
+ return self._result
114
+
115
+ try:
116
+ coro = self.task
117
+ if callable(coro):
118
+ coro = coro()
119
+ self._result = await coro
120
+ return self._result
121
+ except Exception as e:
122
+ raise AsyncTaskException(
123
+ str(e), original_exception=e, caller=self.caller
124
+ ) from e
80
125
 
81
126
 
82
127
  class AsyncTaskRunner:
@@ -144,19 +189,59 @@ class AsyncTaskRunner:
144
189
  """
145
190
  self.run_tasks_even_if_exception = run_tasks_even_if_exception
146
191
 
192
+ @typing.overload
147
193
  @classmethod
148
194
  def add_task(
149
195
  cls,
150
- *tasks: typing.Callable[[], typing.Coroutine | None] | typing.Coroutine,
196
+ task: typing.Callable[..., typing.Awaitable[T]]
197
+ | typing.Awaitable[T]
198
+ | AsyncTask[T],
199
+ *,
200
+ tags: list[str] | None = None,
201
+ instance: "AsyncTaskRunner | None" = None,
202
+ ) -> AsyncTask[T]: ...
203
+
204
+ @typing.overload
205
+ @classmethod
206
+ def add_task(
207
+ cls,
208
+ task1: typing.Callable[..., typing.Awaitable[T]]
209
+ | typing.Awaitable[T]
210
+ | AsyncTask[T],
211
+ task2: typing.Callable[..., typing.Awaitable[T]]
212
+ | typing.Awaitable[T]
213
+ | AsyncTask[T],
214
+ *tasks: typing.Callable[..., typing.Awaitable[T]]
215
+ | typing.Awaitable[T]
216
+ | AsyncTask[T],
217
+ tags: list[str] | None = None,
218
+ instance: "AsyncTaskRunner | None" = None,
219
+ ) -> list[AsyncTask[T]]: ...
220
+ @classmethod
221
+ def add_task(
222
+ cls,
223
+ *tasks: typing.Callable[..., typing.Awaitable[T]]
224
+ | typing.Awaitable[T]
225
+ | AsyncTask[T],
151
226
  tags: list[str] | None = None,
152
227
  instance: "AsyncTaskRunner | None" = None,
153
228
  ):
229
+ """
230
+ Adds one or more tasks to the current context's task list. The tasks will be executed when exiting the context.
231
+
232
+ Args:
233
+ tags (list[str] | None, optional): Tags to associate with the tasks. Defaults to None.
234
+ instance (AsyncTaskRunner | None, optional): The AsyncTaskRunner instance to add tasks to. Defaults to None.
235
+
236
+ Returns:
237
+ AsyncTask[T] | list[AsyncTask[T]]: The added task(s).
238
+ """
154
239
  task_list = cls._get_current_task_list("add_task", instance=instance)
155
240
  # Get caller info
156
241
  frame = inspect.currentframe()
157
242
  caller = inspect.getouterframes(frame, 2)[1] if frame else None
158
243
 
159
- async_tasks = [
244
+ async_tasks: list[AsyncTask[T]] = [
160
245
  AsyncTask(
161
246
  task,
162
247
  caller=CallerInfo(
@@ -171,6 +256,7 @@ class AsyncTaskRunner:
171
256
  for task in tasks
172
257
  ]
173
258
  task_list.extend(async_tasks)
259
+ return async_tasks if len(async_tasks) > 1 else async_tasks[0]
174
260
 
175
261
  @classmethod
176
262
  def remove_tasks_by_tag(
@@ -215,16 +301,16 @@ class AsyncTaskRunner:
215
301
  return
216
302
 
217
303
  if tasks:
218
- exceptions: list[AsyncTaskException] = []
304
+ exceptions_with_index = list[tuple[AsyncTaskException, int]]()
219
305
  futures = await asyncio.gather(
220
306
  *(task() for task in tasks), return_exceptions=True
221
307
  )
222
- for future in futures:
308
+ for index, future in enumerate(futures):
223
309
  if isinstance(future, AsyncTaskException):
224
310
  # Handle exceptions from tasks
225
- exceptions.append(future)
311
+ exceptions_with_index.append((future, index))
226
312
 
227
- if exceptions:
313
+ if exceptions_with_index:
228
314
  raise AsyncTaskRunnerException(
229
315
  f"\n{
230
316
  prettify_dict(
@@ -238,7 +324,7 @@ class AsyncTaskRunner:
238
324
  tb.format_exception(exc.original_exception)
239
325
  ),
240
326
  }
241
- for index, exc in enumerate(exceptions)
327
+ for exc, index in exceptions_with_index
242
328
  },
243
329
  }
244
330
  )
@@ -1 +0,0 @@
1
- __version__ = "1.0.5"
File without changes
File without changes
File without changes
File without changes