Skip to content
Snippets Groups Projects
Commit b55bfe10 authored by Florian Obersteiner's avatar Florian Obersteiner :octopus:
Browse files

Merge branch 'dev' into 'main'

Dev into Main

See merge request !1
parents a2f3218c 6c4cb171
No related branches found
No related tags found
1 merge request!1Dev into Main
......@@ -19,3 +19,4 @@ testdata/
# misc
*~lock*
to_VM.sh
......@@ -15,28 +15,34 @@ Types of changes
## TODOs
- ts plot: add units
- limit data loading to columns that are actually used
- publish on gitlab / KIT / iagos-caribic
- add precision multiplier as a quantity to play around with
- limit data loading to columns that are actually used / should be visible
- add info: departure and destination airports
### Data Processing
- make a specific dataset with BB flag
- add PTRMS prc to SU and MS files
- add VOC data specific parameters (background, uncertainty, etc.)
- publish new dataset on zenodo
## Backlog
- use a polars dataframe for better efficiency - if that turns out to be a bottleneck. (!) Keep this in mind when using pandas-specific functions.
- add pre-commit
- add tests
- add pre-commit
- get a DOI for the code
---
## [Unreleased]
### Added
- DotDict class for configuration
### Changed
- use DotDict class for app config
## v0.0.10 - 2025-03-21
......
VERSION = "v0.0.10"
VERSION = "v0.0.11"
DEBUG = true
# FRONTEND config
......@@ -16,4 +16,84 @@ REQUESTS_PATH_PREFIX = "/" # "/iriscc/" # this is for the reverse-proxy; use "/"
TEMPLATE = "none"
# DATA config
BB_THRESH = 180
BB_THRESH = 145
ACN_PRC_NSIGMA = 3
# anything that is not in VARIABLES will not appear on the dashboard
VARIABLES = [
"lat",
"lon",
"alt",
"p",
"Ozone",
"CO",
"ACE",
"ACE_prc",
"ACN",
"ACN_prc",
"WindSpeed",
"WindDirTr",
"ToAirTmp",
"StcAirTmp",
"Tpot",
"H2Ogas",
"NO",
"NOy",
"TGM",
"GEM",
"CO",
"CO2",
"CH4",
"N4_12",
"N12",
"N18",
"PNumC",
"PSurfC",
"PVolC",
"PMassC",
"PSizeD01",
"PSizeD02",
"PSizeD03",
"PSizeD04",
"PSizeD05",
"PSizeD06",
"PSizeD07",
"PSizeD08",
"PSizeD09",
"PSizeD10",
"SC_Conc",
"BC_Mass_Conc",
"BC_Conc",
"temp__k_",
"pv__pvu_",
"pot_temp__k_",
"eq_pott_temp__k_",
"spec_hum__g_kg_",
"u__m_s_",
"v__m_s_",
"w__mubar_s_",
"wind_speed__m_s_",
"wind_dir__deg_",
"h2o__ppmv_",
"rh___",
"z__0_1_g_m_",
"eq_latitude_deg_n_",
"cc",
"clwc_g_kg_",
"clic_g_kg_",
"p_strop__hpa_",
"p_dtrop__hpa_",
"t_strop__k_",
"t_dtrop__k_",
"pt_strop__k_",
"pt_dtrop__k_",
"pv_strop__pvu_",
"z_strop__01grav_m_",
"z_dtrop__01grav_m_",
"dp_strop__hpa_",
"dp_dtrop__hpa_",
"cpc1_amb_ncm3",
"cpc2_amb_ncm3",
"opc1_amb_ncm3",
"opc12_amb_ncm3",
]
......@@ -3,8 +3,8 @@ TimeCRef;CARIBIC master computer time, seconds since midnight UTC on the date of
CRefTime;CARIBIC master computer time, days since 1899-12-30 00:00:00 UTC.;[d];9999999;1;time;[d];f8;{:.7f};MA;[];[];[];[]
UTC_ARINC;UTC time on ARINC 429 bus from Airbus A340-600 D-AIHE, days since 1899-12-30 00:00:00 UTC.;[d];9999999;1;time;[d];f8;{:.7f};MA;[];[];[];[]
FlightPhase;0-10, 0=nn 1=PowerOn 2=EngineStart 3=TaxiOut 4=TakeOff 5=Climb 6=Cruise 7=Approach 8=RollOut 9=TaxiIn 10=EngineStop;[1];9999999;1;platform_status_flag;[1];u1;{:d};MA;[];[];[];[]
PosLat;Present Position Latitude;[degrees];9999999;1;latitude;[degree_north];f8;{:.3f};MA;[];[];[];[]
PosLong;Present Position Longitude;[degrees];9999999;1;longitude;[degree_east];f8;{:.3f};MA;[];[];[];[]
lat;Present Position Latitude;[degrees];9999999;1;latitude;[degree_north];f8;{:.3f};MA;[];[];[];[]
lon;Present Position Longitude;[degrees];9999999;1;longitude;[degree_east];f8;{:.3f};MA;[];[];[];[]
TrueHead;True Heading;[degrees];9999999;1;platform_orientation;[degree];f4;{:.1f};MA;[];[];[];[]
WindSpeed;Wind Speed;[knots];9999999;1;wind_speed;[(1852/3600) m s-1];f4;{:.1f};MA;[];[];[];[]
WindDirTr;Wind Angle (True);[degrees];9999999;1;wind_to_direction;[degree];f4;{:.1f};MA;[];[];[];[]
......@@ -21,9 +21,9 @@ TotPres;Total Pressure;[mbar];9999999;1;air_total_pressure;[100 Pa];f4;{:d};MA;[
VertSpeed;Vertical Velocity;[feet/min];9999999;1;platform_vertical_speed_wrt_air;[0.3048/60 m s-1];f4;{:.1f};MA;[];[];[];[]
GndSpeed;Ground Speed;[knots];9999999;1;platform_speed_wrt_ground;[(1852/3600) m s-1];f4;{:.1f};MA;[];[];[];[]
LocalTime;Local Solar Time (LST) as day fraction;[d];9999999;1;time;[d];f8;{:.8f};MA;[];[];[];[]
pstatic;Static Pressure;[mbar];9999999;1;air_pressure;[100 Pa];f4;{:.1f};MA;[];[];[];[]
p;Static Pressure;[mbar];9999999;1;air_pressure;[100 Pa];f4;{:.1f};MA;[];[];[];[]
Tpot;Potential Temperature;[K];9999999;1;air_potential_temperature;[K];f4;{:.1f};MA;[];[];[];[]
Altitude;Pressure Altitude;[m];9999999;1;barometric_altitude;[m];f4;{:.1f};MA;[];[];[];[]
alt;Pressure Altitude;[m];9999999;1;barometric_altitude;[m];f4;{:.1f};MA;[];[];[];[]
SZA;Solar zenith angle in degrees calculated from position, time and date according to DIN 5034 algorithm;[deg];9999999;1;solar_zenith_angle;[degree];f8;{:.7f};SU;[];[];[];[]
H2Ostatus;Instrument status, 0: all 3 sensors worked, 1: PA gaseous failure (all H2O >100% saturation vs. ice (T<0C) / liquid (T>0C) is attributed to clouds 2: only Buck-CR2 (total H2O) worked, 3: no data;[1-3];9999999;1;status_flag;[1];u1;{:d};WA;[];[];[];[]
H2Ogas;Gaseous H2O volume mixing ratio;[ppm];9999999;1;mole_fraction_of_water_vapor_in_air;[1e-6];f4;{:.2f};WA;[];[];[];[]
......@@ -41,10 +41,12 @@ CO2;Carbon dioxide dry air mole fraction;[ppm];9999999;1;mole_fraction_of_carbon
CH4;Methane dry air mole fraction;[ppb];9999999;1;mole_fraction_of_methane_in_air;[1e-9];f4;{:.1f};PIC;[];[];[];[]
H2O;Water vapour dry air mole fraction;[ppm];9999999;1;mole_fraction_of_water_vapor_in_air;[1e-6];f4;{:.1f};PIC;[];[];[];[]
CH4_Err;Statistical uncertainty of CH4;[ppb];9999999;1;mole_fraction_of_methane_in_air_standard_error;[1e-9];f4;{:.2f};CM;[];[];[];[]
ACN;Acetonitrile volume mixing ratio;[ppt];9999999;1;mole_fraction_of_aceto_nitrile_in_air;[1e-12];i4;{:d};PTR;[];[];[];[]
ACA;Acetaldehyde volume mixing ratio;[ppt];9999999;1;mole_fraction_of_aceto_aldehyde_in_air;[1e-12];i4;{:d};PTR;[];[];[];[]
BEN;Benzene volume mixing ratio;[ppt];9999999;1;mole_fraction_of_benzene_in_air;[1e-12];i4;{:d};PTR;[];[];[];[]
ACE;Acetone volume mixing ratio;[ppt];9999999;1;mole_fraction_of_acetone_in_air;[1e-12];i4;{:d};PTR;[];[];[];[]
ACE_prc;Acetonitrile measurement precision (absolute);[ppt];9999999;1;mole_fraction_of_acetone_in_air_measurement_precision;[1e-12];i4;{:d};PTR;[];[];[];[]
ACN;Acetonitrile volume mixing ratio;[ppt];9999999;1;mole_fraction_of_aceto_nitrile_in_air;[1e-12];i4;{:d};PTR;[];[];[];[]
ACN_prc;Acetonitrile measurement precision (absolute);[ppt];9999999;1;mole_fraction_of_aceto_nitrile_in_air_measurement_precision;[1e-12];i4;{:d};PTR;[];[];[];[]
BEN;Benzene volume mixing ratio;[ppt];9999999;1;mole_fraction_of_benzene_in_air;[1e-12];i4;{:d};PTR;[];[];[];[]
N4_12;Particle number concentration between 4 and 12 nm dry diameter;[1/cm^3_STP];9999999;1;number_concentration_of_dried_aerosol_particles_at_stp_in_air;[1e6 m-3];i4;{:d};CN;1;[];[];[]
N12;Particle number concentration for particles larger than 12 nm dry diameter;[1/cm^3_STP];9999999;1;number_concentration_of_dried_aerosol_particles_at_stp_in_air;[1e6 m-3];i4;{:d};CN;1;[];[];[]
N18;Particle number concentration for particles larger than 18 nm dry diameter;[1/cm^3_STP];9999999;1;number_concentration_of_dried_aerosol_particles_at_stp_in_air;[1e6 m-3];i4;{:d};CN;1;[];[];[]
......
......@@ -3,7 +3,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# -*- coding: utf-8 -*-
import tomllib
from pathlib import Path
import dash_bootstrap_components as dbc
......@@ -12,30 +11,29 @@ import plotly.graph_objects as go
from dash import Dash
from dash.dependencies import Input, Output
from lib.data import loader
from lib.layout import create_layout
with open("./config/env.toml", "rb") as fobj:
config = tomllib.load(fobj)
varconfig = pd.read_csv("./config/parms_units_2023-11-16.csv", sep=";")
from src.config import DotDict
from src.data import loader
from src.layout import create_layout
appconfig = DotDict.from_toml_file("./config/env.toml")
varconfig = pd.read_csv("./config/parms_units.csv", sep=";")
# fmt: off
app = Dash(
config["TITLE"],
requests_pathname_prefix=config["REQUESTS_PATH_PREFIX"],
appconfig.TITLE,
requests_pathname_prefix=appconfig.REQUESTS_PATH_PREFIX,
external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME],
)
# fmt: on
app.title = config["TITLE"] # type: ignore
app.title = appconfig.TITLE
app.css.config.serve_locally = True
app.scripts.config.serve_locally = True
data = loader(Path("./testdata").glob("*.nc"), config) # type: ignore
data = loader(Path("./testdata").glob("*.nc"), appconfig, True) # type: ignore
app.layout = create_layout(data, config)
app.layout = create_layout(data, appconfig)
server = app.server # for deployment via gunicorn / WSGI server
@app.callback(
......@@ -138,4 +136,4 @@ def update_map(selected_flight: int, selected_variable: str):
if __name__ == "__main__":
app.run(debug=config["DEBUG"]) # type: ignore
app.run(debug=appconfig.DEBUG) # type: ignore
[project]
name = "caribic-dash"
version = "0.0.10"
version = "0.0.11"
description = "IRISCC dashboard with CARIBIC data"
readme = "README.md"
requires-python = ">=3.12"
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
]
dependencies = [
"dash>=2.18.2",
"dash-bootstrap-components>=2.0.0",
"netcdf4>=1.7.2", # indirect; backend for xarray
"pandas>=2.2.3",
"plotly>=6.0.1",
"python-dotenv>=1.0.1",
"xarray>=2025.3.0",
"dash>=2.18.2",
"dash-bootstrap-components>=2.0.0",
"gunicorn>=23.0.0",
"netcdf4>=1.7.2", # indirect; backend for xarray
"pandas>=2.2.3",
"plotly>=6.0.1",
"python-dotenv>=1.0.1",
"xarray>=2025.3.0",
]
[dependency-groups]
......
File moved
import tomllib
class DotDict(dict):
"""Wrapper class around dict for dot-notation access to dictionary keys."""
def __init__(self, *args, **kwargs):
"""Initialize DotDict and recursively convert nested dictionaries."""
super().__init__(*args, **kwargs)
self._convert_nested()
def _convert_nested(self, path=""):
"""Convert nested dictionaries to DotDict instances and validate keys."""
for key, value in list(self.items()):
# Validate keys
if not isinstance(key, str):
continue
# Check for invalid attribute names
if not key.isidentifier():
print(
f"Warning: '{path}{key}' is not a valid Python identifier and cannot be accessed with dot notation"
)
# Check for reserved dictionary method names
if key in dir(dict) and key not in ("__class__", "__dict__"):
print(f"Warning: '{path}{key}' overrides a built-in dictionary method")
# Recursively convert nested dictionaries
if isinstance(value, dict) and not isinstance(value, DotDict):
self[key] = DotDict(value)
self[key]._convert_nested(f"{path}{key}.")
elif isinstance(value, list):
# Handle lists and recursively process any dictionaries within them
self[key] = self._process_list_items(value, f"{path}{key}")
def _process_list_items(self, items, path):
"""Recursively process items in a list, converting any dicts to DotDict."""
processed_items = []
for i, item in enumerate(items):
if isinstance(item, dict) and not isinstance(item, DotDict):
dot_dict_item = DotDict(item)
dot_dict_item._convert_nested(f"{path}[{i}].")
processed_items.append(dot_dict_item)
elif isinstance(item, list):
# Handle nested lists recursively
processed_items.append(self._process_list_items(item, f"{path}[{i}]"))
else:
processed_items.append(item)
return processed_items
@classmethod
def from_toml_data(cls, toml_data):
"""Create a DotDict instance from TOML data."""
return cls(toml_data)
@classmethod
def from_toml_file(cls, file_path):
"""Load a TOML file into a DotDict instance."""
with open(file_path, "rb") as f:
config_data = tomllib.load(f)
return cls(config_data)
def __getattr__(self, key):
"""Enable dot notation access for dictionary keys."""
if key in self:
return self[key]
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
def __setattr__(self, key, value):
"""Enable dot notation assignment for dictionary keys."""
# Convert dictionaries to DotDict when setting attributes
if isinstance(value, dict) and not isinstance(value, DotDict):
value = DotDict(value)
elif isinstance(value, list):
# Process lists when setting them as attributes
value = self._process_list_items(value, key)
self[key] = value
def __delattr__(self, key):
"""Enable dot notation deletion for dictionary keys."""
if key in self:
del self[key]
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
......@@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
import logging as log
from datetime import datetime
from pathlib import Path
from typing import Iterable
......@@ -9,24 +10,36 @@ from typing import Iterable
import pandas as pd
import xarray as xr
rename_config = {"PosLong": "lon", "PosLat": "lat", "Altitude": "alt"}
from .config import DotDict
def loader(paths: Iterable[Path], config: dict) -> pd.DataFrame:
def loader(paths: Iterable[Path], config: DotDict, drop: bool = False) -> pd.DataFrame:
"""
Load multiple netCDF files into a single DataFrame.
"""
dataframes = []
for path in sorted(paths):
df = xr.open_dataset(path).to_dataframe()
# replace column names with more reasonable ones
df = df.rename(columns=rename_config)
fullset = xr.open_dataset(path).to_dataframe()
if drop:
df = pd.DataFrame()
for v in config.VARIABLES:
df[v] = fullset[v]
else:
df = fullset
# extract flight number and date from filename; add as extra columns
df["flight_number"] = int(path.name.split("_")[2])
df["date"] = datetime.strptime(path.stem.split("_")[1], "%Y%m%d").strftime("%Y-%m-%d")
# make a linear interpolation of the acetonitrile column for BB flaggin
if "ACN" in df.columns:
df["BB_flag"] = df["ACN"].interpolate() > config["BB_THRESH"]
if df["ACN"].isna().all():
log.warning("'ACN' column is all NaN values!")
df["BB_flag"] = df["ACN"].interpolate() > (
config.BB_THRESH + config.ACN_PRC_NSIGMA * df["ACN_prc"].interpolate()
)
else:
raise ValueError(f"ACN column not found in {path}")
dataframes.append(df)
return pd.concat(dataframes)
File moved
......@@ -2,11 +2,14 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
import dash
import pandas as pd
from dash import dcc, html
from .config import DotDict
def create_layout(df: pd.DataFrame, config: dict) -> list:
def create_layout(df: pd.DataFrame, config: DotDict) -> list:
"""
Create the layout for the dashboard.
......@@ -29,12 +32,16 @@ def create_layout(df: pd.DataFrame, config: dict) -> list:
}
for c in df.columns
]
var_sel_idx = 0
for idx, vo in enumerate(var_opts):
if vo["label"] == "ACN":
var_sel_idx = idx
return [
html.Header(
[
# Title on the left
html.Div(config["TITLE"] + config["HEADERSUFFIX"], className="title-text"),
html.Div(config.TITLE + config.HEADERSUFFIX, className="title-text"),
# Logos on the right
html.Div(
[
......@@ -42,8 +49,11 @@ def create_layout(df: pd.DataFrame, config: dict) -> list:
html.Div(
[
html.A(
html.Img(src="/assets/iagos_logo.png", className="header-logo"),
href=config["IAGOS_URL"],
html.Img(
src=dash.get_asset_url("iagos_logo.png"),
className="header-logo",
),
href=config.IAGOS_URL,
target="_blank",
)
],
......@@ -54,9 +64,10 @@ def create_layout(df: pd.DataFrame, config: dict) -> list:
[
html.A(
html.Img(
src="/assets/iriscc_logo.svg", className="header-logo"
src=dash.get_asset_url("iriscc_logo.svg"),
className="header-logo"
),
href=config["IRISCC_URL"],
href=config.IRISCC_URL,
target="_blank",
)
],
......@@ -108,7 +119,7 @@ def create_layout(df: pd.DataFrame, config: dict) -> list:
dcc.Dropdown(
id="variable-dropdown",
options=var_opts, # type: ignore
value=var_opts[26]["value"], # type: ignore
value=var_opts[var_sel_idx]["value"], # type: ignore
clearable=False,
searchable=True,
style={
......@@ -171,16 +182,16 @@ def create_layout(df: pd.DataFrame, config: dict) -> list:
),
"data",
],
href=config["DATAURL"],
href=config.DATAURL,
target="_blank",
style={"color": "white"},
),
html.A(
[
html.I(className="fab fa-gitlab", style={"marginRight": "5px"}),
f"src ({config['VERSION']})",
f"src ({config.VERSION})",
],
href=config["CODEURL"],
href=config.CODEURL,
target="_blank",
style={"color": "white", "padding-left": "15px"},
),
......
import os
import sys
import tempfile
import tomllib
import unittest
from io import StringIO
from .config import DotDict
class TestEnhancedDotDict(unittest.TestCase):
"""Test cases for the enhanced DotDict class with TOML support."""
def setUp(self):
"""Initialize test data and capture stdout for warning messages."""
self.sample_dict = {
"server": {"host": "localhost", "port": 8080},
"database": {
"credentials": {"username": "admin", "password": "secret123"},
"settings": {"timeout": 30},
},
"users": [{"name": "Alice", "role": "admin"}, {"name": "Bob", "role": "user"}],
"valid_key": "value",
"invalid-key": "problematic",
"123numeric": "starts with number",
"get": "overrides method",
}
# Valid TOML content
self.valid_toml = """
[server]
host = "localhost"
port = 8080
[database.credentials]
username = "admin"
password = "secret123"
[database.settings]
timeout = 30
[[users]]
name = "Alice"
role = "admin"
[[users]]
name = "Bob"
role = "user"
valid_key = "value"
invalid-key = "problematic"
123numeric = "starts with number"
get = "overrides method"
"""
# Invalid TOML content (syntax error)
self.invalid_toml = """
[server]
host = "localhost
port = 8080
"""
# Create temporary files for TOML testing
self.valid_toml_file = tempfile.NamedTemporaryFile( # noqa: SIM115
delete=False, mode="w", suffix=".toml"
)
self.valid_toml_file.write(self.valid_toml)
self.valid_toml_file.close()
self.invalid_toml_file = tempfile.NamedTemporaryFile( # noqa: SIM115
delete=False, mode="w", suffix=".toml"
)
self.invalid_toml_file.write(self.invalid_toml)
self.invalid_toml_file.close()
# Setup stdout capture
self.captured_output = StringIO()
self.original_stdout = sys.stdout
sys.stdout = self.captured_output
def tearDown(self):
"""Clean up temporary files and restore stdout."""
os.unlink(self.valid_toml_file.name)
os.unlink(self.invalid_toml_file.name)
sys.stdout = self.original_stdout
def test_init_with_dict(self):
"""Test initialization with a dictionary."""
dot_dict = DotDict(self.sample_dict)
# Test basic access
self.assertEqual(dot_dict.server.host, "localhost")
self.assertEqual(dot_dict.server.port, 8080)
self.assertEqual(dot_dict.database.credentials.username, "admin")
# Test list of dicts
self.assertEqual(dot_dict.users[0].name, "Alice")
self.assertEqual(dot_dict.users[1].role, "user")
# Verify warnings were printed
output = self.captured_output.getvalue()
self.assertIn("Warning: 'invalid-key'", output)
self.assertIn("Warning: '123numeric'", output)
self.assertIn("Warning: 'get'", output)
def test_load_valid_toml_file(self):
"""Test loading a valid TOML file."""
# Temporarily reset stdout to avoid capturing import warnings
sys.stdout = self.original_stdout
# Restore stdout capture
sys.stdout = self.captured_output
# Load TOML file
dot_dict = DotDict.from_toml_file(self.valid_toml_file.name)
# Test basic access
self.assertEqual(dot_dict.server.host, "localhost")
self.assertEqual(dot_dict.server.port, 8080)
self.assertEqual(dot_dict.database.credentials.username, "admin")
# Test list of dicts
self.assertEqual(dot_dict.users[0].name, "Alice")
self.assertEqual(dot_dict.users[1].role, "user")
# Verify warnings were printed
output = self.captured_output.getvalue()
self.assertIn("Warning: 'invalid-key'", output)
self.assertIn("Warning: '123numeric'", output)
self.assertIn("Warning: 'get'", output)
def test_load_invalid_toml_file(self):
"""Test loading an invalid TOML file raises appropriate exception."""
# Reset and restore stdout to avoid capturing import warnings
sys.stdout = self.original_stdout
sys.stdout = self.captured_output
# Try to load invalid TOML file
with self.assertRaises(tomllib.TOMLDecodeError):
DotDict.from_toml_file(self.invalid_toml_file.name)
def test_access_invalid_keys(self):
"""Test accessing keys that are not valid Python identifiers."""
dot_dict = DotDict(self.sample_dict)
# These should work with dictionary access
self.assertEqual(dot_dict["invalid-key"], "problematic")
self.assertEqual(dot_dict["123numeric"], "starts with number")
self.assertEqual(dot_dict["get"], "overrides method")
# But fail with attribute access
with self.assertRaises(AttributeError):
_ = dot_dict.invalid - key # noqa
# This might work in Python, but it's a subtraction operation
# dot_dict.invalid-key is interpreted as (dot_dict.invalid) - key
with self.assertRaises(AttributeError):
_ = dot_dict.invalid
# with self.assertRaises(NameError):
# _ = dot_dict.123numeric # SyntaxError at compile time
def test_deep_nesting(self):
"""Test deeply nested structures."""
deep_dict = {"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}
dot_dict = DotDict(deep_dict)
self.assertEqual(dot_dict.level1.level2.level3.level4.value, "deep")
def test_update_nested_attribute(self):
"""Test updating a nested attribute."""
dot_dict = DotDict(self.sample_dict)
# Update nested attribute
dot_dict.server.host = "new-host"
self.assertEqual(dot_dict.server.host, "new-host")
self.assertEqual(dot_dict["server"]["host"], "new-host")
# Update with a new dictionary should convert to DotDict
dot_dict.server = {"host": "another-host", "new_key": "value"}
self.assertEqual(dot_dict.server.host, "another-host")
self.assertEqual(dot_dict.server.new_key, "value")
# Verify it's a DotDict
self.assertIsInstance(dot_dict.server, DotDict)
def test_from_toml(self):
"""Test creating DotDict from already parsed TOML data."""
try:
# Reset and restore stdout to avoid capturing import warnings
sys.stdout = self.original_stdout
with open(self.valid_toml_file.name, "rb") as f:
toml_data = tomllib.load(f)
sys.stdout = self.captured_output
# Create DotDict from parsed TOML
dot_dict = DotDict.from_toml_data(toml_data)
# Test access
self.assertEqual(dot_dict.server.host, "localhost")
self.assertEqual(dot_dict.users[0].name, "Alice")
except ImportError:
self.skipTest("TOML library not available")
def test_nested_list_handling(self):
"""Test handling of nested lists with dictionaries."""
complex_dict = {
"nested_lists": [
[{"name": "item1"}, {"name": "item2"}],
[{"name": "item3"}, {"name": "item4"}],
]
}
dot_dict = DotDict(complex_dict)
# Test that dictionaries in nested lists are converted
self.assertEqual(dot_dict.nested_lists[0][0].name, "item1")
self.assertEqual(dot_dict.nested_lists[1][1].name, "item4")
# Verify outer list items are DotDict instances
self.assertIsInstance(dot_dict.nested_lists[0][0], DotDict)
if __name__ == "__main__":
unittest.main()
version = 1
revision = 1
requires-python = ">=3.13"
requires-python = ">=3.12"
[[package]]
name = "attrs"
......@@ -36,6 +36,10 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 },
{ url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 },
{ url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 },
{ url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 },
{ url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 },
{ url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 },
{ url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 },
......@@ -63,11 +67,12 @@ wheels = [
[[package]]
name = "caribic-dash"
version = "0.1.0"
version = "0.0.11"
source = { virtual = "." }
dependencies = [
{ name = "dash" },
{ name = "dash-bootstrap-components" },
{ name = "gunicorn" },
{ name = "netcdf4" },
{ name = "pandas" },
{ name = "plotly" },
......@@ -87,6 +92,7 @@ dev = [
requires-dist = [
{ name = "dash", specifier = ">=2.18.2" },
{ name = "dash-bootstrap-components", specifier = ">=2.0.0" },
{ name = "gunicorn", specifier = ">=23.0.0" },
{ name = "netcdf4", specifier = ">=1.7.2" },
{ name = "pandas", specifier = ">=2.2.3" },
{ name = "plotly", specifier = ">=6.0.1" },
......@@ -120,6 +126,12 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/ab/c8/1155d1d58003105307c7e5985f422ae5bcb2ca0cbc553cc828f3c5a934a7/cftime-1.6.4.post1.tar.gz", hash = "sha256:50ac76cc9f10ab7bd46e44a71c51a6927051b499b4407df4f29ab13d741b942f", size = 54631 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/50/81/0bb28d54088a61592f61a11e7fcabcea6d261c47af79e18d0f9cbcd940ae/cftime-1.6.4.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a590f73506f4704ba5e154ef55bfbaed5e1b4ac170f3caeb8c58e4f2c619ee4e", size = 226615 },
{ url = "https://files.pythonhosted.org/packages/f3/1e/38dbbf8a828dfb5e0e6e5c912818b77aacf2e7bcb97b262ac6126beeb29f/cftime-1.6.4.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:933cb10e1af4e362e77f513e3eb92b34a688729ddbf938bbdfa5ac20a7f44ba0", size = 209193 },
{ url = "https://files.pythonhosted.org/packages/9b/60/0db884c76311ecaaf31f628aa9358beae5fcb0fbbdc2eb0b790a93aa258f/cftime-1.6.4.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf17a1b36f62e9e73c4c9363dd811e1bbf1170f5ac26d343fb26012ccf482908", size = 1320215 },
{ url = "https://files.pythonhosted.org/packages/8d/7d/2d5fc7af06da4f3bdea59a204f741bf7a30bc5019355991b2f083e557e4e/cftime-1.6.4.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e18021f421aa26527bad8688c1acf0c85fa72730beb6efce969c316743294f2", size = 1367426 },
{ url = "https://files.pythonhosted.org/packages/5d/ab/e8b26d05323fc5629356c82a7f64026248f121ea1361b49df441bbc8f2d7/cftime-1.6.4.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5835b9d622f9304d1c23a35603a0f068739f428d902860f25e6e7e5a1b7cd8ea", size = 1385593 },
{ url = "https://files.pythonhosted.org/packages/af/7b/ca72a075a3f660315b031d62d39a3e9cfef71f7929da2621d5120077a75f/cftime-1.6.4.post1-cp312-cp312-win_amd64.whl", hash = "sha256:7f50bf0d1b664924aaee636eb2933746b942417d1f8b82ab6c1f6e8ba0da6885", size = 178918 },
{ url = "https://files.pythonhosted.org/packages/da/d8/81f086dbdc6f5a4e0bb068263471f1d12861b72562fe8c18df38268e4e29/cftime-1.6.4.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5c89766ebf088c097832ea618c24ed5075331f0b7bf8e9c2d4144aefbf2f1850", size = 223418 },
{ url = "https://files.pythonhosted.org/packages/4a/cc/60a825d92a4023655e330470758280a31e7b82665ef77d0e2a0fe71ea958/cftime-1.6.4.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f27113f7ccd1ca32881fdcb9a4bec806a5f54ae621fc1c374f1171f3ed98ef2", size = 207395 },
{ url = "https://files.pythonhosted.org/packages/ca/90/f5b26949899decce262fc76a1e64915b92050473114e0160cd6f7297f854/cftime-1.6.4.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da367b23eea7cf4df071c88e014a1600d6c5bbf22e3393a4af409903fa397e28", size = 1318113 },
......@@ -143,6 +155,19 @@ version = "3.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
{ url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
{ url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
{ url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
{ url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
{ url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
{ url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
{ url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
{ url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
{ url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
{ url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
......@@ -229,6 +254,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", size = 101735 },
]
[[package]]
name = "gunicorn"
version = "23.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 },
]
[[package]]
name = "idna"
version = "3.10"
......@@ -298,6 +335,16 @@ version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 },
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 },
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 },
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 },
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 },
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 },
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 },
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 },
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 },
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 },
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
......@@ -358,6 +405,11 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/71/ed/4d27fcfa40ebfdad3d2088a3de7ee48dbff7f35163e815ec1870d2a7398c/netcdf4-1.7.2.tar.gz", hash = "sha256:a4c6375540b19989896136943abb6d44850ff6f1fa7d3f063253b1ad3f8b7fce", size = 835064 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/7f/3a0f18a39efca0e093b54d634b66573c25ecab5c482d73138ae14aa55c6d/netCDF4-1.7.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:e73e3baa0b74afc414e53ff5095748fdbec7fb346eda351e567c23f2f0d247f1", size = 2952127 },
{ url = "https://files.pythonhosted.org/packages/ed/c4/8aac0f8ca95a41bdf1364d34ff4e9bcc24494bfe69a1157301d884c2e392/netCDF4-1.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a51da09258b31776f474c1d47e484fc7214914cdc59edf4cee789ba632184591", size = 2460781 },
{ url = "https://files.pythonhosted.org/packages/2d/1a/32b7427aaf62fed3d4e4456f874b25ce39373dbddf6cfde9edbcfc2417fc/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb95b11804fe051897d1f2044b05d82a1847bc2549631cdd2f655dde7de77a9c", size = 9377415 },
{ url = "https://files.pythonhosted.org/packages/fd/bf/5e671495c8bdf6b628e091aa8980793579474a10e51bc6ba302a3af6a778/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d8a848373723f41ef662590b4f5e1832227501c9fd4513e8ad8da58c269977", size = 9260579 },
{ url = "https://files.pythonhosted.org/packages/d4/57/0a0bcdebcfaf72e96e7bcaa512f80ee096bf71945a3318d38253338e9c25/netCDF4-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:568ea369e00b581302d77fc5fd0b8f78e520c7e08d0b5af5219ba51f3f1cd694", size = 6991523 },
{ url = "https://files.pythonhosted.org/packages/e6/7a/ce4f9038d8726c9c90e07b2d3a404ae111a27720d712cfcded0c8ef160e8/netCDF4-1.7.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:205a5f1de3ddb993c7c97fb204a923a22408cc2e5facf08d75a8eb89b3e7e1a8", size = 2948911 },
{ url = "https://files.pythonhosted.org/packages/58/3e/5736880a607edabca4c4fc49f1ccf9a2bb2485f84478e4cd19ba11c3b803/netCDF4-1.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:96653fc75057df196010818367c63ba6d7e9af603df0a7fe43fcdad3fe0e9e56", size = 2455078 },
{ url = "https://files.pythonhosted.org/packages/71/96/d5d8859a6dac29f8ebc815ff8e75770bd513db9f08d7a711e21ae562a948/netCDF4-1.7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30d20e56b9ba2c48884eb89c91b63e6c0612b4927881707e34402719153ef17f", size = 9378149 },
......@@ -371,6 +423,16 @@ version = "2.2.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 },
{ url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 },
{ url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 },
{ url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 },
{ url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 },
{ url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 },
{ url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 },
{ url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 },
{ url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 },
{ url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 },
{ url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 },
{ url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 },
{ url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 },
......@@ -414,6 +476,13 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 },
{ url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 },
{ url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 },
{ url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 },
{ url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 },
{ url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 },
{ url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 },
{ url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
{ url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
{ url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
......@@ -641,11 +710,11 @@ wheels = [
[[package]]
name = "tzdata"
version = "2025.1"
version = "2025.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
]
[[package]]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment