Skip to content

Commit 2bb799b

Browse files
authored
Add files via upload
1 parent e331900 commit 2bb799b

1 file changed

Lines changed: 139 additions & 27 deletions

File tree

src/rheelDM/main.py

Lines changed: 139 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
from pathlib import Path
44
from datetime import datetime, date, time
55
from typing import Any, get_origin, get_args, Union
6-
import ast, types, copy
6+
import ast, types, copy, json, configparser
7+
try: import tomllib # type: ignore
8+
except ModuleNotFoundError: tomllib = None
9+
try: import toml # type: ignore
10+
except ModuleNotFoundError: toml = None
11+
try: import yaml # type: ignore
12+
except ModuleNotFoundError: yaml = None
713

814

915
# =========================================================
@@ -53,29 +59,29 @@ def deserialize(self, value_str: str, typ: type):
5359
registry.register(
5460
"datetime",
5561
datetime,
56-
lambda v: f'"{v.isoformat()}"',
57-
lambda v: datetime.fromisoformat(v.strip('"'))
62+
lambda v: f'{v.isoformat()}',
63+
lambda v: datetime.fromisoformat(v)
5864
)
5965

6066
registry.register(
6167
"date",
6268
date,
63-
lambda v: f'"{v.isoformat()}"',
64-
lambda v: date.fromisoformat(v.strip('"'))
69+
lambda v: f'{v.isoformat()}',
70+
lambda v: date.fromisoformat(v)
6571
)
6672

6773
registry.register(
6874
"time",
6975
time,
70-
lambda v: f'"{v.isoformat()}"',
71-
lambda v: time.fromisoformat(v.strip('"'))
76+
lambda v: f'{v.isoformat()}',
77+
lambda v: time.fromisoformat(v)
7278
)
7379

7480
registry.register(
7581
"Path",
7682
Path,
77-
lambda v: f'"{str(v)}"',
78-
lambda v: Path(v.strip('"'))
83+
lambda v: f'{str(v)}',
84+
lambda v: Path(v)
7985
)
8086

8187

@@ -441,21 +447,127 @@ def load(cls, filename: str | Path, default: dict | Obj | bool | None = None) ->
441447

442448
return obj
443449

444-
@classmethod
445-
def from_dict(cls, data: dict) -> Obj:
446-
"""
447-
Create Obj from a nested dictionary.
448-
449-
Format:
450-
{
451-
"section": {
452-
"key": (type, value)
453-
}
454-
}
455-
"""
456-
obj = cls()
457-
for section_name, items in data.items():
458-
section = obj.section(section_name)
459-
for key, (typ, value) in items.items():
460-
section.set(key, typ, value)
461-
return obj
450+
@classmethod
451+
def from_dict(cls, data: dict, default_section = "not sectioned") -> Obj:
452+
"""
453+
Create Obj from a dictionary.
454+
455+
Rules:
456+
- Top-level dict values become sections
457+
- Top-level non-dict values go into `default_section`
458+
- Values may be:
459+
(type, value) tuples
460+
or plain values (type inferred)
461+
"""
462+
obj = cls()
463+
464+
if not isinstance(data, dict):
465+
raise TypeError("Input data must be a dictionary")
466+
467+
for key, value in data.items():
468+
469+
# -------- Case 1: Proper section --------
470+
if isinstance(value, dict):
471+
section = obj.section(key)
472+
473+
for subkey, entry in value.items():
474+
475+
# (type, value)
476+
if (
477+
isinstance(entry, tuple)
478+
and len(entry) == 2
479+
and isinstance(entry[0], type)
480+
):
481+
typ, val = entry
482+
else:
483+
typ = type(entry)
484+
val = entry
485+
486+
section.set(subkey, typ, val)
487+
488+
# -------- Case 2: Top-level value --------
489+
else:
490+
section = obj.section(default_section)
491+
492+
if (
493+
isinstance(value, tuple)
494+
and len(value) == 2
495+
and isinstance(value[0], type)
496+
):
497+
typ, val = value
498+
else:
499+
typ = type(value)
500+
val = value
501+
502+
section.set(key, typ, val)
503+
504+
return obj
505+
506+
@classmethod
507+
def convert_file(cls, filename: str | Path, default_section = "not sectioned", overwrite = False) -> Obj:
508+
"""
509+
Convert JSON, TOML, YAML, or INI into .rdm format.
510+
511+
If overwrite=True, deletes original file after conversion.
512+
"""
513+
514+
path = Path(filename)
515+
516+
if not path.exists():
517+
raise FileNotFoundError(path)
518+
519+
suffix = path.suffix.lower()
520+
521+
# -------- Load Data --------
522+
523+
if suffix == ".json":
524+
with open(path, "r", encoding="utf-8") as f:
525+
data = json.load(f)
526+
527+
elif suffix == ".toml":
528+
if tomllib:
529+
with open(path, "rb") as f:
530+
data = tomllib.load(f)
531+
elif toml:
532+
with open(path, "r", encoding="utf-8") as f:
533+
data = toml.load(f)
534+
else:
535+
raise RuntimeError("TOML requires Python 3.11+ or 'toml' package.")
536+
537+
elif suffix in (".yaml", ".yml"):
538+
if not yaml:
539+
raise RuntimeError("YAML support requires PyYAML.")
540+
with open(path, "r", encoding="utf-8") as f:
541+
data = yaml.safe_load(f)
542+
543+
elif suffix == ".ini":
544+
parser = configparser.ConfigParser()
545+
parser.read(path)
546+
547+
data = {}
548+
549+
# Sections
550+
for section in parser.sections():
551+
data[section] = dict(parser[section])
552+
553+
# Handle DEFAULT section
554+
if parser.defaults():
555+
data["default"] = dict(parser.defaults())
556+
557+
else:
558+
raise ValueError(f"Unsupported file type: {suffix}")
559+
560+
if not isinstance(data, dict):
561+
raise TypeError("Top-level structure must be a dictionary")
562+
563+
# -------- Convert --------
564+
565+
obj = cls.from_dict(data, default_section)
566+
567+
new_path = path.with_suffix(".rdm")
568+
obj.save(new_path)
569+
570+
if overwrite:
571+
path.unlink()
572+
573+
return obj

0 commit comments

Comments
 (0)