engin 0.0.dev1__py3-none-any.whl → 0.0.2__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.
engin/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
+ from engin import ext
1
2
  from engin._assembler import Assembler
2
3
  from engin._block import Block, invoke, provide
3
4
  from engin._dependency import Entrypoint, Invoke, Provide, Supply
@@ -8,14 +9,15 @@ from engin._lifecycle import Lifecycle
8
9
  __all__ = [
9
10
  "Assembler",
10
11
  "AssemblyError",
12
+ "Block",
11
13
  "Engin",
12
14
  "Entrypoint",
13
15
  "Invoke",
14
16
  "Lifecycle",
15
- "Block",
16
17
  "Option",
17
18
  "Provide",
18
19
  "Supply",
20
+ "ext",
19
21
  "invoke",
20
22
  "provide",
21
23
  ]
engin/_assembler.py CHANGED
@@ -49,7 +49,7 @@ class Assembler:
49
49
  if not providers:
50
50
  if type_id.multi:
51
51
  LOG.warning(f"no provider for '{type_id}' defaulting to empty list")
52
- providers = [(Supply([], type_hint=list[type_id.type]))]
52
+ providers = [(Supply([], type_hint=list[type_id.type]))] # type: ignore[name-defined]
53
53
  else:
54
54
  raise LookupError(f"No Provider registered for dependency '{type_id}'")
55
55
 
@@ -112,15 +112,20 @@ class Assembler:
112
112
 
113
113
  async def get(self, type_: type[T]) -> T:
114
114
  type_id = type_id_of(type_)
115
+ if type_id in self._dependencies:
116
+ return self._dependencies[type_id]
115
117
  if type_id.multi:
116
118
  out = []
117
119
  for provider in self._multiproviders[type_id]:
118
120
  assembled_dependency = await self.assemble(provider)
119
121
  out.extend(await assembled_dependency())
120
- return out
122
+ self._dependencies[type_id] = out
123
+ return out # type: ignore[return-value]
121
124
  else:
122
125
  assembled_dependency = await self.assemble(self._providers[type_id])
123
- return await assembled_dependency()
126
+ value = await assembled_dependency()
127
+ self._dependencies[type_id] = value
128
+ return value # type: ignore[return-value]
124
129
 
125
130
  def has(self, type_: type[T]) -> bool:
126
131
  return type_id_of(type_) in self._providers
engin/_engin.py CHANGED
@@ -21,7 +21,7 @@ class Engin:
21
21
  _LIB_OPTIONS: ClassVar[list[Option]] = [Provide(Lifecycle)]
22
22
 
23
23
  def __init__(self, *options: Option) -> None:
24
- self._providers: dict[TypeId, Provide] = {}
24
+ self._providers: dict[TypeId, Provide] = {TypeId.from_type(Engin): Provide(self._self)}
25
25
  self._invokables: list[Invoke] = []
26
26
  self._stop_event = Event()
27
27
 
@@ -35,13 +35,9 @@ class Engin:
35
35
  async def run(self):
36
36
  await self.start()
37
37
 
38
- # lifecycle startup
39
-
40
38
  # wait till stop signal recieved
41
39
  await self._stop_event.wait()
42
40
 
43
- # lifecycle shutdown
44
-
45
41
  async def start(self) -> None:
46
42
  LOG.info("starting engin")
