YAML to Python Converter

Paste a config file and get the Python back. Anchors and merge keys are resolved the way PyYAML resolves them, dashed keys are renamed to legal identifiers, and every field is typed from the value that sits in the file.

YAML to Python conversion console

YAML in1 document
Python outWaiting for YAML
  • Resolves anchors and merge keys
  • Renames illegal keys
  • Runs in your tab

Turning a YAML config file into Python types

A YAML file is easy to write and easy to get wrong. Nothing in the file tells you a port has to be an integer, nothing stops a teammate from renaming max-retries to maxRetries, and nothing warns you that the string on reaches your code as True. Typing the file in Python is how those problems surface at load time instead of at three in the morning. The five styles above are five answers to the same question, and they differ in what they cost you at runtime.

Five ways to land the same file in Python

Pick by what has to happen after the file is read. A script that prints a report wants nothing more than a dict. A service that refuses to boot on a bad config wants real validation.

StyleWhat you getRuntime costReach for it when
Dict literalThe parsed file as Python sourceNoneYou want the values pasted into a test fixture or a notebook, without reading a file at all
DataclassTyped attributes, __repr__, __eq__Standard library onlyThe config is internal, already trusted, and you want dot access with type checking in the editor
TypedDictTypes over the dict PyYAML returnsNothing at runtimeExisting code already passes the dict around and you do not want to change a single call site
Pydantic v2Parsing, coercion and errors that name the fieldA third party dependencyThe file comes from a person, a deploy pipeline, or anywhere a wrong type should stop the process
Plain classAn __init__ with no decoratorsNoneYou are on an old Python, or the class needs methods and logic a dataclass would fight you over

The split worth internalising is between the first three and Pydantic. A dataclass annotation is a note for your editor. Write port: int, hand the class a string, and Python builds the object without a word of complaint. Pydantic is the only option here that reads the annotation at runtime, rejects port: "eight thousand", and tells you which key in which block was wrong.

What PyYAML hands back for each scalar

Conversion starts with the reader, not with the class. These are the values yaml.safe_load produces, and the type of each field follows from them.

Written in YAMLPython objectWorth knowing
8443intBare digits, no quotes needed
12.5floatOne decimal in a list of whole numbers widens the whole field
true, on, yesboolAll three are True in YAML 1.1, which is what PyYAML implements
~, null, emptyNoneAn empty value is not an empty string
2026-08-14datetime.dateUnquoted dates are parsed, not left as text
2026-08-14T09:21:05Zdatetime.datetimeTimezone aware when the file carries an offset
"8443"strQuotes are the only way to keep a numeric string
| blockstr with newlinesLine breaks survive, trailing newline included
> blockstr, foldedLine breaks become spaces, blank lines stay
[]listEmpty in the sample means no item type to read

The one that bites hardest is the country code for Norway. A list of regions holding NO comes back as the boolean False, because YAML 1.1 treats no, off and n as false. Version numbers do the same thing in reverse, since version: 1.20 is the float 1.2 and the trailing zero is gone. Quote both, and the generator types them str.

Keys YAML allows and Python refuses

A YAML key is any string. A Python attribute is not, and config files break that rule constantly, mostly because kebab-case reads well in a file nobody writes code in.

YAML in
deploy:host: api.toolexe.com
max-retries: 5
class: standard
2fa-required: true
Dataclass out
@dataclass
class Deploy:host: str
max_retries: int
class_: str
k_2fa_required: bool

Renaming has a cost, and it lands in different places depending on the style. A dataclass built with Deploy(**block) raises TypeError on the original key, so something has to translate. Pydantic solves it properly with Field(alias='max-retries') plus populate_by_name=True, which makes both spellings work. A TypedDict cannot rename at all, since its keys are the dict keys, so the output switches to the functional form Deploy = TypedDict('Deploy', {'max-retries': int}) the moment a key stops being a valid identifier.

Anchors and merge keys are resolved, not repeated

A config that defines a block once and reuses it three times is a good config and a confusing one to type. The reader here does what PyYAML does before your code ever sees the file, which is expand every alias into the value it points at.

YAML in
defaults: &defaults
timeout-seconds: 12.5
retries: 3
worker:<<: *defaults
queue: exports
Dataclass out
@dataclass
class Worker:timeout_seconds: float
retries: int
queue: str

The merged keys are written into Worker in full, and a separate Defaults class is written too, since the anchored block is a key in its own right. Two blocks that end up with matching field names and matching types share one class rather than producing near duplicates. Delete the one you do not want after generating, or point both attributes at the same type.

What a list of blocks tells the generator

This is the reason to paste a whole file rather than one trimmed entry. Every item in a list is folded into a single shape before a class is written. A key present in all of them stays required. A key present in some of them comes through as Optional, and the decision log says how many entries were missing it.

YAML in
feature-flags:- name: new-search
enabled: true
rollout: 0.25
- name: bulk-export
enabled: false
Dataclass out
@dataclass
class FeatureFlag:name: str
enabled: bool
rollout: Optional[float] = None

Two more decisions are visible there. The plural key feature-flags was made singular for the class name, so the attribute is List[FeatureFlag] rather than List[FeatureFlags]. And a field that is whole in one entry and decimal in another widens to float, since int would reject half the file.

Defaults, and the trap underneath them

Turning on Turn the values into defaults writes the values from your file into the class. It also walks straight into the oldest bug in Python configuration code, which is why the output handles lists and nested blocks differently from everything else.

What breaks
@dataclass
class Config:origins: List[str] = ['https://toolexe.com']
# ValueError: mutable default
# <class 'list'> for field origins
What is written
@dataclass
class Config:origins: List[str] = field(default_factory=lambda: ['https://toolexe.com'])

