Skip to main content

Utilities for interpreting EPA SWMM input (.inp), report (.rpt), and output (.out) files.

Project description

SWMM Utils

Utilities for interpreting EPA SWMM input (.inp), report (.rpt), and output (.out) files.

Tests Python License

Overview

This project provides a comprehensive toolkit for working with EPA SWMM (Storm Water Management Model) files:

  • Input Files (.inp) - Load, modify, and save SWMM models with typed properties. Support for .inp, JSON, and Parquet formats.
  • Report Files (.rpt) - Parse simulation results and extract all major sections (node depths, link flows, pumping, storage, LID performance, etc.)
  • Output Files (.out) - Extract binary SWMM output data with optional time series loading. Export to JSON or Parquet.

Quick Start

Installation

# Clone the repository
git clone https://github.com/neeraip/swmm-utils.git
cd swmm-utils

# Install the package
pip install -e .

Basic Usage

For detailed examples and API documentation for each file type, see:

Quick example:

from swmm_utils import SwmmInput, SwmmReport, SwmmOutput

# Input files
with SwmmInput("model.inp") as inp:
    inp.title = "Modified Model"
    inp.to_json("model.json")

# Report files
with SwmmReport("simulation.rpt") as report:
    for node in report.node_depth:
        print(f"{node['name']}: {node['maximum_depth']:.2f} ft")

# Output files (metadata only or with time series)
output = SwmmOutput("simulation.out", load_time_series=True)
output.to_json("output_complete.json", pretty=True)

API Reference

Detailed API documentation is available in the respective docs files:

  • SwmmInput API - High-level interface for input files with typed properties
  • SwmmReport API - High-level interface for report files
  • SwmmOutput API - High-level interface for output files with optional time series loading

Core Classes

All classes support context manager pattern (with statement) for clean resource management:

# All three file types use context managers
with SwmmInput("model.inp") as inp:
    ...

with SwmmReport("simulation.rpt") as report:
    ...

with SwmmOutput("simulation.out") as output:
    ...

Lower-Level APIs (Advanced)

For direct decoder/encoder access:

  • SwmmInputDecoder & SwmmInputEncoder - Direct dict-based access to input file data
  • SwmmReportDecoder - Direct dict-based access to report file data

See Input Files documentation for details.

Architecture

Input Files:
.inp file → SwmmInput → Modify Properties → Save (.inp/JSON/Parquet)
               ↓
          Typed Properties
       (title, junctions, etc.)

Report Files:
.rpt file → SwmmReport → Access Results
               ↓
          Typed Properties
       (node_depth, link_flow, etc.)

Output Files:
.out file → SwmmOutput → Export (JSON/Parquet)
               ↓
       Metadata or Time Series
    (default: metadata only)

The architecture uses Python dictionaries as the in-memory data model:

  1. SwmmInput/SwmmReport/SwmmOutput: High-level interfaces with typed properties and context managers
  2. Decoders: Read .inp/.rpt/.out/JSON/Parquet files into Python dict structures
  3. Encoders: Write dict objects to .inp/JSON/Parquet formats (input files only)
  4. Dict Model: Simple Python dictionaries - easy to inspect, modify, and manipulate

Features

Input File Features

  • ✅ Simple, intuitive API with typed properties
  • ✅ Context manager support for clean resource management
  • ✅ Decode all SWMM 5.2.4 input file sections (60+ sections)
  • ✅ Encode to .inp, JSON, and Parquet formats
  • ✅ Decode from .inp, JSON, and Parquet formats
  • ✅ Export to Pandas DataFrames for data analysis and manipulation
  • ✅ Configurable Parquet output (single-file or multi-file modes)
  • ✅ Round-trip conversion (load → modify → save) without data loss
  • ✅ Full support for comments, whitespace, and formatting

Report File Features

  • ✅ Comprehensive parsing of SWMM 5.2 report files
  • ✅ Extract 20+ report sections (hydraulics, hydrology, water quality)
  • ✅ Node results: depth, inflow, flooding, surcharge
  • ✅ Link results: flow, velocity, classification
  • ✅ Pump and storage performance metrics
  • ✅ LID (Low Impact Development) performance analysis
  • ✅ Water quality: pollutant loads, washoff, continuity
  • ✅ Groundwater and RDII tracking
  • ✅ Easy result lookup by element name

