gaard-api 0.2.2__tar.gz → 0.2.3__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.
- {gaard_api-0.2.2/src/gaard_api.egg-info → gaard_api-0.2.3}/PKG-INFO +2 -1
- {gaard_api-0.2.2 → gaard_api-0.2.3}/pyproject.toml +3 -2
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/core/settings.py +1 -1
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/license.py +4 -3
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/package_updates.py +11 -7
- gaard_api-0.2.3/src/gaard_api/py.typed +0 -0
- gaard_api-0.2.3/src/gaard_api/tls_http.py +50 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3/src/gaard_api.egg-info}/PKG-INFO +2 -1
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api.egg-info/SOURCES.txt +4 -1
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api.egg-info/requires.txt +1 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_license_api.py +13 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_settings.py +1 -0
- gaard_api-0.2.3/tests/test_tls_http.py +70 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/MANIFEST.in +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/README.md +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/setup.cfg +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/database.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/defaults.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/models.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/prompt_runtime.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/security.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin/services.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin-web/assets/getgaard.svg +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin-web/assets/main.js +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin-web/assets/styles.css +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin-web/index.html +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/admin-web/package.json +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/v1/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/v1/admin.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/v1/analysis.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/v1/prompts.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/v1/query.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api/v1/schema.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/api_registry.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/cli.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/cli_commands.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/core/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/core/error_handlers.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/core/schema_cache.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/example_data/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/example_data/medical_poc/seed.sql +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/example_database.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/extension_services.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/extensions.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/main.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/query_hooks.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api/server_cli.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api.egg-info/dependency_links.txt +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api.egg-info/entry_points.txt +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/src/gaard_api.egg-info/top_level.txt +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_admin_api.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_analysis_api.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_api_registry.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_error_handlers.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_license_service.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_prompt_runtime.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_query_error_messages.py +0 -0
- {gaard_api-0.2.2 → gaard_api-0.2.3}/tests/test_server_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gaard-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: GAARD backend web services providing admin interface
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,6 +13,7 @@ Requires-Dist: uvicorn[standard]>=0.30.0
|
|
|
13
13
|
Requires-Dist: pydantic>=2.7.0
|
|
14
14
|
Requires-Dist: sqlalchemy>=2.0.0
|
|
15
15
|
Requires-Dist: httpx2>=2.5.0
|
|
16
|
+
Requires-Dist: certifi>=2024.8.30
|
|
16
17
|
Provides-Extra: oracle
|
|
17
18
|
Requires-Dist: oracledb>=2.0.0; extra == "oracle"
|
|
18
19
|
Provides-Extra: mssql
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gaard-api"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "GAARD backend web services providing admin interface"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -18,7 +18,8 @@ dependencies = [
|
|
|
18
18
|
"uvicorn[standard]>=0.30.0",
|
|
19
19
|
"pydantic>=2.7.0",
|
|
20
20
|
"sqlalchemy>=2.0.0",
|
|
21
|
-
"httpx2>=2.5.0"
|
|
21
|
+
"httpx2>=2.5.0",
|
|
22
|
+
"certifi>=2024.8.30"
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[project.optional-dependencies]
|
|
@@ -126,7 +126,7 @@ class Settings:
|
|
|
126
126
|
)
|
|
127
127
|
)
|
|
128
128
|
gaard_package_directory: str = field(
|
|
129
|
-
default_factory=lambda: env_value("GAARD_PACKAGE_DIRECTORY", "
|
|
129
|
+
default_factory=lambda: env_value("GAARD_PACKAGE_DIRECTORY", "extensions")
|
|
130
130
|
)
|
|
131
131
|
gaard_package_install_timeout_seconds: int = field(
|
|
132
132
|
default_factory=lambda: env_int_value(
|
|
@@ -16,6 +16,7 @@ from gaard_core.errors import GaardError
|
|
|
16
16
|
|
|
17
17
|
from gaard_api.admin.models import AdminSetting, DatasourceConnector
|
|
18
18
|
from gaard_api.core.settings import settings
|
|
19
|
+
from gaard_api.tls_http import http_error_summary, post as tls_post
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
@@ -266,7 +267,7 @@ class LicenseService:
|
|
|
266
267
|
self._stop_event = threading.Event()
|
|
267
268
|
self._thread: threading.Thread | None = None
|
|
268
269
|
self._consecutive_transient_failures = 0
|
|
269
|
-
self._http_post =
|
|
270
|
+
self._http_post = tls_post
|
|
270
271
|
|
|
271
272
|
@property
|
|
272
273
|
def state(self) -> LicenseState:
|
|
@@ -281,7 +282,7 @@ class LicenseService:
|
|
|
281
282
|
with self._state_lock:
|
|
282
283
|
self._state = community_state()
|
|
283
284
|
self._consecutive_transient_failures = 0
|
|
284
|
-
self._http_post =
|
|
285
|
+
self._http_post = tls_post
|
|
285
286
|
|
|
286
287
|
def start(self) -> None:
|
|
287
288
|
self.refresh(force=True)
|
|
@@ -595,7 +596,7 @@ class LicenseService:
|
|
|
595
596
|
except httpx.HTTPError as exc:
|
|
596
597
|
return LicenseValidationResult(
|
|
597
598
|
kind="transient",
|
|
598
|
-
message=f"Online license validation failed: {exc
|
|
599
|
+
message=f"Online license validation failed: {http_error_summary(exc)}.",
|
|
599
600
|
)
|
|
600
601
|
|
|
601
602
|
status_code = int(response.status_code)
|
|
@@ -28,6 +28,8 @@ from packaging.version import InvalidVersion, Version
|
|
|
28
28
|
|
|
29
29
|
from gaard_api.core.settings import settings
|
|
30
30
|
from gaard_api.license import LicensePlan, LicenseState, gaard_api_version
|
|
31
|
+
from gaard_api.tls_http import get as tls_get
|
|
32
|
+
from gaard_api.tls_http import http_error_summary, post as tls_post
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
logger = logging.getLogger(__name__)
|
|
@@ -190,15 +192,15 @@ def default_pip_runner(
|
|
|
190
192
|
|
|
191
193
|
class PackageUpdateService:
|
|
192
194
|
def __init__(self) -> None:
|
|
193
|
-
self._http_post: HttpRequest =
|
|
194
|
-
self._http_get: HttpRequest =
|
|
195
|
+
self._http_post: HttpRequest = tls_post
|
|
196
|
+
self._http_get: HttpRequest = tls_get
|
|
195
197
|
self._package_version: PackageVersionResolver = version
|
|
196
198
|
self._pip_runner: PipRunner = default_pip_runner
|
|
197
199
|
self._lock = threading.Lock()
|
|
198
200
|
|
|
199
201
|
def reset_for_tests(self) -> None:
|
|
200
|
-
self._http_post =
|
|
201
|
-
self._http_get =
|
|
202
|
+
self._http_post = tls_post
|
|
203
|
+
self._http_get = tls_get
|
|
202
204
|
self._package_version = version
|
|
203
205
|
self._pip_runner = default_pip_runner
|
|
204
206
|
|
|
@@ -442,7 +444,8 @@ class PackageUpdateService:
|
|
|
442
444
|
)
|
|
443
445
|
except httpx.HTTPError as exc:
|
|
444
446
|
raise PackageUpdateError(
|
|
445
|
-
|
|
447
|
+
"Package download failed before receiving a response: "
|
|
448
|
+
f"{http_error_summary(exc)}."
|
|
446
449
|
) from exc
|
|
447
450
|
|
|
448
451
|
status_code = int(response.status_code)
|
|
@@ -575,7 +578,8 @@ class PackageUpdateService:
|
|
|
575
578
|
)
|
|
576
579
|
except httpx.HTTPError as exc:
|
|
577
580
|
raise PackageUpdateError(
|
|
578
|
-
|
|
581
|
+
"Package file download failed before receiving a response: "
|
|
582
|
+
f"{http_error_summary(exc)}."
|
|
579
583
|
) from exc
|
|
580
584
|
|
|
581
585
|
status_code = int(response.status_code)
|
|
@@ -894,7 +898,7 @@ class PackageUpdateService:
|
|
|
894
898
|
}
|
|
895
899
|
|
|
896
900
|
def _package_root(self) -> Path:
|
|
897
|
-
configured = str(settings.gaard_package_directory or "
|
|
901
|
+
configured = str(settings.gaard_package_directory or "extensions").strip() or "extensions"
|
|
898
902
|
path = Path(configured)
|
|
899
903
|
if not path.is_absolute():
|
|
900
904
|
path = Path.cwd() / path
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
import os
|
|
5
|
+
import ssl
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
import certifi
|
|
10
|
+
import httpx2 as httpx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def post(url: str, **kwargs: Any) -> httpx.Response:
|
|
14
|
+
return request_with_certifi_fallback(httpx.post, url, **kwargs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get(url: str, **kwargs: Any) -> httpx.Response:
|
|
18
|
+
return request_with_certifi_fallback(httpx.get, url, **kwargs)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def request_with_certifi_fallback(request: Any, url: str, **kwargs: Any) -> httpx.Response:
|
|
22
|
+
try:
|
|
23
|
+
return request(url, **kwargs)
|
|
24
|
+
except httpx.ConnectError as exc:
|
|
25
|
+
if not should_retry_with_certifi(url, kwargs):
|
|
26
|
+
raise
|
|
27
|
+
try:
|
|
28
|
+
return request(url, **{**kwargs, "verify": certifi_ssl_context()})
|
|
29
|
+
except httpx.HTTPError as fallback_exc:
|
|
30
|
+
raise fallback_exc from exc
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def should_retry_with_certifi(url: str, kwargs: dict[str, Any]) -> bool:
|
|
34
|
+
if kwargs.get("verify") is not None:
|
|
35
|
+
return False
|
|
36
|
+
if os.environ.get("SSL_CERT_FILE") or os.environ.get("SSL_CERT_DIR"):
|
|
37
|
+
return False
|
|
38
|
+
return urlparse(url).scheme == "https"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@lru_cache(maxsize=1)
|
|
42
|
+
def certifi_ssl_context() -> ssl.SSLContext:
|
|
43
|
+
return ssl.create_default_context(cafile=certifi.where())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def http_error_summary(exc: httpx.HTTPError) -> str:
|
|
47
|
+
detail = str(exc).strip()
|
|
48
|
+
if detail:
|
|
49
|
+
return f"{exc.__class__.__name__}: {detail}"
|
|
50
|
+
return exc.__class__.__name__
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gaard-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: GAARD backend web services providing admin interface
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,6 +13,7 @@ Requires-Dist: uvicorn[standard]>=0.30.0
|
|
|
13
13
|
Requires-Dist: pydantic>=2.7.0
|
|
14
14
|
Requires-Dist: sqlalchemy>=2.0.0
|
|
15
15
|
Requires-Dist: httpx2>=2.5.0
|
|
16
|
+
Requires-Dist: certifi>=2024.8.30
|
|
16
17
|
Provides-Extra: oracle
|
|
17
18
|
Requires-Dist: oracledb>=2.0.0; extra == "oracle"
|
|
18
19
|
Provides-Extra: mssql
|
|
@@ -11,8 +11,10 @@ src/gaard_api/extensions.py
|
|
|
11
11
|
src/gaard_api/license.py
|
|
12
12
|
src/gaard_api/main.py
|
|
13
13
|
src/gaard_api/package_updates.py
|
|
14
|
+
src/gaard_api/py.typed
|
|
14
15
|
src/gaard_api/query_hooks.py
|
|
15
16
|
src/gaard_api/server_cli.py
|
|
17
|
+
src/gaard_api/tls_http.py
|
|
16
18
|
src/gaard_api.egg-info/PKG-INFO
|
|
17
19
|
src/gaard_api.egg-info/SOURCES.txt
|
|
18
20
|
src/gaard_api.egg-info/dependency_links.txt
|
|
@@ -55,4 +57,5 @@ tests/test_license_service.py
|
|
|
55
57
|
tests/test_prompt_runtime.py
|
|
56
58
|
tests/test_query_error_messages.py
|
|
57
59
|
tests/test_server_cli.py
|
|
58
|
-
tests/test_settings.py
|
|
60
|
+
tests/test_settings.py
|
|
61
|
+
tests/test_tls_http.py
|
|
@@ -287,6 +287,19 @@ def test_package_update_requires_paid_license(license_client: TestClient) -> Non
|
|
|
287
287
|
assert response.json()["error"]["code"] == "LICENSE_ENTITLEMENT_REQUIRED"
|
|
288
288
|
|
|
289
289
|
|
|
290
|
+
def test_package_updates_default_to_extensions_directory(
|
|
291
|
+
tmp_path: Path,
|
|
292
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
293
|
+
) -> None:
|
|
294
|
+
monkeypatch.chdir(tmp_path)
|
|
295
|
+
monkeypatch.setattr(settings, "gaard_package_directory", "")
|
|
296
|
+
|
|
297
|
+
package_root = package_update_service._package_root()
|
|
298
|
+
|
|
299
|
+
assert package_root == tmp_path / "extensions"
|
|
300
|
+
assert package_root.is_dir()
|
|
301
|
+
|
|
302
|
+
|
|
290
303
|
def test_paid_license_can_download_extract_and_install_package_updates(
|
|
291
304
|
license_client: TestClient,
|
|
292
305
|
tmp_path: Path,
|
|
@@ -13,6 +13,7 @@ def test_settings_use_llm_defaults_and_ignore_metadata_env(monkeypatch) -> None:
|
|
|
13
13
|
assert settings.gaard_sql_generation_mode == "llm"
|
|
14
14
|
assert settings.gaard_result_interpretation_mode == "llm"
|
|
15
15
|
assert settings.gaard_intent_classification_mode == "auto"
|
|
16
|
+
assert settings.gaard_package_directory == "extensions"
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def test_settings_can_seed_runtime_defaults_from_process_env(monkeypatch) -> None:
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ssl
|
|
4
|
+
|
|
5
|
+
import httpx2 as httpx
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from gaard_api.tls_http import request_with_certifi_fallback
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_https_connect_error_retries_with_certifi_context() -> None:
|
|
12
|
+
calls: list[dict] = []
|
|
13
|
+
|
|
14
|
+
def fake_request(url: str, **kwargs):
|
|
15
|
+
calls.append(kwargs)
|
|
16
|
+
if len(calls) == 1:
|
|
17
|
+
raise httpx.ConnectError(("certificate is not trusted",))
|
|
18
|
+
return httpx.Response(200, json={"ok": True})
|
|
19
|
+
|
|
20
|
+
response = request_with_certifi_fallback(
|
|
21
|
+
fake_request,
|
|
22
|
+
"https://getgaard.com/api/license/validate",
|
|
23
|
+
json={"product": "gaard"},
|
|
24
|
+
timeout=10.0,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assert response.status_code == 200
|
|
28
|
+
assert "verify" not in calls[0]
|
|
29
|
+
assert isinstance(calls[1]["verify"], ssl.SSLContext)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_connect_error_does_not_retry_when_verify_is_explicit() -> None:
|
|
33
|
+
calls = 0
|
|
34
|
+
|
|
35
|
+
def fake_request(url: str, **kwargs):
|
|
36
|
+
nonlocal calls
|
|
37
|
+
calls += 1
|
|
38
|
+
raise httpx.ConnectError(("certificate is not trusted",))
|
|
39
|
+
|
|
40
|
+
context = ssl.create_default_context()
|
|
41
|
+
with pytest.raises(httpx.ConnectError):
|
|
42
|
+
request_with_certifi_fallback(
|
|
43
|
+
fake_request,
|
|
44
|
+
"https://getgaard.com/api/license/validate",
|
|
45
|
+
verify=context,
|
|
46
|
+
timeout=10.0,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
assert calls == 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_connect_error_does_not_retry_when_ssl_cert_file_is_configured(
|
|
53
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
54
|
+
) -> None:
|
|
55
|
+
calls = 0
|
|
56
|
+
|
|
57
|
+
def fake_request(url: str, **kwargs):
|
|
58
|
+
nonlocal calls
|
|
59
|
+
calls += 1
|
|
60
|
+
raise httpx.ConnectError(("certificate is not trusted",))
|
|
61
|
+
|
|
62
|
+
monkeypatch.setenv("SSL_CERT_FILE", "/custom/ca.pem")
|
|
63
|
+
with pytest.raises(httpx.ConnectError):
|
|
64
|
+
request_with_certifi_fallback(
|
|
65
|
+
fake_request,
|
|
66
|
+
"https://getgaard.com/api/license/validate",
|
|
67
|
+
timeout=10.0,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
assert calls == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|