Skip to content

Releases: NarenKarthikBM/python-cdo-wrapper

V1.1.2 - Griddes parser bug fix

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 20 Dec 09:31
6891e8b

Release Notes: v1.1.2

🎯 Feature Enhancement Release

This release significantly enhances the GriddesParser to support all CDO grid types, including rotated pole projections, Gaussian grids, and unstructured meshes, with intelligent fallback handling for unknown attributes.


✨ What's New

Enhanced GriddesParser - Universal Grid Type Support

The Enhancement:
Version 1.1.2 expands GriddesParser from basic lonlat grids to comprehensive support for all 7 major CDO grid types, including advanced projections and irregular meshes.

What's Supported Now:

Grid Type Description New in v1.1.2
lonlat Regular latitude-longitude grids ✅ Enhanced
gaussian Gaussian grids with regular longitude spacing NEW
gaussian_reduced Reduced Gaussian grids with variable longitude points NEW
generic Generic grids with minimal metadata NEW
projection Rotated pole and CF Conventions projections NEW
curvilinear 2D coordinate arrays for structured grids NEW
unstructured Irregular meshes and point clouds NEW

🚀 Key Features

1. Rotated Pole Projection Support (CF Conventions)

Full support for rotated latitude-longitude grids commonly used in regional climate models (RCMs):

from python_cdo_wrapper import CDO

cdo = CDO()
result = cdo.griddes("rotated_grid_data.nc")
grid = result.primary_grid

# Check if grid is rotated
if grid.is_rotated:
    print(f"Rotated pole projection detected!")
    print(f"North pole longitude: {grid.grid_north_pole_longitude}°")
    print(f"North pole latitude: {grid.grid_north_pole_latitude}°")
    print(f"Grid mapping: {grid.grid_mapping_name}")

# Output:
# Rotated pole projection detected!
# North pole longitude: -123.34°
# North pole latitude: 79.95°
# Grid mapping: rotated_latitude_longitude

New GridInfo Fields:

  • grid_mapping - Grid mapping variable name
  • grid_mapping_name - CF Conventions projection name
  • grid_north_pole_longitude - Rotated pole longitude
  • grid_north_pole_latitude - Rotated pole latitude

2. Gaussian Grid Support

Support for both regular and reduced Gaussian grids used in global models:

# Regular Gaussian grid
result = cdo.griddes("gaussian_data.nc")
grid = result.primary_grid

if grid.is_gaussian:
    print(f"Gaussian grid: {grid.gridtype}")
    print(f"Grid size: {grid.gridsize} points")
    print(f"Truncation (np): {grid.np}")
    print(f"Dimensions: {grid.xsize} x {grid.ysize}")

# Reduced Gaussian grid
result = cdo.griddes("reduced_gaussian_data.nc")
grid = result.primary_grid

if grid.gridtype == "gaussian_reduced":
    print(f"Reduced Gaussian grid")
    print(f"Variable longitude points per latitude row")
    if grid.rowlon:
        print(f"Longitude points: {grid.rowlon[:5]}...")  # First 5 rows

New GridInfo Fields:

  • np - Gaussian grid truncation parameter
  • rowlon - Number of longitude points per latitude row (reduced Gaussian)

3. Unstructured Grid Support

Support for irregular meshes and point clouds:

result = cdo.griddes("unstructured_mesh.nc")
grid = result.primary_grid

if grid.is_unstructured:
    print(f"Unstructured grid detected")
    print(f"Total points: {grid.points}")
    print(f"Vertices per cell: {grid.nvertex}")
    print(f"Grid size: {grid.gridsize}")

New GridInfo Fields:

  • points - Number of points in unstructured grid
  • nvertex - Number of vertices per cell

4. Intelligent Fallback Mechanism

The Problem:
CDO grids may have custom or unrecognized attributes from specialized models or future CDO versions.

The Solution:
Unknown attributes are automatically stored in raw_attributes dictionary for inspection:

result = cdo.griddes("custom_grid.nc")
grid = result.primary_grid

# Known attributes parsed normally
print(f"Grid type: {grid.gridtype}")
print(f"Size: {grid.xsize} x {grid.ysize}")