Output File Features

  • ✅ Binary SWMM 5.0+ output file parsing (.out format)
  • ✅ Extract simulation time series metadata and statistics
  • ✅ Access node, link, and subcatchment properties
  • ✅ Time index generation with full timestamp support
  • ✅ Export to JSON and Parquet formats
  • ✅ Export to Pandas DataFrames for time series analysis
  • ✅ Three-level DataFrame export: full data, sections, or individual elements
  • ✅ Pollutant tracking and water quality data
  • ✅ Efficient memory usage (metadata-based access, not full time series loading)
  • ✅ Element lookup by name

Testing

  • ✅ Comprehensive test suite (69 tests passing)
  • ✅ Input file tests (28 tests)
  • ✅ Report file tests (12 tests)
  • ✅ Output file tests (29 tests)

Supported SWMM Sections

Project Configuration

  • [TITLE] - Project title and description
  • [OPTIONS] - Simulation options (34 parameters)
  • [REPORT] - Output reporting options
  • [FILES] - External file references
  • [MAP] - Map extent and units
  • [BACKDROP] - Background image settings
  • [PROFILES] - Longitudinal profile definitions

Hydrology

  • [RAINGAGES] - Rain gage definitions
  • [EVAPORATION] - Evaporation data
  • [SUBCATCHMENTS] - Subcatchment properties
  • [SUBAREAS] - Subcatchment surface areas
  • [INFILTRATION] - Infiltration parameters
  • [AQUIFERS] - Groundwater aquifer properties
  • [GROUNDWATER] - Subcatchment groundwater
  • [GWF] - Groundwater flow equations
  • [SNOWPACKS] - Snow pack parameters
  • [TEMPERATURE] - Temperature data
  • [ADJUSTMENTS] - Climate adjustments

Hydraulic Network - Nodes

  • [JUNCTIONS] - Junction nodes
  • [OUTFALLS] - Outfall nodes
  • [STORAGE] - Storage unit nodes
  • [DIVIDERS] - Flow divider nodes

Hydraulic Network - Links

  • [CONDUITS] - Conduit links
  • [PUMPS] - Pump links
  • [ORIFICES] - Orifice links
  • [WEIRS] - Weir links
  • [OUTLETS] - Outlet links

Cross-Sections

  • [XSECTIONS] - Link cross-section geometry
  • [LOSSES] - Minor losses
  • [TRANSECTS] - Irregular cross-section data

Water Quality

  • [POLLUTANTS] - Pollutant properties
  • [LANDUSES] - Land use categories
  • [COVERAGES] - Subcatchment land use coverage
  • [BUILDUP] - Pollutant buildup functions
  • [WASHOFF] - Pollutant washoff functions
  • [TREATMENT] - Treatment equations
  • [INFLOWS] - External inflows
  • [DWF] - Dry weather inflows
  • [RDII] - RDII inflow parameters
  • [HYDROGRAPHS] - Unit hydrograph data
  • [LOADING] - Initial pollutant loads

LID Controls (Low Impact Development)

  • [LID_CONTROLS] - LID control definitions
  • [LID_USAGE] - LID usage in subcatchments

Street/Inlet Modeling (SWMM 5.2+)

  • [STREETS] - Street cross-section properties
  • [INLETS] - Inlet design parameters
  • [INLET_USAGE] - Inlet usage on streets

Curves & Time Series

  • [TIMESERIES] - Time series data
  • [PATTERNS] - Time patterns
  • [CURVES] - Curve data

Operational Controls

  • [CONTROLS] - Rule-based controls

Visualization

  • [COORDINATES] - Node coordinates
  • [VERTICES] - Link vertices
  • [POLYGONS] - Subcatchment polygons
  • [SYMBOLS] - Rain gage symbols
  • [LABELS] - Map labels
  • [TAGS] - Object tags

Scenarios

Scenario 1: Input File - Decode and Analyze

from swmm_utils import SwmmInputDecoder

decoder = SwmmInputDecoder()
model = decoder.decode_file("large_network.inp")

# Count elements
print(f"Junctions: {len(model.get('junctions', []))}")
print(f"Conduits: {len(model.get('conduits', []))}")
print(f"Subcatchments: {len(model.get('subcatchments', []))}")

