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.
@@ -0,0 +1,4 @@
1
+ """Top-level package for UnitySvc Provider."""
2
+
3
+ __author__ = """Bo Peng"""
4
+ __email__ = "bo.peng@unitysvc.com"
@@ -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
+ )
@@ -0,0 +1,6 @@
1
+ from .listing_v1 import ListingV1
2
+ from .provider_v1 import ProviderV1
3
+ from .seller_v1 import SellerV1
4
+ from .service_v1 import ServiceV1
5
+
6
+ __all__ = ["ProviderV1", "ServiceV1", "ListingV1", "SellerV1"]