Skip to content

Injectable Providers (Services)

Providers are a fundamental concept of the framework. Many of Ascender Framework's classes are providers, some of them are just values, most of them are services or even repositories. The most common thing you may use in them are services. A service is usually a class with it's specific well-defined purpose. It can carry specific business logic tailored for specific purpose. In basic Ascender Framework application it used to supply your controller and routes with a specific business logic for it.

Now in previous chapters we discussed about basics of @Injectables, let's dive deeper into them in this chapter.

Services examples

Let's define an example service which works as file manager and stores text in files when required. We'll call it, DataService

src/data_service.py
@Injectable(
    provided_in="root"
)
class DataService:

    def save_file(self, path: str, content: str) -> None:
        with open(f"{path}/content.txt", "w") as f:
            f.write(content)

        print(f"Created/Updated file in {path}/content.txt")

    def read_file(self, path: str) -> str:
        with open(f"{path}/content.txt", "r") as f:
            content = f.read()

        return content
The @Injectable() decorator marks the DataService as an injectable and tells Ascender Framework's DI to that it can be injected or inject dependencies. Also the parameter of decorator provided_in="root" specifies that it DataService will be available and provided in the application as a singletone.

Creating Service

You don't have to manually define these services as it time consuming. Ascender Framework CLI tries to maximumly optimize your time by creating these structures for you so you can concentrate on important things instead of this routine.

For example, to generate a new ExampleService in src/examples:

  1. Run Ascender Framework CLI command below:
    $ ascender generate service examples/example
    

You can also specify where to provide this service by specifying --module. Let's imagine we have an AscModule in example_module.py:

$ ascender generate service examples/example --module example
2. These commands will create ExampleService with path src/examples/example_service.py

src/examples/example_service.py (Generated by Ascender CLI)
from ascender.core import Injectable


@Injectable()
class ExampleService:
    def __init__(self):
        ...
src/examples/example_service.py (Generated by Ascender CLI)
from ascender.core import Injectable


@Injectable()
class ExampleService:
    def __init__(self):
        ...

and AscModule in src/examples/example_module.py

src/examples/example_module.py (Updated by Ascender CLI)
from ascender.core import AscModule

@AscModule(
    imports=[],
    declarations=[],
    providers=[
        ExampleService
    ],
    exports=[]
)

Injecting Services

To inject service as dependency into controller or other services you can use their class __init__ method. Ascender Framework's DI determines and detects annotations of their __init__ method to supply it with dependencies it needs.

Unlike TypeScript, which loses type information during transpilation to JavaScript, Python retains its type annotations at runtime. This removes restrictions with resolving Type based dependencies and using @Injectable in some cases is not required, but strongly recommended to be used!

src/controllers/example/example_controller.py (Injecting a Service into Controller)
from examples.example_service import ExampleService

@Controller(
    name="example", 
    standalone=False
)
class ExampleController
    def __init__(self, example_service: ExampleService):
        self.example_service = example_service

And here's the service depends on service injection example:

Injecting DataService into ExampleService
from ascender.core import Injectable
from data.data_service import DataService

@Injectable()
class ExampleService:
    def __init__(self, data_service: DataService):
        self.data_service = data_service