# Find high-elevation junctions
for junc in model.get('junctions', []):
    if float(junc['elevation']) > 100:
        print(f"High junction: {junc['name']} at {junc['elevation']}m")

Scenario 2: Report File - Analyze Simulation Results

from swmm_utils import SwmmReport

with SwmmReport("results.rpt") as report:
    # Check for critical conditions
    print(f"Analysis: {report.header['title']}")
    print(f"Flow Units: {report.analysis_options.get('flow_units', 'N/A')}")
    
    # Find nodes with excessive depth
    critical_nodes = [
        node for node in report.node_depth 
        if node['maximum_depth'] > 10
    ]
    print(f"\n{len(critical_nodes)} nodes exceeded 10 ft depth")
    
    # Analyze pump efficiency
    if report.pumping_summary:
        for pump in report.pumping_summary:
            if pump['percent_utilized'] < 20:
                print(f"Pump {pump['pump_name']} underutilized: "
                      f"{pump['percent_utilized']:.1f}%")
    
    # Check system continuity
    continuity = report.continuity.get('flow_routing', {})
    error = continuity.get('continuity_error')
    if error and abs(error) > 1.0:
        print(f"Warning: Continuity error {error:.2f}%")

Scenario 3: Export Input Files to Pandas DataFrames

from swmm_utils import SwmmInput

with SwmmInput("model.inp") as inp:
    # Export all sections as dictionary of DataFrames
    all_dfs = inp.to_dataframe()
    
    # Access specific section
    junctions_df = all_dfs['junctions']
    print(f"Model has {len(junctions_df)} junctions")
    print(junctions_df[['name', 'elevation', 'max_depth']])
    
    # Analyze with pandas operations
    conduits_df = all_dfs['conduits']
    avg_length = conduits_df['length'].astype(float).mean()
    print(f"Average conduit length: {avg_length:.2f}")
    
    # Or export specific section directly
    subcatchments_df = inp.to_dataframe('subcatchments')
    total_area = subcatchments_df['area'].astype(float).sum()
    print(f"Total subcatchment area: {total_area:.2f}")
    
    # Export to CSV for external analysis
    junctions_df.to_csv("junctions.csv", index=False)
    conduits_df.to_csv("conduits.csv", index=False)

Scenario 4: Export Specific Section to DataFrame

from swmm_utils import SwmmInput
import matplotlib.pyplot as plt

with SwmmInput("model.inp") as inp:
    # Export specific section to DataFrame
    junctions_df = inp.to_dataframe('junctions')
    
    print("Junctions Summary:")
    print(junctions_df[['name', 'elevation', 'max_depth', 'init_depth']])
    
    # Perform analysis on the section
    print(f"\nStatistics:")
    print(f"Number of junctions: {len(junctions_df)}")
    print(f"Average elevation: {junctions_df['elevation'].astype(float).mean():.2f}")
    print(f"Min/Max elevation: {junctions_df['elevation'].astype(float).min():.2f} / "
          f"{junctions_df['elevation'].astype(float).max():.2f}")
    
    # Filter and analyze subset
    high_junctions = junctions_df[junctions_df['elevation'].astype(float) > 100]
    print(f"\nHigh-elevation junctions (>100): {len(high_junctions)}")
    
    # Export filtered results
    high_junctions.to_csv("high_elevation_junctions.csv", index=False)
    
    # Visualize if matplotlib available
    try:
        junctions_df['elevation'].astype(float).hist(bins=20)
        plt.xlabel('Elevation')
        plt.ylabel('Count')
        plt.title('Junction Elevation Distribution')
        plt.savefig('elevation_distribution.png')
    except ImportError:
        pass

Scenario 5: Convert Input Files for Analytics

from swmm_utils import SwmmInputDecoder, SwmmInputEncoder

# Decode SWMM model
decoder = SwmmInputDecoder()
model = decoder.decode_file("network.inp")

# Export to Parquet for analysis in pandas/R/SQL
encoder = SwmmInputEncoder()
encoder.encode_to_parquet(model, "network_parquet/", single_file=False)

# Now analyze with pandas
import pandas as pd
junctions = pd.read_parquet("network_parquet/junctions.parquet")
conduits = pd.read_parquet("network_parquet/conduits.parquet")