# Unknown attributes stored for inspection
if grid.raw_attributes:
    print(f"\nUnknown attributes found:")
    for key, value in grid.raw_attributes.items():
        print(f"  {key}: {value}")

# Output:
# Grid type: lonlat
# Size: 50 x 20
#
# Unknown attributes found:
#   custom_attr1: special_value
#   custom_number: 42
#   custom_float: 3.14159

Benefits:

  • ✅ Never fails on unknown attributes
  • ✅ Preserves all information for debugging
  • ✅ Future-proof for new CDO features
  • ✅ Enables inspection of custom grid metadata

5. Comprehensive GridInfo Properties

Eight new convenience properties for grid type checking:

result = cdo.griddes("data.nc")
grid = result.primary_grid

# Grid type classification
print(f"Regular grid: {grid.is_regular}")           # lonlat, generic, projection
print(f"Gaussian grid: {grid.is_gaussian}")         # gaussian, gaussian_reduced
print(f"Structured grid: {grid.is_structured}")     # has regular dimensions
print(f"Unstructured grid: {grid.is_unstructured}") # irregular mesh

# Projection information
print(f"Rotated projection: {grid.is_rotated}")     # rotated pole
print(f"Has projection: {grid.has_projection}")     # any projection info

# Spatial extent (if available)
if grid.lon_range:
    print(f"Longitude range: {grid.lon_range[0]}° to {grid.lon_range[1]}°")
if grid.lat_range:
    print(f"Latitude range: {grid.lat_range[0]}° to {grid.lat_range[1]}°")

🎯 Use Cases

Regional Climate Model Analysis

from python_cdo_wrapper import CDO

cdo = CDO()

# Analyze RCM grid with rotated pole projection
result = cdo.griddes("EUR-11_rotated.nc")
grid = result.primary_grid

if grid.is_rotated:
    print(f"European domain with rotated pole")
    print(f"Rotated coordinates: {grid.xname}/{grid.yname}")
    print(f"Resolution: {grid.xinc}° x {grid.yinc}°")
    print(f"Domain size: {grid.xsize} x {grid.ysize}")
    
    # Coordinate transformation information
    print(f"\nProjection details:")
    print(f"  Mapping: {grid.grid_mapping_name}")
    print(f"  Pole: ({grid.grid_north_pole_longitude}°E, "
          f"{grid.grid_north_pole_latitude}°N)")

Global Model Comparison

# Compare different global model grids
models = ["ERA5_gaussian.nc", "CMIP6_lonlat.nc", "IFS_reduced.nc"]

for model_file in models:
    result = cdo.griddes(model_file)
    grid = result.primary_grid
    
    print(f"\n{model_file}:")
    print(f"  Type: {grid.gridtype}")
    print(f"  Size: {grid.gridsize} points")
    
    if grid.is_gaussian:
        print(f"  Gaussian truncation: N{grid.np}")
    elif grid.is_regular:
        print(f"  Resolution: {grid.xinc}° x {grid.yinc}°")

Unstructured Mesh Inspection

# Analyze unstructured mesh from ocean model
result = cdo.griddes("ocean_mesh.nc")
grid = result.primary_grid

if grid.is_unstructured:
    print(f"Ocean mesh analysis:")
    print(f"  Total mesh points: {grid.points:,}")
    print(f"  Cell structure: {grid.nvertex}-vertex cells")
    print(f"  Grid efficiency: {grid.gridsize / grid.points:.2%}")

🧪 Testing

Comprehensive Test Coverage

Added 13 new test cases covering all grid types and edge cases:

Grid Type Tests:

  1. ✅ Regular lonlat grids
  2. ✅ Rotated pole projection grids
  3. ✅ Gaussian grids (regular)
  4. ✅ Reduced Gaussian grids
  5. ✅ Generic grids
  6. ✅ Curvilinear grids
  7. ✅ Unstructured grids

Feature Tests:
8. ✅ Projection parameter parsing
9. ✅ GridInfo convenience properties (lonlat)
10. ✅ GridInfo convenience properties (rotated)
11. ✅ Unknown attribute fallback
12. ✅ Type-safe attribute parsing
13. ✅ Error handling for malformed output

Test Files:

  • tests/test_parsers/test_grid.py - Unit tests with sample CDO outputs
  • Sample outputs for 7 different grid types included as fixtures

