<!--order=4-->

<h1>Python: Files, Paths, and JSON</h1>
<p>Reading and writing files is where a small Python program starts becoming useful. The <code>pathlib</code> module handles paths clearly, while JSON is a convenient format for configuration and small data files.</p>

<h2>Working with Paths</h2>
<pre>
from pathlib import Path

reports = Path("reports")
reports.mkdir(exist_ok=True)

report_file = reports / "latest.txt"
report_file.write_text("Node is online\n", encoding="utf-8")

print(report_file.exists())
print(report_file.read_text(encoding="utf-8"))
</pre>
<p>Using <code>Path</code> instead of manually joining strings keeps the program portable across operating systems.</p>

<h2>Finding Files</h2>
<pre>
from pathlib import Path

for path in Path(".").glob("**/*.log"):
    print(path)
</pre>
<p>The <code>**</code> pattern searches through subdirectories as well. Be careful with scripts that modify every matching file until you have tested the pattern.</p>

<h2>Writing JSON</h2>
<pre>
import json
from pathlib import Path

settings = {
    "node_name": "field-node-01",
    "frequency": 915,
    "enabled": True,
}

Path("settings.json").write_text(
    json.dumps(settings, indent=2) + "\n",
    encoding="utf-8",
)
</pre>

<h2>Reading JSON</h2>
<pre>
import json
from pathlib import Path

settings = json.loads(
    Path("settings.json").read_text(encoding="utf-8")
)

print(settings["node_name"])
print(settings.get("location", "not set"))
</pre>
<p><code>dict.get()</code> is useful when an optional setting may not exist. For an important setting, access it directly so a missing value fails clearly.</p>

<h2>Safer File Programs</h2>
<ul>
  <li>Use <code>with open(...)</code> or <code>Path.read_text()</code> so files are closed properly.</li>
  <li>Specify an encoding, normally <code>utf-8</code>, when reading text.</li>
  <li>Write to a temporary file before replacing an important configuration file.</li>
  <li>Never build a shell command from untrusted file names.</li>
</ul>

<h2>Handling Missing or Invalid Files</h2>
<pre>
import json
from pathlib import Path

def load_settings(filename):
    try:
        text = Path(filename).read_text(encoding="utf-8")
        return json.loads(text)
    except FileNotFoundError:
        print(f"{filename}: file does not exist")
    except json.JSONDecodeError as error:
        print(f"{filename}: invalid JSON at line {error.lineno}")
    return {}

settings = load_settings("settings.json")
</pre>
<p>Configuration is external input, even when it is stored on the same machine. Report a useful error and choose a safe default instead of silently continuing with half a configuration.</p>

<h2>CSV for Simple Tables</h2>
<pre>
import csv
from pathlib import Path

with Path("readings.csv").open(newline="", encoding="utf-8") as file:
    for row in csv.DictReader(file):
        temperature = float(row["temperature"])
        print(row["node"], temperature)
</pre>
<p>Use JSON when the data is nested or configuration-shaped. CSV is convenient for flat rows that need to be opened in a spreadsheet.</p>

<h2>Safe Replacement of a Configuration File</h2>
<p>If a program is rewriting an important file, write the complete new content first and replace the old file only after the write succeeds:</p>
<pre>
from pathlib import Path

target = Path("settings.json")
temporary = target.with_suffix(".json.tmp")
temporary.write_text(
    '{\n  "enabled": true\n}\n',
    encoding="utf-8",
)
temporary.replace(target)
</pre>
<p>This reduces the chance of leaving an empty or half-written file after a power interruption. For more demanding applications, also keep a timestamped backup.</p>

<h2>References</h2>
<ul>
  <li><a href="https://docs.python.org/3/library/pathlib.html" target="_blank" rel="noopener">Python pathlib ↗</a></li>
  <li><a href="https://docs.python.org/3/library/json.html" target="_blank" rel="noopener">Python JSON Module ↗</a></li>
</ul>