47
43
  assembled_invocations: list[AssembledDependency] = [
@@ -87,3 +83,6 @@ class Engin:
87
83
  LOG.debug(f"ENTRYPOINT {type_id!s:<35}")
88
84
  elif isinstance(opt, Invoke):
89
85
  LOG.debug(f"INVOKE {opt.name:<35}")
86
+
87
+ def _self(self) -> "Engin":
88
+ return self
@@ -1,6 +1,6 @@
1
1
  import traceback
2
2
  import typing
3
- from typing import Protocol, TypeAlias
3
+ from typing import ClassVar, Protocol, TypeAlias
4
4
 
5
5
  from engin import Engin, Option
6
6
 
@@ -18,13 +18,16 @@ class ASGIType(Protocol):
18
18
 
19
19
 
20
20
  class ASGIEngin(Engin, ASGIType):
21
+ _asgi_type: ClassVar[type[ASGIType]] = ASGIType # type: ignore[type-abstract]
21
22
  _asgi_app: ASGIType
22
23
 
23
24
  def __init__(self, *options: Option) -> None:
24
25
  super().__init__(*options)
25
26
 
26
- if not self._assembler.has(ASGIType):
27
- raise LookupError("A provider for `ASGIType` was expected, none found")
27
+ if not self._assembler.has(self._asgi_type):
28
+ raise LookupError(
29
+ f"A provider for `{self._asgi_type.__name__}` was expected, none found"
30
+ )
28
31
 
29
32
  async def __call__(self, scope: _Scope, receive: _Receive, send: _Send) -> None:
30
33
  if scope["type"] == "lifespan":
@@ -44,7 +47,7 @@ class ASGIEngin(Engin, ASGIType):
44
47
 
45
48
  async def _startup(self) -> None:
46
49
  await self.start()
47
- self._asgi_app = await self._assembler.get(ASGIType)
50
+ self._asgi_app = await self._assembler.get(self._asgi_type)
48
51
 
49
52
 
50
53
  class _Rereceive:
engin/ext/fastapi.py ADDED
@@ -0,0 +1,42 @@
1
+ import typing
2
+ from typing import ClassVar, TypeVar
3
+
4
+ from engin import Engin, Invoke, Option
5
+ from engin.ext.asgi import ASGIEngin
6
+
7
+ try:
8
+ from fastapi import FastAPI
9
+ from fastapi.params import Depends
10
+ from starlette.requests import HTTPConnection
11
+ except ImportError:
12
+ raise ImportError("fastapi must be installed to use the corresponding extension")
13
+
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from fastapi import FastAPI
17
+ from fastapi.params import Depends
18
+
19
+ __all__ = ["FastAPIEngin", "Inject"]
20
+
21
+
22
+ def _attach_engin(
23
+ app: FastAPI,
24
+ engin: Engin,
25
+ ) -> None:
26
+ app.state.engin = engin
27
+
28
+
29
+ class FastAPIEngin(ASGIEngin):
30
+ _LIB_OPTIONS: ClassVar[list[Option]] = [*ASGIEngin._LIB_OPTIONS, Invoke(_attach_engin)] # type: ignore[arg-type]
31
+ _asgi_type = FastAPI
32
+
33
+
34
+ T = TypeVar("T")
35
+
36
+
37
+ def Inject(interface: type[T]) -> Depends:
38
+ async def inner(conn: HTTPConnection) -> T:
39
+ engin: Engin = conn.app.state.engin
40
+ return await engin.assembler.get(interface)
41
+
42
+ return Depends(inner)
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: engin
3
+ Version: 0.0.2
4
+ Summary: An async-first modular application framework
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+
9
+ # Engin 🏎️
10
+
11
+ Engin is a zero-dependency application framework for modern Python.
12
+
13
+ ## Features ✨
14
+
15
+ - **Lightweight**: Engin has no dependencies.
16
+ - **Async First**: Engin provides first-class support for applications.
17
+ - **Dependency Injection**: Engin promotes a modular decoupled architecture in your application.
18
+ - **Lifecycle Management**: Engin provides an simple, portable approach for implememting
19
+ startup and shutdown tasks.
20
+ - **Ecosystem Compatability**: seamlessly integrate with frameworks such as FastAPI without
21
+ having to migrate your dependencies.
22
+ - **Code Reuse**: Engin's modular components work great as packages and distributions. Allowing
23
+ low boiler-plate code reuse within your Organisation.
24
+
25
+ ## Installation
26
+
27
+ Engin is available on PyPI, install using your favourite dependency manager:
28
+
29
+ - **pip**:`pip install engin`
30
+ - **poetry**: `poetry add engin`
31
+ - **uv**: `uv add engin`
32
+
33
+ ## Getting Started
34
+
35
+ A minimal example:
36
+
37
+ ```python
38
+ import asyncio
39
+
40
+ from httpx import AsyncClient
41
+
42
+ from engin import Engin, Invoke, Provide
43
+
44
+
45
+ def httpx_client() -> AsyncClient:
46
+ return AsyncClient()
47
+
48
+
49
+ async def main(http_client: AsyncClient) -> None:
50
+ print(await http_client.get("https://httpbin.org/get"))
51
+
52
+ engin = Engin(Provide(httpx_client), Invoke(main))
53
+
54
+ asyncio.run(engin.run())
55
+ ```
56
+
@@ -0,0 +1,16 @@
1
+ engin/__init__.py,sha256=AIE9wnwvfXwS1mwp6Sa9xtatBYyYzhWa36GKfAkEx_M,508
2
+ engin/_assembler.py,sha256=Py159y4HvI54QuDcmbjL2kfXtbucYDE2KFBOgoC4qCM,5243
3
+ engin/_block.py,sha256=XrGMFEeps_rvOGsYTlsefUkJGAY6jqdp71G8jE9FbwU,1109
4
+ engin/_dependency.py,sha256=cL1qQFKDt7Rgg7mx8IXVkCZlUigg5cvhPFF9g2LNrgg,4749
5
+ engin/_engin.py,sha256=KHjtncm0bG_N4sAFt7haxUBMeuLp0zP8Bxpftp7Zgy8,3109
6
+ engin/_exceptions.py,sha256=nkzTqxrW5nkcNgFDGoZ2TBtnHtO2RLk0qghM5LNAEmU,542
7
+ engin/_lifecycle.py,sha256=0hk24fiwaBos5kaZrnG_Qm0VmUhWKnGtwYCjc007XDk,729
8
+ engin/_type_utils.py,sha256=naEk-lknC3Fdsd4jiP4YZAxjX3KXZN0MhFde9EV-Fmo,1835
9
+ engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ engin/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ engin/ext/asgi.py,sha256=7viV6YgEwa0KatypDUrEYZT3YIQ3URQbzfVTGG7OuZE,1931
12
+ engin/ext/fastapi.py,sha256=vf7eB5no6eVuZyYdJZdvDYZmjJAmoKbrKhskgiSWg5g,1005
13
+ engin-0.0.2.dist-info/METADATA,sha256=CxEA114Eb2DF_voKUqPITeZiU1KWfSZaqvzIVNXaUAw,1491
14
+ engin-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ engin-0.0.2.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
16
+ engin-0.0.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,6 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: engin
3
- Version: 0.0.dev1
4
- Summary: An async-first modular application framework
5
- License-File: LICENSE
6
- Requires-Python: >=3.10
@@ -1,15 +0,0 @@
1
- engin/__init__.py,sha256=T2tv-pdULMTp-p0PhB_XYmr9CEWOmMzcu0jebZx7-2Q,475
2
- engin/_assembler.py,sha256=HPnrTGk6XbO-iG3BZfD65zzOxWC4C1yWOZf7Vv3MMKk,4944
3
- engin/_block.py,sha256=XrGMFEeps_rvOGsYTlsefUkJGAY6jqdp71G8jE9FbwU,1109
4
- engin/_dependency.py,sha256=cL1qQFKDt7Rgg7mx8IXVkCZlUigg5cvhPFF9g2LNrgg,4749
5
- engin/_engin.py,sha256=Sg7sxESuwzTXx-4xgFv869YkpBRxE9d-kjIOTOTtcxU,3071
6
- engin/_exceptions.py,sha256=nkzTqxrW5nkcNgFDGoZ2TBtnHtO2RLk0qghM5LNAEmU,542
7
- engin/_lifecycle.py,sha256=0hk24fiwaBos5kaZrnG_Qm0VmUhWKnGtwYCjc007XDk,729
8
- engin/_type_utils.py,sha256=naEk-lknC3Fdsd4jiP4YZAxjX3KXZN0MhFde9EV-Fmo,1835
9
- engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- engin/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- engin/extensions/asgi.py,sha256=EHQhevGJt3UJZp_7Y7AIuYVm-bYUO-seuzE3kMuOJWM,1775
12
- engin-0.0.dev1.dist-info/METADATA,sha256=263kjboSCI7Th0GHl7bbpF5cCHAKsB51_56v5xcJThk,152
13
- engin-0.0.dev1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
14
- engin-0.0.dev1.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
15
- engin-0.0.dev1.dist-info/RECORD,,
File without changes