All tests pass on:

  • ✅ Ubuntu (Python 3.9, 3.10, 3.11, 3.12)
  • ✅ macOS (Python 3.9, 3.10, 3.11, 3.12)

💥 Breaking Changes

None - This is a backward-compatible enhancement. All existing code continues to work as before.

New Features:

  • Existing GridInfo fields remain unchanged
  • New optional fields with default None values
  • New convenience properties are additive
  • raw_attributes field is optional

🔧 Technical Details

Type-Safe Attribute Parsing

New _parse_grid_attribute() method handles automatic type detection:

# Integer fields
gridsize, xsize, ysize, np, points, nvertexint

# Float fields  
xfirst, xinc, yfirst, yinc, scanningMode,
grid_north_pole_longitude, grid_north_pole_latitudefloat

# Float list fields (space-separated)
xvals, yvals, levelslist[float]

# Integer list fields (space-separated)
rowlonlist[int]

# String fields (with quote stripping)
gridtype, xname, xlongname, xunits, etc. → str

Robust Parsing:

  • ✅ Automatic type conversion based on field name
  • ✅ Graceful handling of parsing failures (fallback to raw string)
  • ✅ Preservation of all data (unknown → raw_attributes)
  • ✅ No data loss on parse errors

Known vs Unknown Attributes

The parser distinguishes between:

Known Attributes (mapped to GridInfo fields):

  • Standard grid attributes: gridtype, gridsize, xsize, ysize, etc.
  • Coordinate attributes: xfirst, xinc, xvals, etc.
  • Projection attributes: grid_mapping, grid_north_pole_*, etc.
  • Special attributes: np, rowlon, points, nvertex, etc.

Unknown Attributes (stored in raw_attributes):

  • Custom model-specific attributes
  • Future CDO attributes not yet supported
  • Experimental or non-standard fields

This ensures forward compatibility with future CDO versions.

...

Read more

v1.1.1 - BinaryOPQuery execution bug fix

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 17 Dec 11:08
6d79728

Release Notes: v1.1.1

🐛 Bug Fix Release

This is a critical bug fix release that corrects the command generation for binary operations in CDO queries.


🔧 What's Fixed

Binary Operation Command Generation

The Issue:
Version 1.1.0 incorrectly used bracket notation [ ... ] for binary operations, which is not valid CDO syntax for binary operators. This caused "too many inputs" errors when using nested binary operations like ifthen inside sub.

The Fix:
Binary operators (add, sub, mul, div, min, max) now use CDO's operator chaining syntax instead of brackets.

Command Generation - Before vs After:

Scenario v1.1.0 (Incorrect) v1.1.1 (Correct)
Left has operators cdo -sub [ -yearmean file1 ] file2 cdo -sub -yearmean file1 file2
Both have operators cdo -sub [ -yearmean file1 ] [ -timmean file2 ] cdo -sub -yearmean file1 -timmean file2
Nested binary ops Failed with "too many inputs" ❌ cdo -sub -ifthen mask.nc data.nc clim.nc

Key Points:

  • ✅ Binary operators use operator chaining (no brackets)
  • ✅ Operators are applied to their respective input files left-to-right
  • ✅ Supports nested binary operations (e.g., ifthen inside sub)
  • ✅ All operations execute in a single CDO command (no temporary files)
  • 📝 Bracket notation is only for variadic operators (merge, cat) - not binary operators

💥 Breaking Changes

None - This is a bug fix release that makes the package work correctly. If you were using binary operations in v1.1.0, they may not have been working properly.


🎯 What's Working Now

Example 1: Simple Anomaly Calculation

from python_cdo_wrapper import CDO, F

cdo = CDO()

# Calculate anomalies - works correctly now!
anomaly = cdo.query("data.nc").sub(F("climatology.nc")).compute()
# Generates: cdo -sub data.nc climatology.nc

Example 2: Operations on Both Sides

# With operators on both operands - single command!
result = (
    cdo.query("data.nc")
    .select_var("tas")
    .year_mean()
    .sub(F("climatology.nc").time_mean())
    .compute()
)
# Generates: cdo -sub -yearmean -selname,tas data.nc -timmean climatology.nc