Dataclasses raise at import time, which is the friendly outcome. A plain class does not. Write def __init__(self, origins=[]) and Python builds that list once, at definition time, then shares it between every instance you ever create. Append to one config and the next one starts with your leftovers. In the plain class style the output passes None and rebuilds the value inside __init__, which is the standard fix.

Loading the file once you have the types

Tick Append a safe_load snippet and the output ends with the code that reads the file. Two details there matter more than the rest.

Dataclass, shallow
raw = yaml.safe_load(text)config = Config(**raw)config.database
# {'dsn': '...', 'pool': {...}}
# a plain dict, not Database
Pydantic, all the way down
raw = yaml.safe_load(text)config = Config.model_validate(raw)config.database.pool.max
# 20, typed, checked

Unpacking a dict into a dataclass fills the top level and stops. Nested keys stay dicts, so config.database.pool.max raises AttributeError on a value that looks correct in the editor. Either build the nested objects yourself, add dacite to do it for you, or switch to the Pydantic style, where model_validate walks the whole tree.

The second detail is safe_load rather than load. Plain yaml.load without a loader argument can construct arbitrary Python objects from tags in the file, which turns a config file into code execution. Use safe_load for anything you did not write yourself, and for most things you did.

Reading the decision log

The panel under the editors lists what the generator worked out and what it guessed. It is the part to read before pasting the file into a repository. Square markers in green are choices made from solid evidence, such as a renamed key or a resolved merge. Amber markers are places where your sample did not carry enough information, such as an empty list, a field missing from some entries, or a boolean that started life as the word on. Every amber line is a spot where a second sample file would produce better types.

Where this converter stops

Nothing you paste leaves the page. The reader and the generator both run in your browser, so a config carrying a database password or an API token stays on your machine. Load the page once, drop your connection, and everything above still works.

Questions about generating Python from YAML

Dataclasses against Pydantic, renamed keys, booleans that were meant to be strings, and the fields inference gets wrong.

Should I generate a dataclass or a Pydantic model?

It comes down to whether anything should check the file at runtime. A dataclass annotation is a note for your editor and your type checker. Nothing reads it while the program runs, so a port that arrives as the string "8443" builds the object quietly and fails later somewhere unrelated. Pydantic reads the same annotation at load time, coerces what it safely can, and raises an error naming the field and the block it came from. Use a dataclass for a config you write and control. Use Pydantic for anything a deploy pipeline, a teammate, or a customer can edit.

Why did my key named class come out as class_?

Because class is a reserved word in Python and cannot be an attribute name. The trailing underscore is the convention PEP 8 gives for exactly this case, and the standard library uses it in the same spot. The same rule catches import, from, lambda, global and the rest of the keyword list. In the Pydantic style the original key survives as an alias, so the model still reads the file unchanged. In the dataclass and plain class styles the rename is one way, and you have to map the key yourself when constructing the object.

My region list holds NO for Norway and it became False. What happened?

YAML 1.1 treats no, n, off and false as the same boolean, and PyYAML implements YAML 1.1. So does the reader on this page, which is why the output matches what your program will actually see. The fix is in the file, not in the code. Quote the value as "NO" and it stays a string everywhere. The same trap catches a version key written as 1.20, which is the float 1.2 with the trailing zero gone, and a truthy shell flag written as y.

Why is config.database.pool.max failing after I load the file?

Because Config(**raw) unpacks one level. The top level attributes are set, and every nested key stays the plain dict that safe_load produced, so config.database is a dict and has no pool attribute. Type checkers do not catch this, since the annotation claims otherwise. Three ways out. Build the nested objects by hand, add dacite and call dacite.from_dict, or switch the output to Pydantic where model_validate builds the whole tree in one call.

What does the TypedDict style actually give me?

Type checking over the dict you already have, with no change to any call site and nothing new at runtime. safe_load returns a dict, and a TypedDict describes the keys and value types of that dict, so mypy or Pyright start flagging a misspelled key or a bad comparison. Nothing is constructed and nothing is validated, so a wrong type in the file passes straight through. It is the cheapest way to add types to code that already reads config dicts, and the wrong choice if you wanted the file checked.

Why is one of my fields Optional when the value is right there in the file?

That happens under a list of blocks. Every entry is folded into one shape, so a key that appears in three of five entries becomes optional, and the decision log names the count. It also happens across documents split by --- for the same reason. If a field should always be present, the sample is telling you it is not, and the file is worth a second look before the class is.

Can I paste a Kubernetes manifest or a docker-compose file?

Yes, and multi-document manifests work too. Documents split by --- are all read, then merged into one shape, so a key missing from some documents comes through optional. Bear in mind that yaml.safe_load raises on a multi-document file, and the snippet reminds you to use yaml.safe_load_all and iterate. Manifests with heavy templating are the exception. A file full of Helm placeholders is not valid YAML until the template runs, so render it first.

How do I keep a date as a string?

Quote it in the YAML. An unquoted 2026-08-14 is a datetime.date object as far as PyYAML is concerned, and the generated code matches by typing the field date and writing date(2026, 8, 14) as the default. Quote the value and both the reader and your program treat it as text. Going the other way, a date written as "2026-08-14" with quotes will never parse on its own, so call date.fromisoformat yourself if you want the object back.

Is my config file uploaded anywhere?

No. Reading, type inference and code generation all happen in JavaScript inside this page. Nothing is sent after the page loads, so a file holding a database password or a private key never leaves your machine. Nothing is stored between visits either, and closing the tab clears both panes.