sfq 0.0.20__tar.gz → 0.0.22__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.
@@ -32,6 +32,9 @@ jobs:
32
32
  - name: Sync dependencies with uv
33
33
  run: uv sync
34
34
 
35
+ - name: Install pytest
36
+ run: pip install pytest
37
+
35
38
  - name: Authenticate GitHub CLI
36
39
  run: |
37
40
  echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
@@ -75,12 +78,20 @@ jobs:
75
78
  sed -i -E "s/(user_agent: str = \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/__init__.py
76
79
  sed -i -E "s/(default is \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/__init__.py
77
80
 
81
+ - name: Run tests
82
+ run: pytest --verbose --strict-config
83
+ env:
84
+ SF_INSTANCE_URL: ${{ secrets.SF_INSTANCE_URL }}
85
+ SF_CLIENT_ID: ${{ secrets.SF_CLIENT_ID }}
86
+ SF_CLIENT_SECRET: ${{ secrets.SF_CLIENT_SECRET }}
87
+ SF_REFRESH_TOKEN: ${{ secrets.SF_REFRESH_TOKEN }}
88
+
78
89
  - name: Commit version updates
79
90
  run: |
80
91
  git config user.name "github-actions"
81
92
  git config user.email "github-actions@users.noreply.github.com"
82
93
  git add pyproject.toml uv.lock src/sfq/__init__.py
83
- git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}"
94
+ git commit -m "CI: bump version to ${{ steps.get_version.outputs.version }}"
84
95
  git push
85
96
 
86
97
  - name: Create and push git tag
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfq
3
- Version: 0.0.20
3
+ Version: 0.0.22
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.20"
3
+ version = "0.0.22"
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" }]
@@ -79,12 +79,12 @@ class SFAuth:
79
79
  client_id: str,
80
80
  refresh_token: str, # client_secret & refresh_token will swap positions 2025-AUG-1
81
81
  client_secret: str = "_deprecation_warning", # mandatory after 2025-AUG-1
82
- api_version: str = "v63.0",
82
+ api_version: str = "v64.0",
83
83
  token_endpoint: str = "/services/oauth2/token",
84
84
  access_token: Optional[str] = None,
85
85
  token_expiration_time: Optional[float] = None,
86
86
  token_lifetime: int = 15 * 60,
87
- user_agent: str = "sfq/0.0.20",
87
+ user_agent: str = "sfq/0.0.22",
88
88
  sforce_client: str = "_auto",
89
89
  proxy: str = "_auto",
90
90
  ) -> None:
@@ -95,12 +95,12 @@ class SFAuth:
95
95
  :param client_id: The client ID for OAuth.
96
96
  :param refresh_token: The refresh token for OAuth.
97
97
  :param client_secret: The client secret for OAuth (default is "_deprecation_warning").
98
- :param api_version: The Salesforce API version (default is "v63.0").
98
+ :param api_version: The Salesforce API version (default is "v64.0").
99
99
  :param token_endpoint: The token endpoint (default is "/services/oauth2/token").
100
100
  :param access_token: The access token for the current session (default is None).
101
101
  :param token_expiration_time: The expiration time of the access token (default is None).
102
102
  :param token_lifetime: The lifetime of the access token in seconds (default is 15 minutes).
103
- :param user_agent: Custom User-Agent string (default is "sfq/0.0.20").
103
+ :param user_agent: Custom User-Agent string (default is "sfq/0.0.22").
104
104
  :param sforce_client: Custom Application Identifier (default is user_agent).
105
105
  :param proxy: The proxy configuration, "_auto" to use environment (default is "_auto").
