How to handle different application environments like Prod, Dev, Test, etc.?
Tags: programming, tutorial, codequality
Published: 2023-04-01 17:34:12

## Introduction

There are times when we are in a dilemma as to how to properly use application environments. It gets confusing really fast. I will try to explain the approach (that I see) makes sense.

I will be using Python in this blog, but this implementation is agnostic of the programming language.

## Pre-requisites
1. Python 3.8.x or higher
2. PyYaml library - To read our YAML configuration files

## Lets get started!
- Create a `yaml` file just like below
```yaml
# settings.yml
default: &default
  api_url: "https://example.com/dev"
  aws: &aws
    s3_bucket:
      - my-bucket

dev: &dev
  <<: *default

test: &test
  <<: *default
  api_url: "https://example.com/test"

prod: &prod
  <<: *default
  api_url: "https://prod.example.com/"
  aws:
    <<: *aws
    s3_bucket:
      - my-prod-bucket
```
In the above yaml, we are using the inheritance to override the configurations for individual environments.

- To pass the correct configuration, we can leverage `argparse` which parses command-line arguments for us. The following python script does that.
```python
# main.py
import argparse

def read_cli_arguments():
  arg_parser = argparse.ArgumentParser()
  arg_parser.add_argument(
        "--app-env",
        required=True,
        action="store",
        choices=("prod", "dev", "test"),
        help="Application environment to run in"
    )
  return arg_parser.parse_args()
```

- Next, we read the configuration file based on the environment
```python
# main.py
from typing import Dict
import yaml

def read_settings(app_env: str) -> Dict:
  try:
    with open("settings.yml", mode="r", encoding="utf-8") as config:
      configs: Dict = yaml.safe_load(config)
  except yaml.YAMLError as yaml_err:
    print(f"Error occurred while reading the file. Error: {yaml_err}")
    raise
  return configs[app_env]
```

- Reading the configuration dictionary is now as easy as
```python
# main.py

settings_prod: Dict = read_settings("prod")
print(f"Prod Configurations: {settings_prod}")

settings_test: Dict = read_settings("test")
print(f"Test Configurations: {settings_test}")
```

- Finally, we use this as part of running our `main.py` file from the command-line like this
```bash
$ ls
main.py     settings.yml

$ python3 main.py --app-env prod
```

## Complete Code
```python
# main.py
from typing import Dict
import argparse
import yaml

def read_cli_arguments():
  arg_parser = argparse.ArgumentParser()
  arg_parser.add_argument(
        "--app-env",
        required=True,
        action="store",
        choices=("prod", "dev", "test"),
        help="Application environment to run in"
    )
  return arg_parser.parse_args()

def read_settings(app_env: str) -> Dict:
  try:
    with open("settings.yml", mode="r", encoding="utf-8") as config:
      configs: Dict = yaml.safe_load(config)
  except yaml.YAMLError as yaml_err:
    print(f"Error occurred while reading the file. Error: {yaml_err}")
    raise
  return configs[app_env]


# Program Execution Starts Here
if __name__ == "__main__":
  # Read CLI arguments
  args = read_cli_arguments()

  # Retrieve specific configurations
  settings: Dict = read_settings(app_env=args.app_env)

  print(f"Configs: {settings}")
```

Executing the above code on the terminal
```bash
$ python3 main.py --app-env prod
Configs: {"api_url": "https://prod.example.com/", "aws": {"s3_bucket": ["my-prod-bucket"]}}
```

### Cheers!