@@ -234,69 +234,206 @@ container.add_transient_by_factory(my_factory) # <-- MyClass is used as Key.
234234
235235## Working with generics
236236
237- Generic types are supported.
237+ Generic types are supported. The following example provides a meaningful
238+ demonstration of generics with ` TypeVar ` in a real-world scenario.
238239
239- ``` python {linenums="1", hl_lines="1 6 9 29 34 40-41 44-45"}
240- from typing import Generic, TypeVar
240+ ``` python {linenums="1", hl_lines="9 43-44 47-48"}
241+ from dataclasses import dataclass
242+ from typing import Generic, List, TypeVar
241243
242244from rodi import Container
243245
244-
245246T = TypeVar(" T" )
246247
247248
248- class LoggedVar (Generic[T]):
249- def __init__ (self , value : T, name : str ):
250- self .name = name
251- self .value = value
249+ class Repository (Generic[T]): # interface
250+ """ A generic repository for managing entities of type T."""
251+
252+ def __init__ (self ):
253+ self ._items: List[T] = []
254+
255+ def add (self , item : T):
256+ """ Add an item to the repository."""
257+ self ._items.append(item)
252258
253- def set (self , new : T) :
254- self .log( " Set " + repr ( self .value))
255- self .value = new
259+ def get_all (self ) -> List[T] :
260+ """ Retrieve all items from the repository. """
261+ return self ._items
256262
257- def get (self ) -> T:
258- self .log(" Get " + repr (self .value))
259- return self .value
260263
261- def log (self , message : str ):
262- print (self .name, message)
264+ # Define specific entity classes
265+ @dataclass
266+ class Product :
267+ id : int
268+ name: str
263269
264270
271+ @dataclass
272+ class Customer :
273+ id : int
274+ email: str
275+ first_name: str
276+ last_name: str
277+
278+
279+ # Set up the container
265280container = Container()
266281
282+ # Register repositories
283+ container.add_scoped(Repository[Product], Repository)
284+ container.add_scoped(Repository[Customer], Repository)
267285
268- class A (LoggedVar[ int ]):
269- def __init__ ( self ):
270- super (). __init__ ( 10 , " example " )
286+ # Resolve and use the repositories
287+ product_repo = container.resolve(Repository[Product])
288+ customer_repo = container.resolve(Repository[Customer] )
271289
290+ # Add and retrieve products
291+ product_repo.add(Product(1 , " Laptop" ))
292+ product_repo.add(Product(2 , " Smartphone" ))
293+ print (product_repo.get_all())
294+
295+ # Add and retrieve customers
296+ customer_repo.add(Customer(1 , " alice@wonderland.it" , " Alice" , " WhiteRabbit" ))
297+ customer_repo.add(Customer(1 , " bob@foopower.it" , " Bob" , " TheHamster" ))
298+ print (customer_repo.get_all())
299+ ```
300+
301+ The above prints to screen:
302+
303+ ``` bash
304+ [Product(id=1, name=' Laptop' ), Product(id=2, name=' Smartphone' )]
305+ [Customer(id=1, email=' alice@wonderland.it' , first_name=' Alice' , last_name=' WhiteRabbit' ), Customer(id=1, email=' bob@foopower.it' , first_name=' Bob' , last_name=' TheHamster' )]
306+ ```
307+
308+ /// admonition | GenericAlias in Python is not considered a class.
309+ type: warning
310+
311+ Note how the generics ` Repository[Product] ` and ` Repository[Customer] ` are both
312+ configured to be resolved using ` Repository ` as concrete type. ` GenericAlias `
313+ in Python is not considered an actual class. The following wouldn't work:
314+
315+ ``` python
316+ container.add_scoped(Repository[Product]) # No. 💥
317+ container.add_scoped(Repository[Customer]) # No. 💥
318+ ```
319+ ///
320+
321+ ### Nested generics
322+
323+ When working with nested generics, ensure that the * same type* used to describe
324+ a dependency is registered in the container.
325+
326+ ``` python {linenums="1", hl_lines="12 16-17 26 33"}
327+ from dataclasses import dataclass
328+ from typing import Generic, List, TypeVar
329+
330+ from rodi import Container
331+
332+ T = TypeVar(" T" )
333+
334+
335+ class DBConnection : ...
336+
337+
338+ class Repository (Generic[T]):
339+ db_connection: DBConnection
340+
341+
342+ class Service (Generic[T]):
343+ repository: Repository[T]
272344
273- class B (LoggedVar[str ]):
274- def __init__ (self ):
275- super ().__init__ (" Foo" , " example" )
276345
346+ @dataclass
347+ class Product :
348+ id : int
349+ name: str
277350
278- class C :
279- a: LoggedVar[int ]
280- b: LoggedVar[str ]
281351
352+ class ProductsService (Service[Product]):
353+ ...
282354
283- container.add_scoped(LoggedVar[int ], A)
284- container.add_scoped(LoggedVar[str ], B)
285- container.add_scoped(C)
286355
287- instance = container.resolve(C)
356+ container = Container()
357+
358+ container.add_scoped(DBConnection)
359+ container.add_scoped(Repository[T], Repository)
360+ container.add_scoped(ProductsService)
288361
289- assert isinstance (instance.a, A)
290- assert isinstance (instance.b, B)
362+ service = container.resolve(ProductsService)
363+ assert isinstance (service.repository, Repository)
364+ assert isinstance (service.repository.db_connection, DBConnection)
291365```
292366
293- As described above, use the * most* abstract class as the key to resolve more
294- * concrete* types, in accordance with the Dependency Inversion Principle (DIP). Generics are the ** most** abstract
295- type, so use them as keys like in the example above at lines _ 44-45_ .
367+ ---
368+
369+ The following wouldn't work, because the ` Container ` will look exactly for the
370+ key ` Repository[T] ` when instantiating the ` ProductsService ` , not for
371+ ` Repository[Product] ` :
372+
373+ ``` python
374+ container.add_scoped(Repository[Product], Repository) # No. 💥
375+ ```
376+
377+ Note that, in practice, this does not cause any issues at runtime, because of
378+ ** type erasure** . For more information, refer to [ _ Instantiating generic classes and type erasure_ ] ( https://typing.python.org/en/latest/spec/generics.html#instantiating-generic-classes-and-type-erasure ) .
379+
380+ If you need to define a more specialized class for ` Repository[Product] ` ,
381+ because for example you need to define products-specific methods, you can:
382+
383+ - Define a ` ProductsRepository(Repository[Product]) ` .
384+ - Override the annotation for ` repository ` in ` ProductsService ` .
385+ - Register ` ProductsRepository ` in the container.
386+
387+ ``` python {linenums="1", hl_lines="26 29-30 37"}
388+ from dataclasses import dataclass
389+ from typing import Generic, TypeVar
390+
391+ from rodi import Container
392+
393+ T = TypeVar(" T" )
394+
395+
396+ class DBConnection : ...
397+
398+
399+ class Repository (Generic[T]):
400+ db_connection: DBConnection
401+
402+
403+ class Service (Generic[T]):
404+ repository: Repository[T]
405+
406+
407+ @dataclass
408+ class Product :
409+ id : int
410+ name: str
411+
412+
413+ class ProductsRepository (Repository[Product]): ...
414+
415+
416+ class ProductsService (Service[Product]):
417+ repository: ProductsRepository
418+
419+
420+ container = Container()
421+
422+ container.add_scoped(DBConnection)
423+ container.add_scoped(Repository[T], Repository)
424+ container.add_scoped(ProductsRepository)
425+ container.add_scoped(ProductsService)
426+
427+ service = container.resolve(ProductsService)
428+ assert isinstance (service.repository, Repository)
429+ assert isinstance (service.repository, ProductsRepository)
430+ assert isinstance (service.repository.db_connection, DBConnection)
431+ ```
296432
297433## Checking if a type is registered
298434
299- To check if a type is registered in the container, use the ` __contains__ ` interface:
435+ To check if a type is registered in the container, use the ` __contains__ `
436+ interface:
300437
301438``` python {linenums="1", hl_lines="11-12"}
302439from rodi import Container
@@ -313,8 +450,9 @@ assert A in container # True
313450assert B not in container # True
314451```
315452
316- This can be useful to support alternative ways to register types. For example, tests
317- code can register a mock type for a class, and the code under test can check if any
318- interface is already registered in the container, and skip the registration if it is.
453+ This can be useful for supporting alternative ways to register types. For
454+ example, test code can register a mock type for a class, and the code under
455+ test can check whether an interface is already registered in the container,
456+ skipping the registration if it is.
319457
320458The next page explains how to work with [ async] ( ./async.md ) .
0 commit comments