PyOpenMagnetics - Python Wrapper for OpenMagnetics
🤖 For AI Assistants: If you're an AI assistant helping with this library, please read AGENTS.md FIRST before continuing with this README. It contains essential information about:
- Correct import procedures (this package requires special handling)
- ⚠️ Using API methods instead of manual calculations (CRITICAL)
- Required field specifications for converter topologies
- Troubleshooting common API errors
- Complete working examples
⚠️ AVOID MANUAL MODE: Always use
process_flyback(),calculate_advised_magnetics(), and other API methods rather than doing manual calculations. The MKF engine handles complex magnetic effects that manual calculations cannot.
PyOpenMagnetics is a Python wrapper for MKF (Magnetics Knowledge Foundation), the simulation engine of OpenMagnetics, providing a comprehensive toolkit for designing and analyzing magnetic components such as transformers and inductors.
Features
- 🧲 Core Database: Access to extensive database of core shapes, materials, and manufacturers
- 🔌 Winding Design: Automatic winding calculations with support for various wire types (round, litz, rectangular, planar)
- 📊 Loss Calculations: Core losses (Steinmetz), winding losses (DC, skin effect, proximity effect)
- 🎯 Design Adviser: Automated recommendations for optimal magnetic designs
- 📈 Signal Processing: Harmonic analysis, waveform processing
- 🖼️ Visualization: SVG plotting of cores, windings, magnetic fields
- 🔧 SPICE Export: Export magnetic components as SPICE subcircuits
Installation
From PyPI (recommended)
pip install PyOpenMagnetics
From Source
git clone https://github.com/OpenMagnetics/PyOpenMagnetics.git
cd PyOpenMagnetics
pip install .
Build provenance
The build compiles MKF by globbing its .cpp files directly into the
extension, tracking MKF/MAS main, and builds the Kirchhoff converter-model
library (libKirchhoffApi.so) as an ExternalProject. The exact engine commits a
wheel was compiled from are baked into the package:
import PyOpenMagnetics
print(PyOpenMagnetics.__mkf_commit__) # MKF SHA this wheel was built from
print(PyOpenMagnetics.__mas_commit__) # MAS SHA this wheel was built from
A clean rebuild:
rm -rf build && pip install . --no-deps -v
Importing and error handling
import PyOpenMagnetics works like any other package. Since v1.7.0 every engine
failure raises PyOpenMagnetics.EngineError (a RuntimeError subclass) —
functions never return error strings or {"data": "<error>"} objects:
import PyOpenMagnetics
PyOpenMagnetics.load_databases({})
print(f"✓ Loaded {len(PyOpenMagnetics.get_core_materials())} materials")
print(f"✓ Loaded {len(PyOpenMagnetics.get_core_shapes())} shapes")
try:
PyOpenMagnetics.find_core_shape_by_name("No Such Shape")
except PyOpenMagnetics.EngineError as e:
print(f"Engine error: {e}")
The only exception is the plotting family, which returns a discriminated union
{"success": bool, "error": str, ...} that callers branch on.
See AGENTS.md for more usage guidance.
Quick Start
Basic Example: Creating a Core
import PyOpenMagnetics
# Find a core shape by name
shape = PyOpenMagnetics.find_core_shape_by_name("E 42/21/15")
# Find a core material by name
material = PyOpenMagnetics.find_core_material_by_name("3C95")
# Create a core with gapping. "type" is mandatory; shape/material accept
# either the objects fetched above or plain name strings.
core_data = {
"functionalDescription": {
"type": "two-piece set",
"shape": shape,
"material": material,
"gapping": [{"type": "subtractive", "length": 0.001}], # 1mm gap
"numberStacks": 1
}
}
# Calculate complete core data
core = PyOpenMagnetics.calculate_core_data(core_data, False)
print(f"Effective area: {core['processedDescription']['effectiveParameters']['effectiveArea']} m²")
Design Adviser: Get Magnetic Recommendations
import PyOpenMagnetics
# Define design requirements
inputs = {
"designRequirements": {
"magnetizingInductance": {
"minimum": 100e-6, # 100 µH minimum
"nominal": 110e-6 # 110 µH nominal
},
"turnsRatios": [{"nominal": 5.0}] # 5:1 turns ratio
},
"operatingPoints": [
{
"name": "Nominal",
"conditions": {"ambientTemperature": 25},
"excitationsPerWinding": [
{
"name": "Primary",
"frequency": 100000, # 100 kHz
"current": {
"waveform": {
"data": [0, 1.0, 0],
"time": [0, 5e-6, 10e-6]
}
},
"voltage": {
"waveform": {
"data": [50, 50, -50, -50],
"time": [0, 5e-6, 5e-6, 10e-6]
}
}
}
]
}
]
}
# Process inputs (adds harmonics and validation)
processed_inputs = PyOpenMagnetics.process_inputs(inputs)
# Get magnetic recommendations
# core_mode: "available cores" (stock cores) or "standard cores" (all standard shapes)
result = PyOpenMagnetics.calculate_advised_magnetics(processed_inputs, 5, "standard cores")
# Result format: {"data": [{"mas": {...}, "scoring": float, "scoringPerFilter": {...}}, ...]}
for i, item in enumerate(result["data"]):
mag = item["mas"]["magnetic"]
core = mag["core"]["functionalDescription"]
print(f"{i+1}. {core['shape']['name']} - {core['material']['name']} (score: {item['scoring']:.3f})")
Calculate Core Losses
import PyOpenMagnetics
# A complete core (see "Creating a Core" above)
core = PyOpenMagnetics.calculate_core_data({
"functionalDescription": {
"type": "two-piece set",
"shape": "E 42/21/15",
"material": "3C95",
"gapping": [{"type": "subtractive", "length": 0.0005}],
"numberStacks": 1
}
}, True)
# A wound coil on that core
bobbin = PyOpenMagnetics.create_basic_bobbin(core, True)
coil = PyOpenMagnetics.wind({
"bobbin": bobbin,
"functionalDescription": [{
"name": "Primary",
"numberTurns": 20,
"numberParallels": 1,
"isolationSide": "primary",
"wire": "Round 0.5 - Grade 1"
}]
}, 1, [1.0], [0], [])
# Inputs with the excitation waveforms (see the Design Adviser example)
inputs = PyOpenMagnetics.process_inputs({
"designRequirements": {
"magnetizingInductance": {"nominal": 100e-6},
"turnsRatios": []
},
"operatingPoints": [{
"name": "Nominal",
"conditions": {"ambientTemperature": 25},
"excitationsPerWinding": [{
"name": "Primary",
"frequency": 100000,
"current": {"waveform": {"data": [-1, 1, -1], "time": [0, 5e-6, 10e-6]}},
"voltage": {"waveform": {"data": [50, 50, -50, -50], "time": [0, 5e-6, 5e-6, 10e-6]}}
}]
}]
})
models = {"coreLosses": "IGSE", "reluctance": "ZHANG"}
losses = PyOpenMagnetics.calculate_core_losses(core, coil, inputs, models)
print(f"Core losses: {losses['coreLosses']} W")
Winding a Coil
import PyOpenMagnetics
# core from calculate_core_data(...) as above
bobbin = PyOpenMagnetics.create_basic_bobbin(core, True)
coil_spec = {
"bobbin": bobbin,
"functionalDescription": [
{
"name": "Primary",
"numberTurns": 50,
"numberParallels": 1,
"isolationSide": "primary",
"wire": "Round 0.5 - Grade 1"
},
{
"name": "Secondary",
"numberTurns": 10,
"numberParallels": 3,
"isolationSide": "secondary",
"wire": "Round 1.00 - Grade 1"
}
]
}
# wind(coil, repetitions, proportion_per_winding, pattern, margin_pairs)
coil = PyOpenMagnetics.wind(coil_spec, 1, [0.5, 0.5], [0, 1], [])
print(f"Wound {len(coil['turnsDescription'])} turns")
Converter-Based Design
The converter surface builds complete MAS Inputs straight from converter
specifications (the Kirchhoff topology designer sizes inductance, turns ratios
and waveforms). See examples/converter_design_example.py for the full flow:
import PyOpenMagnetics
flyback_specs = {
"inputVoltage": {"minimum": 185, "maximum": 265},
"desiredInductance": 800e-6, # optional pin; omit to let Kirchhoff size it
"desiredTurnsRatios": [13.5], # optional pin
"efficiency": 0.88,
"operatingPoints": [{
"outputVoltages": [12.0],
"outputCurrents": [2.0],
"switchingFrequency": 100000,
"ambientTemperature": 40
}]
}
inputs = PyOpenMagnetics.process_converter("flyback", flyback_specs)
processed = PyOpenMagnetics.process_inputs(inputs)
result = PyOpenMagnetics.calculate_advised_magnetics(processed, 5, "standard cores")
for item in result["data"]:
print(item["mas"]["magnetic"]["manufacturerInfo"]["reference"], item["scoring"])
A TAS-shaped spec (an object with designRequirements / operatingPoints[].outputs)
is also accepted and passed to Kirchhoff untouched.
API Reference
Database Access
| Function | Description |
|---|---|
get_core_materials() |
Get all available core materials |
get_core_shapes() |
Get all available core shapes |
get_wires() |
Get all available wires |
get_bobbins() |
Get all available bobbins |
find_core_material_by_name(name) |
Find core material by name |
find_core_shape_by_name(name) |
Find core shape by name |
find_wire_by_name(name) |
Find wire by name |
Core Calculations
| Function | Description |
|---|---|
calculate_core_data(core, process) |
Calculate complete core data |
calculate_core_gapping(core, gapping) |
Calculate gapping configuration |
calculate_inductance_from_number_turns_and_gapping(...) |
Calculate inductance |
calculate_core_losses(core, coil, inputs, models) |
Calculate core losses |
Winding Functions
| Function | Description |
|---|---|
wind(coil, repetitions, proportions, pattern, margins) |
Wind coils on a core |
calculate_winding_losses(...) |
Calculate total winding losses |
calculate_ohmic_losses(...) |
Calculate DC losses |
calculate_skin_effect_losses(...) |
Calculate skin effect losses |
calculate_proximity_effect_losses(...) |
Calculate proximity effect losses |
Design Adviser
| Function | Description |
|---|---|
calculate_advised_cores(inputs, max_results) |
Get recommended cores |
calculate_advised_magnetics(inputs, max, mode) |
Get complete designs |
process_inputs(inputs) |
Process and validate inputs |
Visualization
| Function | Description |
|---|---|
plot_core(core, ...) |
Generate SVG of core |
plot_sections(magnetic, ...) |
Plot winding sections |
plot_layers(magnetic, ...) |
Plot winding layers |
plot_turns(magnetic, ...) |
Plot individual turns |
plot_field(magnetic, ...) |
Plot magnetic field |
Settings
| Function | Description |
|---|---|
get_settings() |
Get current settings |
set_settings(settings) |
Configure settings |
reset_settings() |
Reset to defaults |
SPICE Export
| Function | Description |
|---|---|
export_magnetic_as_subcircuit(magnetic, ...) |
Export as SPICE model |
Converter Topologies
All 24 power topologies are exposed with a uniform API. Use the generic
process_converter("<topology>", converter, use_ngspice) (also accepts
"advanced_<topology>"), or the per-topology functions below. The converter
spec is either the legacy flat shape shown in "Converter-Based Design" above
(inputVoltage, optional desiredInductance/desiredTurnsRatios/efficiency/
currentRippleRatio, and operatingPoints[] with outputVoltages[]/
outputCurrents[]/switchingFrequency/ambientTemperature) or a TAS-shaped
spec, which is passed through untouched. Failures raise
PyOpenMagnetics.EngineError.
| Function family | Description |
|---|---|
process_converter(name, json, use_ngspice=True) |
Universal dispatch for every topology |
design_magnetics_from_converter(name, json, max_results, core_mode, ...) |
Converter → advised magnetic designs (single call) |
calculate_<t>_inputs(json) |
Build MAS inputs (basic mode) for topology <t> |
calculate_advanced_<t>_inputs(json) |
Build MAS inputs (advanced mode) |
simulate_<t>_ideal_waveforms(json) |
ngspice ideal-waveform simulation |
generate_<t>_ngspice_circuit(json, input_voltage_index=0, operating_point_index=0) |
Generate ngspice netlist |
<t> ∈ flyback, buck, boost, single_switch_forward, two_switch_forward, active_clamp_forward, push_pull, isolated_buck, isolated_buck_boost, cuk, sepic, zeta, four_switch_buck_boost, weinberg, llc, cllc, clllc, src, dab, psfb, pshb, ahb, vienna. PFC is basic-only (calculate_pfc_inputs,
generate_pfc_ngspice_circuit(json, dc_resistance=0.1, simulation_time=0.02, time_step=1e-8)); common-/differential-mode chokes use the cmc / dmc
families. See AGENTS.md §11 for the full per-topology parity matrix.
Core Materials
PyOpenMagnetics includes materials from major manufacturers:
- TDK/EPCOS: N27, N49, N87, N95, N97, etc.
- Ferroxcube: 3C90, 3C94, 3C95, 3F3, 3F4, etc.
- Fair-Rite: Various ferrite materials
- Magnetics Inc.: Powder cores (MPP, High Flux, Kool Mu)
- Micrometals: Iron powder cores
Core Shapes
Supported shape families include:
- E cores: E, EI, EFD, EQ, ER
- ETD/EC cores: ETD, EC
- PQ/PM cores: PQ, PM
- RM cores: RM, RM/ILP
- Toroidal: Various sizes
- Pot cores: P, PT
- U/UI cores: U, UI, UR
- Planar: E-LP, EQ-LP, etc.
Wire Types
- Round enamelled wire: Various AWG and IEC sizes
- Litz wire: Multiple strand configurations
- Rectangular wire: For high-current applications
- Foil: For planar magnetics
- Planar PCB: For integrated designs
Configuration
Use set_settings() to configure:
settings = PyOpenMagnetics.get_settings()
settings["coilAllowMarginTape"] = True
settings["coilWindEvenIfNotFit"] = False
settings["painterNumberPointsX"] = 50
PyOpenMagnetics.set_settings(settings)
Contributing
Contributions are welcome! Please see the OpenMagnetics organization for contribution guidelines.
Documentation
Quick Start
- llms.txt - Comprehensive API reference optimized for AI assistants and quick lookup
- examples/ - Practical example scripts for common design workflows
- PyOpenMagnetics.pyi - Type stubs for IDE autocompletion
Tutorials
- notebooks/ - Interactive Jupyter notebook tutorials with visualizations
- Getting Started - Introduction to PyOpenMagnetics
- Buck Inductor - Complete inductor design workflow
- Core Losses - In-depth core loss analysis
Reference
- docs/errors.md - Common errors and solutions
- docs/performance.md - Performance optimization guide
- docs/compatibility.md - Python/platform version compatibility
Validation
- api/validation.py - Runtime JSON schema validation for inputs
License
This project is licensed under the MIT License - see the LICENSE file for details.
Related Projects
- MKF - C++ magnetics library
- MAS - Magnetic Agnostic Structure schema
- OpenMagnetics Web - Online design tool
References
- Maniktala, S. "Switching Power Supplies A-Z", 2nd Edition
- Basso, C. "Switch-Mode Power Supplies", 2nd Edition
- McLyman, C. "Transformer and Inductor Design Handbook"
Support
For questions and support:
- 🐛 GitHub Issues
- 💬 Discussions
- 📧 Contact the maintainers
Release files for PyOpenMagnetics 1.7.21
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyopenmagnetics-1.7.21.tar.gz | 709.3 kB | Details |
Built distributions (wheels)
Total release size: 138.2 MB
Release files / pyopenmagnetics-1.7.21.tar.gz
| Download URL | pyopenmagnetics-1.7.21.tar.gz |
|---|---|
| Size | 709.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
892d91f78b29ef788aed0e470dd17a6d4fffdd97c8c44cff9b75a310faf8da5c
|
|
BLAKE2b-256 checksum How to use checksums |
4b064528d581b345b0f828742a21830a00de397f2947dd8dc3883a8a98b42961
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyopenmagnetics-1.7.21-cp314-cp314-win_amd64.whl
| Download URL | pyopenmagnetics-1.7.21-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 12.4 MB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
fe61232a0a282c0d42798ee2096a4833020ac49b76b0d99eef8c833d08d18072
|
|
BLAKE2b-256 checksum How to use checksums |
0082fdceed6b807f32a6f28637b5bd253992c5e8fc12176d1186573d6bdd8767
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / pyopenmagnetics-1.7.21-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyopenmagnetics-1.7.21-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 15.2 MB |
| Tags | CPython 3.14 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
dac00823ec86ba5813b38a94dad1eca26b7db8db64ad05eba688f57bc7a5bd06
|
|
BLAKE2b-256 checksum How to use checksums |
f425a1c7b4a43d74a471ca67bdacfd84c8c3fd738444cd6d01d4fff10b683d5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyopenmagnetics-1.7.21-cp313-cp313-win_amd64.whl
| Download URL | pyopenmagnetics-1.7.21-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
281fb091c4f82bd28265411dc16078ea06c9a0b7261633e50390fd01cf5f76e5
|
|
BLAKE2b-256 checksum How to use checksums |
aab44fdf883db57e8ec4c1b2bed4b6edd1f1a8d0402197857ff2f3def0c8454f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / pyopenmagnetics-1.7.21-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyopenmagnetics-1.7.21-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 15.2 MB |
| Tags | CPython 3.13 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
620c40a6e0d964b58dbff3fbaede93f0d59b28d9a9dd03dd211a3427e21950eb
|
|
BLAKE2b-256 checksum How to use checksums |
261913a2870a283485849cf66f7a31918b348c707f27d8a93692c1cf501733ba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyopenmagnetics-1.7.21-cp312-cp312-win_amd64.whl
| Download URL | pyopenmagnetics-1.7.21-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
db8dfb8044faf8204acaa073a72386b7a283338f02e0a166edbaa7eb213d7049
|
|
BLAKE2b-256 checksum How to use checksums |
f21fb6e09c80694bc517e69f2e4f6ed644d87f6c2893f1f33e9947bd4afa3667
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / pyopenmagnetics-1.7.21-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyopenmagnetics-1.7.21-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 15.2 MB |
| Tags | CPython 3.12 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
17fe6de9ae2ecd6f4342f6170e9135633f69f3c8a3c0f906a44b7c2222990bd1
|
|
BLAKE2b-256 checksum How to use checksums |
9d4a2c857b5638fdeed00528697348cdada9bb6dc683b02fc7147a664e1cfcd4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyopenmagnetics-1.7.21-cp311-cp311-win_amd64.whl
| Download URL | pyopenmagnetics-1.7.21-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
368c46e8c8191b6729d3522ba332509816e6b37393d3115d076a2fc5231792af
|
|
BLAKE2b-256 checksum How to use checksums |
3c6e64896652f75793b752bf68e58d92730ae525c40d69c6445f0f94893d926d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / pyopenmagnetics-1.7.21-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyopenmagnetics-1.7.21-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 15.2 MB |
| Tags | CPython 3.11 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
c5ab89fd38ea1f68e0ceadcb10540121391319f46d696e15ed42183448d46422
|
|
BLAKE2b-256 checksum How to use checksums |
ea89d6b5c5d0134435a50d484ea3fafd565c3701e3ae9e91bfa181a8fc0acd2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / pyopenmagnetics-1.7.21-cp310-cp310-win_amd64.whl
| Download URL | pyopenmagnetics-1.7.21-cp310-cp310-win_amd64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.10 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
92307b7b956b17b88ccc0ba6665c797777d95af3f462304c5b216286d62c478c
|
|
BLAKE2b-256 checksum How to use checksums |
fc805917db66ac45b225c6ad5104954cb4ac30f7e3e74090aacf4f991a601e00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / pyopenmagnetics-1.7.21-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyopenmagnetics-1.7.21-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 15.2 MB |
| Tags | CPython 3.10 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
10aa51ca6581b438eeb9dffea1be5040d0139136df9f8f7a43c5ece4a93edaac
|
|
BLAKE2b-256 checksum How to use checksums |
f894d58dfaf7e41fb663d6e8528aae79f7dbaad2a7bafc06a1f5b7b65023fb0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|