sfq 0.0.39__tar.gz → 0.0.41__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.
- {sfq-0.0.39 → sfq-0.0.41}/.github/workflows/publish.yml +3 -2
- {sfq-0.0.39 → sfq-0.0.41}/PKG-INFO +1 -1
- {sfq-0.0.39 → sfq-0.0.41}/pyproject.toml +1 -1
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/__init__.py +41 -3
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/auth.py +2 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/http_client.py +1 -1
- sfq-0.0.41/tests/test_SFTokenAuth_e2e.py +58 -0
- {sfq-0.0.39 → sfq-0.0.41}/uv.lock +1 -1
- {sfq-0.0.39 → sfq-0.0.41}/.gitignore +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/.python-version +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/README.md +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/_cometd.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/crud.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/debug_cleanup.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/exceptions.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/py.typed +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/query.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/soap.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/timeout_detector.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/src/sfq/utils.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_complex_nested.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_complex_nested_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_empty_list.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_empty_list_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_int_float_bool.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_int_float_bool_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_list_value.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_list_value_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_multiple_dicts.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_multiple_dicts_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_nested_dict.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_nested_dict_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_none_value.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_none_value_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_other_types.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_other_types_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_sample_report.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_sample_report_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_single_flat_dict.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_single_flat_dict_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_bool.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_bool_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_float.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_float_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_int.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_int_styled.html +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_auth.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_cdelete.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_compatibility.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_cquery.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_create.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_crud.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_crud_e2e.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_cupdate.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_debug_cleanup_e2e.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_debug_cleanup_unit.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_http_client.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_http_client_retry.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_limits_api.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_log_trace_redact.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_open_frontdoor.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_query.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_client.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_client_timeout_integration.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_e2e.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_integration.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_records_to_html.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_soap.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_soap_batch_operation.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_static_resources.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_detector.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_edge_cases.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_scenarios_comprehensive.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_scenarios_summary.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_utils.py +0 -0
- {sfq-0.0.39 → sfq-0.0.41}/tests/test_utils_html_table.py +0 -0
@@ -33,7 +33,7 @@ jobs:
|
|
33
33
|
run: uv sync
|
34
34
|
|
35
35
|
- name: Install pytest
|
36
|
-
run: pip install pytest
|
36
|
+
run: pip install pytest pytest-xdist[psutil]
|
37
37
|
|
38
38
|
- name: Authenticate GitHub CLI
|
39
39
|
run: |
|
@@ -87,13 +87,14 @@ jobs:
|
|
87
87
|
sed -i -E "s/(user_agent: str = \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\",)/\1$VERSION\2/" src/sfq/http_client.py
|
88
88
|
|
89
89
|
- name: Run tests
|
90
|
-
run: pytest --verbose --strict-config
|
90
|
+
run: pytest --verbose --strict-config -n auto
|
91
91
|
env:
|
92
92
|
PYTHONPATH: src
|
93
93
|
SF_INSTANCE_URL: ${{ secrets.SF_INSTANCE_URL }}
|
94
94
|
SF_CLIENT_ID: ${{ secrets.SF_CLIENT_ID }}
|
95
95
|
SF_CLIENT_SECRET: ${{ secrets.SF_CLIENT_SECRET }}
|
96
96
|
SF_REFRESH_TOKEN: ${{ secrets.SF_REFRESH_TOKEN }}
|
97
|
+
SF_ACCESS_TOKEN: ${{ secrets.SF_ACCESS_TOKEN }}
|
97
98
|
|
98
99
|
- name: Commit version updates
|
99
100
|
run: |
|
@@ -45,7 +45,7 @@ __all__ = [
|
|
45
45
|
"__version__",
|
46
46
|
]
|
47
47
|
|
48
|
-
__version__ = "0.0.
|
48
|
+
__version__ = "0.0.41"
|
49
49
|
"""
|
50
50
|
### `__version__`
|
51
51
|
|
@@ -56,6 +56,44 @@ __version__ = "0.0.39"
|
|
56
56
|
"""
|
57
57
|
logger = get_logger("sfq")
|
58
58
|
|
59
|
+
class _SFTokenAuth:
|
60
|
+
def __init__(
|
61
|
+
self,
|
62
|
+
instance_url: str,
|
63
|
+
access_token: str,
|
64
|
+
api_version: str = "v64.0",
|
65
|
+
token_endpoint: str = "/services/oauth2/token",
|
66
|
+
user_agent: str = "sfq/0.0.41",
|
67
|
+
sforce_client: str = "_auto",
|
68
|
+
proxy: str = "_auto",
|
69
|
+
) -> None:
|
70
|
+
from . import SFAuth
|
71
|
+
|
72
|
+
self._sf_auth = SFAuth(
|
73
|
+
instance_url=instance_url,
|
74
|
+
client_id="_",
|
75
|
+
refresh_token="_",
|
76
|
+
client_secret=str("_").strip(),
|
77
|
+
api_version=api_version,
|
78
|
+
token_endpoint=token_endpoint,
|
79
|
+
access_token=access_token,
|
80
|
+
token_expiration_time=-1.0,
|
81
|
+
user_agent=user_agent,
|
82
|
+
sforce_client=sforce_client,
|
83
|
+
proxy=proxy,
|
84
|
+
)
|
85
|
+
|
86
|
+
self._sf_auth._auth_manager.access_token = access_token
|
87
|
+
self._sf_auth._auth_manager.token_expiration_time = -1.0
|
88
|
+
|
89
|
+
def __getattr__(self, name: str) -> Any:
|
90
|
+
return getattr(self._sf_auth, name)
|
91
|
+
|
92
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
93
|
+
if name == "_sf_auth":
|
94
|
+
super().__setattr__(name, value)
|
95
|
+
else:
|
96
|
+
setattr(self._sf_auth, name, value)
|
59
97
|
|
60
98
|
class SFAuth:
|
61
99
|
def __init__(
|
@@ -69,7 +107,7 @@ class SFAuth:
|
|
69
107
|
access_token: Optional[str] = None,
|
70
108
|
token_expiration_time: Optional[float] = None,
|
71
109
|
token_lifetime: int = 15 * 60,
|
72
|
-
user_agent: str = "sfq/0.0.
|
110
|
+
user_agent: str = "sfq/0.0.41",
|
73
111
|
sforce_client: str = "_auto",
|
74
112
|
proxy: str = "_auto",
|
75
113
|
) -> None:
|
@@ -134,7 +172,7 @@ class SFAuth:
|
|
134
172
|
self._debug_cleanup = DebugCleanup(sf_auth=self)
|
135
173
|
|
136
174
|
# Store version information
|
137
|
-
self.__version__ = "0.0.
|
175
|
+
self.__version__ = "0.0.41"
|
138
176
|
"""
|
139
177
|
### `__version__`
|
140
178
|
|
@@ -245,6 +245,8 @@ class AuthManager:
|
|
245
245
|
|
246
246
|
:return: True if token is expired or missing, False otherwise
|
247
247
|
"""
|
248
|
+
if self.token_expiration_time == -1.0:
|
249
|
+
return False # Token never expires
|
248
250
|
try:
|
249
251
|
return time.time() >= float(self.token_expiration_time)
|
250
252
|
except (TypeError, ValueError):
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
End-to-end tests for the SFTokenAuth module.
|
3
|
+
|
4
|
+
These tests run against a real Salesforce instance using environment variables
|
5
|
+
to ensure the SFTokenAuth functionality works correctly in practice.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
|
10
|
+
import pytest
|
11
|
+
|
12
|
+
from sfq import _SFTokenAuth
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.fixture(scope="module")
|
16
|
+
def sf_instance():
|
17
|
+
"""Create an AuthManager instance for E2E testing."""
|
18
|
+
required_env_vars = [
|
19
|
+
"SF_INSTANCE_URL",
|
20
|
+
"SF_ACCESS_TOKEN",
|
21
|
+
]
|
22
|
+
|
23
|
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
24
|
+
if missing_vars:
|
25
|
+
pytest.fail(f"Missing required env vars: {', '.join(missing_vars)}")
|
26
|
+
|
27
|
+
sf = _SFTokenAuth(
|
28
|
+
instance_url=os.getenv("SF_INSTANCE_URL"),
|
29
|
+
access_token=os.getenv("SF_ACCESS_TOKEN"),
|
30
|
+
)
|
31
|
+
return sf
|
32
|
+
|
33
|
+
|
34
|
+
def test_query(sf_instance):
|
35
|
+
"""Ensure that a simple query returns the expected results."""
|
36
|
+
query = "SELECT Id FROM FeedComment LIMIT 1"
|
37
|
+
response = sf_instance.query(query)
|
38
|
+
|
39
|
+
assert response and isinstance(response, dict), (
|
40
|
+
f"Query did not return a dict: {response}"
|
41
|
+
)
|
42
|
+
|
43
|
+
assert "records" in response, f"No records in response: {response}"
|
44
|
+
assert len(response["records"]) == 1, (
|
45
|
+
f"Expected 1 record, got {len(response['records'])}: {response}"
|
46
|
+
)
|
47
|
+
assert "Id" in response["records"][0], (
|
48
|
+
f"No Id in record: {response['records'][0]}"
|
49
|
+
)
|
50
|
+
assert response["records"][0]["Id"], (
|
51
|
+
f"Id is empty in record: {response['records'][0]}"
|
52
|
+
)
|
53
|
+
assert response["done"] is True, f"Query not marked as done: {response}"
|
54
|
+
assert "totalSize" in response, f"No totalSize in response: {response}"
|
55
|
+
assert response["totalSize"] == 1, (
|
56
|
+
f"Expected totalSize 1, got {response['totalSize']}: {response}"
|
57
|
+
)
|
58
|
+
|
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
|
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
|