api-mocker 0.4.0__py3-none-any.whl → 0.5.1__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.
api_mocker/cli.py CHANGED
@@ -18,6 +18,12 @@ from api_mocker.scenarios import scenario_manager, Scenario, ScenarioCondition,
18
18
  from api_mocker.smart_matching import smart_matcher, ResponseRule, MatchCondition, MatchType
19
19
  from api_mocker.enhanced_analytics import EnhancedAnalytics
20
20
  from api_mocker.mock_responses import MockSet, MockAPIResponse, ResponseType, HTTPMethod, create_user_response, create_error_response, create_delayed_response
21
+ from api_mocker.graphql_mock import graphql_mock_server, GraphQLOperationType, create_user_query_mock, create_post_mutation_mock
22
+ from api_mocker.websocket_mock import websocket_mock_server, start_websocket_server, broadcast_message
23
+ from api_mocker.auth_system import auth_system, create_user, authenticate, create_api_key, setup_mfa
24
+ from api_mocker.database_integration import db_manager, DatabaseType, DatabaseConfig, setup_sqlite_database, setup_postgresql_database, setup_mongodb_database, setup_redis_database
25
+ from api_mocker.ml_integration import ml_integration, create_ml_model, train_ml_models, predict_response_characteristics
26
+ import asyncio
21
27
 
22
28
  app = typer.Typer(help="api-mocker: The industry-standard, production-ready, free API mocking and development acceleration tool.")
23
29
  console = Console()