Example 3: Nested Binary Operations (The Bug Case)

# The original issue: ifthen inside sub
masked_data = cdo.query("data.nc").ifthen(F("mask.nc"))
anomaly = masked_data.sub(F("climatology.nc")).compute()
# Generates: cdo -sub -ifthen mask.nc data.nc climatology.nc
# This now works correctly! ✅

Example 4: Complex Multi-Step Operations

# Standardized anomaly: (data - mean) / std
std_anomaly = (
    cdo.query("data.nc")
    .sub(F("climatology.nc"))
    .div(F("std_dev.nc"))
    .compute()
)
# Generates: cdo -div -sub data.nc climatology.nc std_dev.nc

🧪 Testing

New Integration Tests

Added 5 comprehensive integration tests that execute actual CDO commands with real NetCDF files:

  1. ✅ Binary operations with operators on left operand
  2. ✅ Binary operations with operators on both sides
  3. ✅ Nested binary operations (ifthen inside sub) - the original bug case
  4. ✅ Anomaly calculations (data - time_mean)
  5. ✅ Chained binary operations (sub then div)

All tests pass with flying colors on:

  • ✅ Ubuntu (Python 3.9, 3.10, 3.11, 3.12)
  • ✅ macOS (Python 3.9, 3.10, 3.11, 3.12)

📦 Installation & Upgrade

Upgrading from v1.1.0

# Upgrade to the bug fix release
pip install --upgrade python-cdo-wrapper

# Or with shapefile support
pip install --upgrade python-cdo-wrapper[shapefiles]

New Installation

# Core package
pip install python-cdo-wrapper

# With shapefile support
pip install python-cdo-wrapper[shapefiles]

Prerequisites

  • Python >= 3.9
  • CDO >= 1.9.8 (recommended: >= 2.0.0)
  • Optional: geopandas >= 0.10.0, shapely >= 2.0.0 (for shapefile masking)

🔗 Technical Details

Why Operator Chaining?

CDO processes operators right-to-left and applies them to their respective input files. Binary operators (which take exactly 2 inputs) use operator chaining syntax, not bracket notation.

From the CDO Tutorial:

"Operators can be chained directly to their input files without brackets for binary operations."
CDO Tutorial: Combining Operators

Example Flow:

cdo -sub -yearmean file1.nc -timmean file2.nc output.nc

CDO evaluation:

  1. Apply -yearmean to file1.nc → result1
  2. Apply -timmean to file2.nc → result2
  3. Apply -sub with (result1, result2) → output

📝 Documentation Updates

All documentation has been updated to reflect the correct syntax:

  • README.md - Updated binary operation examples
  • CHANGELOG.md - Documented the fix with examples
  • MIGRATION_GUIDE.md - Updated v1.0.0 examples
  • .github/copilot-instructions.md - Updated all references
  • examples/v1.0.0-demo.ipynb - Updated example outputs

🙏 Acknowledgments

Special thanks to:

  • The CDO team for excellent documentation on operator chaining

🐛 Known Issues & Limitations

None in this release!

This bug fix resolves all known command generation issues with binary operations.

Future Improvements

  • Consider adding more optimization hints for large-scale operations
  • Expand documentation with more climate science use cases
  • Add performance benchmarks for operator chaining

📊 Full Changelog

[v1.1.1] - 2025-12-17

Fixed

  • BinaryOpQuery command generation: Corrected to use CDO's operator chaining syntax (removed incorrect bracket notation)
    • Binary operators (sub, add, mul, div, etc.) now use standard operator chaining
    • ❌ Old (incorrect): cdo -sub [ -yearmean file1 ] [ file2 ]
    • ✅ New (correct): cdo -sub -yearmean file1 -timmean file2
    • Operators are applied directly to their respective input files from left to right
    • Supports nested binary operations (e.g., ifthen inside sub)
    • All operations execute in single CDO command
    • Bracket notation is NOT used for binary operators - only for variadic operators (merge, cat)

Testing

  • Added 5 integration tests for binary operations with actual CDO execution
  • All tests pass on Ubuntu and macOS with Python 3.9-3.12

Documentation

  • Updated all documentation to reflect correct operator chaining syntax
  • Added technical notes explaining CDO's evaluation model
  • Updated examples in README, MIGRATION_GUIDE, and notebooks

