Skip to content

Commit 9dfa618

Browse files
authored
Merge branch 'main' into main
2 parents 0eeabb2 + 5c62a5c commit 9dfa618

9 files changed

Lines changed: 154 additions & 129 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/__init__.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
from .entities import (
77
ValidationError,
88
ValidationResult,
9-
JsonFile,
10-
JsonEntity,
9+
GtsFile,
10+
GtsEntity,
1111
GtsConfig,
1212
DEFAULT_GTS_CONFIG,
1313
)
14-
from .path_resolver import JsonPathResolver
14+
from .path_resolver import GtsPathResolver
1515
from .store import (
1616
GtsReader,
1717
GtsStore,
@@ -26,12 +26,21 @@
2626
"GtsWildcard",
2727
"ValidationError",
2828
"ValidationResult",
29-
"JsonFile",
30-
"JsonEntity",
31-
"JsonPathResolver",
29+
"GtsFile",
30+
"GtsEntity",
31+
"GtsPathResolver",
3232
"GtsConfig",
3333
"DEFAULT_GTS_CONFIG",
3434
"GtsReader",
3535
"GtsStore",
3636
"GtsFileReader",
37+
# Backward compatibility aliases
38+
"JsonFile",
39+
"JsonEntity",
40+
"JsonPathResolver",
3741
]
42+
43+
# Backward compatibility aliases
44+
JsonFile = GtsFile
45+
JsonEntity = GtsEntity
46+
JsonPathResolver = GtsPathResolver

gts/src/gts/entities.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from typing import Any, Dict, List, Optional, Set, Tuple
55

66
from .gts import GtsID
7-
from .path_resolver import JsonPathResolver
8-
from .schema_cast import JsonEntityCastResult, SchemaCastError
7+
from .path_resolver import GtsPathResolver
8+
from .schema_cast import GtsEntityCastResult, SchemaCastError
99

1010

1111
@dataclass
@@ -24,7 +24,7 @@ class ValidationResult:
2424

2525

2626
@dataclass
27-
class JsonFile:
27+
class GtsFile:
2828
path: str
2929
name: str
3030
content: Any
@@ -72,10 +72,10 @@ class GtsConfig:
7272

7373

7474
@dataclass
75-
class JsonEntity:
75+
class GtsEntity:
7676
gts_id: Optional[GtsID] = None
7777
is_schema: bool = False
78-
file: Optional[JsonFile] = None
78+
file: Optional[GtsFile] = None
7979
list_sequence: Optional[int] = None
8080
label: str = ""
8181
content: Any = None
@@ -90,7 +90,7 @@ class JsonEntity:
9090
def __init__(
9191
self,
9292
*,
93-
file: Optional[JsonFile] = None,
93+
file: Optional[GtsFile] = None,
9494
list_sequence: Optional[int] = None,
9595
content: Any = None,
9696
cfg: Optional[GtsConfig] = None,
@@ -187,7 +187,7 @@ def cast(
187187
raise SchemaCastError("Target must be a schema")
188188
if not from_schema.is_schema:
189189
raise SchemaCastError("Source schema must be a schema")
190-
return JsonEntityCastResult.cast(
190+
return GtsEntityCastResult.cast(
191191
self.gts_id.id,
192192
to_schema.gts_id.id,
193193
self.content,

gts/src/gts/files_reader.py

Lines changed: 34 additions & 22 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,33 +74,44 @@ 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(
88-
path=str(file_path), name=file_path.name, content=content
90+
content = self._load_file(file_path)
91+
json_file = GtsFile(
92+
path=str(file_path),
93+
name=file_path.name,
94+
content=content
8995
)
9096

9197
# Handle both single objects and arrays
9298
if isinstance(content, list):
9399
for idx, item in enumerate(content):
94-
entity = JsonEntity(
95-
file=json_file, list_sequence=idx, content=item, cfg=self.cfg
100+
entity = GtsEntity(
101+
file=json_file,
102+
list_sequence=idx,
103+
content=item,
104+
cfg=self.cfg
96105
)
97106
if entity.gts_id:
98107
logging.debug(f"- discovered entity: {entity.gts_id.id}")
99108
entities.append(entity)
100109
else:
101-
entity = JsonEntity(
102-
file=json_file, list_sequence=None, content=content, cfg=self.cfg
110+
entity = GtsEntity(
111+
file=json_file,
112+
list_sequence=None,
113+
content=content,
114+
cfg=self.cfg
103115
)
104116
if entity.gts_id:
105117
logging.debug(f"- discovered entity: {entity.gts_id.id}")
@@ -110,8 +122,8 @@ def _process_file(self, file_path: Path) -> List[JsonEntity]:
110122

111123
return entities
112124

113-
def __iter__(self) -> Iterator[JsonEntity]:
114-
"""Iterate over all JsonEntity objects from all files."""
125+
def __iter__(self) -> Iterator[GtsEntity]:
126+
"""Iterate over all GtsEntity objects from all files."""
115127
if not self._initialized:
116128
self._collect_files()
117129
self._initialized = True
@@ -122,9 +134,9 @@ def __iter__(self) -> Iterator[JsonEntity]:
122134
for entity in entities:
123135
yield entity
124136

125-
def read_by_id(self, entity_id: str) -> Optional[JsonEntity]:
137+
def read_by_id(self, entity_id: str) -> Optional[GtsEntity]:
126138
"""
127-
Read a JsonEntity by its ID.
139+
Read a GtsEntity by its ID.
128140
For FileReader, this returns None as we don't support random access by ID.
129141
"""
130142
return None

gts/src/gts/ops.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
from pathlib import Path as SysPath
88

99
from .gts import GtsID, GtsWildcard
10-
from .entities import DEFAULT_GTS_CONFIG, GtsConfig, JsonEntity
10+
from .entities import DEFAULT_GTS_CONFIG, GtsConfig, GtsEntity
1111
from .files_reader import GtsFileReader
12-
from .path_resolver import JsonPathResolver
12+
from .path_resolver import GtsPathResolver
1313
from .store import GtsStore, GtsStoreQueryResult
14-
from .schema_cast import JsonEntityCastResult
14+
from .schema_cast import GtsEntityCastResult
1515

1616
# Interface helpers
1717

gts/src/gts/path_resolver.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
@dataclass
8-
class JsonPathResolver:
8+
class GtsPathResolver:
99
gts_id: str
1010
content: Any
1111
path: str = ""
@@ -70,7 +70,7 @@ def _collect_from(self, node: Any) -> List[str]:
7070
self._list_available(node, "", acc)
7171
return acc
7272

73-
def resolve(self, path: str) -> JsonPathResolver:
73+
def resolve(self, path: str) -> GtsPathResolver:
7474
self.path = path
7575
self.value = None
7676
self.resolved = False
@@ -121,7 +121,7 @@ def resolve(self, path: str) -> JsonPathResolver:
121121
self.resolved = True
122122
return self
123123

124-
def failure(self, path: str, error: str) -> JsonPathResolver:
124+
def failure(self, path: str, error: str) -> GtsPathResolver:
125125
self.path = path
126126
self.value = None
127127
self.resolved = False

0 commit comments

Comments
 (0)