mcp-security-framework 2.3.0__tar.gz → 2.3.1__tar.gz

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.
Files changed (66) hide show
  1. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/PKG-INFO +5 -1
  2. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/__init__.py +1 -1
  3. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/cert_manager.py +18 -1
  4. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/ssl_manager.py +1 -0
  5. mcp_security_framework-2.3.1/mcp_security_framework/examples/tls_certificate_regressions_example.py +142 -0
  6. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/fastapi_middleware.py +1 -1
  7. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/schemas/config.py +19 -13
  8. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework.egg-info/PKG-INFO +5 -1
  9. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework.egg-info/SOURCES.txt +1 -0
  10. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework.egg-info/requires.txt +4 -0
  11. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/pyproject.toml +6 -2
  12. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/LICENSE +0 -0
  13. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/README.md +0 -0
  14. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/cli/__init__.py +0 -0
  15. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/cli/cert_cli.py +0 -0
  16. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/cli/security_cli.py +0 -0
  17. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/constants.py +0 -0
  18. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/__init__.py +0 -0
  19. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/adapter_wrapper.py +0 -0
  20. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/audit_logger.py +0 -0
  21. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/auth_manager.py +0 -0
  22. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/permission_manager.py +0 -0
  23. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/rate_limiter.py +0 -0
  24. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/security_adapter.py +0 -0
  25. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/core/security_manager.py +0 -0
  26. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/__init__.py +0 -0
  27. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/adapter_example.py +0 -0
  28. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/auth_methods_example.py +0 -0
  29. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/cert_inspection_example.py +0 -0
  30. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/certificate_roles_example.py +0 -0
  31. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/comprehensive_example.py +0 -0
  32. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/crl_retrieval_example.py +0 -0
  33. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/fastapi_example.py +0 -0
  34. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/flask_example.py +0 -0
  35. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/gateway_example.py +0 -0
  36. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/microservice_example.py +0 -0
  37. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/protected_keys_example.py +0 -0
  38. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/examples/standalone_example.py +0 -0
  39. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/__init__.py +0 -0
  40. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/auth_middleware.py +0 -0
  41. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/fastapi_auth_middleware.py +0 -0
  42. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/flask_auth_middleware.py +0 -0
  43. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/flask_middleware.py +0 -0
  44. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/mtls_middleware.py +0 -0
  45. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/rate_limit_middleware.py +0 -0
  46. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/middleware/security_middleware.py +0 -0
  47. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/schemas/__init__.py +0 -0
  48. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/schemas/models.py +0 -0
  49. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/schemas/operation_context.py +0 -0
  50. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/schemas/responses.py +0 -0
  51. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/__init__.py +0 -0
  52. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/__init__.py +0 -0
  53. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/chain.py +0 -0
  54. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/errors.py +0 -0
  55. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/inspection.py +0 -0
  56. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/parsing.py +0 -0
  57. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/revocation.py +0 -0
  58. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/cert_utils/roles.py +0 -0
  59. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/crl_client.py +0 -0
  60. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/crypto_utils.py +0 -0
  61. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/datetime_compat.py +0 -0
  62. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework/utils/validation_utils.py +0 -0
  63. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework.egg-info/dependency_links.txt +0 -0
  64. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework.egg-info/entry_points.txt +0 -0
  65. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/mcp_security_framework.egg-info/top_level.txt +0 -0
  66. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-security-framework
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Added extensible security adapter system for custom operation validation, structured audit logging, and operation context support. Comprehensive test coverage with 1104+ tests. Fully backward compatible. Requires cryptography>=42.0.0 for certificate operations.
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
@@ -60,6 +60,10 @@ Requires-Dist: pytest>=7.0.0; extra == "test"
60
60
  Requires-Dist: pytest-cov>=4.0.0; extra == "test"
61
61
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
62
62
  Requires-Dist: pytest-mock>=3.8.0; extra == "test"
63
+ Requires-Dist: fastapi>=0.68.0; extra == "test"
64
+ Requires-Dist: uvicorn[standard]>=0.15.0; extra == "test"
65
+ Requires-Dist: flask>=2.0.0; extra == "test"
66
+ Requires-Dist: flask-cors>=3.0.0; extra == "test"
63
67
  Requires-Dist: httpx>=0.24.0; extra == "test"
64
68
  Dynamic: license-file
65
69
 
@@ -79,7 +79,7 @@ from mcp_security_framework.schemas.responses import (
79
79
  )
80
80
 
81
81
  # Version information
82
- __version__ = "2.3.0"
82
+ __version__ = "2.3.1"
83
83
  __author__ = "Vasiliy Zdanovskiy"