For complete details, see CHANGELOG.md.


📞 Support & Feedback

Found a bug? Please open an issue on GitHub:
https://github.com/NarenKarthikBM/python-cdo-wrapper/issues

Have questions? Check out:


🔗 Resources


Thank you for using python-cdo-wrapper! 🎉


Note: This release fixes a critical bug in v1.1.0. All users on v1.1.0 are encouraged to upgrade immediately.

V1.1.0 - Added Shapefile mixin and fixed BinaryOPQuery bug

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 16 Dec 10:54
d74a427

🎉 What's New

Shapefile Masking Support

The headline feature of v1.1.0 is comprehensive shapefile masking for NetCDF files. Users can now clip climate data to geographic regions defined by shapefiles in a single, chainable operation.

Key Highlights:

# One-liner regional masking
masked = cdo.query("global_data.nc").mask_by_shapefile("region.shp").compute()

# Chain with other CDO operators
result = (
    cdo.query("daily_precip.nc")
    .mask_by_shapefile("sahel.shp")
    .year_mean()
    .field_mean()
    .compute()
)

Features:

  • ✅ Complete automated pipeline (load shapefile → create mask → apply → cleanup)
  • ✅ Support for both 1D (regular) and 2D (curvilinear) grids
  • ✅ Automatic CRS reprojection to WGS84
  • ✅ Multi-polygon shapefile support
  • ✅ Custom coordinate naming (lat_name, lon_name parameters)
  • ✅ Automatic temporary file cleanup
  • ✅ Secure temp file handling (no race conditions)

Installation:

pip install python-cdo-wrapper[shapefiles]

This optional dependency group includes geopandas>=0.10.0 and shapely>=2.0.0.


🔧 Technical Improvements

Enhanced Binary Operations

Fixed: Binary operators (add, sub, mul, div, min, max) no longer generate unnecessary brackets in CDO commands.

  • Binary operators take exactly two inputs and CDO assigns them unambiguously right-to-left
  • Commands are now cleaner: cdo -sub -yearmean data.nc clim.nc
  • Previously generated: cdo -sub [ -yearmean data.nc ] clim.nc
  • Follows CDO best practices and may improve performance

Security Enhancements

  • Replaced unsafe tempfile.mktemp() with secure tempfile.mkstemp() in shapefile masking
  • Prevents race condition vulnerabilities when creating temporary mask files
  • Follows Python security best practices

📋 Complete Feature List

New Operators

Shapefile Masking

  • mask_by_shapefile(shapefile_path, lat_name="lat", lon_name="lon")
    • Clip NetCDF to shapefile polygon extent
    • Chainable with all other CDO operators
    • Returns masked xarray.Dataset

Advanced Utilities

  • create_mask_from_shapefile(shapefile_path, reference_nc, lat_name, lon_name)
    • Create reusable binary mask files
    • Useful for processing multiple files with same region
    • Returns xarray.Dataset with mask variable

Information Operators (Query API)

All CDO information commands are now available as terminating query methods (from v1.0.1, included in this release):

Variable Information:

  • .showname() - List variable names
  • .showcode() - List variable codes
  • .showunit() - List variable units
  • .showlevel() - List vertical levels

Time Information:

  • .showdate() - List timestamps
  • .showtime() - List time values
  • .ntime() - Count timesteps

Dataset Information:

  • .sinfo() - Structured file info (returns SinfoResult)
  • .info() - General info (returns InfoResult)
  • .vlist() - Variable list (returns VlistResult)
  • .partab() - Parameter table (returns PartabResult)
  • .nvar() - Count variables
  • .nlevel() - Count levels

Grid Information:

  • .griddes() - Grid description (returns GriddesResult)
  • .zaxisdes() - Z-axis description (returns ZaxisdesResult)

These methods execute immediately and return structured results, making them convenient for pipeline inspection.


🔄 Migration Guide

From v1.0.x to v1.1.0

No breaking changes - v1.1.0 is fully backward compatible with v1.0.x.

Optional: Adopt Shapefile Masking

If you currently mask data manually with separate mask files:

Before (v1.0.x):

# Manual masking with pre-created mask
result = cdo.query("data.nc").select_mask("region_mask.nc").compute()

