Skip to content

Multi-file projects

Real services have models, business logic, database adapters — all in separate files. fastgrpc supports this without any configuration.

Example layout

my_service/
├── main.py        # @service classes (the gRPC surface)
├── business.py    # business logic (knows nothing about gRPC)
├── models.py      # dataclasses (imported by both)
└── db.py          # database adapter

models.py:

from dataclasses import dataclass

@dataclass
class Address:
    street: str
    city: str

@dataclass
class User:
    id: int
    name: str
    address: Address

business.py:

from models import User, Address

def find_user(user_id: int) -> User | None:
    # ... query db, build User dataclass ...
    return User(id=user_id, name="Ashesh",
                address=Address(street="MG Road", city="Bangalore"))

main.py:

from business import find_user
from models import User
from fastgrpc import rpc, service
from fastgrpc.exceptions import NotFound

@service
class UserService:
    @rpc
    async def get_user(self, user_id: int) -> User:
        user = find_user(user_id)
        if user is None:
            raise NotFound(f"user {user_id} not found")
        return user

Run it:

fastgrpc dev main.py

How the import resolution works

When you run fastgrpc dev main.py, the framework:

  1. Resolves main.py to its absolute path.
  2. Adds the parent directory (my_service/) to sys.path.
  3. Imports main.py as a top-level module.

That's why sibling imports like from business import find_user work — business.py lives next to main.py, and the parent directory is now on sys.path.

Hot reload follows imports

Edit business.py or models.py and the server reloads with your changes — same as editing main.py itself. See the Watch mode guide for the details on why this requires an explicit sys.modules purge.

Multiple services in one file

You can decorate as many classes as you want; they all become separate gRPC services on the same server:

@service
class UserService:
    @rpc
    async def get_user(self, user_id: int) -> User: ...

@service
class OrderService:
    @rpc
    async def get_order(self, order_id: str) -> Order: ...
$ grpcurl -plaintext 127.0.0.1:50051 list
fastgrpc.OrderService
fastgrpc.UserService
grpc.reflection.v1alpha.ServerReflection

Multiple services across files

You can also split services across files; just import them into your entry-point so the decorators run:

# main.py
from user_service import UserService  # noqa: F401 — import for side effects
from order_service import OrderService  # noqa: F401

The @service decorators register both classes regardless of where they're defined.

Service name collisions

If two @service classes happen to have the same __name__ (e.g. both files define class UserService:), fastgrpc fails fast with an error that names both modules:

ValidationError: duplicate service class name(s) detected:
  'UserService' defined in: app.users:UserService, app.admin:UserService
Service class names must be unique across all files.

Rename one of them.