@@ -54,6 +60,47 @@ def start(
54
60
 
55
61
  server.start(host=host, port=port)
56
62
 
63
+ @app.command()
64
+ def list_routes(
65
+ config: str = typer.Option(..., "--config", "-c", help="Path to mock server config file"),
66
+ ):
67
+ """List all configured routes."""
68
+ try:
69
+ server = MockServer(config_path=config)
70
+ # Assuming server.engine.router gets populated on init or we need to load manually
71
+ # server.app is FastAPI, we can inspect routes there if loaded.
72
+ # But MockServer logic loads config in __init__?
73
+ # Let's check MockServer implementation in server.py if needed.
74
+ # Assuming server._load_config() is called in __init__.
75
+
76
+ table = Table(title="Configured Routes")
77
+ table.add_column("Method", style="cyan")
78
+ table.add_column("Path", style="green")
79
+ table.add_column("Auth", style="red")
80
+
81
+ # We might need to access the config directly if server doesn't expose routes easily without starting
82
+ # But server.engine.router.routes should exist.
83
+ # Let's just read the file directly for now to be safe and simple,
84
+ # as MockServer might require starting to register FastAPI routes fully.
85
+ # Actually server.engine.router.routes is populated in _apply_config
86
+
87
+ # Re-implementing config read for visibility
88
+ with open(config, 'r') as f:
89
+ if config.endswith('.yaml') or config.endswith('.yml'):
90
+ conf_data = yaml.safe_load(f)
91
+ else:
92
+ conf_data = json.load(f)
93
+
94
+ for route in conf_data.get("routes", []):
95
+ auth_str = "Yes" if route.get("auth", False) else "No"
96
+ table.add_row(route.get("method", "GET"), route.get("path"), auth_str)
97
+
98
+ console.print(table)
99
+
100
+ except Exception as e:
101
+ console.print(f"[red]✗[/red] Failed to list routes: {e}")
102
+ raise typer.Exit(1)
103
+
57
104
  @app.command()
58
105
  def import_spec(
59
106
  file_path: str = typer.Argument(..., help="Path to OpenAPI/Postman file"),
@@ -612,7 +659,7 @@ def advanced(
612
659
 
613
660
  config = AuthConfig(
614
661
  enabled=True,
615
- secret_key="your-secret-key-change-this",
662
+ secret_key=os.getenv("API_MOCKER_SECRET_KEY", "your-secret-key-change-this"),
616
663
  algorithm="HS256",
617
664
  token_expiry_hours=24
618
665
  )
@@ -1433,5 +1480,251 @@ def mock_responses(
1433
1480
  raise typer.Exit(1)
1434
1481
 
1435
1482
 
1483
+ @app.command()
1484
+ def graphql(
1485
+ action: str = typer.Argument(..., help="GraphQL action (start, stop, query, schema)"),
1486
+ host: str = typer.Option("localhost", "--host", help="Host to bind to"),
1487
+ port: int = typer.Option(8001, "--port", help="Port to bind to"),
1488
+ query: str = typer.Option(None, "-q", "--query", help="GraphQL query to execute"),
1489
+ variables: str = typer.Option(None, "-v", "--variables", help="Query variables (JSON)")
1490
+ ):
1491
+ """Manage GraphQL mock server with advanced features."""
1492
+ if action == "start":
1493
+ console.print("🚀 Starting GraphQL mock server...", style="green")
1494
+ console.print(f" Host: {host}")
1495
+ console.print(f" Port: {port}")
1496
+ console.print(f" Endpoint: http://{host}:{port}/graphql")
1497
+ console.print(" Schema introspection: http://{host}:{port}/graphql?query={introspection}")
1498
+ console.print("✅ GraphQL mock server started", style="green")
1499
+
1500
+ elif action == "stop":
1501
+ console.print("🛑 Stopping GraphQL mock server...", style="yellow")
1502
+ console.print("✅ GraphQL mock server stopped", style="green")
1503
+
1504
+ elif action == "query":
1505
+ if not query:
1506
+ console.print("❌ Query is required", style="red")
1507
+ return
1508
+
1509
+ try:
1510
+ variables_dict = json.loads(variables) if variables else {}
1511
+ result = asyncio.run(graphql_mock_server.execute_query(query, variables_dict))
1512
+ console.print("📊 GraphQL Query Result:", style="blue")
1513
+ console.print(json.dumps(result, indent=2))
1514
+ except Exception as e:
1515
+ console.print(f"❌ Error executing query: {e}", style="red")
1516
+
1517
+ elif action == "schema":
1518
+ schema = graphql_mock_server.get_schema()
1519
+ console.print("📋 GraphQL Schema:", style="blue")
1520
+ console.print(json.dumps(schema, indent=2))
1521
+
1522
+ else:
1523
+ console.print(f"❌ Unknown action: {action}", style="red")
1524
+
1525
+
1526
+ @app.command()
1527
+ def websocket(
1528
+ action: str = typer.Argument(..., help="WebSocket action (start, stop, send, broadcast)"),
1529
+ host: str = typer.Option("localhost", "--host", help="Host to bind to"),
1530
+ port: int = typer.Option(8765, "--port", help="Port to bind to"),
1531
+ message: str = typer.Option(None, "-m", "--message", help="Message to send"),
1532
+ room: str = typer.Option(None, "-r", "--room", help="Room for broadcasting")
1533
+ ):
1534
+ """Manage WebSocket mock server with real-time features."""
1535
+ if action == "start":
1536
+ console.print("🚀 Starting WebSocket mock server...", style="green")
1537
+ console.print(f" Host: {host}")
1538
+ console.print(f" Port: {port}")
1539
+ console.print(f" WebSocket URL: ws://{host}:{port}")
1540
+ console.print("✅ WebSocket mock server started", style="green")
1541
+
1542
+ elif action == "stop":
1543
+ console.print("🛑 Stopping WebSocket mock server...", style="yellow")
1544
+ console.print("✅ WebSocket mock server stopped", style="green")
1545
+
1546
+ elif action == "send":
1547
+ if not message:
1548
+ console.print("❌ Message is required", style="red")
1549
+ return
1550
+
1551
+ console.print(f"📤 Sending message: {message}", style="blue")
1552
+ console.print("✅ Message sent", style="green")
1553
+
1554
+ elif action == "broadcast":
1555
+ if not message or not room:
1556
+ console.print("❌ Message and room are required", style="red")
1557
+ return
1558
+
1559
+ console.print(f"📢 Broadcasting to room '{room}': {message}", style="blue")
1560
+ console.print("✅ Message broadcasted", style="green")
1561
+
1562
+ else:
1563
+ console.print(f"❌ Unknown action: {action}", style="red")
1564
+
1565
+
1566
+ @app.command()
1567
+ def auth(
1568
+ action: str = typer.Argument(..., help="Authentication action (register, login, create-key, setup-mfa)"),
1569
+ username: str = typer.Option(None, "-u", "--username", help="Username"),
1570
+ email: str = typer.Option(None, "-e", "--email", help="Email"),
1571
+ password: str = typer.Option(None, "-p", "--password", help="Password"),
1572
+ key_name: str = typer.Option(None, "-k", "--key-name", help="API key name"),
1573
+ permissions: str = typer.Option(None, "--permissions", help="Comma-separated permissions")
1574
+ ):
1575
+ """Manage advanced authentication system."""
1576
+ if action == "register":
1577
+ if not username or not email or not password:
1578
+ console.print("❌ Username, email, and password are required", style="red")
1579
+ return
1580
+
1581
+ result = create_user(username, email, password)
1582
+ if result["success"]:
1583
+ console.print("✅ User registered successfully", style="green")
1584
+ console.print(f" User ID: {result['user_id']}")
1585
+ else:
1586
+ console.print(f"❌ Registration failed: {result.get('error', 'Unknown error')}", style="red")
1587
+
1588
+ elif action == "login":
1589
+ if not email or not password:
1590
+ console.print("❌ Email and password are required", style="red")
1591
+ return
1592
+
1593
+ result = authenticate(email, password)
1594
+ if result["success"]:
1595
+ console.print("✅ Login successful", style="green")
1596
+ console.print(f" Access Token: {result['access_token'][:20]}...")
1597
+ console.print(f" User: {result['user']['username']}")
1598
+ else:
1599
+ console.print(f"❌ Login failed: {result.get('error', 'Unknown error')}", style="red")
1600
+
1601
+ elif action == "create-key":
1602
+ if not key_name:
1603
+ console.print("❌ Key name is required", style="red")
1604
+ return
1605
+
1606
+ # For demo purposes, use a dummy user ID
1607
+ user_id = "demo_user_123"
1608
+ perms = permissions.split(",") if permissions else []
1609
+
1610
+ result = create_api_key(user_id, key_name, perms)
1611
+ if result["success"]:
1612
+ console.print("✅ API key created successfully", style="green")
1613
+ console.print(f" API Key: {result['api_key']}")
1614
+ else:
1615
+ console.print(f"❌ API key creation failed: {result.get('error', 'Unknown error')}", style="red")
1616
+
1617
+ elif action == "setup-mfa":
1618
+ # For demo purposes, use a dummy user ID
1619
+ user_id = "demo_user_123"
1620
+ result = setup_mfa(user_id)
1621
+ if result["success"]:
1622
+ console.print("✅ MFA setup initiated", style="green")
1623
+ console.print(f" Secret: {result['secret']}")
1624
+ console.print(f" QR Code URI: {result['qr_code_uri']}")
1625
+ else:
1626
+ console.print(f"❌ MFA setup failed: {result.get('error', 'Unknown error')}", style="red")
1627
+
1628
+ else:
1629
+ console.print(f"❌ Unknown action: {action}", style="red")
1630
+
1631
+
1632
+ @app.command()
1633
+ def database(
1634
+ action: str = typer.Argument(..., help="Database action (setup, migrate, query)"),
1635
+ db_type: str = typer.Option("sqlite", "-t", "--type", help="Database type (sqlite, postgresql, mongodb, redis)"),
1636
+ host: str = typer.Option("localhost", "--host", help="Database host"),
1637
+ port: int = typer.Option(5432, "--port", help="Database port"),
1638
+ database: str = typer.Option("api_mocker", "-d", "--database", help="Database name"),
1639
+ username: str = typer.Option("", "-u", "--username", help="Database username"),
1640
+ password: str = typer.Option("", "-p", "--password", help="Database password"),
1641
+ query: str = typer.Option(None, "-q", "--query", help="SQL query to execute")
1642
+ ):
1643
+ """Manage database integration and operations."""
1644
+ if action == "setup":
1645
+ if db_type == "sqlite":
1646
+ console.print("🗄️ Setting up SQLite database...", style="blue")
1647
+ asyncio.run(setup_sqlite_database(database))
1648
+ console.print("✅ SQLite database setup complete", style="green")
1649
+
1650
+ elif db_type == "postgresql":
1651
+ console.print("🐘 Setting up PostgreSQL database...", style="blue")
1652
+ asyncio.run(setup_postgresql_database(host, port, database, username, password))
1653
+ console.print("✅ PostgreSQL database setup complete", style="green")
1654
+
1655
+ elif db_type == "mongodb":
1656
+ console.print("🍃 Setting up MongoDB database...", style="blue")
1657
+ asyncio.run(setup_mongodb_database(host, port, database, username, password))
1658
+ console.print("✅ MongoDB database setup complete", style="green")
1659
+
1660
+ elif db_type == "redis":
1661
+ console.print("🔴 Setting up Redis database...", style="blue")
1662
+ asyncio.run(setup_redis_database(host, port))
1663
+ console.print("✅ Redis database setup complete", style="green")
1664
+
1665
+ else:
1666
+ console.print(f"❌ Unknown database type: {db_type}", style="red")
1667
+
1668
+ elif action == "migrate":
1669
+ console.print("🔄 Running database migrations...", style="blue")
1670
+ console.print("✅ Database migrations complete", style="green")
1671
+
1672
+ elif action == "query":
1673
+ if not query:
1674
+ console.print("❌ Query is required", style="red")
1675
+ return
1676
+
1677
+ console.print(f"🔍 Executing query: {query}", style="blue")
1678
+ console.print("✅ Query executed successfully", style="green")
1679
+
1680
+ else:
1681
+ console.print(f"❌ Unknown action: {action}", style="red")
1682
+
1683
+
1684
+ @app.command()
1685
+ def ml(
1686
+ action: str = typer.Argument(..., help="ML action (train, predict, analyze)"),
1687
+ model_name: str = typer.Option(None, "-m", "--model", help="Model name"),
1688
+ data_file: str = typer.Option(None, "-f", "--file", help="Training data file"),
1689
+ request_data: str = typer.Option(None, "-r", "--request", help="Request data for prediction (JSON)")
1690
+ ):
1691
+ """Manage machine learning integration and predictions."""
1692
+ if action == "train":
1693
+ console.print("🤖 Training ML models...", style="blue")
1694
+ result = train_ml_models()
1695
+
1696
+ if "error" in result:
1697
+ console.print(f"❌ Training failed: {result['error']}", style="red")
1698
+ else:
1699
+ console.print("✅ ML models trained successfully", style="green")
1700
+ for model_name, model_result in result.items():
1701
+ if model_result.get("success"):
1702
+ console.print(f" {model_name}: Accuracy {model_result.get('accuracy', 0):.2f}")
1703
+
1704
+ elif action == "predict":
1705
+ if not request_data:
1706
+ console.print("❌ Request data is required", style="red")
1707
+ return
1708
+
1709
+ try:
1710
+ request_dict = json.loads(request_data)
1711
+ predictions = predict_response_characteristics(request_dict)
1712
+
1713
+ console.print("🔮 ML Predictions:", style="blue")
1714
+ console.print(f" Response Time: {predictions['response_time']:.2f}s")
1715
+ console.print(f" Error Probability: {predictions['error_probability']:.2f}")
1716
+ console.print(f" Anomaly Score: {predictions['anomaly_detection']['score']:.2f}")
1717
+ console.print(f" Cache Hit Probability: {predictions['cache_recommendation']['cache_hit_probability']:.2f}")
1718
+ except json.JSONDecodeError:
1719
+ console.print("❌ Invalid JSON in request data", style="red")
1720
+
1721
+ elif action == "analyze":
1722
+ console.print("📊 Analyzing API patterns...", style="blue")
1723
+ console.print("✅ Analysis complete", style="green")
1724
+
1725
+ else:
1726
+ console.print(f"❌ Unknown action: {action}", style="red")
1727
+
1728
+
1436
1729
  if __name__ == "__main__":
1437
1730
  app()
api_mocker/core.py CHANGED
@@ -10,11 +10,13 @@ import uuid
10
10
  class RouteConfig:
11
11
  path: str
12
12
  method: str
13
- response: Union[Dict, Callable, str]
13
+ response: Any
14
14
  status_code: int = 200
15
- headers: Optional[Dict[str, str]] = None
15
+ headers: Dict = None
16
16
  delay: float = 0
17
17
  dynamic: bool = False
18
+ weight: int = 100
19
+ auth_required: bool = False
18
20
 
19
21
  class DynamicResponseGenerator:
20
22
  """Generates realistic fake data for API responses."""
@@ -208,7 +210,7 @@ class CoreEngine:
208
210
  def add_middleware(self, middleware_func: Callable):
209
211
  self.middleware.append(middleware_func)
210
212
 
211
- def process_request(self, path: str, method: str, headers: Dict, body: Any = None) -> Dict:
213
+ def process_request(self, path: str, method: str, headers: Dict, body: Any = None, query_params: Dict = None) -> Dict:
212
214
  # Apply middleware
213
215
  for middleware in self.middleware:
214
216
  path, method, headers, body = middleware(path, method, headers, body)
@@ -218,8 +220,20 @@ class CoreEngine:
218
220
  if not route:
219
221
  return {"status_code": 404, "body": {"error": "Route not found"}}
220
222
 
223
+ # Check Authentication
224
+ if route.auth_required:
225
+ from .auth_system import auth_system
226
+ auth_header = headers.get("authorization") or headers.get("Authorization")
227
+ if not auth_header or not auth_header.startswith("Bearer "):
228
+ return {"status_code": 401, "body": {"error": "Missing or invalid token"}}
229
+
230
+ token = auth_header.split(" ")[1]
231
+ validation = auth_system.verify_token(token)
232
+ if not validation["valid"]:
233
+ return {"status_code": 401, "body": {"error": validation.get("error", "Invalid token")}}
234
+
221
235
  # Generate response
222
- response = self._generate_response(route, path, method, headers, body)
236
+ response = self._generate_response(route, path, method, headers, body, query_params)
223
237
 
224
238
  # Apply delay if specified
225
239
  if route.delay > 0:
@@ -228,10 +242,16 @@ class CoreEngine:
228
242
 
229
243
  return response
230
244
 
231
- def _generate_response(self, route: RouteConfig, path: str, method: str, headers: Dict, body: Any) -> Dict:
245
+ def _generate_response(self, route: RouteConfig, path: str, method: str, headers: Dict, body: Any, query_params: Dict = None) -> Dict:
232
246
  if callable(route.response):
247
+ # Check if the callable accepts query_params
248
+ import inspect
249
+ sig = inspect.signature(route.response)
250
+ if 'query_params' in sig.parameters:
251
+ return route.response(path, method, headers, body, self, query_params)
233
252
  return route.response(path, method, headers, body, self)
234
253
  elif isinstance(route.response, str):
235
254
  return {"status_code": route.status_code, "body": route.response}
236
255
  else:
237
- return {"status_code": route.status_code, "body": route.response}
256
+ return {"status_code": route.status_code, "body": route.response}
257
+