fluidkit 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.
fluidkit/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """
2
+ FluidKit - Automatic TypeScript client code generation for FastAPI
3
+ """
4
+
5
+ __version__ = "2.0.0"
6
+
7
+ def _check_dependencies():
8
+ """Check for required dependencies and provide helpful error messages"""
9
+ missing = []
10
+
11
+ try:
12
+ import fastapi
13
+ except ImportError:
14
+ missing.append("fastapi")
15
+
16
+ try:
17
+ import pydantic
18
+ except ImportError:
19
+ missing.append("pydantic")
20
+
21
+ if missing:
22
+ deps = " and ".join(missing)
23
+ raise ImportError(
24
+ f"FluidKit requires {deps} to be installed.\n"
25
+ f"Install with: pip install {' '.join(missing)}\n"
26
+ f"FluidKit works with your existing {deps} versions."
27
+ )
28
+
29
+ # Check dependencies on import
30
+ _check_dependencies()
31
+
32
+ # Import main API only after dependency check
33
+ from .core.schema import LanguageType
34
+ from .core.integrator import integrate, introspect_only, generate_only
35
+
36
+ __all__ = [
37
+ # Main functions
38
+ 'integrate',
39
+ 'introspect_only',
40
+ 'generate_only',
41
+
42
+ # Enums
43
+ 'LanguageType',
44
+
45
+ # Version
46
+ '__version__'
47
+ ]
@@ -0,0 +1,35 @@
1
+ """
2
+ Core FluidKit components - for advanced users and plugin developers
3
+ """
4
+
5
+ # Main data structures
6
+ from .schema import (
7
+ FluidKitApp,
8
+ RouteNode,
9
+ ModelNode,
10
+ Field,
11
+ FieldAnnotation,
12
+ FieldConstraints,
13
+ ModuleLocation,
14
+
15
+ # Enums
16
+ BaseType,
17
+ LanguageType,
18
+ ContainerType,
19
+ ParameterType,
20
+ )
21
+
22
+ # Main integration function
23
+ from .integrator import integrate, introspect_only, generate_only
24
+
25
+ __all__ = [
26
+ # Functions
27
+ 'integrate', 'introspect_only', 'generate_only',
28
+
29
+ # Data structures
30
+ 'FluidKitApp', 'RouteNode', 'ModelNode', 'Field',
31
+ 'FieldAnnotation', 'FieldConstraints', 'ModuleLocation',
32
+
33
+ # Enums
34
+ 'LanguageType', 'BaseType', 'ContainerType', 'ParameterType',
35
+ ]
@@ -0,0 +1,32 @@
1
+ """
2
+ FluidKit constants for runtime imports and code generation
3
+ """
4
+
5
+ class FluidKitRuntime:
6
+ """FluidKit runtime function and type names"""
7
+
8
+ # TypeScript runtime exports
9
+ API_RESULT_TYPE = "ApiResult"
10
+ GET_BASE_URL_FN = "getBaseUrl"
11
+ HANDLE_RESPONSE_FN = "handleResponse"
12
+
13
+ # Runtime file locations
14
+ RUNTIME_DIR = ".fluidkit"
15
+ RUNTIME_FILE = "runtime.ts"
16
+
17
+ @classmethod
18
+ def get_all_imports(cls) -> list[str]:
19
+ """Get all runtime imports for TypeScript"""
20
+ return [cls.API_RESULT_TYPE, cls.GET_BASE_URL_FN, cls.HANDLE_RESPONSE_FN]
21
+
22
+
23
+ class GenerationPaths:
24
+ """Standard paths for code generation"""
25
+
26
+ FLUIDKIT_DIR = ".fluidkit"
27
+ TYPESCRIPT_RUNTIME = "runtime.ts"
28
+
29
+ # Future language runtimes
30
+ PYTHON_RUNTIME = "runtime.py"
31
+ JAVASCRIPT_RUNTIME = "runtime.js"
32
+
@@ -0,0 +1,299 @@
1
+ """
2
+ FluidKit V2 App Integration
3
+
4
+ Main integration API for FluidKit with FastAPI applications with multi-language support.
5
+ Orchestrates route collection, model discovery, and optional code generation.
6
+ """
7
+
8
+ import logging
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from fastapi import FastAPI
12
+ from typing import List, Dict, Tuple
13
+ from fastapi.routing import APIRoute
14
+
15
+ from fluidkit.introspection.routes import route_to_node
16
+ from fluidkit.introspection.models import discover_models_from_routes
17
+ from fluidkit.core.schema import FluidKitApp, RouteNode, LanguageType
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def integrate(
24
+ app: FastAPI,
25
+ lang: str = "typescript",
26
+ strategy: str = "mirror",
27
+ verbose: bool = False,
28
+ **options
29
+ ) -> Tuple[FluidKitApp, Dict[str, str]]:
30
+ """
31
+ Integrate FluidKit with FastAPI app using runtime introspection.
32
+
33
+ Args:
34
+ app: FastAPI application instance
35
+ lang: Target language for code generation ("ts"/"typescript" default)
36
+ strategy: Generation strategy ("co-locate" or "mirror")
37
+ verbose: Enable detailed logging
38
+ **options: Additional options (project_root, runtime config, etc.)
39
+
40
+ Returns:
41
+ (FluidKitApp, generated_files_dict)
42
+ """
43
+ if verbose:
44
+ logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
45
+
46
+ project_root = options.get('project_root') or str(Path.cwd().resolve())
47
+
48
+ if verbose:
49
+ logger.info("Starting FluidKit integration with FastAPI app")
50
+ logger.debug(f"Project root: {project_root}")
51
+
52
+ # Collect and convert routes
53
+ api_routes = _collect_fastapi_routes(app)
54
+ route_nodes = _convert_routes_to_nodes(api_routes)
55
+
56
+ # Discover models (project types only)
57
+ model_nodes = discover_models_from_routes(route_nodes, project_root)
58
+
59
+ # Build FluidKitApp
60
+ fluid_app = FluidKitApp(
61
+ models=model_nodes,
62
+ routes=route_nodes,
63
+ app_instance=app,
64
+ metadata={'project_root': project_root, **options}
65
+ )
66
+
67
+ if verbose:
68
+ logger.info(f"Introspection complete: {len(route_nodes)} routes, {len(model_nodes)} models")
69
+
70
+ # Generate TypeScript files
71
+ normalized_lang = _normalize_language(lang)
72
+
73
+ if normalized_lang == LanguageType.TYPESCRIPT:
74
+ generated_files = _generate_and_write_typescript(fluid_app, strategy, verbose, **options)
75
+
76
+ if not verbose:
77
+ print(f"FluidKit: Generated {len(generated_files)} TypeScript files ({strategy} strategy)")
78
+
79
+ return fluid_app, generated_files
80
+
81
+ else:
82
+ raise NotImplementedError(f"Language '{lang}' not yet supported. Currently supported: ts, typescript")
83
+
84
+
85
+ def _generate_and_write_typescript(
86
+ fluid_app: FluidKitApp,
87
+ strategy: str,
88
+ verbose: bool,
89
+ **options
90
+ ) -> Dict[str, str]:
91
+ """Generate TypeScript files and write them to disk."""
92
+ from fluidkit.generators.typescript.pipeline import generate_typescript_files
93
+
94
+ generated_files = generate_typescript_files(
95
+ fluid_app=fluid_app,
96
+ strategy=strategy,
97
+ **options
98
+ )
99
+
100
+ _write_generated_files(generated_files, verbose)
101
+ return generated_files
102
+
103
+
104
+ def _write_generated_files(generated_files: Dict[str, str], verbose: bool):
105
+ """Write generated files to disk with auto-generated headers."""
106
+ for file_path, content in generated_files.items():
107
+ try:
108
+ file_path_obj = Path(file_path)
109
+ file_path_obj.parent.mkdir(parents=True, exist_ok=True)
110
+
111
+ header = _get_file_header(file_path_obj.suffix)
112
+ final_content = header + content
113
+
114
+ with open(file_path_obj, 'w', encoding='utf-8') as f:
115
+ f.write(final_content)
116
+
117
+ if verbose:
118
+ logger.debug(f"Generated: {file_path}")
119
+
120
+ except Exception as e:
121
+ error_msg = f"Failed to write {file_path}: {e}"
122
+ if verbose:
123
+ logger.error(error_msg)
124
+ else:
125
+ print(f"āŒ {error_msg}")
126
+
127
+
128
+ def _get_file_header(file_extension: str) -> str:
129
+ """Get auto-generated file header based on file type."""
130
+ if file_extension == '.ts':
131
+ return '''/**
132
+ * Auto-generated by FluidKit from FastAPI routes and models - DO NOT EDIT
133
+ * Changes will be overwritten on regeneration.
134
+ */
135
+
136
+ '''
137
+ elif file_extension == '.py':
138
+ return '''"""
139
+ Auto-generated by FluidKit from FastAPI routes and models - DO NOT EDIT
140
+ Changes will be overwritten on regeneration.
141
+ """
142
+
143
+ '''
144
+ else:
145
+ return '''/**
146
+ * Auto-generated by FluidKit from FastAPI routes and models - DO NOT EDIT
147
+ * Changes will be overwritten on regeneration.
148
+ */
149
+
150
+ '''
151
+
152
+
153
+ def _normalize_language(lang: str) -> LanguageType:
154
+ """Normalize language string to LanguageType enum."""
155
+ lang_lower = lang.lower()
156
+
157
+ if lang_lower in ["ts", "typescript"]:
158
+ return LanguageType.TYPESCRIPT
159
+
160
+ else:
161
+ valid_langs = ["ts", "typescript"]
162
+ raise ValueError(f"Unsupported language '{lang}'. Supported: {', '.join(valid_langs)}")
163
+
164
+
165
+ def _collect_fastapi_routes(app: FastAPI) -> List[APIRoute]:
166
+ """Collect user-defined API routes from FastAPI app."""
167
+ user_routes = []
168
+
169
+ for route in app.routes:
170
+ if isinstance(route, APIRoute) and _is_user_defined_route(route):
171
+ user_routes.append(route)
172
+
173
+ return user_routes
174
+
175
+
176
+ def _convert_routes_to_nodes(api_routes: List[APIRoute]) -> List[RouteNode]:
177
+ """Convert FastAPI APIRoute objects to RouteNode objects."""
178
+ route_nodes = []
179
+
180
+ for route in api_routes:
181
+ try:
182
+ route_node = route_to_node(route)
183
+ if route_node:
184
+ route_nodes.append(route_node)
185
+ except Exception as e:
186
+ logger.warning(f"Failed to convert route {route.path}: {e}")
187
+ continue
188
+
189
+ return route_nodes
190
+
191
+
192
+ def _is_user_defined_route(route: APIRoute) -> bool:
193
+ """Determine if route is user-defined using module-based filtering."""
194
+ endpoint = route.endpoint
195
+
196
+ if (not endpoint or not callable(endpoint) or
197
+ not hasattr(endpoint, '__name__') or endpoint.__name__ == '<lambda>' or
198
+ not hasattr(endpoint, '__module__') or not route.methods):
199
+ return False
200
+
201
+ endpoint_module = endpoint.__module__
202
+ system_prefixes = ('fastapi.', 'starlette.')
203
+
204
+ if any(endpoint_module.startswith(prefix) for prefix in system_prefixes):
205
+ return False
206
+
207
+ valid_methods = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE'}
208
+ if not any(method in valid_methods for method in route.methods):
209
+ return False
210
+
211
+ if hasattr(endpoint, 'app') and hasattr(endpoint, '__call__'):
212
+ return False
213
+
214
+ return True
215
+
216
+
217
+ def introspect_only(app: FastAPI, **options) -> FluidKitApp:
218
+ """Convenience function for introspection only (no code generation)."""
219
+ project_root = options.get('project_root') or str(Path.cwd().resolve())
220
+
221
+ api_routes = _collect_fastapi_routes(app)
222
+ route_nodes = _convert_routes_to_nodes(api_routes)
223
+ model_nodes = discover_models_from_routes(route_nodes, project_root)
224
+
225
+ fluid_app = FluidKitApp(
226
+ models=model_nodes,
227
+ routes=route_nodes,
228
+ app_instance=app,
229
+ metadata={'project_root': project_root, **options}
230
+ )
231
+
232
+ print(f"FluidKit: Introspected {len(route_nodes)} routes, {len(model_nodes)} models")
233
+ return fluid_app
234
+
235
+
236
+ def generate_only(app: FastAPI, strategy: str = "mirror", **options) -> Dict[str, str]:
237
+ """Convenience function to generate files without writing to disk."""
238
+ from fluidkit.generators.typescript.pipeline import generate_typescript_files
239
+
240
+ project_root = options.get('project_root') or str(Path.cwd().resolve())
241
+
242
+ api_routes = _collect_fastapi_routes(app)
243
+ route_nodes = _convert_routes_to_nodes(api_routes)
244
+ model_nodes = discover_models_from_routes(route_nodes, project_root)
245
+
246
+ fluid_app = FluidKitApp(
247
+ models=model_nodes,
248
+ routes=route_nodes,
249
+ app_instance=app,
250
+ metadata={'project_root': project_root, **options}
251
+ )
252
+
253
+ generated_files = generate_typescript_files(
254
+ fluid_app=fluid_app,
255
+ strategy=strategy,
256
+ **options
257
+ )
258
+
259
+ print(f"FluidKit: Generated {len(generated_files)} TypeScript files ({strategy} strategy) - not written to disk")
260
+ return generated_files
261
+
262
+
263
+ # === TESTING FUNCTION === #
264
+
265
+ def test_integration():
266
+ """Test the integration with file writing."""
267
+ try:
268
+ from tests.sample.app import app
269
+
270
+ print("=== FLUIDKIT V2 INTEGRATION TEST ===")
271
+
272
+ # Test 1: Default integration (generates and writes files)
273
+ print("\n1. Default integration (mirror strategy):")
274
+ fluid_app, files = integrate(app)
275
+
276
+ # Test 2: Co-locate strategy
277
+ print("\n2. Co-locate strategy:")
278
+ fluid_app, files = integrate(app, strategy="co-locate")
279
+
280
+ # Test 3: Introspection only
281
+ print("\n3. Introspection only:")
282
+ fluid_app = introspect_only(app)
283
+
284
+ # Test 4: Generate only (don't write)
285
+ print("\n4. Generate only (no file writing):")
286
+ files = generate_only(app)
287
+
288
+ print("\nāœ… All tests passed!")
289
+
290
+ except ImportError:
291
+ print("āŒ Could not import v2.examples.test - ensure example files exist")
292
+ except Exception as e:
293
+ print(f"āŒ Test failed: {e}")
294
+ import traceback
295
+ traceback.print_exc()
296
+
297
+
298
+ if __name__ == "__main__":
299
+ test_integration()