print(junctions.describe())
print(f"Average pipe length: {conduits['length'].astype(float).mean():.2f}")

Scenario 6: Complete Workflow - Simulate and Analyze

import subprocess
from swmm_utils import SwmmInput, SwmmReport

# Step 1: Modify input file
with SwmmInput("model.inp") as inp:
    # Increase all pipe roughness by 10%
    for conduit in inp.conduits:
        roughness = float(conduit.get('roughness', 0.01))
        conduit['roughness'] = str(roughness * 1.1)
    
    inp.to_inp("modified.inp")

# Step 2: Run SWMM simulation
subprocess.run([
    "./bin/runswmm", 
    "modified.inp", 
    "modified.rpt", 
    "modified.out"
])

# Step 3: Analyze results
with SwmmReport("modified.rpt") as report:
    print(f"Simulation complete!")
    print(f"Total runtime: {report.analysis_time.get('elapsed', 'N/A')}")
    
    # Compare peak flows
    for link in report.link_flow[:10]:
        print(f"{link['name']}: {link['maximum_flow']:.2f} CFS")

Scenario 8: Output File - Time Series Analysis with DataFrames

from swmm_utils import SwmmOutput
import pandas as pd

# Load output file with full time series
output = SwmmOutput("simulation.out", load_time_series=True)

# Export full time series to dict of DataFrames
full_data = output.to_dataframe()

# Metadata for this simulation
print(f"Version: {full_data['metadata']['version'].iloc[0]}")
print(f"Flow units: {full_data['metadata']['flow_unit'].iloc[0]}")
print(f"Total periods: {full_data['metadata']['n_periods'].iloc[0]}")

# Access each section - MultiIndex DataFrames (timestamp, element_name)
nodes_df = full_data['nodes']        # All nodes, all timesteps
links_df = full_data['links']        # All links, all timesteps
subcatchments_df = full_data['subcatchments']  # All subcatchments, all timesteps

print(f"\nNodes: {nodes_df.shape[0]} rows, {nodes_df.shape[1]} columns")
print(f"Links: {links_df.shape[0]} rows, {links_df.shape[1]} columns")
print(f"Subcatchments: {subcatchments_df.shape[0]} rows, {subcatchments_df.shape[1]} columns")

# Find peak inflow across all timesteps (if available)
if 'value_0' in links_df.columns:
    peak_by_link = links_df.groupby(level='element_name')['value_0'].max()
    print(f"\nPeak flows by link:")
    print(peak_by_link.nlargest(5))

Scenario 9: Output File - Export Specific Section

from swmm_utils import SwmmOutput

output = SwmmOutput("simulation.out", load_time_series=True)

# Export only subcatchments section (no metadata)
subcatchments_df = output.to_dataframe('subcatchments')

print(f"Subcatchment Time Series Data:")
print(f"Shape: {subcatchments_df.shape} (timesteps × elements × properties)")
print(f"Index levels: {subcatchments_df.index.names}")

# Filter: all timesteps for a specific subcatchment
subcatch_name = subcatchments_df.index.get_level_values('element_name')[0]
single_subcatch = output.to_dataframe('subcatchments', subcatch_name)

print(f"\nSingle Subcatchment ({subcatch_name}):")
print(f"Time period: {single_subcatch.index.min()} to {single_subcatch.index.max()}")
print(f"Properties measured: {single_subcatch.shape[1]} values")
print(single_subcatch.head())

# Export to CSV for external analysis
single_subcatch.to_csv(f"subcatchment_{subcatch_name}.csv")

Scenario 10: Output File - Single Element Time Series

from swmm_utils import SwmmOutput
import matplotlib.pyplot as plt

output = SwmmOutput("simulation.out", load_time_series=True)

# Get time series for a specific link
link_name = "Conduit1"
link_df = output.to_dataframe('links', link_name)

print(f"Conduit {link_name} time series:")
print(f"Simulation period: {link_df.index.min()} to {link_df.index.max()}")
print(f"Number of timesteps: {len(link_df)}")
print("\nFirst 5 timesteps:")
print(link_df.head())