After (v1.1.0):

# Direct shapefile masking
result = cdo.query("data.nc").mask_by_shapefile("region.shp").compute()

Optional: Install Shapefile Support

# If you need shapefile masking
pip install --upgrade python-cdo-wrapper[shapefiles]

# Or just core package (no shapefile support)
pip install --upgrade python-cdo-wrapper

📦 Installation & Upgrade

New Installation

# Core package
pip install python-cdo-wrapper

# With shapefile support
pip install python-cdo-wrapper[shapefiles]

# All optional features
pip install python-cdo-wrapper[shapefiles,dev,test]

Upgrading from v1.0.x

pip install --upgrade python-cdo-wrapper
# Or with shapefiles:
pip install --upgrade python-cdo-wrapper[shapefiles]

Prerequisites

  • Python >= 3.9
  • CDO >= 1.9.8 (recommended: >= 2.0.0)
  • For shapefile masking: geopandas >= 0.10.0, shapely >= 2.0.0

📚 Documentation

Updated Documentation

  • README.md - Added comprehensive shapefile masking section
  • CHANGELOG.md - Detailed changelog for v1.1.0
  • examples/shapefile_masking_example.py - 7 usage scenarios

Key Examples

Example 1: Simple Regional Analysis

from python_cdo_wrapper import CDO

cdo = CDO()

# Analyze temperature over Amazon basin
regional_temp = (
    cdo.query("global_temperature.nc")
    .mask_by_shapefile("amazon_basin.shp")
    .year_mean()
    .field_mean()
    .compute()
)

Example 2: Multi-step Climate Analysis

# Calculate regional precipitation anomaly
anomaly = (
    cdo.query("monthly_precip.nc")
    .mask_by_shapefile("sahel.shp")
    .sub(F("climatology.nc"))
    .year_mean()
    .compute()
)

Example 3: Reusable Masks for Multiple Files

from python_cdo_wrapper import create_mask_from_shapefile

# Create mask once
mask_ds = create_mask_from_shapefile(
    shapefile_path="europe.shp",
    reference_nc="template.nc"
)
mask_ds.to_netcdf("europe_mask.nc")

# Reuse for multiple files
file1 = cdo.query("data1.nc").select_mask("europe_mask.nc").compute()
file2 = cdo.query("data2.nc").select_mask("europe_mask.nc").compute()

⚡ Performance Notes

Shapefile Masking Performance

For small to medium grids (< 100k points): Masking is fast, typically < 1 second.

For large grids (e.g., global 0.5° = ~260k points): The point-in-polygon test loop can take several seconds.

Optimization strategies:

  1. Pre-create and reuse masks for multiple files with the same region
  2. Use coarser grids when high resolution isn't needed
  3. Consider CDO's native region selection (.select_region()) for simple rectangular boxes

Example of mask reuse:

# Create mask once (may be slow for large grids)
mask_ds = create_mask_from_shapefile("region.shp", "data.nc")
mask_ds.to_netcdf("region_mask.nc")

# Reuse for many files (fast)
for file in file_list:
    result = cdo.query(file).select_mask("region_mask.nc").year_mean().compute()

🧪 Testing

Test Coverage

  • 9 unit tests for shapefile masking (all passing)
  • 4 integration tests (require CDO installation)
  • Security test verifying secure temp file creation
  • Cleanup test verifying automatic temp file deletion

Running Tests

# Install with test dependencies
pip install python-cdo-wrapper[test,shapefiles]

# Run shapefile masking tests
pytest tests/test_shapefile_masking.py -v

# Run all tests
pytest

🐛 Known Issues & Limitations

Shapefile Masking

  1. Large grids: Point-in-polygon tests can be slow (see Performance Notes above)
  2. Complex polygons: Very complex multi-polygon shapefiles may take longer to process
  3. CRS requirements: Shapefile must be in geographic coordinates or reprojectable to WGS84

Workarounds

  • For repeated analyses on the same region, create and reuse mask files
  • For simple rectangular regions, use .select_region() instead
  • For very large grids, consider downsampling before masking

🔗 Resources


🙏 Acknowledgments

Special thanks to:

  • The CDO team for the excellent climate data operators
  • The geopandas and shapely communities for robust geospatial tools