106
106
  """
@@ -0,0 +1,16 @@
1
+ # tests/conftest.py
2
+
3
+ executed_tests = 0
4
+
5
+ def pytest_runtest_logreport(report):
6
+ global executed_tests
7
+ if report.when == "call" and report.passed:
8
+ executed_tests += 1
9
+ elif report.when == "call" and report.failed:
10
+ executed_tests += 1
11
+
12
+ def pytest_sessionfinish(session, exitstatus):
13
+ if session.testscollected > 0 and executed_tests == 0:
14
+ print()
15
+ print("❌ Pytest collected tests, but all were skipped. Failing the run.")
16
+ session.exitstatus = 1
@@ -0,0 +1,44 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ # --- Setup local import path ---
8
+ project_root = Path(__file__).resolve().parents[1]
9
+ src_path = project_root / "src"
10
+ sys.path.insert(0, str(src_path))
11
+ from sfq import SFAuth # noqa: E402
12
+
13
+
14
+ @pytest.fixture(scope="module")
15
+ def sf_instance():
16
+ required_env_vars = [
17
+ "SF_INSTANCE_URL",
18
+ "SF_CLIENT_ID",
19
+ "SF_CLIENT_SECRET",
20
+ "SF_REFRESH_TOKEN",
21
+ ]
22
+
23
+ missing_vars = [var for var in required_env_vars if not os.getenv(var)]
24
+ if missing_vars:
25
+ pytest.skip(f"Missing required env vars: {', '.join(missing_vars)}")
26
+
27
+ sf = SFAuth(
28
+ instance_url=os.getenv("SF_INSTANCE_URL"),
29
+ client_id=os.getenv("SF_CLIENT_ID"),
30
+ client_secret=os.getenv("SF_CLIENT_SECRET"),
31
+ refresh_token=os.getenv("SF_REFRESH_TOKEN"),
32
+ )
33
+ return sf
34
+
35
+
36
+ def test_limits_api(sf_instance):
37
+ """
38
+ Test the limits API endpoint.
39
+ """
40
+
41
+ limits = sf_instance.limits()
42
+
43
+ assert isinstance(limits, dict)
44
+ assert "DailyApiRequests" in limits.keys()
@@ -0,0 +1,79 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ from io import StringIO
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ # --- Setup local import path ---
10
+ project_root = Path(__file__).resolve().parents[1]
11
+ src_path = project_root / "src"
12
+ sys.path.insert(0, str(src_path))
13
+ from sfq import SFAuth # noqa: E402
14
+
15
+
16
+ @pytest.fixture(scope="module")
17
+ def sf_instance():
18
+ required_env_vars = [
19
+ "SF_INSTANCE_URL",
20
+ "SF_CLIENT_ID",
21
+ "SF_CLIENT_SECRET",
22
+ "SF_REFRESH_TOKEN",
23
+ ]
24
+
25
+ missing_vars = [var for var in required_env_vars if not os.getenv(var)]
26
+ if missing_vars:
27
+ pytest.skip(f"Missing required env vars: {', '.join(missing_vars)}")
28
+
29
+ sf = SFAuth(
30
+ instance_url=os.getenv("SF_INSTANCE_URL"),
31
+ client_id=os.getenv("SF_CLIENT_ID"),
32
+ client_secret=os.getenv("SF_CLIENT_SECRET"),
33
+ refresh_token=os.getenv("SF_REFRESH_TOKEN"),
34
+ )
35
+ return sf
36
+
37
+
38
+ @pytest.fixture
39
+ def capture_logs():
40
+ """
41
+ Fixture to capture logs emitted to 'sfq' logger at TRACE level.
42
+ """
43
+ log_stream = StringIO()
44
+ handler = logging.StreamHandler(log_stream)
45
+ handler.setLevel(5)
46
+
47
+ logger = logging.getLogger("sfq")
48
+ original_level = logger.level
49
+ original_handlers = logger.handlers[:]
50
+
51
+ logger.setLevel(5)
52
+ for h in original_handlers:
53
+ logger.removeHandler(h)
54
+ logger.addHandler(handler)
55
+
56
+ yield logger, log_stream
57
+
58
+ # Teardown - restore original handlers and level
59
+ logger.removeHandler(handler)
60
+ for h in original_handlers:
61
+ logger.addHandler(h)
62
+ logger.setLevel(original_level)
63
+
64
+
65
+ def test_access_token_redacted_in_logs(sf_instance, capture_logs):
66
+ """
67
+ Ensure access tokens are redacted in log output to prevent leakage.
68
+ """
69
+ logger, log_stream = capture_logs
70
+
71
+ sf_instance._get_common_headers()
72
+
73
+ logger.handlers[0].flush()
74
+ log_contents = log_stream.getvalue()
75
+
76
+ assert "access_token" in log_contents, "Expected access_token key in logs"
77
+ assert "'access_token': '********'," in log_contents in log_contents, (
78
+ "Access token was not properly redacted in logs"
79
+ )
@@ -3,5 +3,5 @@ requires-python = ">=3.9"
3
3
 
4
4
  [[package]]
5
5
  name = "sfq"
6
- version = "0.0.20"
6
+ version = "0.0.22"
7
7
  source = { editable = "." }
File without changes
File without changes
File without changes
File without changes
File without changes