unitysvc-services 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unitysvc_services/__init__.py +4 -0
- unitysvc_services/cli.py +21 -0
- unitysvc_services/format_data.py +145 -0
- unitysvc_services/list.py +245 -0
- unitysvc_services/models/__init__.py +6 -0
- unitysvc_services/models/base.py +352 -0
- unitysvc_services/models/listing_v1.py +72 -0
- unitysvc_services/models/provider_v1.py +53 -0
- unitysvc_services/models/seller_v1.py +110 -0
- unitysvc_services/models/service_v1.py +80 -0
- unitysvc_services/populate.py +186 -0
- unitysvc_services/publisher.py +925 -0
- unitysvc_services/query.py +471 -0
- unitysvc_services/scaffold.py +1039 -0
- unitysvc_services/update.py +293 -0
- unitysvc_services/utils.py +240 -0
- unitysvc_services/validator.py +515 -0
- unitysvc_services-0.1.0.dist-info/METADATA +172 -0
- unitysvc_services-0.1.0.dist-info/RECORD +23 -0
- unitysvc_services-0.1.0.dist-info/WHEEL +5 -0
- unitysvc_services-0.1.0.dist-info/entry_points.txt +2 -0
- unitysvc_services-0.1.0.dist-info/licenses/LICENSE +21 -0
- unitysvc_services-0.1.0.dist-info/top_level.txt +1 -0
unitysvc_services/cli.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
"""Console script for unitysvc_services."""
|
2
|
+
|
3
|
+
import typer
|
4
|
+
|
5
|
+
from . import format_data, populate, publisher, query, scaffold, update, validator
|
6
|
+
from . import list as list_cmd
|
7
|
+
|
8
|
+
app = typer.Typer()
|
9
|
+
|
10
|
+
# Register command groups
|
11
|
+
# Init commands are defined in scaffold.py alongside their implementation
|
12
|
+
app.add_typer(scaffold.app, name="init")
|
13
|
+
app.add_typer(list_cmd.app, name="list")
|
14
|
+
app.add_typer(query.app, name="query")
|
15
|
+
app.add_typer(publisher.app, name="publish")
|
16
|
+
app.add_typer(update.app, name="update")
|
17
|
+
|
18
|
+
# Register standalone commands at root level
|
19
|
+
app.command("format")(format_data.format_data)
|
20
|
+
app.command("validate")(validator.validate)
|
21
|
+
app.command("populate")(populate.populate)
|
@@ -0,0 +1,145 @@
|
|
1
|
+
"""Format command - format data files."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import typer
|
7
|
+
from rich.console import Console
|
8
|
+
|
9
|
+
app = typer.Typer(help="Format data files")
|
10
|
+
console = Console()
|
11
|
+
|
12
|
+
|
13
|
+
@app.command()
|
14
|
+
def format_data(
|
15
|
+
data_dir: Path | None = typer.Argument(
|
16
|
+
None,
|
17
|
+
help="Directory containing data files to format (default: ./data or UNITYSVC_DATA_DIR env var)",
|
18
|
+
),
|
19
|
+
check_only: bool = typer.Option(
|
20
|
+
False,
|
21
|
+
"--check",
|
22
|
+
help="Check if files are formatted without modifying them",
|
23
|
+
),
|
24
|
+
):
|
25
|
+
"""
|
26
|
+
Format data files (JSON, TOML, MD) to match pre-commit requirements.
|
27
|
+
|
28
|
+
This command:
|
29
|
+
- Formats JSON files with 2-space indentation
|
30
|
+
- Removes trailing whitespace
|
31
|
+
- Ensures files end with a newline
|
32
|
+
- Validates TOML syntax
|
33
|
+
"""
|
34
|
+
import json as json_lib
|
35
|
+
|
36
|
+
# Set data directory
|
37
|
+
if data_dir is None:
|
38
|
+
data_dir_str = os.getenv("UNITYSVC_DATA_DIR")
|
39
|
+
if data_dir_str:
|
40
|
+
data_dir = Path(data_dir_str)
|
41
|
+
else:
|
42
|
+
data_dir = Path.cwd() / "data"
|
43
|
+
|
44
|
+
if not data_dir.is_absolute():
|
45
|
+
data_dir = Path.cwd() / data_dir
|
46
|
+
|
47
|
+
if not data_dir.exists():
|
48
|
+
console.print(f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red")
|
49
|
+
raise typer.Exit(code=1)
|
50
|
+
|
51
|
+
console.print(f"[blue]{'Checking' if check_only else 'Formatting'} files in:[/blue] {data_dir}\n")
|
52
|
+
|
53
|
+
# Find all JSON, TOML, and MD files
|
54
|
+
all_files: list[Path] = []
|
55
|
+
for ext in ["json", "toml", "md"]:
|
56
|
+
all_files.extend(data_dir.rglob(f"*.{ext}"))
|
57
|
+
|
58
|
+
if not all_files:
|
59
|
+
console.print("[yellow]No files found to format.[/yellow]")
|
60
|
+
return
|
61
|
+
|
62
|
+
console.print(f"[cyan]Found {len(all_files)} file(s) to process[/cyan]\n")
|
63
|
+
|
64
|
+
files_formatted = 0
|
65
|
+
files_with_issues = []
|
66
|
+
files_failed = []
|
67
|
+
|
68
|
+
for file_path in sorted(all_files):
|
69
|
+
try:
|
70
|
+
# Read file content
|
71
|
+
with open(file_path, encoding="utf-8") as f:
|
72
|
+
original_content = f.read()
|
73
|
+
|
74
|
+
modified_content = original_content
|
75
|
+
changes = []
|
76
|
+
|
77
|
+
# Format based on file type
|
78
|
+
if file_path.suffix == ".json":
|
79
|
+
# Parse and reformat JSON
|
80
|
+
try:
|
81
|
+
data = json_lib.loads(original_content)
|
82
|
+
formatted_json = json_lib.dumps(data, indent=2, sort_keys=True, separators=(",", ": "))
|
83
|
+
modified_content = formatted_json
|
84
|
+
if modified_content != original_content.rstrip("\n"):
|
85
|
+
changes.append("reformatted JSON")
|
86
|
+
except json_lib.JSONDecodeError as e:
|
87
|
+
console.print(f"[red]✗[/red] Invalid JSON in {file_path}: {e}")
|
88
|
+
files_failed.append(str(file_path.relative_to(data_dir)))
|
89
|
+
continue
|
90
|
+
|
91
|
+
# Remove trailing whitespace from each line
|
92
|
+
lines = modified_content.split("\n")
|
93
|
+
stripped_lines = [line.rstrip() for line in lines]
|
94
|
+
modified_content = "\n".join(stripped_lines)
|
95
|
+
if "\n".join([line.rstrip() for line in original_content.split("\n")]) != modified_content:
|
96
|
+
if "reformatted JSON" not in changes:
|
97
|
+
changes.append("removed trailing whitespace")
|
98
|
+
|
99
|
+
# Ensure file ends with single newline
|
100
|
+
if not modified_content.endswith("\n"):
|
101
|
+
modified_content += "\n"
|
102
|
+
changes.append("added end-of-file newline")
|
103
|
+
elif modified_content.endswith("\n\n"):
|
104
|
+
# Remove extra newlines at end
|
105
|
+
modified_content = modified_content.rstrip("\n") + "\n"
|
106
|
+
changes.append("fixed multiple end-of-file newlines")
|
107
|
+
|
108
|
+
# Check if file was modified
|
109
|
+
if modified_content != original_content:
|
110
|
+
files_with_issues.append(str(file_path.relative_to(data_dir)))
|
111
|
+
|
112
|
+
if check_only:
|
113
|
+
console.print(f"[yellow]✗ Would format:[/yellow] {file_path.relative_to(data_dir)}")
|
114
|
+
if changes:
|
115
|
+
console.print(f" [dim]Changes: {', '.join(changes)}[/dim]")
|
116
|
+
else:
|
117
|
+
# Write formatted content
|
118
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
119
|
+
f.write(modified_content)
|
120
|
+
console.print(f"[green]✓ Formatted:[/green] {file_path.relative_to(data_dir)}")
|
121
|
+
if changes:
|
122
|
+
console.print(f" [dim]Changes: {', '.join(changes)}[/dim]")
|
123
|
+
files_formatted += 1
|
124
|
+
else:
|
125
|
+
if not check_only:
|
126
|
+
console.print(f"[dim]✓ Already formatted:[/dim] {file_path.relative_to(data_dir)}")
|
127
|
+
|
128
|
+
except Exception as e:
|
129
|
+
console.print(f"[red]✗ Error processing {file_path.relative_to(data_dir)}: {e}[/red]")
|
130
|
+
files_failed.append(str(file_path.relative_to(data_dir)))
|
131
|
+
|
132
|
+
# Print summary
|
133
|
+
console.print("\n" + "=" * 50)
|
134
|
+
console.print("[bold]Format Summary:[/bold]")
|
135
|
+
console.print(f" Total files: {len(all_files)}")
|
136
|
+
if check_only:
|
137
|
+
console.print(f" [yellow]Files needing formatting: {len(files_with_issues)}[/yellow]")
|
138
|
+
else:
|
139
|
+
console.print(f" [green]✓ Files formatted: {files_formatted}[/green]")
|
140
|
+
console.print(f" [dim]Already formatted: {len(all_files) - files_formatted - len(files_failed)}[/dim]")
|
141
|
+
if files_failed:
|
142
|
+
console.print(f" [red]✗ Failed: {len(files_failed)}[/red]")
|
143
|
+
|
144
|
+
if files_failed or (check_only and files_with_issues):
|
145
|
+
raise typer.Exit(code=1)
|
@@ -0,0 +1,245 @@
|
|
1
|
+
"""List command group - list local data files."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import typer
|
7
|
+
from rich.console import Console
|
8
|
+
from rich.table import Table
|
9
|
+
|
10
|
+
from .utils import (
|
11
|
+
find_files_by_schema,
|
12
|
+
resolve_provider_name,
|
13
|
+
resolve_service_name_for_listing,
|
14
|
+
)
|
15
|
+
|
16
|
+
app = typer.Typer(help="List data files in directory")
|
17
|
+
console = Console()
|
18
|
+
|
19
|
+
|
20
|
+
@app.command("providers")
|
21
|
+
def list_providers(
|
22
|
+
data_dir: Path | None = typer.Argument(
|
23
|
+
None,
|
24
|
+
help="Directory containing provider files (default: ./data or UNITYSVC_DATA_DIR env var)",
|
25
|
+
),
|
26
|
+
):
|
27
|
+
"""List all provider files found in the data directory."""
|
28
|
+
# Set data directory
|
29
|
+
if data_dir is None:
|
30
|
+
data_dir_str = os.getenv("UNITYSVC_DATA_DIR")
|
31
|
+
if data_dir_str:
|
32
|
+
data_dir = Path(data_dir_str)
|
33
|
+
else:
|
34
|
+
data_dir = Path.cwd() / "data"
|
35
|
+
|
36
|
+
if not data_dir.is_absolute():
|
37
|
+
data_dir = Path.cwd() / data_dir
|
38
|
+
|
39
|
+
if not data_dir.exists():
|
40
|
+
console.print(
|
41
|
+
f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red"
|
42
|
+
)
|
43
|
+
raise typer.Exit(code=1)
|
44
|
+
|
45
|
+
console.print(f"[blue]Searching for providers in:[/blue] {data_dir}\n")
|
46
|
+
|
47
|
+
# Find provider files by schema
|
48
|
+
provider_files = find_files_by_schema(data_dir, "provider_v1")
|
49
|
+
|
50
|
+
if not provider_files:
|
51
|
+
console.print("[yellow]No provider files found.[/yellow]")
|
52
|
+
return
|
53
|
+
|
54
|
+
# Create table
|
55
|
+
table = Table(title="Provider Files", show_lines=True)
|
56
|
+
table.add_column("File", style="cyan")
|
57
|
+
table.add_column("Name", style="green")
|
58
|
+
table.add_column("Display Name", style="blue")
|
59
|
+
|
60
|
+
for file_path, _file_format, data in sorted(provider_files, key=lambda x: x[0]):
|
61
|
+
table.add_row(
|
62
|
+
str(file_path.relative_to(data_dir)),
|
63
|
+
data.get("name", "N/A"),
|
64
|
+
data.get("display_name", "N/A"),
|
65
|
+
)
|
66
|
+
|
67
|
+
console.print(table)
|
68
|
+
console.print(f"\n[green]Total:[/green] {len(provider_files)} provider file(s)")
|
69
|
+
|
70
|
+
|
71
|
+
@app.command("sellers")
|
72
|
+
def list_sellers(
|
73
|
+
data_dir: Path | None = typer.Argument(
|
74
|
+
None,
|
75
|
+
help="Directory containing seller files (default: ./data or UNITYSVC_DATA_DIR env var)",
|
76
|
+
),
|
77
|
+
):
|
78
|
+
"""List all seller files found in the data directory."""
|
79
|
+
# Set data directory
|
80
|
+
if data_dir is None:
|
81
|
+
data_dir_str = os.getenv("UNITYSVC_DATA_DIR")
|
82
|
+
if data_dir_str:
|
83
|
+
data_dir = Path(data_dir_str)
|
84
|
+
else:
|
85
|
+
data_dir = Path.cwd() / "data"
|
86
|
+
|
87
|
+
if not data_dir.is_absolute():
|
88
|
+
data_dir = Path.cwd() / data_dir
|
89
|
+
|
90
|
+
if not data_dir.exists():
|
91
|
+
console.print(
|
92
|
+
f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red"
|
93
|
+
)
|
94
|
+
raise typer.Exit(code=1)
|
95
|
+
|
96
|
+
console.print(f"[blue]Searching for sellers in:[/blue] {data_dir}\n")
|
97
|
+
|
98
|
+
# Find seller files by schema
|
99
|
+
seller_files = find_files_by_schema(data_dir, "seller_v1")
|
100
|
+
|
101
|
+
if not seller_files:
|
102
|
+
console.print("[yellow]No seller files found.[/yellow]")
|
103
|
+
return
|
104
|
+
|
105
|
+
# Create table
|
106
|
+
table = Table(title="Seller Files", show_lines=True)
|
107
|
+
table.add_column("File", style="cyan")
|
108
|
+
table.add_column("Name", style="green")
|
109
|
+
table.add_column("Display Name", style="blue")
|
110
|
+
|
111
|
+
for file_path, _file_format, data in sorted(seller_files, key=lambda x: x[0]):
|
112
|
+
table.add_row(
|
113
|
+
str(file_path.relative_to(data_dir)),
|
114
|
+
data.get("name", "N/A"),
|
115
|
+
data.get("display_name", "N/A"),
|
116
|
+
)
|
117
|
+
|
118
|
+
console.print(table)
|
119
|
+
console.print(f"\n[green]Total:[/green] {len(seller_files)} seller file(s)")
|
120
|
+
|
121
|
+
|
122
|
+
@app.command("offerings")
|
123
|
+
def list_offerings(
|
124
|
+
data_dir: Path | None = typer.Argument(
|
125
|
+
None,
|
126
|
+
help="Directory containing service files (default: ./data or UNITYSVC_DATA_DIR env var)",
|
127
|
+
),
|
128
|
+
):
|
129
|
+
"""List all service offering files found in the data directory."""
|
130
|
+
# Set data directory
|
131
|
+
if data_dir is None:
|
132
|
+
data_dir_str = os.getenv("UNITYSVC_DATA_DIR")
|
133
|
+
if data_dir_str:
|
134
|
+
data_dir = Path(data_dir_str)
|
135
|
+
else:
|
136
|
+
data_dir = Path.cwd() / "data"
|
137
|
+
|
138
|
+
if not data_dir.is_absolute():
|
139
|
+
data_dir = Path.cwd() / data_dir
|
140
|
+
|
141
|
+
if not data_dir.exists():
|
142
|
+
console.print(
|
143
|
+
f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red"
|
144
|
+
)
|
145
|
+
raise typer.Exit(code=1)
|
146
|
+
|
147
|
+
console.print(f"[blue]Searching for service offerings in:[/blue] {data_dir}\n")
|
148
|
+
|
149
|
+
# Find service files by schema
|
150
|
+
service_files = find_files_by_schema(data_dir, "service_v1")
|
151
|
+
|
152
|
+
if not service_files:
|
153
|
+
console.print("[yellow]No service offering files found.[/yellow]")
|
154
|
+
return
|
155
|
+
|
156
|
+
# Create table
|
157
|
+
table = Table(title="Service Offering Files", show_lines=True)
|
158
|
+
table.add_column("File", style="cyan")
|
159
|
+
table.add_column("Provider", style="yellow")
|
160
|
+
table.add_column("Name", style="green")
|
161
|
+
table.add_column("Display Name", style="blue")
|
162
|
+
table.add_column("Status", style="magenta")
|
163
|
+
|
164
|
+
for file_path, _file_format, data in sorted(service_files, key=lambda x: x[0]):
|
165
|
+
provider_name = resolve_provider_name(file_path) or "N/A"
|
166
|
+
table.add_row(
|
167
|
+
str(file_path.relative_to(data_dir)),
|
168
|
+
provider_name,
|
169
|
+
data.get("name", "N/A"),
|
170
|
+
data.get("display_name", "N/A"),
|
171
|
+
data.get("upstream_status", "N/A"),
|
172
|
+
)
|
173
|
+
|
174
|
+
console.print(table)
|
175
|
+
console.print(
|
176
|
+
f"\n[green]Total:[/green] {len(service_files)} service offering file(s)"
|
177
|
+
)
|
178
|
+
|
179
|
+
|
180
|
+
@app.command("listings")
|
181
|
+
def list_listings(
|
182
|
+
data_dir: Path | None = typer.Argument(
|
183
|
+
None,
|
184
|
+
help="Directory containing listing files (default: ./data or UNITYSVC_DATA_DIR env var)",
|
185
|
+
),
|
186
|
+
):
|
187
|
+
"""List all service listing files found in the data directory."""
|
188
|
+
# Set data directory
|
189
|
+
if data_dir is None:
|
190
|
+
data_dir_str = os.getenv("UNITYSVC_DATA_DIR")
|
191
|
+
if data_dir_str:
|
192
|
+
data_dir = Path(data_dir_str)
|
193
|
+
else:
|
194
|
+
data_dir = Path.cwd() / "data"
|
195
|
+
|
196
|
+
if not data_dir.is_absolute():
|
197
|
+
data_dir = Path.cwd() / data_dir
|
198
|
+
|
199
|
+
if not data_dir.exists():
|
200
|
+
console.print(
|
201
|
+
f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red"
|
202
|
+
)
|
203
|
+
raise typer.Exit(code=1)
|
204
|
+
|
205
|
+
console.print(f"[blue]Searching for service listings in:[/blue] {data_dir}\n")
|
206
|
+
|
207
|
+
# Find seller definition to get display names
|
208
|
+
seller_files = find_files_by_schema(data_dir, "seller_v1")
|
209
|
+
seller_info = {}
|
210
|
+
if seller_files:
|
211
|
+
# Use the first (and should be only) seller file
|
212
|
+
seller_info = seller_files[0][-1]
|
213
|
+
|
214
|
+
# Find listing files by schema
|
215
|
+
listing_files = find_files_by_schema(data_dir, "listing_v1")
|
216
|
+
|
217
|
+
if not listing_files:
|
218
|
+
console.print("[yellow]No service listing files found.[/yellow]")
|
219
|
+
return
|
220
|
+
|
221
|
+
# Create table
|
222
|
+
table = Table(title="Service Listing Files", show_lines=True)
|
223
|
+
table.add_column("File", style="cyan")
|
224
|
+
table.add_column("Provider", style="yellow")
|
225
|
+
table.add_column("Service", style="blue")
|
226
|
+
table.add_column("Seller", style="green")
|
227
|
+
table.add_column("Status", style="magenta")
|
228
|
+
|
229
|
+
for file_path, _file_format, data in sorted(listing_files, key=lambda x: x[0]):
|
230
|
+
# Resolve provider and service names using the utility functions
|
231
|
+
provider_name = resolve_provider_name(file_path) or "N/A"
|
232
|
+
service_name = resolve_service_name_for_listing(file_path, data) or "N/A"
|
233
|
+
|
234
|
+
table.add_row(
|
235
|
+
str(file_path.relative_to(data_dir)),
|
236
|
+
provider_name,
|
237
|
+
service_name,
|
238
|
+
seller_info.get("name", "N/A"),
|
239
|
+
data.get("listing_status", "N/A"),
|
240
|
+
)
|
241
|
+
|
242
|
+
console.print(table)
|
243
|
+
console.print(
|
244
|
+
f"\n[green]Total:[/green] {len(listing_files)} service listing file(s)"
|
245
|
+
)
|