# Analyze the flow data
if 'value_0' in link_df.columns:
    flow = link_df['value_0']
    print(f"\nFlow Statistics:")
    print(f"  Peak: {flow.max():.2f}")
    print(f"  Mean: {flow.mean():.2f}")
    print(f"  Min: {flow.min():.2f}")
    
    # Plot time series if matplotlib available
    try:
        flow.plot(figsize=(12, 5))
        plt.xlabel('Time')
        plt.ylabel('Flow (CFS)')
        plt.title(f'Conduit {link_name} Flow Time Series')
        plt.tight_layout()
        plt.savefig(f'{link_name}_flow_timeseries.png')
    except ImportError:
        pass

Scenario 11: Batch Processing

from pathlib import Path
from swmm_utils import SwmmInputDecoder, SwmmInputEncoder

decoder = SwmmInputDecoder()
encoder = SwmmInputEncoder()

# Convert all .inp files in a directory to JSON
for inp_file in Path("models/").glob("*.inp"):
    model = decoder.decode_file(str(inp_file))
    json_file = inp_file.with_suffix('.json')
    encoder.encode_to_json(model, str(json_file), pretty=True)
    print(f"Converted {inp_file.name}{json_file.name}")

Scenario 12: LID Performance Analysis

from swmm_utils import SwmmReport

with SwmmReport("lid_scenario.rpt") as report:
    # Analyze LID performance
    if report.lid_performance:
        # Group by subcatchment
        from collections import defaultdict
        by_subcatchment = defaultdict(list)
        
        for lid in report.lid_performance:
            by_subcatchment[lid['subcatchment']].append(lid)
        
        # Calculate total infiltration per subcatchment
        for sub, lids in by_subcatchment.items():
            total_infil = sum(lid['infil_loss'] for lid in lids)
            total_inflow = sum(lid['total_inflow'] for lid in lids)
            reduction = (total_infil / total_inflow * 100) if total_inflow > 0 else 0
            
            print(f"{sub}: {reduction:.1f}% runoff reduction via infiltration")

Scenario 13: Round-Trip Conversion

from swmm_utils import SwmmInputDecoder, SwmmInputEncoder

decoder = SwmmInputDecoder()
encoder = SwmmInputEncoder()

# Decode from .inp
model = decoder.decode_file("original.inp")

# Encode to JSON
encoder.encode_to_json(model, "model.json", pretty=True)

# Decode from JSON
json_model = decoder.decode_json("model.json")

# Encode to Parquet (single file)
encoder.encode_to_parquet(json_model, "model.parquet", single_file=True)

# Decode from Parquet
parquet_model = decoder.decode_parquet("model.parquet")

# Encode back to .inp
encoder.encode_to_inp_file(parquet_model, "final.inp")

# All data preserved throughout the round-trip!

Testing

# Run all tests
pytest -q

# Run with coverage
pytest --cov=swmm_utils --cov-report=html

# Run specific test file
pytest tests/test_rpt.py -v

All 40 tests pass, including comprehensive format conversion, round-trip tests, and report parsing.

Running Scenarios

Before running these scenarios, make sure you have the built SWMM binary executable runswmm in the /bin directory.

# Scenario 1: Basic input file operations
python examples/example1/example1.py

# Scenario 5: Report parsing with water quality
python examples/example2/example2.py

Project Structure

swmm-utils/
├── src/
│   └── swmm_utils/              # Main package
│       ├── __init__.py          # Package exports
│       ├── inp.py               # High-level input file interface
│       ├── inp_decoder.py       # Decode .inp/JSON/Parquet → dict
│       ├── inp_encoder.py       # Encode dict → .inp/JSON/Parquet
│       ├── rpt.py               # High-level report file interface
│       └── rpt_decoder.py       # Decode .rpt → dict
├── examples/
│   ├── example1/                # Basic input file example
│   └── example2/                # Report parsing example
├── tests/
│   ├── test_inp.py              # Input file interface tests
│   ├── test_inp_decoder_encoder.py  # Core parsing tests
│   ├── test_inp_formats.py      # Format conversion tests
│   └── test_rpt.py              # Report parser tests
├── data/                        # Location for sample SWMM files
├── bin/                         # Location for swmm binary executable
├── docs/
│   └── SWMM_INPUT_FILE.md       # Complete SWMM input file reference
├── setup.py                     # Package configuration
├── requirements.txt             # Core dependencies
├── requirements-dev.txt         # Development dependencies
└── README.md                    # Project information

Performance

Input Files

