PYTHON-5565 Add minimum version test for Encryption (#2547)
This commit is contained in:
parent
448a4944ff
commit
fad2ccb0e7
@ -151,6 +151,7 @@ functions:
|
||||
- VERSION
|
||||
- IS_WIN32
|
||||
- REQUIRE_FIPS
|
||||
- TEST_MIN_DEPS
|
||||
type: test
|
||||
- command: subprocess.exec
|
||||
params:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -179,6 +179,7 @@ buildvariants:
|
||||
- name: encryption-rhel8
|
||||
tasks:
|
||||
- name: .test-non-standard
|
||||
- name: .test-min-deps
|
||||
display_name: Encryption RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
@ -209,6 +210,7 @@ buildvariants:
|
||||
- name: encryption-crypt_shared-rhel8
|
||||
tasks:
|
||||
- name: .test-non-standard
|
||||
- name: .test-min-deps
|
||||
display_name: Encryption crypt_shared RHEL8
|
||||
run_on:
|
||||
- rhel87-small
|
||||
|
||||
@ -6,7 +6,8 @@ SCRIPT_DIR=$(dirname ${BASH_SOURCE:-$0})
|
||||
SCRIPT_DIR="$( cd -- "$SCRIPT_DIR" > /dev/null 2>&1 && pwd )"
|
||||
ROOT_DIR="$(dirname $SCRIPT_DIR)"
|
||||
|
||||
pushd $ROOT_DIR
|
||||
PREV_DIR=$(pwd)
|
||||
cd $ROOT_DIR
|
||||
|
||||
# Try to source the env file.
|
||||
if [ -f $SCRIPT_DIR/scripts/env.sh ]; then
|
||||
@ -25,7 +26,17 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start the test runner.
|
||||
uv run ${UV_ARGS} --reinstall .evergreen/scripts/run_tests.py "$@"
|
||||
cleanup_tests() {
|
||||
# Avoid leaving the lock file in a changed state when we change the resolution type.
|
||||
if [ -n "${TEST_MIN_DEPS:-}" ]; then
|
||||
git checkout uv.lock || true
|
||||
fi
|
||||
cd $PREV_DIR
|
||||
}
|
||||
|
||||
popd
|
||||
trap "cleanup_tests" SIGINT ERR
|
||||
|
||||
# Start the test runner.
|
||||
uv run ${UV_ARGS} --reinstall-package pymongo .evergreen/scripts/run_tests.py "$@"
|
||||
|
||||
cleanup_tests
|
||||
|
||||
@ -143,7 +143,7 @@ def create_encryption_variants() -> list[BuildVariant]:
|
||||
):
|
||||
expansions = get_encryption_expansions(encryption)
|
||||
display_name = get_variant_name(encryption, host, **expansions)
|
||||
tasks = [".test-non-standard"]
|
||||
tasks = [".test-non-standard", ".test-min-deps"]
|
||||
if host != "rhel8":
|
||||
tasks = [".test-non-standard !.pypy"]
|
||||
variant = create_variant(
|
||||
@ -528,22 +528,20 @@ def create_aws_lambda_variants():
|
||||
|
||||
def create_server_version_tasks():
|
||||
tasks = []
|
||||
task_inputs = []
|
||||
task_combos = set()
|
||||
# All combinations of topology, auth, ssl, and sync should be tested.
|
||||
for (topology, auth, ssl, sync), python in zip_cycle(
|
||||
list(product(TOPOLOGIES, ["auth", "noauth"], ["ssl", "nossl"], SYNCS)), ALL_PYTHONS
|
||||
):
|
||||
task_inputs.append((topology, auth, ssl, sync, python))
|
||||
task_combos.add((topology, auth, ssl, sync, python))
|
||||
|
||||
# Every python should be tested with sharded cluster, auth, ssl, with sync and async.
|
||||
for python, sync in product(ALL_PYTHONS, SYNCS):
|
||||
task_input = ("sharded_cluster", "auth", "ssl", sync, python)
|
||||
if task_input not in task_inputs:
|
||||
task_inputs.append(task_input)
|
||||
task_combos.add(("sharded_cluster", "auth", "ssl", sync, python))
|
||||
|
||||
# Assemble the tasks.
|
||||
seen = set()
|
||||
for topology, auth, ssl, sync, python in task_inputs:
|
||||
for topology, auth, ssl, sync, python in sorted(task_combos):
|
||||
combo = f"{topology}-{auth}-{ssl}"
|
||||
tags = ["server-version", f"python-{python}", combo, sync]
|
||||
if combo in [
|
||||
@ -558,7 +556,12 @@ def create_server_version_tasks():
|
||||
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology)
|
||||
if python not in PYPYS:
|
||||
expansions["COVERAGE"] = "1"
|
||||
name = get_task_name("test-server-version", python=python, sync=sync, **expansions)
|
||||
name = get_task_name(
|
||||
"test-server-version",
|
||||
python=python,
|
||||
sync=sync,
|
||||
**expansions,
|
||||
)
|
||||
server_func = FunctionCall(func="run server", vars=expansions)
|
||||
test_vars = expansions.copy()
|
||||
test_vars["PYTHON_VERSION"] = python
|
||||
@ -590,15 +593,15 @@ def create_no_toolchain_tasks():
|
||||
def create_test_non_standard_tasks():
|
||||
"""For variants that set a TEST_NAME."""
|
||||
tasks = []
|
||||
task_combos = []
|
||||
task_combos = set()
|
||||
# For each version and topology, rotate through the CPythons.
|
||||
for (version, topology), python in zip_cycle(list(product(ALL_VERSIONS, TOPOLOGIES)), CPYTHONS):
|
||||
pr = version == "latest"
|
||||
task_combos.append((version, topology, python, pr))
|
||||
task_combos.add((version, topology, python, pr))
|
||||
# For each PyPy and topology, rotate through the the versions.
|
||||
for (python, topology), version in zip_cycle(list(product(PYPYS, TOPOLOGIES)), ALL_VERSIONS):
|
||||
task_combos.append((version, topology, python, False))
|
||||
for version, topology, python, pr in task_combos:
|
||||
task_combos.add((version, topology, python, False))
|
||||
for version, topology, python, pr in sorted(task_combos):
|
||||
auth, ssl = get_standard_auth_ssl(topology)
|
||||
tags = [
|
||||
"test-non-standard",
|
||||
@ -621,6 +624,22 @@ def create_test_non_standard_tasks():
|
||||
return tasks
|
||||
|
||||
|
||||
def create_min_deps_tasks():
|
||||
"""For variants that support testing with minimum dependencies."""
|
||||
tasks = []
|
||||
for topology in TOPOLOGIES:
|
||||
auth, ssl = get_standard_auth_ssl(topology)
|
||||
tags = ["test-min-deps", f"{topology}-{auth}-{ssl}"]
|
||||
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology)
|
||||
server_func = FunctionCall(func="run server", vars=expansions)
|
||||
test_vars = expansions.copy()
|
||||
test_vars["TEST_MIN_DEPS"] = "1"
|
||||
name = get_task_name("test-min-deps", python=CPYTHONS[0], sync="sync", **test_vars)
|
||||
test_func = FunctionCall(func="run tests", vars=test_vars)
|
||||
tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func]))
|
||||
return tasks
|
||||
|
||||
|
||||
def create_standard_tasks():
|
||||
"""For variants that do not set a TEST_NAME."""
|
||||
tasks = []
|
||||
@ -794,9 +813,12 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name):
|
||||
tags.append("pr")
|
||||
|
||||
task_name = get_task_name(
|
||||
f"test-ocsp-{algo}-{base_task_name}", python=python, version=version
|
||||
f"test-ocsp-{algo}-{base_task_name}",
|
||||
python=python,
|
||||
version=version,
|
||||
)
|
||||
tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func]))
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
@ -1075,6 +1097,7 @@ def create_run_tests_func():
|
||||
"VERSION",
|
||||
"IS_WIN32",
|
||||
"REQUIRE_FIPS",
|
||||
"TEST_MIN_DEPS",
|
||||
]
|
||||
args = [".evergreen/just.sh", "setup-tests", "${TEST_NAME}", "${SUB_TEST_NAME}"]
|
||||
setup_cmd = get_subprocess_exec(include_expansions_in_env=includes, args=args)
|
||||
|
||||
@ -42,6 +42,7 @@ DISPLAY_LOOKUP = dict(
|
||||
sync={"sync": "Sync", "async": "Async"},
|
||||
coverage={"1": "cov"},
|
||||
no_ext={"1": "No C"},
|
||||
test_min_deps={True: "Min Deps"},
|
||||
)
|
||||
HOSTS = dict()
|
||||
|
||||
@ -202,7 +203,7 @@ def get_common_name(base: str, sep: str, **kwargs) -> str:
|
||||
name = f"Python{value}"
|
||||
else:
|
||||
name = f"PyPy{value.replace('pypy', '')}"
|
||||
elif key.lower() in DISPLAY_LOOKUP:
|
||||
elif key.lower() in DISPLAY_LOOKUP and value in DISPLAY_LOOKUP[key.lower()]:
|
||||
name = DISPLAY_LOOKUP[key.lower()][value]
|
||||
else:
|
||||
continue
|
||||
|
||||
@ -30,13 +30,14 @@ SUB_TEST_NAME = os.environ.get("SUB_TEST_NAME")
|
||||
|
||||
|
||||
def list_packages():
|
||||
packages = dict()
|
||||
packages = set()
|
||||
for distribution in importlib_metadata.distributions():
|
||||
packages[distribution.name] = distribution
|
||||
if distribution.name:
|
||||
packages.add(distribution.name)
|
||||
print("Package Version URL")
|
||||
print("------------------- ----------- ----------------------------------------------------")
|
||||
for name in sorted(packages):
|
||||
distribution = packages[name]
|
||||
distribution = importlib_metadata.distribution(name)
|
||||
url = ""
|
||||
if distribution.origin is not None:
|
||||
url = distribution.origin.url
|
||||
@ -136,6 +137,9 @@ def handle_aws_lambda() -> None:
|
||||
|
||||
|
||||
def run() -> None:
|
||||
# Add diagnostic for python version.
|
||||
print("Running with python", sys.version)
|
||||
|
||||
# List the installed packages.
|
||||
list_packages()
|
||||
|
||||
|
||||
@ -160,7 +160,6 @@ def handle_test_env() -> None:
|
||||
|
||||
write_env("PIP_QUIET") # Quiet by default.
|
||||
write_env("PIP_PREFER_BINARY") # Prefer binary dists by default.
|
||||
write_env("UV_FROZEN") # Do not modify lock files.
|
||||
|
||||
# Set an environment variable for the test name and sub test name.
|
||||
write_env(f"TEST_{test_name.upper()}")
|
||||
@ -178,6 +177,9 @@ def handle_test_env() -> None:
|
||||
if group := GROUP_MAP.get(test_name, ""):
|
||||
UV_ARGS.append(f"--group {group}")
|
||||
|
||||
if opts.test_min_deps:
|
||||
UV_ARGS.append("--resolution=lowest-direct")
|
||||
|
||||
if test_name == "auth_oidc":
|
||||
from oidc_tester import setup_oidc
|
||||
|
||||
@ -233,7 +235,7 @@ def handle_test_env() -> None:
|
||||
if is_set("MONGODB_URI"):
|
||||
write_env("PYMONGO_MUST_CONNECT", "true")
|
||||
|
||||
if is_set("DISABLE_TEST_COMMANDS") or opts.disable_test_commands:
|
||||
if opts.disable_test_commands:
|
||||
write_env("PYMONGO_DISABLE_TEST_COMMANDS", "1")
|
||||
|
||||
if test_name == "enterprise_auth":
|
||||
@ -345,10 +347,10 @@ def handle_test_env() -> None:
|
||||
if not (ROOT / "libmongocrypt").exists():
|
||||
setup_libmongocrypt()
|
||||
|
||||
# TODO: Test with 'pip install pymongocrypt'
|
||||
UV_ARGS.append(
|
||||
"--with pymongocrypt@git+https://github.com/mongodb/libmongocrypt@master#subdirectory=bindings/python"
|
||||
)
|
||||
if not opts.test_min_deps:
|
||||
UV_ARGS.append(
|
||||
"--with pymongocrypt@git+https://github.com/mongodb/libmongocrypt@master#subdirectory=bindings/python"
|
||||
)
|
||||
|
||||
# Use the nocrypto build to avoid dependency issues with older windows/python versions.
|
||||
BASE = ROOT / "libmongocrypt/nocrypto"
|
||||
@ -377,7 +379,7 @@ def handle_test_env() -> None:
|
||||
if sub_test_name == "pyopenssl":
|
||||
UV_ARGS.append("--extra ocsp")
|
||||
|
||||
if is_set("TEST_CRYPT_SHARED") or opts.crypt_shared:
|
||||
if opts.crypt_shared:
|
||||
config = read_env(f"{DRIVERS_TOOLS}/mo-expansion.sh")
|
||||
CRYPT_SHARED_DIR = Path(config["CRYPT_SHARED_LIB_PATH"]).parent.as_posix()
|
||||
LOGGER.info("Using crypt_shared_dir %s", CRYPT_SHARED_DIR)
|
||||
@ -447,14 +449,14 @@ def handle_test_env() -> None:
|
||||
|
||||
# Add coverage if requested.
|
||||
# Only cover CPython. PyPy reports suspiciously low coverage.
|
||||
if (is_set("COVERAGE") or opts.cov) and platform.python_implementation() == "CPython":
|
||||
if opts.cov and platform.python_implementation() == "CPython":
|
||||
# Keep in sync with combine-coverage.sh.
|
||||
# coverage >=5 is needed for relative_files=true.
|
||||
UV_ARGS.append("--group coverage")
|
||||
TEST_ARGS = f"{TEST_ARGS} --cov"
|
||||
write_env("COVERAGE")
|
||||
|
||||
if is_set("GREEN_FRAMEWORK") or opts.green_framework:
|
||||
if opts.green_framework:
|
||||
framework = opts.green_framework or os.environ["GREEN_FRAMEWORK"]
|
||||
UV_ARGS.append(f"--group {framework}")
|
||||
|
||||
|
||||
@ -60,6 +60,9 @@ NO_RUN_ORCHESTRATION = [
|
||||
"ocsp",
|
||||
]
|
||||
|
||||
# Mapping of env variables to options
|
||||
OPTION_TO_ENV_VAR = {"cov": "COVERAGE", "crypt_shared": "TEST_CRYPT_SHARED"}
|
||||
|
||||
|
||||
def get_test_options(
|
||||
description, require_sub_test_name=True, allow_extra_opts=False
|
||||
@ -94,6 +97,9 @@ def get_test_options(
|
||||
)
|
||||
parser.add_argument("--auth", action="store_true", help="Whether to add authentication.")
|
||||
parser.add_argument("--ssl", action="store_true", help="Whether to add TLS configuration.")
|
||||
parser.add_argument(
|
||||
"--test-min-deps", action="store_true", help="Test against minimum dependency versions"
|
||||
)
|
||||
|
||||
# Add the test modifiers.
|
||||
if require_sub_test_name:
|
||||
@ -127,26 +133,53 @@ def get_test_options(
|
||||
opts, extra_opts = parser.parse_args(), []
|
||||
else:
|
||||
opts, extra_opts = parser.parse_known_args()
|
||||
if opts.verbose:
|
||||
LOGGER.setLevel(logging.DEBUG)
|
||||
elif opts.quiet:
|
||||
LOGGER.setLevel(logging.WARNING)
|
||||
|
||||
# Convert list inputs to strings.
|
||||
for name in vars(opts):
|
||||
value = getattr(opts, name)
|
||||
if isinstance(value, list):
|
||||
setattr(opts, name, value[0])
|
||||
|
||||
# Handle validation and environment variable overrides.
|
||||
test_name = opts.test_name
|
||||
sub_test_name = opts.sub_test_name if require_sub_test_name else ""
|
||||
if require_sub_test_name and test_name in SUB_TEST_REQUIRED and not sub_test_name:
|
||||
raise ValueError(f"Test '{test_name}' requires a sub_test_name")
|
||||
if "auth" in test_name or os.environ.get("AUTH") == "auth":
|
||||
handle_env_overrides(parser, opts)
|
||||
if "auth" in test_name:
|
||||
opts.auth = True
|
||||
# 'auth_aws ecs' shouldn't have extra auth set.
|
||||
if test_name == "auth_aws" and sub_test_name == "ecs":
|
||||
opts.auth = False
|
||||
if os.environ.get("SSL") == "ssl":
|
||||
opts.ssl = True
|
||||
if opts.verbose:
|
||||
LOGGER.setLevel(logging.DEBUG)
|
||||
elif opts.quiet:
|
||||
LOGGER.setLevel(logging.WARNING)
|
||||
return opts, extra_opts
|
||||
|
||||
|
||||
def handle_env_overrides(parser: argparse.ArgumentParser, opts: argparse.Namespace) -> None:
|
||||
# Get the options, and then allow environment variable overrides.
|
||||
for key in vars(opts):
|
||||
if key in OPTION_TO_ENV_VAR:
|
||||
env_var = OPTION_TO_ENV_VAR[key]
|
||||
else:
|
||||
env_var = key.upper()
|
||||
if env_var in os.environ:
|
||||
if parser.get_default(key) != getattr(opts, key):
|
||||
LOGGER.info("Overriding env var '%s' with cli option", env_var)
|
||||
elif env_var == "AUTH":
|
||||
opts.auth = os.environ.get("AUTH") == "auth"
|
||||
elif env_var == "SSL":
|
||||
ssl_opt = os.environ.get("SSL", "")
|
||||
opts.ssl = ssl_opt and ssl_opt.lower() != "nossl"
|
||||
elif isinstance(getattr(opts, key), bool):
|
||||
if os.environ[env_var]:
|
||||
setattr(opts, key, True)
|
||||
else:
|
||||
setattr(opts, key, os.environ[env_var])
|
||||
|
||||
|
||||
def read_env(path: Path | str) -> dict[str, str]:
|
||||
config = dict()
|
||||
with Path(path).open() as fid:
|
||||
|
||||
@ -382,6 +382,11 @@ If you are running one of the `no-responder` tests, omit the `run-server` step.
|
||||
- Finally, you can use `just setup-tests --debug-log`.
|
||||
- For evergreen patch builds, you can use `evergreen patch --param DEBUG_LOG=1` to enable debug logs for failed tests in the patch.
|
||||
|
||||
## Testing minimum dependencies
|
||||
|
||||
To run any of the test suites with minimum supported dependencies, pass `--test-min-deps` to
|
||||
`just setup-tests`.
|
||||
|
||||
## Adding a new test suite
|
||||
|
||||
- If adding new tests files that should only be run for that test suite, add a pytest marker to the file and add
|
||||
|
||||
@ -48,8 +48,7 @@ Tracker = "https://jira.mongodb.org/projects/PYTHON/issues"
|
||||
[dependency-groups]
|
||||
dev = []
|
||||
pip = ["pip"]
|
||||
# TODO: PYTHON-5464
|
||||
gevent = ["gevent", "cffi>=2.0.0b1;python_version=='3.14'"]
|
||||
gevent = ["gevent>=20.6.0"]
|
||||
coverage = [
|
||||
"pytest-cov",
|
||||
"coverage>=5,<=7.10.6"
|
||||
@ -57,7 +56,7 @@ coverage = [
|
||||
mockupdb = [
|
||||
"mockupdb@git+https://github.com/mongodb-labs/mongo-mockup-db@master"
|
||||
]
|
||||
perf = ["simplejson"]
|
||||
perf = ["simplejson>=3.17.0"]
|
||||
typing = [
|
||||
"mypy==1.18.1",
|
||||
"pyright==1.1.405",
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
pymongo-auth-aws>=1.1.0,<2.0.0
|
||||
pymongocrypt>=1.13.0,<2.0.0
|
||||
certifi;os.name=='nt' or sys_platform=='darwin'
|
||||
certifi>=2023.7.22;os.name=='nt' or sys_platform=='darwin'
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# Fallback to certifi on Windows if we can't load CA certs from the system
|
||||
# store and just use certifi on macOS.
|
||||
# https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_default_verify_paths
|
||||
certifi;os.name=='nt' or sys_platform=='darwin'
|
||||
certifi>=2023.7.22;os.name=='nt' or sys_platform=='darwin'
|
||||
pyopenssl>=17.2.0
|
||||
requests<3.0.0
|
||||
cryptography>=2.5
|
||||
|
||||
@ -519,6 +519,19 @@ class ClientContext:
|
||||
"Libmongocrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_pymongocrypt_min(self, *ver):
|
||||
other_version = Version(*ver)
|
||||
if not _HAVE_PYMONGOCRYPT:
|
||||
version = Version.from_string("0.0.0")
|
||||
else:
|
||||
from pymongocrypt import __version__ as pymongocrypt_version
|
||||
|
||||
version = Version.from_string(pymongocrypt_version)
|
||||
return self._require(
|
||||
lambda: version >= other_version,
|
||||
"PyMongoCrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_auth(self, func):
|
||||
"""Run a test only if the server is running with auth enabled."""
|
||||
return self._require(
|
||||
|
||||
@ -519,6 +519,19 @@ class AsyncClientContext:
|
||||
"Libmongocrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_pymongocrypt_min(self, *ver):
|
||||
other_version = Version(*ver)
|
||||
if not _HAVE_PYMONGOCRYPT:
|
||||
version = Version.from_string("0.0.0")
|
||||
else:
|
||||
from pymongocrypt import __version__ as pymongocrypt_version
|
||||
|
||||
version = Version.from_string(pymongocrypt_version)
|
||||
return self._require(
|
||||
lambda: version >= other_version,
|
||||
"PyMongoCrypt version must be at least %s" % str(other_version),
|
||||
)
|
||||
|
||||
def require_auth(self, func):
|
||||
"""Run a test only if the server is running with auth enabled."""
|
||||
return self._require(
|
||||
|
||||
@ -3448,6 +3448,7 @@ class TestExplicitTextEncryptionProse(AsyncEncryptionIntegrationTest):
|
||||
@async_client_context.require_no_standalone
|
||||
@async_client_context.require_version_min(8, 2, -1)
|
||||
@async_client_context.require_libmongocrypt_min(1, 15, 1)
|
||||
@async_client_context.require_pymongocrypt_min(1, 16, 0)
|
||||
async def asyncSetUp(self):
|
||||
await super().asyncSetUp()
|
||||
# Load the file key1-document.json as key1Document.
|
||||
|
||||
@ -3430,6 +3430,7 @@ class TestExplicitTextEncryptionProse(EncryptionIntegrationTest):
|
||||
@client_context.require_no_standalone
|
||||
@client_context.require_version_min(8, 2, -1)
|
||||
@client_context.require_libmongocrypt_min(1, 15, 1)
|
||||
@client_context.require_pymongocrypt_min(1, 16, 0)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Load the file key1-document.json as key1Document.
|
||||
|
||||
12
uv.lock
generated
12
uv.lock
generated
@ -1201,7 +1201,6 @@ coverage = [
|
||||
{ name = "pytest-cov" },
|
||||
]
|
||||
gevent = [
|
||||
{ name = "cffi", version = "2.0.0b1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.14.*'" },
|
||||
{ name = "gevent" },
|
||||
]
|
||||
mockupdb = [
|
||||
@ -1222,8 +1221,8 @@ typing = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'encryption') or (sys_platform == 'darwin' and extra == 'encryption')" },
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')" },
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'encryption') or (sys_platform == 'darwin' and extra == 'encryption')", specifier = ">=2023.7.22" },
|
||||
{ name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')", specifier = ">=2023.7.22" },
|
||||
{ name = "cryptography", marker = "extra == 'ocsp'", specifier = ">=2.5" },
|
||||
{ name = "dnspython", specifier = ">=1.16.0,<3.0.0" },
|
||||
{ name = "furo", marker = "extra == 'docs'", specifier = "==2025.7.19" },
|
||||
@ -1254,12 +1253,9 @@ coverage = [
|
||||
{ name = "pytest-cov" },
|
||||
]
|
||||
dev = []
|
||||
gevent = [
|
||||
{ name = "cffi", marker = "python_full_version == '3.14.*'", specifier = ">=2.0.0b1" },
|
||||
{ name = "gevent" },
|
||||
]
|
||||
gevent = [{ name = "gevent", specifier = ">=20.6.0" }]
|
||||
mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mockup-db?rev=master" }]
|
||||
perf = [{ name = "simplejson" }]
|
||||
perf = [{ name = "simplejson", specifier = ">=3.17.0" }]
|
||||
pip = [{ name = "pip" }]
|
||||
typing = [
|
||||
{ name = "mypy", specifier = "==1.18.1" },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user