📝 Full Changelog

For complete details, see CHANGELOG.md.

v1.1.0 Summary

  • ✅ Added shapefile masking with mask_by_shapefile() operator
  • ✅ Fixed binary operation bracket generation
  • ✅ Enhanced security with secure temp file creation
  • ✅ Improved performance documentation
  • ✅ Enhanced test coverage for temp file cleanup
  • ✅ Updated documentation and examples

Questions or Issues? Please open an issue on GitHub: https://github.com/NarenKarthikBM/python-cdo-wrapper/issues

V1.0.1 - Added terminal info query functions

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 16 Dec 07:28
205d85c

🎉 Release v1.0.1 - Information Operators as Query Terminators

This release brings a major enhancement to the Query API: all CDO information operators are now available as terminating methods directly on CDOQuery. Extract metadata from processed data without intermediate files, making workflows more intuitive and efficient.

✨ New Features

Information Operators as Terminating Query Methods

All 15 CDO information operators can now be called directly on query chains, executing immediately (like .compute()) but returning metadata instead of datasets:

Variable Information Methods:

  • .showname() - Get list of variable names
  • .showcode() - Get list of variable codes
  • .showunit() - Get list of variable units
  • .showlevel() - Get list of vertical levels

Time Information Methods:

  • .showdate() - Get list of dates (YYYY-MM-DD format)
  • .showtime() - Get list of times (HH:MM:SS format)
  • .ntime() - Get number of timesteps (alias for .count())

Count Methods:

  • .nvar() - Get number of variables
  • .nlevel() - Get number of vertical levels

Structured Dataset Information:

  • .sinfo()SinfoResult - Comprehensive dataset summary
  • .info()InfoResult - Timestep-by-timestep statistics
  • .vlist()VlistResult - Variable list with metadata
  • .partab()PartabResult - Parameter table information

Grid Information:

  • .griddes()GriddesResult - Grid description
  • .zaxisdes()ZaxisdesResult - Vertical axis description

Key Benefits

  • No intermediate files needed when inspecting processed data
  • Chain processing and metadata extraction in a single pipeline
  • Consistent Query API pattern following Django ORM conventions
  • Works with or without operators in the pipeline

📊 Example Usage

from python_cdo_wrapper import CDO

cdo = CDO()

# Get variable names after processing - no intermediate file!
vars = cdo.query("data.nc").year_mean().showname()
# Returns: ['tas', 'pr', 'psl']

# Get number of timesteps after selection
n = cdo.query("data.nc").select_year(2020).ntime()
# Returns: 12

# Get dates after complex processing
dates = (
    cdo.query("data.nc")
    .select_var("tas")
    .select_year(2020, 2021)
    .showdate()
)
# Returns: ['2020-01-01', '2020-02-01', ..., '2021-12-01']

# Get comprehensive dataset info after processing

