api-mocker 0.3.0__py3-none-any.whl → 0.4.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.
api_mocker/cli.py CHANGED
@@ -16,7 +16,8 @@ from api_mocker.dashboard import DashboardManager
16
16
  from api_mocker.advanced import AdvancedFeatures, RateLimitConfig, CacheConfig, AuthConfig
17
17
  from api_mocker.scenarios import scenario_manager, Scenario, ScenarioCondition, ScenarioResponse, ScenarioType
18
18
  from api_mocker.smart_matching import smart_matcher, ResponseRule, MatchCondition, MatchType
19
- from api_mocker.enhanced_analytics import EnhancedAnalytics, PerformanceMetrics, UsagePattern, APIDependency, CostOptimizationInsight
19
+ from api_mocker.enhanced_analytics import EnhancedAnalytics
20
+ from api_mocker.mock_responses import MockSet, MockAPIResponse, ResponseType, HTTPMethod, create_user_response, create_error_response, create_delayed_response
20
21
 
21
22
  app = typer.Typer(help="api-mocker: The industry-standard, production-ready, free API mocking and development acceleration tool.")
22
23
  console = Console()
@@ -1306,5 +1307,131 @@ def enhanced_analytics(
1306
1307
  raise typer.Exit(1)
1307
1308
 
1308
1309
 
1310
+ @app.command()
1311
+ def mock_responses(
1312
+ action: str = typer.Argument(..., help="Mock response action (create, list, find, test, export, import)"),
1313
+ name: str = typer.Option(None, "--name", "-n", help="Response name"),
1314
+ path: str = typer.Option(None, "--path", "-p", help="Response path"),
1315
+ method: str = typer.Option("GET", "--method", "-m", help="HTTP method"),
1316
+ status_code: int = typer.Option(200, "--status", "-s", help="Status code"),
1317
+ response_type: str = typer.Option("static", "--type", "-t", help="Response type (static, dynamic, templated, conditional, delayed, error)"),
1318
+ file: str = typer.Option(None, "--file", "-f", help="Configuration file"),
1319
+ output: str = typer.Option(None, "--output", "-o", help="Output file")
1320
+ ):
1321
+ """Manage mock API responses with advanced features."""
1322
+
1323
+ if action == "create":
1324
+ if not name or not path:
1325
+ console.print("❌ Name and path are required for creating responses")
1326
+ raise typer.Exit(1)
1327
+
1328
+ # Create mock response based on type
1329
+ if response_type == "static":
1330
+ response = MockAPIResponse(
1331
+ path=path,
1332
+ method=HTTPMethod(method),
1333
+ status_code=status_code,
1334
+ name=name,
1335
+ response_type=ResponseType.STATIC,
1336
+ body={"message": "Static response"}
1337
+ )
1338
+ elif response_type == "templated":
1339
+ response = MockAPIResponse(
1340
+ path=path,
1341
+ method=HTTPMethod(method),
1342
+ status_code=status_code,
1343
+ name=name,
1344
+ response_type=ResponseType.TEMPLATED,
1345
+ template_vars={"id": "123", "name": "John Doe"},
1346
+ body={"id": "{{id}}", "name": "{{name}}"}
1347
+ )
1348
+ elif response_type == "delayed":
1349
+ response = MockAPIResponse(
1350
+ path=path,
1351
+ method=HTTPMethod(method),
1352
+ status_code=status_code,
1353
+ name=name,
1354
+ response_type=ResponseType.DELAYED,
1355
+ delay_ms=1000,
1356
+ body={"message": "Delayed response"}
1357
+ )
1358
+ elif response_type == "error":
1359
+ response = MockAPIResponse(
1360
+ path=path,
1361
+ method=HTTPMethod(method),
1362
+ status_code=500,
1363
+ name=name,
1364
+ response_type=ResponseType.ERROR,
1365
+ error_probability=1.0,
1366
+ body={"error": "Simulated error"}
1367
+ )
1368
+ else:
1369
+ response = create_user_response("123", "John Doe")
1370
+ response.name = name
1371
+ response.path = path
1372
+ response.method = HTTPMethod(method)
1373
+ response.status_code = status_code
1374
+
1375
+ console.print(f"✅ Created mock response: {name}")
1376
+
1377
+ elif action == "list":
1378
+ # This would typically load from a file or database
1379
+ console.print("📋 Available mock responses:")
1380
+ console.print(" (Use 'create' to add responses)")
1381
+
1382
+ elif action == "find":
1383
+ if not path:
1384
+ console.print("❌ Path is required for finding responses")
1385
+ raise typer.Exit(1)
1386
+
1387
+ # Simulate finding responses
1388
+ console.print(f"🔍 Searching for responses matching: {path}")
1389
+ console.print(" (Use 'create' to add responses first)")
1390
+
1391
+ elif action == "test":
1392
+ if not path:
1393
+ console.print("❌ Path is required for testing responses")
1394
+ raise typer.Exit(1)
1395
+
1396
+ # Create a test response and test it
1397
+ test_response = create_user_response("123", "John Doe")
1398
+ test_response.path = path
1399
+ test_response.method = HTTPMethod(method)
1400
+
1401
+ result = test_response.generate_response()
1402
+ console.print(f"🧪 Test response for {path}:")
1403
+ console.print(f" Status: {result['status_code']}")
1404
+ console.print(f" Body: {result['body']}")
1405
+
1406
+ elif action == "export":
1407
+ if not output:
1408
+ output = f"mock_responses_{int(time.time())}.yaml"
1409
+
1410
+ # Create a sample mock set and export it
1411
+ mock_set = MockSet("sample_mocks")
1412
+ mock_set.add_response(create_user_response("123", "John Doe"))
1413
+ mock_set.add_response(create_error_response(404, "Not found"))
1414
+ mock_set.add_response(create_delayed_response(1000))
1415
+
1416
+ mock_set.save_to_file(output)
1417
+ console.print(f"✅ Mock responses exported to {output}")
1418
+
1419
+ elif action == "import":
1420
+ if not file:
1421
+ console.print("❌ File is required for importing responses")
1422
+ raise typer.Exit(1)
1423
+
1424
+ try:
1425
+ mock_set = MockSet.load_from_file(file)
1426
+ console.print(f"✅ Imported {len(mock_set.responses)} responses from {file}")
1427
+ except Exception as e:
1428
+ console.print(f"❌ Error importing from {file}: {e}")
1429
+ raise typer.Exit(1)
1430
+
1431
+ else:
1432
+ console.print(f"❌ Unknown action: {action}")
1433
+ raise typer.Exit(1)
1434
+
1435
+
1309
1436
  if __name__ == "__main__":
1310
1437
  app()
@@ -0,0 +1,622 @@
1
+ """
2
+ Mock API Response Management System
3
+
4
+ This module provides comprehensive functionality for creating and managing mock API responses
5
+ with support for pytest integration, automated testing, and efficient response management.
6
+ """
7
+
8
+ import json
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Dict, List, Optional, Union, Callable
12
+ from enum import Enum
13
+ import pytest
14
+ from pathlib import Path
15
+ import yaml
16
+
17
+
18
+ class ResponseType(Enum):
19
+ """Types of mock responses"""
20
+ STATIC = "static"
21
+ DYNAMIC = "dynamic"
22
+ TEMPLATED = "templated"
23
+ CONDITIONAL = "conditional"
24
+ DELAYED = "delayed"
25
+ ERROR = "error"
26
+
27
+
28
+ class HTTPMethod(Enum):
29
+ """HTTP methods supported by mock responses"""
30
+ GET = "GET"
31
+ POST = "POST"
32
+ PUT = "PUT"
33
+ DELETE = "DELETE"
34
+ PATCH = "PATCH"
35
+ HEAD = "HEAD"
36
+ OPTIONS = "OPTIONS"
37
+
38
+
39
+ @dataclass
40
+ class MockAPIResponse:
41
+ """
42
+ Core class for creating and managing mock API responses.
43
+
44
+ This class provides comprehensive functionality for defining mock responses
45
+ with support for static data, dynamic generation, templating, and conditional logic.
46
+ """
47
+
48
+ # Basic response properties
49
+ path: str
50
+ method: HTTPMethod = HTTPMethod.GET
51
+ status_code: int = 200
52
+ headers: Dict[str, str] = field(default_factory=dict)
53
+ body: Any = None
54
+
55
+ # Response type and behavior
56
+ response_type: ResponseType = ResponseType.STATIC
57
+ delay_ms: int = 0
58
+ error_probability: float = 0.0
59
+
60
+ # Conditional logic
61
+ conditions: List[Dict[str, Any]] = field(default_factory=list)
62
+ priority: int = 0
63
+
64
+ # Dynamic response properties
65
+ template_vars: Dict[str, Any] = field(default_factory=dict)
66
+ generator_func: Optional[Callable] = None
67
+ cache_ttl: int = 300 # 5 minutes default
68
+
69
+ # Metadata
70
+ name: Optional[str] = None
71
+ description: Optional[str] = None
72
+ tags: List[str] = field(default_factory=list)
73
+ created_at: float = field(default_factory=time.time)
74
+ updated_at: float = field(default_factory=time.time)
75
+
76
+ def __post_init__(self):
77
+ """Initialize response after creation"""
78
+ if self.name is None:
79
+ # Clean up path for name generation - remove leading slash and replace with single underscore
80
+ clean_path = self.path.lstrip('/').replace('/', '_').replace('{', '').replace('}', '')
81
+ self.name = f"{self.method.value}_{clean_path}"
82
+
83
+ # Set default headers if not provided
84
+ if not self.headers:
85
+ self.headers = {
86
+ "Content-Type": "application/json",
87
+ "X-Mock-Response": "true"
88
+ }
89
+
90
+ def matches_request(self, request_path: str, request_method: str,
91
+ request_headers: Dict[str, str] = None,
92
+ **kwargs) -> bool:
93
+ """
94
+ Check if this response matches the given request.
95
+
96
+ Args:
97
+ request_path: The request path
98
+ request_method: The HTTP method
99
+ request_headers: Request headers
100
+ request_body: Request body
101
+
102
+ Returns:
103
+ bool: True if response matches request
104
+ """
105
+ # Basic path and method matching
106
+ if not self._path_matches(request_path) or self.method.value != request_method:
107
+ return False
108
+
109
+ # Check conditions if any
110
+ if self.conditions:
111
+ request_body = kwargs.get('body')
112
+ return self._check_conditions(request_headers, request_body)
113
+
114
+ return True
115
+
116
+ def _path_matches(self, request_path: str) -> bool:
117
+ """Check if the request path matches this response's path"""
118
+ # Exact match
119
+ if self.path == request_path:
120
+ return True
121
+
122
+ # Pattern matching with wildcards
123
+ if '*' in self.path:
124
+ return self._wildcard_match(request_path)
125
+
126
+ # Parameter matching (e.g., /users/{id})
127
+ if '{' in self.path:
128
+ return self._parameter_match(request_path)
129
+
130
+ return False
131
+
132
+ def _wildcard_match(self, request_path: str) -> bool:
133
+ """Match paths with wildcards"""
134
+ import re
135
+ pattern = self.path.replace('*', '.*')
136
+ return bool(re.match(pattern, request_path))
137
+
138
+ def _parameter_match(self, request_path: str) -> bool:
139
+ """Match paths with parameters"""
140
+ import re
141
+ # Convert /users/{id} to regex pattern
142
+ pattern = re.sub(r'\{[^}]+\}', r'[^/]+', self.path)
143
+ # Add end anchor to prevent partial matches
144
+ pattern = pattern + '$'
145
+ return bool(re.match(pattern, request_path))
146
+
147
+ def _check_conditions(self, headers: Dict[str, str] = None,
148
+ body: Any = None) -> bool:
149
+ """Check if request meets all conditions"""
150
+ if not headers:
151
+ headers = {}
152
+
153
+ for condition in self.conditions:
154
+ if not self._evaluate_condition(condition, headers, body):
155
+ return False
156
+
157
+ return True
158
+
159
+ def _evaluate_condition(self, condition: Dict[str, Any],
160
+ headers: Dict[str, str], body: Any) -> bool:
161
+ """Evaluate a single condition"""
162
+ condition_type = condition.get('type', 'header')
163
+
164
+ if condition_type == 'header':
165
+ header_name = condition.get('name')
166
+ expected_value = condition.get('value')
167
+ return headers.get(header_name) == expected_value
168
+
169
+ elif condition_type == 'body':
170
+ field_path = condition.get('field')
171
+ expected_value = condition.get('value')
172
+ actual_value = self._get_nested_value(body, field_path)
173
+ return actual_value == expected_value
174
+
175
+ elif condition_type == 'custom':
176
+ func = condition.get('function')
177
+ return func(headers, body) if callable(func) else False
178
+
179
+ return False
180
+
181
+ def _get_nested_value(self, obj: Any, path: str) -> Any:
182
+ """Get nested value from object using dot notation"""
183
+ if not path:
184
+ return obj
185
+
186
+ keys = path.split('.')
187
+ current = obj
188
+
189
+ for key in keys:
190
+ if isinstance(current, dict):
191
+ current = current.get(key)
192
+ elif isinstance(current, list) and key.isdigit():
193
+ current = current[int(key)]
194
+ else:
195
+ return None
196
+
197
+ return current
198
+
199
+ def generate_response(self, request_context: Dict[str, Any] = None) -> Dict[str, Any]:
200
+ """
201
+ Generate the actual response based on type and context.
202
+
203
+ Args:
204
+ request_context: Additional context for response generation
205
+
206
+ Returns:
207
+ Dict containing status_code, headers, and body
208
+ """
209
+ # Check for errors
210
+ if self.error_probability > 0 and self._should_return_error():
211
+ return self._generate_error_response()
212
+
213
+ # Apply delay if specified
214
+ if self.delay_ms > 0:
215
+ time.sleep(self.delay_ms / 1000.0)
216
+
217
+ # Generate response based on type
218
+ if self.response_type == ResponseType.STATIC:
219
+ body = self.body
220
+ elif self.response_type == ResponseType.DYNAMIC:
221
+ body = self._generate_dynamic_response(request_context)
222
+ elif self.response_type == ResponseType.TEMPLATED:
223
+ body = self._generate_templated_response(request_context)
224
+ else:
225
+ body = self.body
226
+
227
+ return {
228
+ 'status_code': self.status_code,
229
+ 'headers': self.headers.copy(),
230
+ 'body': body
231
+ }
232
+
233
+ def _should_return_error(self) -> bool:
234
+ """Determine if an error should be returned based on probability"""
235
+ import random
236
+ return random.random() < self.error_probability
237
+
238
+ def _generate_error_response(self) -> Dict[str, Any]:
239
+ """Generate an error response"""
240
+ return {
241
+ 'status_code': 500,
242
+ 'headers': {
243
+ 'Content-Type': 'application/json',
244
+ 'X-Mock-Error': 'true'
245
+ },
246
+ 'body': {
247
+ 'error': 'Internal Server Error',
248
+ 'message': 'Mock error response',
249
+ 'timestamp': time.time()
250
+ }
251
+ }
252
+
253
+ def _generate_dynamic_response(self, context: Dict[str, Any] = None) -> Any:
254
+ """Generate dynamic response using generator function"""
255
+ if self.generator_func:
256
+ return self.generator_func(context or {})
257
+ return self.body
258
+
259
+ def _generate_templated_response(self, context: Dict[str, Any] = None) -> Any:
260
+ """Generate templated response with variable substitution"""
261
+ if isinstance(self.body, dict):
262
+ # Handle dictionary body with template variables
263
+ result = {}
264
+ vars_dict = {**self.template_vars, **(context or {})}
265
+
266
+ for key, value in self.body.items():
267
+ if isinstance(value, str):
268
+ # Replace template variables in string values
269
+ for var_key, var_value in vars_dict.items():
270
+ value = value.replace(f'{{{{{var_key}}}}}', str(var_value))
271
+ result[key] = value
272
+ return result
273
+ elif isinstance(self.body, str):
274
+ template = self.body
275
+ vars_dict = {**self.template_vars, **(context or {})}
276
+
277
+ for key, value in vars_dict.items():
278
+ template = template.replace(f'{{{{{key}}}}}', str(value))
279
+
280
+ try:
281
+ return json.loads(template)
282
+ except json.JSONDecodeError:
283
+ return template
284
+
285
+ return self.body
286
+
287
+ def to_dict(self) -> Dict[str, Any]:
288
+ """Convert response to dictionary for serialization"""
289
+ return {
290
+ 'name': self.name,
291
+ 'path': self.path,
292
+ 'method': self.method.value,
293
+ 'status_code': self.status_code,
294
+ 'headers': self.headers,
295
+ 'body': self.body,
296
+ 'response_type': self.response_type.value,
297
+ 'delay_ms': self.delay_ms,
298
+ 'error_probability': self.error_probability,
299
+ 'conditions': self.conditions,
300
+ 'priority': self.priority,
301
+ 'template_vars': self.template_vars,
302
+ 'description': self.description,
303
+ 'tags': self.tags,
304
+ 'created_at': self.created_at,
305
+ 'updated_at': self.updated_at
306
+ }
307
+
308
+ @classmethod
309
+ def from_dict(cls, data: Dict[str, Any]) -> 'MockAPIResponse':
310
+ """Create response from dictionary"""
311
+ data = data.copy()
312
+ data['method'] = HTTPMethod(data['method'])
313
+ data['response_type'] = ResponseType(data['response_type'])
314
+ return cls(**data)
315
+
316
+ def update(self, **kwargs) -> None:
317
+ """Update response properties"""
318
+ for key, value in kwargs.items():
319
+ if hasattr(self, key):
320
+ setattr(self, key, value)
321
+ self.updated_at = time.time()
322
+
323
+
324
+ @dataclass
325
+ class MockSet:
326
+ """
327
+ Efficient collection for managing multiple mock responses.
328
+
329
+ Provides fast lookup, filtering, and management capabilities for large
330
+ collections of mock responses.
331
+ """
332
+
333
+ name: str
334
+ responses: List[MockAPIResponse] = field(default_factory=list)
335
+ metadata: Dict[str, Any] = field(default_factory=dict)
336
+
337
+ def __post_init__(self):
338
+ """Initialize the mock set"""
339
+ self._build_index()
340
+
341
+ def _build_index(self) -> None:
342
+ """Build internal indexes for fast lookup"""
343
+ self._path_index = {}
344
+ self._method_index = {}
345
+ self._tag_index = {}
346
+ self._name_index = {}
347
+
348
+ for response in self.responses:
349
+ # Index by path
350
+ if response.path not in self._path_index:
351
+ self._path_index[response.path] = []
352
+ self._path_index[response.path].append(response)
353
+
354
+ # Index by method
355
+ method_key = response.method.value
356
+ if method_key not in self._method_index:
357
+ self._method_index[method_key] = []
358
+ self._method_index[method_key].append(response)
359
+
360
+ # Index by tags
361
+ for tag in response.tags:
362
+ if tag not in self._tag_index:
363
+ self._tag_index[tag] = []
364
+ self._tag_index[tag].append(response)
365
+
366
+ # Index by name
367
+ self._name_index[response.name] = response
368
+
369
+ def add_response(self, response: MockAPIResponse) -> None:
370
+ """Add a response to the set"""
371
+ self.responses.append(response)
372
+ self._build_index()
373
+
374
+ def remove_response(self, response_name: str) -> bool:
375
+ """Remove a response by name"""
376
+ if response_name in self._name_index:
377
+ response = self._name_index[response_name]
378
+ self.responses.remove(response)
379
+ self._build_index()
380
+ return True
381
+ return False
382
+
383
+ def find_matching_response(self, path: str, method: str,
384
+ headers: Dict[str, str] = None,
385
+ body: Any = None) -> Optional[MockAPIResponse]:
386
+ """
387
+ Find the best matching response for a request.
388
+
389
+ Returns the highest priority response that matches the request.
390
+ """
391
+ matching_responses = []
392
+
393
+ # Find all responses that match the request
394
+ for response in self.responses:
395
+ if response.matches_request(path, method, headers, body):
396
+ matching_responses.append(response)
397
+
398
+ if not matching_responses:
399
+ return None
400
+
401
+ # Return the highest priority response
402
+ return max(matching_responses, key=lambda r: r.priority)
403
+
404
+ def get_by_path(self, path: str) -> List[MockAPIResponse]:
405
+ """Get all responses for a specific path"""
406
+ return self._path_index.get(path, [])
407
+
408
+ def get_by_method(self, method: str) -> List[MockAPIResponse]:
409
+ """Get all responses for a specific HTTP method"""
410
+ return self._method_index.get(method, [])
411
+
412
+ def get_by_tag(self, tag: str) -> List[MockAPIResponse]:
413
+ """Get all responses with a specific tag"""
414
+ return self._tag_index.get(tag, [])
415
+
416
+ def get_by_name(self, name: str) -> Optional[MockAPIResponse]:
417
+ """Get a response by name"""
418
+ return self._name_index.get(name)
419
+
420
+ def filter(self, **kwargs) -> List[MockAPIResponse]:
421
+ """Filter responses by multiple criteria"""
422
+ filtered = self.responses
423
+
424
+ for key, value in kwargs.items():
425
+ if key == 'status_code':
426
+ filtered = [r for r in filtered if r.status_code == value]
427
+ elif key == 'response_type':
428
+ filtered = [r for r in filtered if r.response_type == value]
429
+ elif key == 'tags':
430
+ if isinstance(value, str):
431
+ filtered = [r for r in filtered if value in r.tags]
432
+ else:
433
+ filtered = [r for r in filtered if any(tag in r.tags for tag in value)]
434
+
435
+ return filtered
436
+
437
+ def to_dict(self) -> Dict[str, Any]:
438
+ """Convert mock set to dictionary"""
439
+ return {
440
+ 'name': self.name,
441
+ 'responses': [r.to_dict() for r in self.responses],
442
+ 'metadata': self.metadata
443
+ }
444
+
445
+ @classmethod
446
+ def from_dict(cls, data: Dict[str, Any]) -> 'MockSet':
447
+ """Create mock set from dictionary"""
448
+ responses = [MockAPIResponse.from_dict(r) for r in data['responses']]
449
+ return cls(
450
+ name=data['name'],
451
+ responses=responses,
452
+ metadata=data.get('metadata', {})
453
+ )
454
+
455
+ def save_to_file(self, filepath: str) -> None:
456
+ """Save mock set to file"""
457
+ with open(filepath, 'w') as f:
458
+ yaml.dump(self.to_dict(), f, default_flow_style=False)
459
+
460
+ @classmethod
461
+ def load_from_file(cls, filepath: str) -> 'MockSet':
462
+ """Load mock set from file"""
463
+ with open(filepath, 'r') as f:
464
+ data = yaml.safe_load(f)
465
+ return cls.from_dict(data)
466
+
467
+
468
+ # Example subclasses for common API interactions
469
+ class CommitResponse(MockAPIResponse):
470
+ """Mock response for Git commit operations"""
471
+
472
+ def __init__(self, **kwargs):
473
+ super().__init__(
474
+ path="/repos/{owner}/{repo}/git/commits",
475
+ method=HTTPMethod.POST,
476
+ status_code=201,
477
+ response_type=ResponseType.TEMPLATED,
478
+ template_vars={
479
+ 'sha': 'abc123def456',
480
+ 'message': 'feat: add new feature',
481
+ 'author': 'John Doe'
482
+ },
483
+ body={
484
+ 'sha': '{{sha}}',
485
+ 'message': '{{message}}',
486
+ 'author': {
487
+ 'name': '{{author}}',
488
+ 'email': 'john@example.com'
489
+ },
490
+ 'committer': {
491
+ 'name': '{{author}}',
492
+ 'email': 'john@example.com'
493
+ }
494
+ },
495
+ **kwargs
496
+ )
497
+
498
+
499
+ class ForkResponse(MockAPIResponse):
500
+ """Mock response for repository fork operations"""
501
+
502
+ def __init__(self, **kwargs):
503
+ super().__init__(
504
+ path="/repos/{owner}/{repo}/forks",
505
+ method=HTTPMethod.POST,
506
+ status_code=202,
507
+ response_type=ResponseType.STATIC,
508
+ body={
509
+ 'id': 12345,
510
+ 'name': 'forked-repo',
511
+ 'full_name': 'new-owner/forked-repo',
512
+ 'fork': True,
513
+ 'source': {
514
+ 'id': 67890,
515
+ 'name': 'original-repo',
516
+ 'full_name': 'original-owner/original-repo'
517
+ }
518
+ },
519
+ **kwargs
520
+ )
521
+
522
+
523
+ class PushResponse(MockAPIResponse):
524
+ """Mock response for Git push operations"""
525
+
526
+ def __init__(self, **kwargs):
527
+ super().__init__(
528
+ path="/repos/{owner}/{repo}/git/refs/heads/{branch}",
529
+ method=HTTPMethod.PATCH,
530
+ status_code=200,
531
+ response_type=ResponseType.TEMPLATED,
532
+ template_vars={
533
+ 'ref': 'refs/heads/main',
534
+ 'sha': 'def456ghi789'
535
+ },
536
+ body={
537
+ 'ref': '{{ref}}',
538
+ 'sha': '{{sha}}',
539
+ 'url': 'https://api.github.com/repos/owner/repo/git/refs/heads/main'
540
+ },
541
+ **kwargs
542
+ )
543
+
544
+
545
+ class ForcePushResponse(MockAPIResponse):
546
+ """Mock response for force push operations"""
547
+
548
+ def __init__(self, **kwargs):
549
+ super().__init__(
550
+ path="/repos/{owner}/{repo}/git/refs/heads/{branch}",
551
+ method=HTTPMethod.PATCH,
552
+ status_code=200,
553
+ response_type=ResponseType.STATIC,
554
+ body={
555
+ 'ref': 'refs/heads/main',
556
+ 'sha': 'force123push456',
557
+ 'force': True,
558
+ 'url': 'https://api.github.com/repos/owner/repo/git/refs/heads/main'
559
+ },
560
+ **kwargs
561
+ )
562
+
563
+
564
+ # Pytest fixture for easy integration
565
+ @pytest.fixture
566
+ def setup_api_mocks():
567
+ """
568
+ Pytest fixture for setting up mock API responses in tests.
569
+
570
+ Usage:
571
+ def test_api_call(setup_api_mocks):
572
+ mock_set = setup_api_mocks
573
+ mock_set.add_response(CommitResponse())
574
+ # Your test code here
575
+ """
576
+ mock_set = MockSet("test_mocks")
577
+ return mock_set
578
+
579
+
580
+ # Convenience functions for common operations
581
+ def create_user_response(user_id: str = "123", name: str = "John Doe") -> MockAPIResponse:
582
+ """Create a mock user response"""
583
+ return MockAPIResponse(
584
+ path=f"/users/{user_id}",
585
+ method=HTTPMethod.GET,
586
+ status_code=200,
587
+ response_type=ResponseType.TEMPLATED,
588
+ template_vars={'user_id': user_id, 'name': name},
589
+ body={
590
+ 'id': '{{user_id}}',
591
+ 'name': '{{name}}',
592
+ 'email': 'john@example.com',
593
+ 'created_at': '2023-01-01T00:00:00Z'
594
+ }
595
+ )
596
+
597
+
598
+ def create_error_response(status_code: int = 404, message: str = "Not found") -> MockAPIResponse:
599
+ """Create a mock error response"""
600
+ return MockAPIResponse(
601
+ path="*",
602
+ method=HTTPMethod.GET,
603
+ status_code=status_code,
604
+ response_type=ResponseType.STATIC,
605
+ body={
606
+ 'error': True,
607
+ 'message': message,
608
+ 'status_code': status_code
609
+ }
610
+ )
611
+
612
+
613
+ def create_delayed_response(delay_ms: int = 1000) -> MockAPIResponse:
614
+ """Create a mock response with delay"""
615
+ return MockAPIResponse(
616
+ path="/slow-endpoint",
617
+ method=HTTPMethod.GET,
618
+ status_code=200,
619
+ response_type=ResponseType.STATIC,
620
+ delay_ms=delay_ms,
621
+ body={'message': 'Response delayed'}
622
+ )
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-mocker
3
- Version: 0.3.0
4
- Summary: 🚀 The Ultimate API Development Acceleration Tool - 3000+ Downloads! Production-ready FastAPI mock server with AI-powered generation, scenario-based mocking, smart response matching, enhanced analytics, and comprehensive testing framework.
3
+ Version: 0.4.0
4
+ Summary: 🚀 The Ultimate API Development Acceleration Tool - 3000+ Downloads! Production-ready FastAPI mock server with AI-powered generation, scenario-based mocking, smart response matching, enhanced analytics, comprehensive testing framework, and advanced mock response management system.
5
5
  Author-email: sherin joseph roy <sherin.joseph2217@gmail.com>
6
6
  License: MIT License
7
7
 
@@ -2,11 +2,12 @@ api_mocker/__init__.py,sha256=4krN1yJyngDrqVf6weYU5n3cpHpej8tE97frHPXxYqM,168
2
2
  api_mocker/advanced.py,sha256=vf7pzC-ouVgT7PkSSMKLa423Z--Lj9ihC-OCNAhPOro,13429
3
3
  api_mocker/ai_generator.py,sha256=mdha8_9HKiD9CKOT2MnJvaPC_59RwFW5NcUsGuKPP2c,17420
4
4
  api_mocker/analytics.py,sha256=dO4uuoi-YmY6bSBYYahiwnXvTXOylR6RVDiq46UIMoA,14205
5
- api_mocker/cli.py,sha256=SwRZv2OL970YysfrT7YL8BqcbUNYxLn3s6J7EKAfO4Q,55523
5
+ api_mocker/cli.py,sha256=5DQlVRddvvEmajXHqlnOWkTc06mNvtZTyWe7WPftkYU,60718
6
6
  api_mocker/config.py,sha256=zNlJCk1Bs0BrGU-92wiFv2ZTBRu9dJQ6sF8Dh6kIhLQ,913
7
7
  api_mocker/core.py,sha256=K3rP5_cJIEpr02Qgcc_n1Ga3KPo4HumsA6Dlynaj_nQ,8478
8
8
  api_mocker/dashboard.py,sha256=OnZOTNgKXgDU3FON6avwZ4R7NRJjqCUhQePadvRBHHM,14000
9
9
  api_mocker/enhanced_analytics.py,sha256=cSTLOft7oKZwDuy5ibUvfuSfRHmkAr9GQYU5DvtVOwI,23028
10
+ api_mocker/mock_responses.py,sha256=au9-aXBXqfct_hLhCbwYgbNEfxJqPTCmETqJVsbszqU,21153
10
11
  api_mocker/openapi.py,sha256=Pb1gKbBWosEV5i739rW0Nb3ArNq62lgMN0ecyvigNKY,7403
11
12
  api_mocker/plugins.py,sha256=OK3OVHJszDky46JHntMVsZUH1ajBjBhAKq3TCDYuxWI,8178
12
13
  api_mocker/recorder.py,sha256=7tiT2Krxy3nLDxFAE7rpZSimuD-rKeiwdU72cp0dg6E,9984
@@ -14,9 +15,9 @@ api_mocker/scenarios.py,sha256=wadcxu4Gp8w7i-UlPr6PlbcYnrSd1ehZA81e9dxGTgc,13392
14
15
  api_mocker/server.py,sha256=xfczRj4xFXGVaGn2pVPgGvYyv3IHUlYTEz3Hop1KQu0,3812
15
16
  api_mocker/smart_matching.py,sha256=DvTSKQwo4MhPEUHWdV3zF_H_dmp-l-47I59zz41tNe0,15067
16
17
  api_mocker/testing.py,sha256=z4yJqS5MaSBOThpf3GtUY4dCzXTgopmnGnCuvnmKkF4,24949
17
- api_mocker-0.3.0.dist-info/licenses/LICENSE,sha256=FzyeLcPe623lrwpFx3xQ3W0Hb_S2sbHqLzhSXaTmcGg,1074
18
- api_mocker-0.3.0.dist-info/METADATA,sha256=nCBFAoH3iip-BahAo_5Cf5zHfXtf1gw4gKEkpzN_MCs,14512
19
- api_mocker-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- api_mocker-0.3.0.dist-info/entry_points.txt,sha256=dj0UIkQ36Uq3oeSjGzmRRUQKFriq4WMCzg7TCor7wkM,51
21
- api_mocker-0.3.0.dist-info/top_level.txt,sha256=ZcowEudKsJ6xbvOXIno2zZcPhjB-gGO1w7uzoUKRKDM,11
22
- api_mocker-0.3.0.dist-info/RECORD,,
18
+ api_mocker-0.4.0.dist-info/licenses/LICENSE,sha256=FzyeLcPe623lrwpFx3xQ3W0Hb_S2sbHqLzhSXaTmcGg,1074
19
+ api_mocker-0.4.0.dist-info/METADATA,sha256=TIiH9dsICsrjrA69vqUuQT1y8VgMrJc3Ixi5y4F3hlI,14554
20
+ api_mocker-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ api_mocker-0.4.0.dist-info/entry_points.txt,sha256=dj0UIkQ36Uq3oeSjGzmRRUQKFriq4WMCzg7TCor7wkM,51
22
+ api_mocker-0.4.0.dist-info/top_level.txt,sha256=ZcowEudKsJ6xbvOXIno2zZcPhjB-gGO1w7uzoUKRKDM,11
23
+ api_mocker-0.4.0.dist-info/RECORD,,