How to read a config file in Python

Learn how to read config files in Python. Discover various methods, tips, real-world applications, and how to debug common errors.

How to read a config file in Python
Published on: 
Tue
Mar 17, 2026
Updated on: 
Fri
Mar 20, 2026
The Replit Team

Python configuration files store parameters that control application behavior. To manage these settings effectively, you must know how to read them, a fundamental skill for any developer.

In this article, you'll explore various techniques to handle configuration files. You will find practical tips, real-world applications, and common debugging advice to select the best approach for your project.

Basic file reading approach

config_file = open('config.txt', 'r')
config_data = config_file.read().splitlines()
config_file.close()

for line in config_data:
   if line.startswith('SERVER='):
       print(f"Server: {line.split('=')[1]}")--OUTPUT--Server: production

This approach treats the configuration file as plain text, offering a straightforward method that doesn't rely on external libraries. The code reads the entire file into a list of strings using read().splitlines(), allowing you to process each line individually.

  • The script iterates through each line, searching for a specific key with startswith('SERVER=').
  • Once a match is found, split('=')[1] isolates and extracts the value. This technique is effective for simple key-value formats but can become cumbersome as complexity grows.

Common configuration file formats

Instead of parsing files line by line, you can use Python's powerful libraries to handle common configuration formats like INI, JSON, and YAML more efficiently.

Using the configparser module for INI files

import configparser

config = configparser.ConfigParser()
config.read('config.ini')
database_host = config['DATABASE']['Host']
database_port = config['DATABASE']['Port']
print(f"Database: {database_host}:{database_port}")--OUTPUT--Database: localhost:5432

The configparser module is Python's built-in tool for handling INI files, which organize settings into sections. After creating a ConfigParser object, you use the read() method to parse your file. The module treats the configuration like a nested dictionary, making data access intuitive.

  • You can retrieve values by specifying the section and key, like config['DATABASE']['Host'].
  • This approach is much cleaner than manual parsing, especially for files with multiple sections.

Reading JSON configuration files

import json

with open('config.json', 'r') as file:
   config = json.load(file)
   
print(f"Server: {config['server']['host']}")
print(f"Debug mode: {config['server']['debug']}")--OUTPUT--Server: example.com
Debug mode: True

JSON's human-readable structure makes it a go-to for configuration, and Python's built-in json module simplifies parsing. The json.load() function takes a file object and converts the data into a Python dictionary, preserving its nested structure.

  • The with statement handles opening and closing the file automatically, which is a safe and efficient practice.
  • You can then access configuration values just as you would with any other dictionary, using keys like config['server']['host'].

Working with YAML configuration files

import yaml

with open('config.yaml', 'r') as file:
   config = yaml.safe_load(file)
   
print(f"Application name: {config['app']['name']}")
print(f"Version: {config['app']['version']}")--OUTPUT--Application name: MyAwesomeApp
Version: 1.0.0

YAML provides a highly readable syntax, but unlike the json module, it requires installing an external library like PyYAML. The key function here is yaml.safe_load(), which securely parses the file into a Python dictionary. Using safe_load() is essential as it protects against potential security vulnerabilities.

  • After loading, the configuration data is accessible just like any other dictionary.
  • You can retrieve nested values with standard key lookups, like config['app']['name'].

Advanced configuration techniques

Once you're comfortable parsing configuration files, you can make your applications more robust by layering in environment variables, centralizing access, and validating your settings.

Using environment variables with config files

import os
import json

with open('config.json', 'r') as file:
   config = json.load(file)
   
# Override with environment variables if present
if 'DATABASE_URL' in os.environ:
   config['database']['url'] = os.environ['DATABASE_URL']
   
print(f"Database URL: {config['database']['url']}")--OUTPUT--Database URL: postgres://user:password@localhost:5432/mydb

Combining configuration files with environment variables is a powerful strategy for managing settings across different deployment environments. It lets you keep default values in your config file while using environment variables for sensitive data or environment-specific overrides.

  • The script uses os.environ to access the system's environment variables as a dictionary.
  • It checks if a specific variable, like DATABASE_URL, is present.
  • If found, the value from the environment variable overwrites the corresponding setting loaded from the configuration file, giving it higher priority.

Creating a configuration singleton class

