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.
Files changed (76) hide show
  1. {sfq-0.0.39 → sfq-0.0.41}/.github/workflows/publish.yml +3 -2
  2. {sfq-0.0.39 → sfq-0.0.41}/PKG-INFO +1 -1
  3. {sfq-0.0.39 → sfq-0.0.41}/pyproject.toml +1 -1
  4. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/__init__.py +41 -3
  5. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/auth.py +2 -0
  6. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/http_client.py +1 -1
  7. sfq-0.0.41/tests/test_SFTokenAuth_e2e.py +58 -0
  8. {sfq-0.0.39 → sfq-0.0.41}/uv.lock +1 -1
  9. {sfq-0.0.39 → sfq-0.0.41}/.gitignore +0 -0
  10. {sfq-0.0.39 → sfq-0.0.41}/.python-version +0 -0
  11. {sfq-0.0.39 → sfq-0.0.41}/README.md +0 -0
  12. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/_cometd.py +0 -0
  13. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/crud.py +0 -0
  14. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/debug_cleanup.py +0 -0
  15. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/exceptions.py +0 -0
  16. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/py.typed +0 -0
  17. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/query.py +0 -0
  18. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/soap.py +0 -0
  19. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/timeout_detector.py +0 -0
  20. {sfq-0.0.39 → sfq-0.0.41}/src/sfq/utils.py +0 -0
  21. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_complex_nested.html +0 -0
  22. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_complex_nested_styled.html +0 -0
  23. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_empty_list.html +0 -0
  24. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_empty_list_styled.html +0 -0
  25. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_int_float_bool.html +0 -0
  26. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_int_float_bool_styled.html +0 -0
  27. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_list_value.html +0 -0
  28. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_list_value_styled.html +0 -0
  29. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_multiple_dicts.html +0 -0
  30. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_multiple_dicts_styled.html +0 -0
  31. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_nested_dict.html +0 -0
  32. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_nested_dict_styled.html +0 -0
  33. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_none_value.html +0 -0
  34. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_none_value_styled.html +0 -0
  35. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_other_types.html +0 -0
  36. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_other_types_styled.html +0 -0
  37. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_sample_report.html +0 -0
  38. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_sample_report_styled.html +0 -0
  39. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_single_flat_dict.html +0 -0
  40. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_single_flat_dict_styled.html +0 -0
  41. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_bool.html +0 -0
  42. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_bool_styled.html +0 -0
  43. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_float.html +0 -0
  44. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_float_styled.html +0 -0
  45. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_int.html +0 -0
  46. {sfq-0.0.39 → sfq-0.0.41}/tests/html/test_typecastable_keys_int_styled.html +0 -0
  47. {sfq-0.0.39 → sfq-0.0.41}/tests/test_auth.py +0 -0
  48. {sfq-0.0.39 → sfq-0.0.41}/tests/test_cdelete.py +0 -0
  49. {sfq-0.0.39 → sfq-0.0.41}/tests/test_compatibility.py +0 -0
  50. {sfq-0.0.39 → sfq-0.0.41}/tests/test_cquery.py +0 -0
  51. {sfq-0.0.39 → sfq-0.0.41}/tests/test_create.py +0 -0
  52. {sfq-0.0.39 → sfq-0.0.41}/tests/test_crud.py +0 -0
  53. {sfq-0.0.39 → sfq-0.0.41}/tests/test_crud_e2e.py +0 -0
  54. {sfq-0.0.39 → sfq-0.0.41}/tests/test_cupdate.py +0 -0
  55. {sfq-0.0.39 → sfq-0.0.41}/tests/test_debug_cleanup_e2e.py +0 -0
  56. {sfq-0.0.39 → sfq-0.0.41}/tests/test_debug_cleanup_unit.py +0 -0
  57. {sfq-0.0.39 → sfq-0.0.41}/tests/test_http_client.py +0 -0
  58. {sfq-0.0.39 → sfq-0.0.41}/tests/test_http_client_retry.py +0 -0
  59. {sfq-0.0.39 → sfq-0.0.41}/tests/test_limits_api.py +0 -0
  60. {sfq-0.0.39 → sfq-0.0.41}/tests/test_log_trace_redact.py +0 -0
  61. {sfq-0.0.39 → sfq-0.0.41}/tests/test_open_frontdoor.py +0 -0
  62. {sfq-0.0.39 → sfq-0.0.41}/tests/test_query.py +0 -0
  63. {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_client.py +0 -0
  64. {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_client_timeout_integration.py +0 -0
  65. {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_e2e.py +0 -0
  66. {sfq-0.0.39 → sfq-0.0.41}/tests/test_query_integration.py +0 -0
  67. {sfq-0.0.39 → sfq-0.0.41}/tests/test_records_to_html.py +0 -0
  68. {sfq-0.0.39 → sfq-0.0.41}/tests/test_soap.py +0 -0
  69. {sfq-0.0.39 → sfq-0.0.41}/tests/test_soap_batch_operation.py +0 -0
  70. {sfq-0.0.39 → sfq-0.0.41}/tests/test_static_resources.py +0 -0
  71. {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_detector.py +0 -0
  72. {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_edge_cases.py +0 -0
  73. {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_scenarios_comprehensive.py +0 -0
  74. {sfq-0.0.39 → sfq-0.0.41}/tests/test_timeout_scenarios_summary.py +0 -0
  75. {sfq-0.0.39 → sfq-0.0.41}/tests/test_utils.py +0 -0
  76. {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: |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfq
3
- Version: 0.0.39
3
+ Version: 0.0.41
4
4
  Summary: Python wrapper for the Salesforce's Query API.
5
5
  Author-email: David Moruzzi <sfq.pypi@dmoruzi.com>
6
6
  Keywords: salesforce,salesforce query
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sfq"
3
- version = "0.0.39"
3
+ version = "0.0.41"
4
4
  description = "Python wrapper for the Salesforce's Query API."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "David Moruzzi", email = "sfq.pypi@dmoruzi.com" }]
@@ -45,7 +45,7 @@ __all__ = [
45
45
  "__version__",
46
46
  ]
47
47
 
48
- __version__ = "0.0.39"
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.39",
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.39"
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):
@@ -29,7 +29,7 @@ class HTTPClient:
29
29
  def __init__(
30
30
  self,
31
31
  auth_manager: AuthManager,
32
- user_agent: str = "sfq/0.0.39",
32
+ user_agent: str = "sfq/0.0.41",
33
33
  sforce_client: str = "_auto",
34
34
  high_api_usage_threshold: int = 80,
35
35
  ) -> None:
@@ -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
+
@@ -3,5 +3,5 @@ requires-python = ">=3.9"
3
3
 
4
4
  [[package]]
5
5
  name = "sfq"
6
- version = "0.0.39"
6
+ version = "0.0.41"
7
7
  source = { editable = "." }
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