Skip to content

Commit f3bc573

Browse files
committed
added couple more llm briefs
started adding the code for the client generator and multiple schemas added Fast_API__Route__Extractor based on current routes path extraction
1 parent 85c075e commit f3bc573

25 files changed

Lines changed: 5482 additions & 0 deletions

docs/llm-brief/v3.1.1__osbot-utils__type-safe__and__python-formatting__guidance.md

Lines changed: 1339 additions & 0 deletions
Large diffs are not rendered by default.

docs/llm-brief/v3.1.1__osbot-utils__type-safe__testing-guidance.md

Lines changed: 1286 additions & 0 deletions
Large diffs are not rendered by default.

osbot_fast_api/client/Client__Generator__AST.py

Lines changed: 470 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# from typing import List, Dict
2+
# from osbot_utils.type_safe.Type_Safe import Type_Safe
3+
# from osbot_fast_api.client.schemas.Schema__Contract__Diff import Schema__Contract__Diff
4+
# from osbot_fast_api.client.schemas.Schema__Endpoint__Contract import Schema__Endpoint__Contract
5+
# from osbot_fast_api.client.schemas.Schema__Service__Contract import Schema__Service__Contract
6+
#
7+
#
8+
# class Contract__Comparator(Type_Safe):
9+
#
10+
# def compare(self, old_contract : Schema__Service__Contract , # Previous version of contract
11+
# new_contract : Schema__Service__Contract # New version of contract
12+
# ) -> Schema__Contract__Diff: # Compare two contracts and identify changes
13+
#
14+
# diff = Schema__Contract__Diff()
15+
# # Create lookup dictionaries for efficient comparison
16+
# old_endpoints_map = self._create_endpoint_map(old_contract.endpoints)
17+
# new_endpoints_map = self._create_endpoint_map(new_contract.endpoints)
18+
# # Find removed endpoints (breaking change)
19+
# for key, old_endpoint in old_endpoints_map.items():
20+
# if key not in new_endpoints_map:
21+
# diff.removed_endpoints.append(old_endpoint)
22+
# diff.breaking_changes.append(f"Removed endpoint: {old_endpoint.method} {old_endpoint.path_pattern}")
23+
# # Find added endpoints (non-breaking)
24+
# for key, new_endpoint in new_endpoints_map.items():
25+
# if key not in old_endpoints_map:
26+
# diff.added_endpoints.append(new_endpoint)
27+
# diff.non_breaking_changes.append(f"Added endpoint: {new_endpoint.method} {new_endpoint.path_pattern}")
28+
# # Find modified endpoints
29+
# for key, new_endpoint in new_endpoints_map.items():
30+
# if key in old_endpoints_map:
31+
# old_endpoint = old_endpoints_map[key]
32+
# changes = self._compare_endpoints(old_endpoint, new_endpoint)
33+
#
34+
# if changes:
35+
# diff.modified_endpoints.append(new_endpoint)
36+
# # Classify changes as breaking or non-breaking
37+
# for change_type, description in changes:
38+
# if change_type == 'breaking':
39+
# diff.breaking_changes.append(description)
40+
# else:
41+
# diff.non_breaking_changes.append(description)
42+
#
43+
# return diff
44+
#
45+
# def _create_endpoint_map(self, endpoints: List[Schema__Endpoint__Contract] # List of endpoints to map
46+
# ) -> Dict[str, Schema__Endpoint__Contract]: # Create a map of endpoints keyed by method+path for efficient lookup
47+
#
48+
# endpoint_map = {}
49+
#
50+
# for endpoint in endpoints: # Use method + path as unique key
51+
# key = f"{endpoint.method.value}:{endpoint.path_pattern}"
52+
# endpoint_map[key] = endpoint
53+
#
54+
# return endpoint_map
55+
#
56+
# def _compare_endpoints(self, old_endpoint : Schema__Endpoint__Contract , # Old version of endpoint
57+
# new_endpoint : Schema__Endpoint__Contract # New version of endpoint
58+
# ) -> List[Tuple[str, str]]: # Compare two endpoints and return list of changes
59+
#
60+
# changes = []
61+
# # Check if required parameters were removed (breaking)
62+
# old_required_params = self._get_required_params(old_endpoint)
63+
# new_required_params = self._get_required_params(new_endpoint)
64+
#
65+
# for param_name in old_required_params:
66+
# if param_name not in new_required_params:
67+
# changes.append(('breaking', f"Removed required parameter '{param_name}' from {old_endpoint.path_pattern}"))
68+
# # Check if new required parameters were added (breaking for existing clients)
69+
# for param_name in new_required_params:
70+
# if param_name not in old_required_params: # Check if it has a default value
71+
# new_param = self._find_param(new_endpoint, param_name)
72+
# if new_param and not new_param.default:
73+
# changes.append(('breaking', f"Added required parameter '{param_name}' to {new_endpoint.path_pattern}"))
74+
# else:
75+
# changes.append(('non-breaking', f"Added optional parameter '{param_name}' to {new_endpoint.path_pattern}"))
76+
# # Check if response schema changed (potentially breaking)
77+
# if old_endpoint.response_schema != new_endpoint.response_schema:
78+
# changes.append(('breaking', f"Response schema changed from '{old_endpoint.response_schema}' to '{new_endpoint.response_schema}' for {old_endpoint.path_pattern}"))
79+
# # Check if request schema changed (breaking)
80+
# if old_endpoint.request_schema != new_endpoint.request_schema:
81+
# changes.append(('breaking', f"Request schema changed from '{old_endpoint.request_schema}' to '{new_endpoint.request_schema}' for {old_endpoint.path_pattern}"))
82+
# # Check if error codes changed (informational)
83+
# old_errors = set(old_endpoint.error_codes)
84+
# new_errors = set(new_endpoint.error_codes)
85+
#
86+
# if old_errors != new_errors:
87+
# added_errors = new_errors - old_errors
88+
# removed_errors = old_errors - new_errors
89+
#
90+
# if added_errors:
91+
# changes.append(('non-breaking', f"Added error codes {added_errors} to {old_endpoint.path_pattern}"))
92+
# if removed_errors:
93+
# changes.append(('non-breaking', f"Removed error codes {removed_errors} from {old_endpoint.path_pattern}"))
94+
#
95+
# return changes
96+
#
97+
# def _get_required_params(self, endpoint: Schema__Endpoint__Contract # Endpoint to extract params from
98+
# ) -> set: # Get set of required parameter names from endpoint
99+
#
100+
# required = set()
101+
# # Path parameters are always required
102+
# for param in endpoint.path_params:
103+
# required.add(param.name)
104+
# # Add required query parameters
105+
# for param in endpoint.query_params:
106+
# if param.required:
107+
# required.add(param.name)
108+
# # Add required header parameters
109+
# for param in endpoint.header_params:
110+
# if param.required:
111+
# required.add(param.name)
112+
#
113+
# return required
114+
#
115+
# def _find_param(self, endpoint : Schema__Endpoint__Contract , # Endpoint to search in
116+
# param_name : str # Parameter name to find
117+
# ): # Find a parameter by name in endpoint
118+
# # Check all parameter lists
119+
# for param in endpoint.path_params + endpoint.query_params + endpoint.header_params:
120+
# if param.name == param_name:
121+
# return param
122+
#
123+
# return None
124+
#
125+
# def generate_change_summary(self, diff: Schema__Contract__Diff # Diff to summarize
126+
# ) -> str: # Generate human-readable summary of changes
127+
#
128+
# lines = ["Contract Changes Summary", "=" * 40]
129+
#
130+
# if diff.added_endpoints:
131+
# lines.append(f"\nAdded Endpoints ({len(diff.added_endpoints)}):")
132+
# for endpoint in diff.added_endpoints:
133+
# lines.append(f" + {endpoint.method} {endpoint.path_pattern}")
134+
#
135+
# if diff.removed_endpoints:
136+
# lines.append(f"\nRemoved Endpoints ({len(diff.removed_endpoints)}):")
137+
# for endpoint in diff.removed_endpoints:
138+
# lines.append(f" - {endpoint.method} {endpoint.path_pattern}")
139+
#
140+
# if diff.modified_endpoints:
141+
# lines.append(f"\nModified Endpoints ({len(diff.modified_endpoints)}):")
142+
# for endpoint in diff.modified_endpoints:
143+
# lines.append(f" ~ {endpoint.method} {endpoint.path_pattern}")
144+
#
145+
# if diff.breaking_changes:
146+
# lines.append(f"\n⚠️ Breaking Changes ({len(diff.breaking_changes)}):")
147+
# for change in diff.breaking_changes:
148+
# lines.append(f" • {change}")
149+
#
150+
# if diff.non_breaking_changes:
151+
# lines.append(f"\nNon-Breaking Changes ({len(diff.non_breaking_changes)}):")
152+
# for change in diff.non_breaking_changes:
153+
# lines.append(f" • {change}")
154+
#
155+
# if not diff.has_changes():
156+
# lines.append("\n✓ No changes detected")
157+
#
158+
# return '\n'.join(lines)
159+
#
160+
# def suggest_version_bump(self, diff : Schema__Contract__Diff , # Changes detected
161+
# current_version : str # Current semantic version
162+
# ) -> str: # Suggest version bump based on semantic versioning
163+
# # Parse current version
164+
# parts = current_version.split('.')
165+
# major = int(parts[0]) if len(parts) > 0 else 0
166+
# minor = int(parts[1]) if len(parts) > 1 else 0
167+
# patch = int(parts[2]) if len(parts) > 2 else 0
168+
# # Determine version bump
169+
# if diff.has_breaking_changes(): # Breaking changes require major version bump
170+
# return f"{major + 1}.0.0"
171+
# elif diff.has_changes(): # Non-breaking changes require minor version bump
172+
# return f"{major}.{minor + 1}.0"
173+
# else: # No changes, suggest patch bump
174+
# return f"{major}.{minor}.{patch + 1}"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# from typing import Optional, Dict
2+
# from osbot_utils.type_safe.Type_Safe import Type_Safe
3+
# from osbot_fast_api.api.Fast_API import Fast_API
4+
# from osbot_fast_api.api.contracts.Contract__Extractor import Contract__Extractor
5+
# from osbot_fast_api.api.contracts.Client__Generator__AST import Client__Generator__AST
6+
# from osbot_fast_api.api.contracts.Schema__Service__Contract import Schema__Service__Contract
7+
#
8+
#
9+
# class Fast_API__Client__Generator(Type_Safe):
10+
# fast_api : Fast_API # Fast_API instance to generate client for
11+
#
12+
# def extract_contract(self) -> Schema__Service__Contract: # Extract service contract from Fast_API instance
13+
#
14+
# extractor = Contract__Extractor(fast_api=self.fast_api)
15+
# return extractor.extract_contract()
16+
#
17+
# def generate_client(self, client_name: Optional[str] = None # Optional client class name
18+
# ) -> Dict[str, str]: # Generate client code from Fast_API instance
19+
# # Returns dictionary of filename -> code content
20+
# # Extract contract
21+
# contract = self.extract_contract()
22+
# # Generate client code
23+
# generator = Client__Generator__AST(contract = contract ,
24+
# client_name = client_name )
25+
#
26+
# return generator.generate_client_files()
27+
#
28+
# def save_client_files(self, output_dir : str , # Directory to save files to
29+
# client_name : Optional[str] = None # Optional client class name
30+
# ): # Generate and save client files to directory
31+
#
32+
# from pathlib import Path
33+
# import os
34+
# # Generate client code
35+
# files = self.generate_client(client_name)
36+
# # Create output directory
37+
# output_path = Path(output_dir)
38+
# output_path.mkdir(parents=True, exist_ok=True)
39+
# # Save each file
40+
# for file_path, content in files.items():
41+
# file_full_path = output_path / file_path
42+
# # Create subdirectories if needed
43+
# file_full_path.parent.mkdir(parents=True, exist_ok=True)
44+
# # Save file
45+
# with open(file_full_path, 'w') as f:
46+
# f.write(content)
47+
#
48+
# return list(files.keys())
49+
#
50+
# def print_client_summary(self): # Print summary of what would be generated
51+
#
52+
# contract = self.extract_contract()
53+
#
54+
# print(f"Service: {contract.service_name} v{contract.version}")
55+
# print(f"Modules: {len(contract.modules)}")
56+
# print(f"Total Endpoints: {len(contract.endpoints)}")
57+
# print()
58+
#
59+
# for module in contract.modules:
60+
# print(f" Module '{module.module_name}':")
61+
# print(f" Classes: {', '.join(module.route_classes)}")
62+
# print(f" Endpoints: {len(module.endpoints)}")
63+
#
64+
# for endpoint in module.endpoints[:3]: # Show first 3 endpoints
65+
# print(f" - {endpoint.method} {endpoint.path_pattern} ({endpoint.route_method})")
66+
#
67+
# if len(module.endpoints) > 3:
68+
# print(f" ... and {len(module.endpoints) - 3} more")
69+
# print()
70+
#
71+
#
72+
# # Add method to Fast_API class
73+
# def generate_client(self, output_dir : Optional[str] = None , # Optional directory to save files
74+
# client_name : Optional[str] = None # Optional name for the client
75+
# ) -> Dict[str, str]: # Generate client code for this Fast_API service
76+
# # Args:
77+
# # output_dir: Optional directory to save files to
78+
# # client_name: Optional name for the client (defaults to service name)
79+
# # Returns:
80+
# # Dictionary of filename -> code content
81+
#
82+
# generator = Fast_API__Client__Generator(fast_api=self)
83+
#
84+
# if output_dir: # Save to files and return filenames
85+
# filenames = generator.save_client_files(output_dir, client_name)
86+
# return {name: f"Saved to {output_dir}/{name}" for name in filenames}
87+
# else: # Return generated code
88+
# return generator.generate_client(client_name)
89+
#
90+
# # Monkey-patch the method onto Fast_API class
91+
# # This allows any Fast_API instance to generate a client
92+
# Fast_API.generate_client = generate_client

0 commit comments

Comments
 (0)