84
84
  __email__ = "vasilyvz@gmail.com"
85
85
  __license__ = "MIT"
@@ -27,6 +27,7 @@ Version: 1.0.0
27
27
  License: MIT
28
28
  """
29
29
 
30
+ import ipaddress
30
31
  import logging
31
32
  import os
32
33
  from datetime import datetime, timedelta, timezone
@@ -248,6 +249,14 @@ def _certificate_type_of(certificate) -> CertificateType:
248
249
  return CertificateType.CLIENT
249
250
 
250
251
 
252
+ def _subject_alt_name(value: str) -> x509.GeneralName:
253
+ """Return the correct SAN type for a DNS name or IP literal string."""
254
+ try:
255
+ return x509.IPAddress(ipaddress.ip_address(value))
256
+ except ValueError:
257
+ return x509.DNSName(value)
258
+
259
+
251
260
  class CertificateManager:
252
261
  """
253
262
  Certificate Management Class
@@ -1023,6 +1032,10 @@ class CertificateManager:
1023
1032
  x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
1024
1033
  critical=False,
1025
1034
  )
1035
+ builder = builder.add_extension(
1036
+ x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
1037
+ critical=False,
1038
+ )
1026
1039
 
1027
1040
  # Add roles and permissions to certificate extensions
1028
1041
  # Validate and normalize roles using CertificateRole enum
@@ -1333,11 +1346,15 @@ class CertificateManager:
1333
1346
  x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
1334
1347
  critical=False,
1335
1348
  )
1349
+ builder = builder.add_extension(
1350
+ x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
1351
+ critical=False,
1352
+ )
1336
1353
 
1337
1354
  # Add Subject Alternative Name (SAN) if provided
1338
1355
  if server_config.subject_alt_names:
1339
1356
  san_names = [
1340
- x509.DNSName(name) for name in server_config.subject_alt_names
1357
+ _subject_alt_name(name) for name in server_config.subject_alt_names
1341
1358
  ]
1342
1359
 
