IaC python - postgres DB how to set sleepApplication
matmair
HOBBYOP

a month ago

I am trying to switch my setup from web defined to the new IaC (with python) and railway config plan shows


~ Update Postgres deploy.sleepApplication

    └ deploy.sleepApplication (true → null)

I am trying to use the following setup


    Postgres = postgres(

        "Postgres",

        region="sfo",

        deploy={

            "sleepApplication": True,

        },

    )
Solved$10 Bounty

Pinned Solution

arthurveisseire
PRO

a month ago

deploy= isn't wired up on the database helpers, so your dict never reaches the graph. That's why the plan shows a removal instead of a no-op: the compiled node has no deploy.sleepApplication at all, so diffing it against the environment (where the dashboard already set it to true) comes out as true -> null.

postgres() is a thin wrapper over database(), and database() cherry-picks options by name:

def database(name: str, engine: str, **options: Any) -> Service:
    image_name = options["image"]
    output = options.get("output") or "DATABASE_URL"
    node: dict[str, Any] = { ...address / type / kind / engine / name / image / output / source... }
    if options.get("defaultMountPath"):
        node["defaultMountPath"] = options["defaultMountPath"]
    if options.get("region"):
        node["deploy"] = {"multiRegionConfig": {options["region"]: {"numReplicas": 1}}}
    return Service(node)

Only image, output, defaultMountPath and region are ever read. Anything else is swallowed by **options and dropped without a warning. service() goes through a different path (_service_node -> _normalize_deploy) which does merge deploy, which is why the same dict works there and not here.

Your call is doubly unlucky: region="sfo" assigns node["deploy"] outright, so even if deploy were merged in, the region branch would overwrite it. Both kwargs land on the same key.

Compiling the node shows it (railway-sdk, railwayapp/railway-py-sdk @ e2df8d4):

\>>> postgres("Postgres", region="sfo", deploy={"sleepApplication": True}).to_graph()["deploy"]
{'multiRegionConfig': {'sfo': {'numReplicas': 1}}}

\>>> "deploy" in postgres("Postgres", deploy={"sleepApplication": True}).to_graph()
False

\>>> service("web", source=github("org/app"), deploy={"sleepApplication": True}).to_graph()["deploy"]
{'sleepApplication': True}

Until the helper takes it, set the field on the node yourself. with_fields() is a public method on the returned Service:

Postgres = postgres("Postgres").with_fields(
    deploy={
        "multiRegionConfig": {"sfo": {"numReplicas": 1}},
        "sleepApplication": True,
    }
)

with_fields shallow-merges onto the node, so it replaces deploy wholesale. That's why the region has to be written back by hand, and why you want to drop region="sfo" from the postgres() call rather than leave both in and depend on which one runs last. Result:

{'multiRegionConfig': {'sfo': {'numReplicas': 1}}, 'sleepApplication': True}

Same shape service() would have produced, so the plan should stop trying to unset the field.

Probably worth an issue on railwayapp/railway-py-sdk too. The helpers accept **config and silently discard whatever they don't recognise, so any typo or unsupported key fails exactly like this: no error, and a plan that quietly reverts something you set in the dashboard.

8 Replies

Railway
BOT

a month ago

This thread has been opened as a bounty so the community can help solve it.

Status changed to Open Railway • about 1 month ago


arthurveisseire
PRO

a month ago

deploy= isn't wired up on the database helpers, so your dict never reaches the graph. That's why the plan shows a removal instead of a no-op: the compiled node has no deploy.sleepApplication at all, so diffing it against the environment (where the dashboard already set it to true) comes out as true -> null.

postgres() is a thin wrapper over database(), and database() cherry-picks options by name:

def database(name: str, engine: str, **options: Any) -> Service:
    image_name = options["image"]
    output = options.get("output") or "DATABASE_URL"
    node: dict[str, Any] = { ...address / type / kind / engine / name / image / output / source... }
    if options.get("defaultMountPath"):
        node["defaultMountPath"] = options["defaultMountPath"]
    if options.get("region"):
        node["deploy"] = {"multiRegionConfig": {options["region"]: {"numReplicas": 1}}}
    return Service(node)

Only image, output, defaultMountPath and region are ever read. Anything else is swallowed by **options and dropped without a warning. service() goes through a different path (_service_node -> _normalize_deploy) which does merge deploy, which is why the same dict works there and not here.

Your call is doubly unlucky: region="sfo" assigns node["deploy"] outright, so even if deploy were merged in, the region branch would overwrite it. Both kwargs land on the same key.

Compiling the node shows it (railway-sdk, railwayapp/railway-py-sdk @ e2df8d4):

\>>> postgres("Postgres", region="sfo", deploy={"sleepApplication": True}).to_graph()["deploy"]
{'multiRegionConfig': {'sfo': {'numReplicas': 1}}}

\>>> "deploy" in postgres("Postgres", deploy={"sleepApplication": True}).to_graph()
False

\>>> service("web", source=github("org/app"), deploy={"sleepApplication": True}).to_graph()["deploy"]
{'sleepApplication': True}

Until the helper takes it, set the field on the node yourself. with_fields() is a public method on the returned Service:

Postgres = postgres("Postgres").with_fields(
    deploy={
        "multiRegionConfig": {"sfo": {"numReplicas": 1}},
        "sleepApplication": True,
    }
)

with_fields shallow-merges onto the node, so it replaces deploy wholesale. That's why the region has to be written back by hand, and why you want to drop region="sfo" from the postgres() call rather than leave both in and depend on which one runs last. Result:

{'multiRegionConfig': {'sfo': {'numReplicas': 1}}, 'sleepApplication': True}

Same shape service() would have produced, so the plan should stop trying to unset the field.

Probably worth an issue on railwayapp/railway-py-sdk too. The helpers accept **config and silently discard whatever they don't recognise, so any typo or unsupported key fails exactly like this: no error, and a plan that quietly reverts something you set in the dashboard.


matmair
HOBBYOP

a month ago

thank you, that worked!


matmair

thank you, that worked!

arthurveisseire
PRO

a month ago

Could you please approve my response ?

Thanks :)


arthurveisseire

Could you please approve my response ? Thanks :)

matmair
HOBBYOP

a month ago

i am no emloyee of railway so no


matmair

i am no emloyee of railway so no

a month ago

You don't need to be; there was a simple "Approve" button.


Status changed to Awaiting Conductor Response Railway • about 1 month ago


Status changed to Solved brody • about 1 month ago


brody

You don't need to be; there was a simple "Approve" button.

matmair
HOBBYOP

a month ago

I did that 11h ago and it just changed to showing some unhelpful "awaiting conductor" badge


Status changed to Awaiting Conductor Response Railway • about 1 month ago


a month ago

That is the correct state, moderators (conductors) do the final approval.


Status changed to Awaiting User Response Railway • about 1 month ago


matmair
HOBBYOP

a month ago

than write that in a info bubble or tooltip; these childish train terms are not helpful for grown up engineers that have stuff to do


Status changed to Awaiting Conductor Response Railway • about 1 month ago


Status changed to Solved brody • about 1 month ago


Welcome!

Sign in to your Railway account to join the conversation.

Loading...