Tested on various SWMM models:

  • Decode .inp: ~0.05 seconds (240 junctions)
  • Encode to JSON: 873 KB (240 junctions)
  • Encode to Parquet (multi-file): 18 files, ~110 KB total
  • Encode to Parquet (single-file): 1 file, ~109 KB
  • Round-trip (.inp → JSON → Parquet → .inp): All data preserved

Report Files

Tested on diverse simulation results:

  • Parse .rpt: ~0.02 seconds (small models) to ~0.5 seconds (large models)
  • Large model support: Successfully parsed 809 KB report with 2,227 nodes
  • Memory efficient: Processes reports on-demand without loading entire file

Documentation

Dependencies

Required

  • Python 3.8+
  • pandas >= 1.0.0 (for Parquet support)
  • pyarrow >= 10.0.0 (for Parquet support)

Development

  • pytest >= 7.0.0
  • pytest-cov >= 4.0.0

Known Limitations

Input Files

  1. Round-trip Formatting: Some cosmetic differences

    • Comments may not be preserved in exact original positions
    • Whitespace normalized to SWMM standard format
    • All data and structure fully preserved
  2. Complex Sections: Some sections have simplified handling

    • [CONTROLS] - Stored as text (complex rule syntax)
    • [TRANSECTS] - Multi-line format preserved

Report Files

  1. Read-only: Report files are parsed for reading only (no modification/encoding)
  2. Section Availability: Not all sections appear in every report (depends on simulation settings)
  3. Format Variations: Minor format differences across SWMM versions handled gracefully

Changelog

Latest Changes (Feb 2026)

Output File Parser Bug Fixes:

Fixed 4 critical bugs in the .out file decoder (out_decoder.py) that caused incorrect parsing of SWMM binary output files:

  1. Variable counts not reading codes - The parser read the variable count but didn't skip the variable codes that follow each count. This caused all subsequent reads to be offset.

    # Before (wrong): read count, then immediately read next count
    # After (fixed): read count, then read N codes, then read next count
    
  2. Start date format - Was reading as 5 integers (year, month, day, hour, minute), but SWMM stores it as a double (Excel serial date format, 8 bytes).

    # Before: self._read_datetime(f)  # Expected 5 ints
    # After: self._read_double(f)  # Excel serial date (days since 1899-12-30)
    
  3. Record size missing timestamp - Each time series record starts with an 8-byte timestamp that wasn't included in the record size calculation.

    # Before: record_size = (n_subcatch * n_subcatch_vars + ...) * 4
    # After: record_size = 8 + (n_subcatch * n_subcatch_vars + ...) * 4
    
  4. Time series read position - Used incorrect file position after metadata parsing instead of the correct position from the file footer.

Impact: These fixes enable correct parsing of all time series data from .out files, including:

  • 6 node variables (depth, head, volume, lateral_inflow, total_inflow, flooding)
  • 5 link variables (flow, depth, velocity, volume, capacity)
  • 15 system variables
  • Correct timestamps

Added: Variable codes are now exposed in the metadata (variable_codes field) for reference.

License

MIT LICENSE

Contact

For questions or issues, please open a GitHub issue.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

swmm_utils-1.1.0.tar.gz (100.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swmm_utils-1.1.0-py3-none-any.whl (73.1 kB view details)

Uploaded Python 3

File details

Details for the file swmm_utils-1.1.0.tar.gz.

File metadata

  • Download URL: swmm_utils-1.1.0.tar.gz
  • Upload date:
  • Size: 100.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for swmm_utils-1.1.0.tar.gz
Algorithm Hash digest
SHA256 d55bdb37ebf950d05398129e719f947f6875728c1b092a6582350864e3a7a6d5
MD5 00452353e3423374f0e39c425ccf4c51
BLAKE2b-256 96d2554e6e05a4457c73d0bc32d726e18736c4188e9d6f52703cfda759b410ae

See more details on using hashes here.

File details

Details for the file swmm_utils-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: swmm_utils-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 73.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for swmm_utils-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 072c4a0d4a59c470e64aa6f1c4dd86728da136bd9756131613b3b268c191c385
MD5 7438250c627ea9b5617f59369babcb1a
BLAKE2b-256 ffa11a30e8887a61db2c9a1d0f3ea3e0ff03f339c8ff355fece4b26868d55ce6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page