Skip to content

Commit bddf7e0

Browse files
committed
feat: add YAML and TypeSpec support
- Added PyYAML dependency (pyyaml>=6.0,<7) - Extended file reader to support .yaml and .yml extensions - Implemented format-aware file loading (YAML vs JSON) - Updated all class references in files_reader.py to use Gts* names - Added comprehensive format support documentation - Documented TypeSpec pre-compilation workflow YAML files are automatically detected and parsed. TypeSpec requires external compilation to JSON Schema before use.
1 parent 00cdc48 commit bddf7e0

3 files changed

Lines changed: 70 additions & 23 deletions

File tree

gts/README.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,48 @@
11
# GTS Python Library
22

3-
A minimal, idiomatic Python library for working with **GTS** ([Global Type System](https://github.com/gts-spec/gts-spec)) identifiers and JSON/JSON Schema artifacts.
3+
A minimal, idiomatic Python library for working with **GTS** ([Global Type System](https://github.com/gts-spec/gts-spec)) identifiers and type definitions.
4+
5+
## File Format Support
6+
7+
GTS Python supports multiple file formats for schemas and instances:
8+
9+
### JSON (Native)
10+
Standard JSON format with `.json`, `.jsonc`, and `.gts` extensions.
11+
12+
### YAML
13+
Full YAML support with `.yaml` and `.yml` extensions. YAML files are automatically parsed and treated identically to JSON.
14+
15+
```python
16+
from gts import GtsFileReader
17+
18+
# Reads both JSON and YAML files
19+
reader = GtsFileReader("path/to/schemas/")
20+
for entity in reader:
21+
print(f"{entity.gts_id.id}: {entity.file.name}")
22+
```
23+
24+
### TypeSpec
25+
TypeSpec (`.tsp`) schemas must be pre-compiled to JSON Schema before use with gts-python.
26+
27+
**Setup:**
28+
```bash
29+
# Install TypeSpec compiler
30+
npm install -g @typespec/compiler @typespec/json-schema
31+
32+
# Compile TypeSpec to JSON Schema
33+
tsp compile --emit @typespec/json-schema your-schemas/
34+
```
35+
36+
**Usage:**
37+
```python
38+
from gts import GtsFileReader
39+
40+
# Point to the generated JSON Schema output directory
41+
reader = GtsFileReader("tsp-output/@typespec/json-schema/")
42+
entities = list(reader)
43+
```
44+
45+
See [gts-spec TypeSpec examples](https://github.com/globaltypesystem/gts-spec/tree/main/examples/typespec) for sample TypeSpec definitions.
446

547
## Featureset
648

@@ -23,10 +65,10 @@ print(is_valid) # True or False
2365

2466
```python
2567
import json
26-
from gts import JsonEntity, DEFAULT_GTS_CONFIG
68+
from gts import GtsEntity, DEFAULT_GTS_CONFIG
2769

2870
content = json.load(open("path/to/file.json"))
29-
entity = JsonEntity(content=content, cfg=DEFAULT_GTS_CONFIG)
71+
entity = GtsEntity(content=content, cfg=DEFAULT_GTS_CONFIG)
3072
if entity.gts_id:
3173
print(entity.gts_id.id)
3274
```

gts/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ requires-python = ">=3.9"
1313
dependencies = [
1414
"jsonschema>=4.18,<5",
1515
"fastapi>=0.110,<1",
16-
"uvicorn>=0.23,<1"
16+
"uvicorn>=0.23,<1",
17+
"pyyaml>=6.0,<7"
1718
]
1819

1920
[project.urls]

gts/src/gts/files_reader.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from __future__ import annotations
22

33
import json
4+
import yaml
45
from pathlib import Path
56
import os
67
from typing import Iterator, List, Optional, Any
78

89
from .store import GtsReader
9-
from .entities import JsonEntity, JsonFile, DEFAULT_GTS_CONFIG, GtsConfig
10+
from .entities import GtsEntity, GtsFile, DEFAULT_GTS_CONFIG, GtsConfig
1011

1112
import logging
1213

@@ -15,7 +16,7 @@
1516

1617

1718
class GtsFileReader(GtsReader):
18-
"""Reads JSON entities from files and directories specified by path."""
19+
"""Reads GTS entities from JSON and YAML files in directories specified by path."""
1920

2021
def __init__(self, path: str | List[str], cfg: Optional[GtsConfig] = None) -> None:
2122
"""
@@ -34,13 +35,13 @@ def __init__(self, path: str | List[str], cfg: Optional[GtsConfig] = None) -> No
3435
self.cfg = cfg or DEFAULT_GTS_CONFIG
3536
self._files: List[Path] = []
3637
self._current_index = 0
37-
self._current_file_entities: List[JsonEntity] = []
38+
self._current_file_entities: List[GtsEntity] = []
3839
self._current_entity_index = 0
3940
self._initialized = False
4041

4142
def _collect_files(self) -> None:
42-
"""Collect all JSON files from the specified paths, following symlinks."""
43-
valid_extensions = {'.json', '.jsonc', '.gts'}
43+
"""Collect all JSON and YAML files from the specified paths, following symlinks."""
44+
valid_extensions = {'.json', '.jsonc', '.gts', '.yaml', '.yml'}
4445
seen: set[str] = set()
4546
collected: List[Path] = []
4647

@@ -73,18 +74,21 @@ def _collect_files(self) -> None:
7374

7475
self._files = collected
7576

76-
def _load_json_file(self, file_path: Path) -> Any:
77-
"""Load JSON content from a file."""
77+
def _load_file(self, file_path: Path) -> Any:
78+
"""Load content from JSON or YAML file."""
7879
with file_path.open("r", encoding="utf-8") as f:
79-
return json.load(f)
80+
if file_path.suffix.lower() in {'.yaml', '.yml'}:
81+
return yaml.safe_load(f)
82+
else:
83+
return json.load(f)
8084

81-
def _process_file(self, file_path: Path) -> List[JsonEntity]:
82-
"""Process a single JSON file and return list of JsonEntity objects."""
83-
entities: List[JsonEntity] = []
85+
def _process_file(self, file_path: Path) -> List[GtsEntity]:
86+
"""Process a single JSON or YAML file and return list of GtsEntity objects."""
87+
entities: List[GtsEntity] = []
8488

8589
try:
86-
content = self._load_json_file(file_path)
87-
json_file = JsonFile(
90+
content = self._load_file(file_path)
91+
json_file = GtsFile(
8892
path=str(file_path),
8993
name=file_path.name,
9094
content=content
@@ -93,7 +97,7 @@ def _process_file(self, file_path: Path) -> List[JsonEntity]:
9397
# Handle both single objects and arrays
9498
if isinstance(content, list):
9599
for idx, item in enumerate(content):
96-
entity = JsonEntity(
100+
entity = GtsEntity(
97101
file=json_file,
98102
list_sequence=idx,
99103
content=item,
@@ -103,7 +107,7 @@ def _process_file(self, file_path: Path) -> List[JsonEntity]:
103107
logging.debug(f"- discovered entity: {entity.gts_id.id}")
104108
entities.append(entity)
105109
else:
106-
entity = JsonEntity(
110+
entity = GtsEntity(
107111
file=json_file,
108112
list_sequence=None,
109113
content=content,
@@ -118,8 +122,8 @@ def _process_file(self, file_path: Path) -> List[JsonEntity]:
118122

119123
return entities
120124

121-
def __iter__(self) -> Iterator[JsonEntity]:
122-
"""Iterate over all JsonEntity objects from all files."""
125+
def __iter__(self) -> Iterator[GtsEntity]:
126+
"""Iterate over all GtsEntity objects from all files."""
123127
if not self._initialized:
124128
self._collect_files()
125129
self._initialized = True
@@ -130,9 +134,9 @@ def __iter__(self) -> Iterator[JsonEntity]:
130134
for entity in entities:
131135
yield entity
132136

133-
def read_by_id(self, entity_id: str) -> Optional[JsonEntity]:
137+
def read_by_id(self, entity_id: str) -> Optional[GtsEntity]:
134138
"""
135-
Read a JsonEntity by its ID.
139+
Read a GtsEntity by its ID.
136140
For FileReader, this returns None as we don't support random access by ID.
137141
"""
138142
return None

0 commit comments

Comments
 (0)