1343
1360
  builder = builder.add_extension(
@@ -518,6 +518,7 @@ class SSLManager:
518
518
  else:
519
519
  # For other modes, use default context with verification
520
520
  context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
521
+ context.check_hostname = bool(self.config.check_hostname)
521
522
  context.verify_mode = self._get_verify_mode(actual_verify_mode)
522
523
 
523
524
  # Set minimum TLS version
@@ -0,0 +1,142 @@
1
+ """
2
+ TLS and certificate regression examples.
3
+
4
+ These examples are deliberately small and file-backed. They exercise public
5
+ configuration and certificate APIs with real SSL contexts and X.509 artifacts,
6
+ so callers can copy the patterns into their own applications without relying on
7
+ test doubles.
8
+ """
9
+
10
+ import ipaddress
11
+ from pathlib import Path
12
+ from tempfile import TemporaryDirectory
13
+ from typing import Dict
14
+
15
+ from cryptography import x509
16
+ from cryptography.x509.oid import ExtensionOID
17
+
18
+ from mcp_security_framework import SSLConfig
19
+ from mcp_security_framework.core.cert_manager import CertificateManager
20
+ from mcp_security_framework.core.ssl_manager import SSLManager
21
+ from mcp_security_framework.schemas.config import (
22
+ CAConfig,
23
+ CertificateConfig,
24
+ ServerCertConfig,
25
+ )
26
+
27
+
28
+ def _manager(root: Path) -> CertificateManager:
29
+ """Create a CertificateManager that writes only under the given root."""
30
+ return CertificateManager(
31
+ CertificateConfig(
32
+ enabled=True,
33
+ ca_creation_mode=True,
34
+ cert_storage_path=str(root / "certs"),
35
+ key_storage_path=str(root / "keys"),
36
+ )
37
+ )
38
+
39
+
40
+ def ca_only_client_tls_config_is_valid() -> SSLConfig:
41
+ """Build one-way client TLS config that trusts a CA without client identity."""
42
+ with TemporaryDirectory(prefix="sf-ca-only-client-tls-") as tmp:
43
+ root = Path(tmp)
44
+ ca_pair = _manager(root).create_root_ca(
45
+ CAConfig(
46
+ common_name="Example CA",
47
+ organization="Example Org",
48
+ key_size=2048,
49
+ )
50
+ )
51
+ return SSLConfig(
52
+ enabled=True,
53
+ verify=True,
54
+ ca_cert_file=ca_pair.certificate_path,
55
+ check_hostname=True,
56
+ )
57
+
58
+
59
+ def client_context_honors_disabled_hostname_check() -> bool:
60
+ """Return whether create_client_context preserves check_hostname=False."""
61
+ config = SSLConfig(enabled=False, verify=True, check_hostname=False)
62
+ context = SSLManager(config).create_client_context()
63
+ return context.check_hostname
64
+
65
+
66
+ def issued_leaf_extension_presence() -> Dict[str, bool]:
67
+ """Return whether an issued server certificate has RFC 5280 key ids."""
68
+ with TemporaryDirectory(prefix="sf-leaf-key-ids-") as tmp:
69
+ root = Path(tmp)
70
+ manager = _manager(root)
71
+ ca_pair = manager.create_root_ca(
72
+ CAConfig(
73
+ common_name="Key Identifier CA",
74
+ organization="Example Org",
75
+ key_size=2048,
76
+ )
77
+ )
78
+ server_pair = manager.create_server_certificate(
79
+ ServerCertConfig(
80
+ common_name="localhost",
81
+ organization="Example Org",
82
+ key_size=2048,
83
+ ca_cert_path=ca_pair.certificate_path,
84
+ ca_key_path=ca_pair.private_key_path,
85
+ subject_alt_names=["localhost"],
86
+ )
87
+ )
88
+ cert = x509.load_pem_x509_certificate(
89
+ Path(server_pair.certificate_path).read_bytes()
90
+ )
91
+ return {
92
+ "subject_key_identifier": _has_extension(
93
+ cert, ExtensionOID.SUBJECT_KEY_IDENTIFIER
94
+ ),
95
+ "authority_key_identifier": _has_extension(
96
+ cert, ExtensionOID.AUTHORITY_KEY_IDENTIFIER
97
+ ),
98
+ }
99
+
100
+
101
+ def ip_subject_alt_name_is_encoded_as_ip_address() -> Dict[str, bool]:
102
+ """Return how create_server_certificate encodes a loopback SAN."""
103
+ with TemporaryDirectory(prefix="sf-ip-san-") as tmp:
104
+ root = Path(tmp)
105
+ manager = _manager(root)
106
+ ca_pair = manager.create_root_ca(
107
+ CAConfig(
108
+ common_name="IP SAN CA",
109
+ organization="Example Org",
110
+ key_size=2048,
111
+ )
112
+ )
113
+ server_pair = manager.create_server_certificate(
114
+ ServerCertConfig(
115
+ common_name="127.0.0.1",
116
+ organization="Example Org",
117
+ key_size=2048,
118
+ ca_cert_path=ca_pair.certificate_path,
119
+ ca_key_path=ca_pair.private_key_path,
120
+ subject_alt_names=["127.0.0.1"],
121
+ )
122
+ )
123
+ cert = x509.load_pem_x509_certificate(
124
+ Path(server_pair.certificate_path).read_bytes()
125
+ )
126
+ san = cert.extensions.get_extension_for_oid(
127
+ ExtensionOID.SUBJECT_ALTERNATIVE_NAME
128
+ ).value
129
+ expected_ip = ipaddress.ip_address("127.0.0.1")
130
+ return {
131
+ "has_ip_address": expected_ip in san.get_values_for_type(x509.IPAddress),
132
+ "has_dns_name": "127.0.0.1" in san.get_values_for_type(x509.DNSName),
133
+ }
134
+
135
+
136
+ def _has_extension(cert: x509.Certificate, oid: x509.ObjectIdentifier) -> bool:
137
+ """Return True when the certificate contains the extension."""
138
+ try:
139
+ cert.extensions.get_extension_for_oid(oid)
140
+ except x509.ExtensionNotFound:
141
+ return False
142
+ return True
@@ -73,7 +73,7 @@ def require_permissions(*permissions: str) -> Any:
73
73
  permissions to routes that opt in.
74
74
  """
75
75
 
76
- def _required_permissions_marker() -> None:
76
+ async def _required_permissions_marker() -> None:
77
77
  """
78
78
  Carry the declared permissions; it is never called for its result.
79
79
 
@@ -231,21 +231,27 @@ class SSLConfig(BaseModel):
231
231
  def validate_ssl_configuration(self) -> Self:
232
232
  """Validate SSL configuration consistency."""
233
233
  if self.enabled:
234
- # If verify is disabled, don't require certificate files or memory flag
235
- if self.verify and not self.material_from_memory:
236
- # material_from_memory=False means files are required.
237
- # material_from_memory=True means bytes will be supplied at call time,
238
- # so files are not checked here. This keeps the early error for the
239
- # common case — everyone who configures file paths — and makes the
240
- # intent visible in the configuration itself, which is where an auditor
241
- # will look. An explicit opt-in prevents the false positive that occurs
242
- # when someone sets verify=True in secure-by-default code but hasn't yet
243
- # assembled the bytes.
244
- if not self.cert_file or not self.key_file:
234
+ if self.cert_file or self.key_file:
235
+ if not (self.cert_file and self.key_file):
245
236
  raise ValueError(
246
- "SSL enabled with verification but certificate and key "
247
- "files are required"
237
+ "SSL certificate and key files must be configured together"
248
238
  )
239
+ if self.client_cert_file or self.client_key_file:
240
+ if not (self.client_cert_file and self.client_key_file):
241
+ raise ValueError(
242
+ "SSL client certificate and key files must be configured "
243
+ "together"
244
+ )
245
+ if (
246
+ self.verify
247
+ and not self.material_from_memory
248
+ and not self.ca_cert_file
249
+ and not (self.cert_file and self.key_file)
250
+ ):
251
+ raise ValueError(
252
+ "SSL enabled with verification but certificate and key "
253
+ "files are required"
254
+ )
249
255
  return self
250
256
 
251
257
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-security-framework
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Added extensible security adapter system for custom operation validation, structured audit logging, and operation context support. Comprehensive test coverage with 1104+ tests. Fully backward compatible. Requires cryptography>=42.0.0 for certificate operations.
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
@@ -60,6 +60,10 @@ Requires-Dist: pytest>=7.0.0; extra == "test"
60
60
  Requires-Dist: pytest-cov>=4.0.0; extra == "test"
61
61
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
62
62
  Requires-Dist: pytest-mock>=3.8.0; extra == "test"
63
+ Requires-Dist: fastapi>=0.68.0; extra == "test"
64
+ Requires-Dist: uvicorn[standard]>=0.15.0; extra == "test"
65
+ Requires-Dist: flask>=2.0.0; extra == "test"
66
+ Requires-Dist: flask-cors>=3.0.0; extra == "test"
63
67
  Requires-Dist: httpx>=0.24.0; extra == "test"
64
68
  Dynamic: license-file
65
69
 
@@ -35,6 +35,7 @@ mcp_security_framework/examples/gateway_example.py
35
35
  mcp_security_framework/examples/microservice_example.py
36
36
  mcp_security_framework/examples/protected_keys_example.py
37
37
  mcp_security_framework/examples/standalone_example.py
38
+ mcp_security_framework/examples/tls_certificate_regressions_example.py
38
39
  mcp_security_framework/middleware/__init__.py
39
40
  mcp_security_framework/middleware/auth_middleware.py
40
41
  mcp_security_framework/middleware/fastapi_auth_middleware.py
@@ -37,4 +37,8 @@ pytest>=7.0.0
37
37
  pytest-cov>=4.0.0
38
38
  pytest-asyncio>=0.21.0
39
39
  pytest-mock>=3.8.0
40
+ fastapi>=0.68.0
41
+ uvicorn[standard]>=0.15.0
42
+ flask>=2.0.0
43
+ flask-cors>=3.0.0
40
44
  httpx>=0.24.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mcp-security-framework"
7
- version = "2.3.0"
7
+ version = "2.3.1"
8
8
  description = "Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Added extensible security adapter system for custom operation validation, structured audit logging, and operation context support. Comprehensive test coverage with 1104+ tests. Fully backward compatible. Requires cryptography>=42.0.0 for certificate operations."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -81,6 +81,10 @@ test = [
81
81
  "pytest-cov>=4.0.0",
82
82
  "pytest-asyncio>=0.21.0",
83
83
  "pytest-mock>=3.8.0",
84
+ "fastapi>=0.68.0",
85
+ "uvicorn[standard]>=0.15.0",
86
+ "flask>=2.0.0",
87
+ "flask-cors>=3.0.0",
84
88
  "httpx>=0.24.0",
85
89
  ]
86
90
 
@@ -161,6 +165,7 @@ python_files = ["test_*.py", "*_test.py"]
161
165
  python_classes = ["Test*"]
162
166
  python_functions = ["test_*"]
163
167
  markers = [
168
+ "asyncio: marks tests that run on an asyncio event loop",
164
169
  "slow: marks tests as slow (deselect with '-m \"not slow\"')",
165
170
  "integration: marks tests as integration tests",
166
171
  "unit: marks tests as unit tests",
@@ -189,4 +194,3 @@ exclude_lines = [
189
194
  "class .*\\bProtocol\\):",
190
195
  "@(abc\\.)?abstractmethod",
191
196
  ]
192
-