class Config:
   _instance = None
   
   def __new__(cls):
       if cls._instance is None:
           cls._instance = super(Config, cls).__new__(cls)
           cls._instance._load_config()
       return cls._instance
       
   def _load_config(self):
       import json
       with open('config.json', 'r') as f:
           self._config = json.load(f)
           
config = Config()
print(f"API key: {config._config['api']['key']}")--OUTPUT--API key: abc123xyz456

A singleton class ensures you only ever have one instance of an object, which is perfect for managing configurations. This pattern prevents your application from repeatedly reading the same file, saving resources. The class loads the settings once and then provides that same instance everywhere it's needed.

  • The __new__ method intercepts object creation to enforce the single-instance rule.
  • On the first call, it creates an instance and runs _load_config() to parse the file.
  • All subsequent calls return this original instance, giving you consistent access to settings without re-reading the file.

Implementing configuration validation

import json
from jsonschema import validate

schema = {
   "type": "object",
   "properties": {
       "timeout": {"type": "integer", "minimum": 1},
       "debug": {"type": "boolean"}
   },
   "required": ["timeout", "debug"]
}

with open('config.json', 'r') as file:
   config = json.load(file)
   
validate(instance=config, schema=schema)
print("Configuration is valid!")--OUTPUT--Configuration is valid!

Validating your configuration file ensures it has the correct structure and data types, preventing unexpected runtime errors. This example uses the jsonschema library to define a set of rules, or a schema, that your settings must follow. The schema object acts as a blueprint for your settings. In this case:

  • The timeout value must be an integer of at least 1.
  • The debug value must be a boolean.
  • Both keys are required.

The validate() function then compares your loaded configuration against this schema. If the rules aren't met, it raises an error, stopping the application before bad data can cause problems.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

The configuration management techniques from this article can be the foundation for powerful tools. With Replit Agent, you can build them from a simple description.

  • Build a deployment utility that switches between development, staging, and production settings by reading different configuration files or overriding with environment variables.
  • Create a theme customizer for a web application that loads color schemes and fonts from a user-editable config.json file.
  • Deploy a server monitoring dashboard that pulls connection details like IP addresses and ports from a config.ini file to check service status.

Bring your idea to life by describing it, and Replit Agent will write the code, test it, and fix issues automatically, all in your browser.

Common errors and challenges

Even with the right tools, you'll likely run into issues like missing files, incorrect data types, or invalid keys when reading configurations.

Here’s how to navigate a few common challenges:

  • Handling missing configuration files: Your application will crash with a FileNotFoundError if it can't locate the config file. The best way to handle this is to wrap your file-reading logic in a try...except block. This lets you catch the error and fall back to default settings or alert the user without stopping the program.
  • Managing type conversion errors: Because configparser reads everything as a string, you can get a ValueError if you try to convert a value to the wrong type. While helper methods like getint() are useful, they'll fail if the value isn't a valid number. Always be ready to catch these errors to make your code more robust.
  • Safely accessing nested values: In nested formats like JSON, trying to access a key that doesn't exist will raise a KeyError. Instead of direct access, use the get() method. It returns None if a key is missing, which is much safer than letting a simple missing value crash your entire application.

Handling missing configuration files

A FileNotFoundError is a classic issue that will crash your program if the configuration file is missing. Without the file, your code has no instructions on how to proceed. The example below shows this exact scenario in action.

import json

# This will crash if config.json doesn't exist
with open('config.json', 'r') as file:
   config = json.load(file)
   
print(f"Server: {config['server']}")

The code directly calls open() to read a file. If the file is missing, this function raises a FileNotFoundError and halts the program because no fallback is defined. See how to prevent this crash in the next example.

import json
import os

# Gracefully handle missing config file
if os.path.exists('config.json'):
   with open('config.json', 'r') as file:
       config = json.load(file)
else:
   config = {"server": "default"}
   
print(f"Server: {config['server']}")

This improved approach uses os.path.exists() to check for the file before attempting to open it. If the file is found, it's loaded normally. If not, the code falls back to a default dictionary, preventing a FileNotFoundError. This simple conditional logic ensures your program can continue running with a default state, making it a crucial safeguard for any application that depends on external resources that might be missing.

Handling type conversion errors with configparser

Since configparser reads all values as strings, you can’t perform mathematical operations on them directly. Attempting to do so leads to logical errors, not the numerical results you'd expect. The code below demonstrates this common mistake in action.

