Porting .NET 8 to Python 1:1

A lot of enterprises have a .NET 8 control plane sitting in front of their data platform. The exercise here is not to rewrite the behavior, it is to preserve it. Same routes, same status codes, same response shapes, typed config, typed services. The runtime changes. Nothing else changes.

The port map

Program to main. Options to pydantic-settings. Controllers to routers. Services to services. Health checks to async checks.

One-to-one mapping. Every file has a target with the same responsibility.
config/database_options.py
python
from pydantic_settings import BaseSettings, SettingsConfigDict


class DatabaseOptions(BaseSettings):
    model_config = SettingsConfigDict(env_prefix='DB_', env_file='.env', extra='ignore')

    postgres: str = ''  # was Postgres in .NET
    mysql: str = ''     # was MySql in .NET
    command_timeout_seconds: int = 30  # was CommandTimeoutSeconds

The .NET DatabaseOptions.cs had [Required] and IConfiguration injection. The Python port uses pydantic-settings with env_prefix and typed defaults. Same field names, same env vars, same runtime behavior.

healthchecks/mysql.py
python
import aiomysql


class MySqlHealthCheck:
    def __init__(self, opts):
        self.opts = opts

    async def check(self) -> dict:
        if not self.opts.mysql:
            return {'status': 'unavailable', 'detail': 'mysql not configured'}
        try:
            url = parse_mysql_url(self.opts.mysql)
            conn = await aiomysql.connect(**url, connect_timeout=self.opts.command_timeout_seconds)
            try:
                async with conn.cursor() as cur:
                    await cur.execute('SELECT 1')
            finally:
                conn.close()
            return {'status': 'healthy'}
        except Exception as exc:
            return {'status': 'unavailable', 'detail': str(exc)[:200]}

The MySqlHealthCheck. The .NET version implemented IHealthCheck.CheckHealthAsync. The Python version is a plain async function. Same contract: never raises, always returns a status.

Every downstream consumer that parsed the old shape. BI dashboards, notebooks, test suites, monitoring. The whole point of 1:1 is that the port is invisible to consumers. You only change behavior when the stakeholders sign off, not as a side effect of a rewrite.

AI prompt: Try it: port a .NET controller to FastAPI

Loading practice…

Quiz: Quiz

Loading practice…