Skip to content

Commit 60a34d2

Browse files
authored
Merge pull request #191 from SANDAG/yml-to-toml
[PULL REQUEST] POC transition from yml to toml
2 parents 3a50ed7 + 77e3193 commit 60a34d2

10 files changed

Lines changed: 129 additions & 186 deletions

File tree

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
*.yml diff
1+
*.toml diff

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ __pycache__/
77
*.so
88

99
# Secrets file
10-
secrets.yml
10+
secrets.toml
1111

1212
# Log file
1313
log.txt

README.md

Lines changed: 86 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,110 @@
11
## Setup
22

3-
Clone the repository and ensure an installation of [uv](https://docs.astral.sh/uv/getting-started/installation/) exists. Create a local virtual environment by running `uv venv` then `uv sync` in the command line. Ensure that a `secrets.yml` file exists
4-
5-
### Configuration of Private Data in secrets.yml
6-
In order to avoid exposing certain data to the public this repository uses a secrets file to store sensitive configurations in addition to a standard configuration file. This file is stored in the root directory of the repository as `secrets.yml` and is included in the `.gitignore` intentionally to avoid it ever being committed to the repository.
7-
8-
The `secrets.yml` should mirror the following structure.
9-
10-
```yaml
11-
sql:
12-
estimates:
13-
server: <SqlInstanceName> # SQL instance containing estimates database
14-
database: <SqlDatabaseName> # database within SQL instance containing SQL build objects
15-
gis:
16-
server: <SqlInstanceName> # SQL instance containing GIS database
17-
database: <SqlDatabaseName> # database within instance containing GIS datasets (GQ/LUDU)
18-
staging: <FolderPath> # unconditional network folder path visible to SQL instance for BULK INSERT
3+
Clone the repository and ensure an installation of [uv](https://docs.astral.sh/uv/getting-started/installation/) exists. Create a local virtual environment by running `uv venv` then `uv sync` in the command line. Ensure that a `secrets.toml` file exists
4+
5+
### Configuration of Private Data in secrets.toml
6+
In order to avoid exposing certain data to the public this repository uses a secrets file to store sensitive configurations in addition to a standard configuration file. This file is stored in the root directory of the repository as `secrets.toml` and is included in the `.gitignore` intentionally to avoid it ever being committed to the repository.
7+
8+
The `secrets.toml` should mirror the following structure.
9+
10+
```toml
11+
[sql.estimates]
12+
server = "<SqlInstanceName>" # SQL instance containing estimates database
13+
database = "<SqlDatabaseName>" # database within SQL instance containing SQL build objects
14+
15+
[sql.gis]
16+
server = "<SqlInstanceName>" # SQL instance containing GIS database
17+
database = "<SqlDatabaseName>" # database within instance containing GIS datasets (GQ/LUDU)
18+
19+
[sql]
20+
staging = "<FolderPath>" # unconditional network folder path visible to SQL instance for BULK INSERT
1921
```
2022

2123
## Running
2224

23-
Set the configuration file `config.yml` parameters specific to the run in the project root directory. Finally, simply execute `uv run main.py` in the main project directory
25+
Set the configuration file `config.toml` parameters specific to the run in the project root directory. Finally, simply execute `uv run main.py` in the main project directory
2426

2527
### Configuration File Settings
2628

2729
The default version of the runtime configuration file is copied here, with comments explaining each and every key/value pair
2830

29-
```yaml
31+
```toml
3032
# Configuration for what parts of the Estimates Program to run. Since this file may be
3133
# modified from the default settings, you can always restore to default using the copy
3234
# stored in README.md. For brevity, detailed comments have been removed from this file
3335

3436
# The 'run' section contains configuration for running every module of the Estimates
3537
# Program for a specified set of years
36-
run:
37-
38-
# Whether to use the 'run' section. Mutually exclusive with 'debug' mode
39-
enabled: False
40-
41-
# The MGRA series to use for this run. Currently only 'mgra15' is valid
42-
mgra: mgra15
43-
44-
# The first year inclusive to start running from
45-
start_year: 2020
46-
47-
# The last year inclusive to end running with
48-
end_year: 2023
49-
50-
# The code version
51-
version: 0.0.0-dev
52-
53-
# Additional notes on this run
54-
comments: Example comment
38+
[run]
39+
40+
# Whether to use the 'run' section. Mutually exclusive with 'debug' mode
41+
enabled = false
42+
43+
# The MGRA series to use for this run. Currently only 'mgra15' is valid
44+
mgra = "mgra15"
45+
46+
# The first year inclusive to start running from
47+
start_year = 2020
48+
49+
# The last year inclusive to end running with
50+
end_year = 2023
51+
52+
# The code version
53+
version = "0.0.0-dev"
54+
55+
# Additional notes on this run
56+
comments = "Example comment"
5557

5658
# The 'debug' section contains configuration for running a subset of modules of the
5759
# Estimates Program for a given set of years. All parameters must be provided except for
58-
# 'run_id', 'version', and 'comments'. If 'run_id' is 'null', then a new 'run_id' will
60+
# 'run_id', 'version', and 'comments'. If 'run_id' is -1, then a new 'run_id' will
5961
# be automatically created, similar to 'run' mode
60-
debug:
61-
62-
# Whether to use the 'debug' section. Mutually exclusive with 'run' mode
63-
enabled: False
64-
65-
# (Optional) If provided, then most parameters in the 'debug' section will be pulled
66-
# from '[run].[metadata]'. If not provided, then a new 'run_id' will be automatically
67-
# created
68-
run_id: null
69-
70-
# The first year inclusive and last year inclusive to run. In the case that...
71-
# * The value of 'run_id' is 'null', the values will be loaded into [metadata].[run]
72-
# and will be used as is
73-
# * The value of 'run_id' is not 'null', the values will be checked against the values
74-
# already in '[run].[metadata]'
75-
start_year: 2020
76-
end_year: 2023
77-
78-
# (Optional) The code version. If provided, then 'run_id' must be 'null'
79-
version: 0.0.0-dev
80-
81-
# (Optional) Additional notes on this run. If provided, then 'run_id' must be 'null'
82-
comments: null
83-
84-
# Whether to run the 'startup' module
85-
startup: False
86-
87-
# Whether to run the 'housing_and_households' module. If enabled, then any above
88-
# modules must all be enabled due to module dependencies
89-
housing_and_households: False
90-
91-
# Whether to run the 'population' module. If enabled, then any above modules must all
92-
# be enabled due to module dependencies
93-
population: False
94-
95-
# Whether to run the 'population_by_ase' module. If enabled, then any above modules
96-
# must all be enabled due to module dependencies
97-
population_by_ase: False
98-
99-
# Whether to run the 'household_characteristics' module. If enabled, then any above
100-
# modules must all be enabled due to module dependencies
101-
household_characteristics: False
102-
103-
# Whether to run the 'staging' module. If enabled, then any above modules must all be
104-
# enabled due to module dependencies
105-
staging: False
62+
[debug]
63+
64+
# Whether to use the 'debug' section. Mutually exclusive with 'run' mode
65+
enabled = false
66+
67+
# (Optional) If provided, then most parameters in the 'debug' section will be pulled
68+
# from '[run].[metadata]'. If not provided, then a new 'run_id' will be automatically
69+
# created. Use -1 to indicate no run_id (TOML doesn't support null)
70+
run_id = -1
71+
72+
# The first year inclusive and last year inclusive to run. In the case that...
73+
# * The value of 'run_id' is -1, the values will be loaded into [metadata].[run]
74+
# and will be used as is
75+
# * The value of 'run_id' is not -1, the values will be checked against the values
76+
# already in '[run].[metadata]'
77+
start_year = 2020
78+
end_year = 2023
79+
80+
# (Optional) The code version. If provided, then 'run_id' must be -1
81+
version = "0.0.0-dev"
82+
83+
# (Optional) Additional notes on this run. If provided, then 'run_id' must be -1
84+
comments = ""
85+
86+
# Whether to run the 'startup' module
87+
startup = false
88+
89+
# Whether to run the 'housing_and_households' module. If enabled, then any above
90+
# modules must all be enabled due to module dependencies
91+
housing_and_households = false
92+
93+
# Whether to run the 'population' module. If enabled, then any above modules must all
94+
# be enabled due to module dependencies
95+
population = false
96+
97+
# Whether to run the 'population_by_ase' module. If enabled, then any above modules
98+
# must all be enabled due to module dependencies
99+
population_by_ase = false
100+
101+
# Whether to run the 'household_characteristics' module. If enabled, then any above
102+
# modules must all be enabled due to module dependencies
103+
household_characteristics = false
104+
105+
# Whether to run the 'staging' module. If enabled, then any above modules must all be
106+
# enabled due to module dependencies
107+
staging = false
106108
```
107109

108110
### Production Database Schema

config.yml renamed to config.toml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@
44

55
# The `run` section contains configuration for running every module of the Estimates
66
# Program for a specified set of years
7-
run:
8-
enabled: True
9-
mgra: mgra15
10-
start_year: 2020
11-
end_year: 2024
12-
version: 1.1.1-dev
13-
comments: Example comment
7+
[run]
8+
enabled = true
9+
mgra = "mgra15"
10+
start_year = 2020
11+
end_year = 2024
12+
version = "1.1.1-dev"
13+
comments = "Example comment"
1414

1515
# The `debug` section contains configuration for running a subset of modules of the
1616
# Estimates Program for a given set of years. All parameters must be provided except for
1717
# `run_id` and `comments`. If `run_id` is `null`, then a new `run_id` will be
1818
# automatically created, similar to `run` mode
19-
debug:
20-
enabled: False
21-
run_id: null
22-
start_year: 2022
23-
end_year: 2023
24-
version: 1.1.1-dev
25-
comments: null
26-
startup: False
27-
housing_and_households: False
28-
population: False
29-
population_by_ase: False
30-
household_characteristics: False
31-
staging: False
19+
[debug]
20+
enabled = false
21+
run_id = -1 # -1 is interpreted as None
22+
start_year = 2022
23+
end_year = 2023
24+
version = "1.1.1-dev"
25+
comments = ""
26+
startup = false
27+
housing_and_households = false
28+
population = false
29+
population_by_ase = false
30+
household_characteristics = false
31+
staging = false

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "Estimates-Program"
3-
version = "0.0.0" # Not used, refer to config.yml instead
3+
version = "0.0.0" # Not used, refer to config.toml instead
44
requires-python = ">=3.11"
55
dependencies = [
66
"black>=25.12.0,<26.0.0",
@@ -9,6 +9,5 @@ dependencies = [
99
"numpy>=2.4.0,<3.0.0",
1010
"pandas>=2.3.3,<3.0.0",
1111
"pyodbc>=5.3.0,<6.0.0",
12-
"pyyaml>=6.0.3,<7.0.0",
1312
"sqlalchemy>=2.0.45,<3.0.0",
1413
]

python/parsers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class InputParser:
2222
_end_year (int): Used to store the end_year, whether of a new run or an
2323
existing run
2424
run_instructions (dict): Explicit instructions on which modules/years to run.
25-
The YAML config file is parsed and this dictionary is filled no matter if
25+
The toml config file is parsed and this dictionary is filled no matter if
2626
run or debug mode is enabled
2727
run_id (int): The run identifier parsed from the configuration.
2828
mgra_version (int): The MGRA version we are running on. Depending on run mode,
@@ -60,6 +60,10 @@ def parse_config(self) -> None:
6060
Returns:
6161
None
6262
"""
63+
# Convert -1 to None for run_id (TOML doesn't support null/None)
64+
if self._config.get("debug", {}).get("run_id") == -1:
65+
self._config["debug"]["run_id"] = None
66+
6367
self._validate_config()
6468
self.run_id = self._parse_run_id()
6569
self.mgra_version = self._parse_mgra_version()

python/utils.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import math
33
import pathlib
4-
import yaml
4+
import tomllib
55

66
import numpy as np
77
import pandas as pd
@@ -50,12 +50,12 @@
5050
# SQL CONFIGURATION #
5151
#####################
5252

53-
# Load secrets YAML file
53+
# Load secrets TOML file
5454
try:
55-
with open(ROOT_FOLDER / "secrets.yml", "r") as file:
56-
_secrets = yaml.safe_load(file)
55+
with open(ROOT_FOLDER / "secrets.toml", "rb") as file:
56+
_secrets = tomllib.load(file)
5757
except IOError:
58-
raise IOError("secrets.yml does not exist, see README.md")
58+
raise IOError("secrets.toml does not exist, see README.md")
5959

6060
# Create SQLAlchemy engine(s)
6161
ESTIMATES_ENGINE = sql.create_engine(
@@ -89,15 +89,15 @@
8989
# RUNTIME CONFIGURATION #
9090
#########################
9191

92-
# Load configuration YAML file
92+
# Load configuration TOML file
9393
try:
94-
with open(ROOT_FOLDER / "config.yml", "r") as file:
95-
config = yaml.safe_load(file)
94+
with open(ROOT_FOLDER / "config.toml", "rb") as file:
95+
config = tomllib.load(file)
9696
except IOError:
97-
raise IOError("config.yml does not exist, see README.md")
97+
raise IOError("config.toml does not exist, see README.md")
9898

9999
# Initialize input parser
100-
# Parse the configuration YAML file and validate its contents
100+
# Parse the configuration TOML file and validate its contents
101101
input_parser = parsers.InputParser(config=config, engine=ESTIMATES_ENGINE)
102102
input_parser.parse_config()
103103

reporting/reporting.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
# We cannot import python.utils, as just importing will cause a new [run_id]` value and
1010
# new log file to be created. Instead, copy what we need for now :(
11-
import yaml
11+
import tomllib
1212
import pathlib
1313
import textwrap
1414
import sqlalchemy as sql
@@ -17,10 +17,10 @@
1717

1818
ROOT_FOLDER = pathlib.Path(__file__).parent.resolve().parent
1919
try:
20-
with open(ROOT_FOLDER / "secrets.yml", "r") as file:
21-
_secrets = yaml.safe_load(file)
20+
with open(ROOT_FOLDER / "secrets.toml", "rb") as file:
21+
_secrets = tomllib.load(file)
2222
except IOError:
23-
raise IOError("secrets.yml does not exist, see README.md")
23+
raise IOError("secrets.toml does not exist, see README.md")
2424

2525
# Create SQLAlchemy engine(s)
2626
ESTIMATES_ENGINE = sql.create_engine(

0 commit comments

Comments
 (0)