import configparser

config = configparser.ConfigParser()
config.read('config.ini')

# Will fail if timeout isn't a valid integer
timeout = config['SERVER']['timeout']
max_connections = config['SERVER']['max_connections'] * 2  # Error: string * int

The * operator on the string from config['SERVER']['max_connections'] causes string repetition, not mathematical multiplication. This creates a logical error instead of the expected numerical result. The next example demonstrates the correct approach to prevent this.

import configparser

config = configparser.ConfigParser()
config.read('config.ini')

# Safely convert strings to appropriate types
timeout = int(config['SERVER']['timeout'])
max_connections = int(config['SERVER']['max_connections']) * 2

The solution correctly handles this by explicitly converting the string values to their intended types. By wrapping config['SERVER']['max_connections'] in the int() function, you turn the string into a number. This ensures that mathematical operations like multiplication work as expected, preventing logical errors where a string might be repeated instead of calculated. You'll need to do this anytime you're working with numbers or booleans from an INI file to avoid unexpected behavior.

Safely accessing nested configuration values

Directly accessing nested values in formats like JSON is risky. If a key like 'database' or 'port' is missing from your configuration, your program will stop with a KeyError. The code below shows this fragile approach in action.

import json

with open('config.json', 'r') as file:
   config = json.load(file)
   
# Will raise KeyError if 'database' or 'port' keys don't exist
database_port = config['database']['port']
print(f"Database port: {database_port}")

The code's direct dictionary access, config['database']['port'], is brittle because it assumes the keys always exist. If that assumption is wrong, a KeyError halts the program. See a more resilient approach below.

import json

with open('config.json', 'r') as file:
   config = json.load(file)
   
# Use get() with default values to handle missing keys
database_port = config.get('database', {}).get('port', 3306)
print(f"Database port: {database_port}")

This improved approach uses the get() method to safely access nested keys. Instead of crashing, config.get('database', {}) returns an empty dictionary if the 'database' key is missing. This allows you to chain another get() call, which then retrieves the 'port' or falls back to a default value like 3306. This technique prevents KeyError exceptions and makes your application more resilient to incomplete configuration files.

Real-world applications

Now that you know how to handle configurations safely, you can use them to implement powerful patterns like feature flags and setting hierarchies.

Using configuration for feature flags

Configuration files provide a simple way to implement feature flags, which let you enable or disable application functionality on the fly without a full redeployment.

import json

with open('features.json', 'r') as file:
   features = json.load(file)

# Check if a feature is enabled
if features.get('dark_mode', {}).get('enabled', False):
   print("Dark mode is active")
else:
   print("Using light theme")

This script reads settings from a features.json file using the json.load() function. The core of the logic is in the if statement, where it checks for a feature's status without risking a crash from missing keys.

  • It uses chained get() methods to safely navigate the data. The first get() looks for 'dark_mode', returning an empty dictionary if it's missing.
  • The second get() then checks for an 'enabled' key, defaulting to False. This pattern prevents KeyError exceptions and ensures your application runs smoothly even with incomplete configuration files.

Implementing configuration fallbacks and hierarchy

A configuration hierarchy provides a powerful way to manage settings by layering them, giving environment variables final say over config files and hardcoded defaults.

import os
import json

# Default configuration
config = {"timeout": 30, "retries": 3}

# Override with file configuration if available
if os.path.exists('config.json'):
   with open('config.json', 'r') as f:
       file_config = json.load(f)
       config.update(file_config)

# Environment variables have highest priority
if 'APP_TIMEOUT' in os.environ:
   config['timeout'] = int(os.environ['APP_TIMEOUT'])

print(f"Timeout: {config['timeout']} seconds")
print(f"Retries: {config['retries']}")

This script shows how to create a dynamic configuration object. It starts with a dictionary of hardcoded defaults, ensuring your app always has a baseline to run on.

  • It then attempts to load settings from a config.json file, using config.update() to seamlessly blend them with the defaults.
  • Finally, it checks os.environ for an environment variable like APP_TIMEOUT, allowing for on-the-fly adjustments that make the configuration highly adaptable.

Get started with Replit

Turn your knowledge into a real tool. Describe your idea to Replit Agent, like “build a utility that reads API keys from a config.ini” or “create a theme switcher using a features.json file.”

The agent will write the code, test for errors, and deploy your application for you. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.