```python
# Get structured dataset info after processing
info = cdo.query("data.nc").year_mean().sinfo()
print(f"Variables: {info.var_names}")
print(f"Time range: {info.time_range}")

# Get grid info after remapping
grid = cdo.query("data.nc").remap_bil("r180x90").griddes()
print(f"Grid type: {grid.grids[0].gridtype}")
print(f"Grid size: {grid.grids[0].xsize} x {grid.grids[0].ysize}")

🔧 Improvements

  • Parser Enhancements: Improved SinfoParser and PartabParser robustness

    • Fixed variable name handling in SinfoParser (sinfo only provides parameter IDs)
    • Enhanced regex patterns in PartabParser for better field extraction
    • Better error handling for optional fields and edge cases
  • Test Data Quality: Upgraded test fixtures to CF-1.8 compliant standards

    • Changed temperaturetas (standard CF variable name)
    • Added proper GRIB parameter codes (tas=167, pr=228, psl=151)
    • Improved NetCDF encoding and coordinate attributes
  • Type Safety: Added proper type casting with typing.cast for better IDE support

  • Documentation: Added comprehensive example script examples/info_operators_example.py

  • Agent Configuration: Updated GitHub Copilot agent tool definitions

Backward Compatibility

Fully backward compatible - all existing code continues to work without changes. The new methods are additions to the Query API and don't affect existing functionality.

Recommended migration (optional):

# Old way (still works)
cdo.showname("data.nc")

# New way (recommended for processed data)
cdo.query("data.nc").year_mean().showname()  # No intermediate file!

Installation

pip install --upgrade python-cdo-wrapper

Full Changelog: v1.0.0...v1.0.1

V1.0.0 - Django-ORM style query chaining

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 12 Dec 13:35

What's Changed

  • V1.0.0 major overhaul - Add integration and unit tests for CDO wrapper functionality by @NarenKarthikBM in #4

Full Changelog: V0.2.1...V1.0.0

v0.2.1 - Enhanced SinfoParser

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 10 Dec 11:47
6c99a7a

🎉 Release v0.2.1 - Enhanced SinfoParser

This release significantly enhances the SinfoParser to provide comprehensive parsing of all CDO sinfo output sections, making it easier to programmatically access climate data metadata.

✨ New Features

Complete Sinfo Output Parsing

  • Metadata Extraction: Parse file format, institute, source, and variable field headers
  • Grid Coordinates: Full grid information with automatic spatial resolution calculation
    • Extracts grid type, dimensions, and coordinate ranges
    • Automatically calculates lon_resolution and lat_resolution (e.g., 0.25 degrees)
  • Vertical Coordinates: Parse vertical axis information (surface, pressure, hybrid levels)
  • Time Coordinates: Comprehensive temporal data extraction
    • Total timesteps, reference time, units, and calendar
    • Complete list of all timestep values
    • Automatic temporal resolution calculation (detects hourly, 6-hourly, daily, monthly intervals)
    • Handles both regular and irregular time series

Enhanced Variable Parsing

  • Support for both modern sinfo format (Institut/Source/Steptype) and legacy format (Date/Time)
  • Robust type conversion for numeric fields
  • Graceful handling of malformed data

📊 Example Usage

from python_cdo_wrapper import CDO, parse_cdo_output

cdo = CDO()
output = cdo("sinfo", input="data.nc")
parsed = parse_cdo_output("sinfo", output)

# Access grid resolution
print(parsed["grid"]["lon_resolution"])  # 0.25
print(parsed["grid"]["lat_resolution"])  # 0.25

# Access time resolution
print(parsed["time"]["time_resolution"]["interval"])  # "1 day"
print(parsed["time"]["time_resolution"]["regular"])   # True

# Access all timesteps
print(parsed["time"]["timesteps"][:5])  # First 5 timesteps

🔧 Improvements

  • Added 12 comprehensive test cases for sinfo parsing
  • Improved parser robustness and error handling
  • Better documentation with detailed docstrings

Backward Compatibility

Fully backward compatible - all existing code continues to work without changes.

Installation

pip install --upgrade python-cdo-wrapper

Full Changelog: V0.2.0...V0.2.1

V0.2.0 - Structured Output Support

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 10 Dec 10:13

🎉 New Features

  • Structured Dictionary Output: Add return_dict=True parameter to cdo() function
  • Smart Parsers: Automatic parsing of CDO text output into Python dictionaries
  • Type Safety: TypedDict definitions for all structured outputs
  • 8 Parser Implementations: Support for griddes, sinfo, showatts, zaxisdes, partab, vct, and more

📋 Supported Commands

  • Grid Info: griddes, griddes2, zaxisdes
  • Dataset Info: sinfo, info, infon, infov, sinfon, sinfov
  • Variable Info: vlist, showatts, showattsglob
  • Parameter Tables: partab, codetab, vct, vct2

📖 Example Usage

from python_cdo_wrapper import cdo

# New: Get structured dictionary output
grid_info = cdo("griddes data.nc", return_dict=True)
print(grid_info['gridtype'])  # 'lonlat'
print(grid_info['xsize'])     # 360

# Old: Still works - returns string
grid_text = cdo("griddes data.nc")

Backward Compatibility

Fully backward compatible - all existing code continues to work without changes.

🔧 Installation

pip install --upgrade python-cdo-wrapper

Full Changelog: V0.1.0...V0.2.0

V0.1.0 - Initial Release

Choose a tag to compare

@NarenKarthikBM NarenKarthikBM released this 05 Dec 18:37
Fix ruff version mismatch