-
Notifications
You must be signed in to change notification settings - Fork 9
Fix light mode #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix light mode #101
Changes from all commits
13d4cd6
6021cf8
311e9b0
e7b8ddb
2fdefca
392bee9
a756909
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| """ | ||
|
|
||
| from typing import Dict, Any, Optional | ||
| import requests | ||
| import httpx | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify httpx is declared as a dependency. The code now imports and uses Run the following script to check if httpx is declared as a dependency: 🏁 Script executed: #!/bin/bash
# Check if httpx is declared in common dependency files
echo "=== Checking for httpx in dependency files ==="
fd -t f 'requirements.*\.txt$|setup\.py$|pyproject\.toml$|Pipfile$' --exec echo "File: {}" \; --exec grep -n "httpx" {} \; 2>/dev/null || echo "No httpx dependency found"Length of output: 95 I need to inspect the actual contents of 🏁 Script executed: #!/bin/bash
# View the contents of pyproject.toml to see current dependencies
cat ./pyproject.tomlLength of output: 2651 Add The code imports dependencies = [
"pydantic >=2.10.6,<3.0.0",
"pydantic[email] >=2.10.6,<3.0.0",
"requests >=2.32.3,<3.0.0",
"typing_extensions >= 4.12.2,<5.0.0",
"cryptography >=41.0.7",
"httpx", # Add version constraint as appropriate
]🤖 Prompt for AI Agents |
||
|
|
||
| from mpesakit.errors import MpesaError, MpesaApiException | ||
| from .http_client import HttpClient | ||
|
|
@@ -54,14 +54,15 @@ def post( | |
| """ | ||
| try: | ||
| full_url = f"{self.base_url}{url}" | ||
| response = requests.post(full_url, json=json, headers=headers, timeout=10) | ||
| with httpx.Client(timeout=10) as client: | ||
| response = client.post(full_url, json=json, headers=headers) | ||
|
|
||
| try: | ||
| response_data = response.json() | ||
| except ValueError: | ||
| response_data = {"errorMessage": response.text.strip() or ""} | ||
|
|
||
| if not response.ok: | ||
| if response.status_code >= 400: | ||
| error_message = response_data.get("errorMessage", "") | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
|
|
@@ -74,23 +75,15 @@ def post( | |
|
|
||
| return response_data | ||
|
|
||
| except requests.Timeout: | ||
| except httpx.TimeoutException: | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
| error_code="REQUEST_TIMEOUT", | ||
| error_message="Request to Mpesa timed out.", | ||
| status_code=None, | ||
| ) | ||
| ) | ||
| except requests.ConnectionError: | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
| error_code="CONNECTION_ERROR", | ||
| error_message="Failed to connect to Mpesa API. Check network or URL.", | ||
| status_code=None, | ||
| ) | ||
| ) | ||
| except requests.RequestException as e: | ||
| except httpx.RequestError as e: | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
| error_code="REQUEST_FAILED", | ||
|
|
@@ -124,16 +117,15 @@ def get( | |
| headers = {} | ||
| full_url = f"{self.base_url}{url}" | ||
|
|
||
| response = requests.get( | ||
| full_url, params=params, headers=headers, timeout=10 | ||
| ) # Add timeout | ||
| with httpx.Client(timeout=10) as client: | ||
| response = client.get(full_url, params=params, headers=headers) | ||
|
|
||
| try: | ||
| response_data = response.json() | ||
| except ValueError: | ||
| response_data = {"errorMessage": response.text.strip() or ""} | ||
|
|
||
| if not response.ok: | ||
| if not response.is_success: | ||
| error_message = response_data.get("errorMessage", "") | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
|
|
@@ -146,23 +138,15 @@ def get( | |
|
|
||
| return response_data | ||
|
|
||
| except requests.Timeout: | ||
| except httpx.TimeoutException: | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
| error_code="REQUEST_TIMEOUT", | ||
| error_message="Request to Mpesa timed out.", | ||
| status_code=None, | ||
| ) | ||
| ) | ||
| except requests.ConnectionError: | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
| error_code="CONNECTION_ERROR", | ||
| error_message="Failed to connect to Mpesa API. Check network or URL.", | ||
| status_code=None, | ||
| ) | ||
| ) | ||
| except requests.RequestException as e: | ||
| except httpx.RequestError as e: | ||
| raise MpesaApiException( | ||
| MpesaError( | ||
| error_code="REQUEST_FAILED", | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |||||||||||||||||||||
| HTTP POST and GET request handling, and error handling for various scenarios. | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import requests | ||||||||||||||||||||||
| import httpx | ||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||
| from unittest.mock import Mock, patch | ||||||||||||||||||||||
| from mpesakit.http_client.mpesa_http_client import MpesaHttpClient | ||||||||||||||||||||||
|
|
@@ -32,9 +32,9 @@ def test_base_url_production(): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_post_success(client): | ||||||||||||||||||||||
| """Test successful POST request returns expected JSON.""" | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.requests.post") as mock_post: | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.httpx.Client.post") as mock_post: | ||||||||||||||||||||||
| mock_response = Mock() | ||||||||||||||||||||||
| mock_response.ok = True | ||||||||||||||||||||||
| mock_response.status_code = 200 | ||||||||||||||||||||||
| mock_response.json.return_value = {"foo": "bar"} | ||||||||||||||||||||||
| mock_post.return_value = mock_response | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -45,9 +45,8 @@ def test_post_success(client): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_post_http_error(client): | ||||||||||||||||||||||
| """Test POST request returns MpesaApiException on HTTP error.""" | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.requests.post") as mock_post: | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.httpx.Client.post") as mock_post: | ||||||||||||||||||||||
| mock_response = Mock() | ||||||||||||||||||||||
| mock_response.ok = False | ||||||||||||||||||||||
| mock_response.status_code = 400 | ||||||||||||||||||||||
| mock_response.json.return_value = {"errorMessage": "Bad Request"} | ||||||||||||||||||||||
| mock_post.return_value = mock_response | ||||||||||||||||||||||
|
|
@@ -60,9 +59,8 @@ def test_post_http_error(client): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_post_json_decode_error(client): | ||||||||||||||||||||||
| """Test POST request handles JSON decode error gracefully.""" | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.requests.post") as mock_post: | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.httpx.Client.post") as mock_post: | ||||||||||||||||||||||
| mock_response = Mock() | ||||||||||||||||||||||
| mock_response.ok = False | ||||||||||||||||||||||
| mock_response.status_code = 500 | ||||||||||||||||||||||
| mock_response.json.side_effect = ValueError() | ||||||||||||||||||||||
| mock_response.text = "Internal Server Error" | ||||||||||||||||||||||
|
|
@@ -77,8 +75,8 @@ def test_post_json_decode_error(client): | |||||||||||||||||||||
| def test_post_request_exception(client): | ||||||||||||||||||||||
| """Test POST request raises MpesaApiException on generic exception.""" | ||||||||||||||||||||||
| with patch( | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.requests.post", | ||||||||||||||||||||||
| side_effect=requests.RequestException("boom"), | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.httpx.Client.post", | ||||||||||||||||||||||
| side_effect=httpx.RequestError("boom"), | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| with pytest.raises(MpesaApiException) as exc: | ||||||||||||||||||||||
| client.post("/fail", json={}, headers={}) | ||||||||||||||||||||||
|
|
@@ -88,8 +86,8 @@ def test_post_request_exception(client): | |||||||||||||||||||||
| def test_post_timeout(client): | ||||||||||||||||||||||
| """Test POST request raises MpesaApiException on timeout.""" | ||||||||||||||||||||||
| with patch( | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.requests.post", | ||||||||||||||||||||||
| side_effect=requests.Timeout, | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.httpx.Client.post", | ||||||||||||||||||||||
| side_effect=httpx.TimeoutException, | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| with pytest.raises(MpesaApiException) as exc: | ||||||||||||||||||||||
| client.post("/timeout", json={}, headers={}) | ||||||||||||||||||||||
|
|
@@ -99,8 +97,8 @@ def test_post_timeout(client): | |||||||||||||||||||||
| def test_post_connection_error(client): | ||||||||||||||||||||||
| """Test POST request raises MpesaApiException on connection error.""" | ||||||||||||||||||||||
| with patch( | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.requests.post", | ||||||||||||||||||||||
| side_effect=requests.ConnectionError, | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.httpx.Client.post", | ||||||||||||||||||||||
| side_effect=httpx.ConnectError, | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix httpx.ConnectError instantiation. httpx.ConnectError requires a Apply this diff: - side_effect=httpx.ConnectError,
+ side_effect=httpx.ConnectError("Connection failed"),🤖 Prompt for AI Agents |
||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| with pytest.raises(MpesaApiException) as exc: | ||||||||||||||||||||||
| client.post("/conn", json={}, headers={}) | ||||||||||||||||||||||
|
|
@@ -109,7 +107,7 @@ def test_post_connection_error(client): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_get_success(client): | ||||||||||||||||||||||
| """Test successful GET request returns expected JSON.""" | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.requests.get") as mock_get: | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.httpx.Client.get") as mock_get: | ||||||||||||||||||||||
| mock_response = Mock() | ||||||||||||||||||||||
| mock_response.status_code = 200 | ||||||||||||||||||||||
| mock_response.json.return_value = {"foo": "bar"} | ||||||||||||||||||||||
|
|
@@ -122,9 +120,8 @@ def test_get_success(client): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_get_http_error(client): | ||||||||||||||||||||||
| """Test GET request returns MpesaApiException on HTTP error.""" | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.requests.get") as mock_get: | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.httpx.Client.get") as mock_get: | ||||||||||||||||||||||
| mock_response = Mock() | ||||||||||||||||||||||
| mock_response.ok = False | ||||||||||||||||||||||
| mock_response.status_code = 404 | ||||||||||||||||||||||
| mock_response.json.return_value = {"errorMessage": "Not Found"} | ||||||||||||||||||||||
| mock_get.return_value = mock_response | ||||||||||||||||||||||
|
Comment on lines
124
to
127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add The implementation at Apply this diff: mock_response = Mock()
mock_response.status_code = 404
+ mock_response.is_success = False
mock_response.json.return_value = {"errorMessage": "Not Found"}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
@@ -137,9 +134,8 @@ def test_get_http_error(client): | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| def test_get_json_decode_error(client): | ||||||||||||||||||||||
| """Test GET request handles JSON decode error gracefully.""" | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.requests.get") as mock_get: | ||||||||||||||||||||||
| with patch("mpesakit.http_client.mpesa_http_client.httpx.Client.get") as mock_get: | ||||||||||||||||||||||
| mock_response = Mock() | ||||||||||||||||||||||
| mock_response.ok = False | ||||||||||||||||||||||
| mock_response.status_code = 500 | ||||||||||||||||||||||
| mock_response.json.side_effect = ValueError() | ||||||||||||||||||||||
| mock_response.text = "Internal Server Error" | ||||||||||||||||||||||
|
|
@@ -154,8 +150,8 @@ def test_get_json_decode_error(client): | |||||||||||||||||||||
| def test_get_request_exception(client): | ||||||||||||||||||||||
| """Test GET request raises MpesaApiException on generic exception.""" | ||||||||||||||||||||||
| with patch( | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.requests.get", | ||||||||||||||||||||||
| side_effect=requests.RequestException("boom"), | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.httpx.Client.get", | ||||||||||||||||||||||
| side_effect=httpx.RequestError("boom"), | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| with pytest.raises(MpesaApiException) as exc: | ||||||||||||||||||||||
| client.get("/fail") | ||||||||||||||||||||||
|
|
@@ -165,8 +161,8 @@ def test_get_request_exception(client): | |||||||||||||||||||||
| def test_get_timeout(client): | ||||||||||||||||||||||
| """Test GET request raises MpesaApiException on timeout.""" | ||||||||||||||||||||||
| with patch( | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.requests.get", | ||||||||||||||||||||||
| side_effect=requests.Timeout, | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.httpx.Client.get", | ||||||||||||||||||||||
| side_effect=httpx.TimeoutException, | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix httpx.TimeoutException instantiation. httpx.TimeoutException requires a Apply this diff: - side_effect=httpx.TimeoutException,
+ side_effect=httpx.TimeoutException("Connection timeout"),🤖 Prompt for AI Agents |
||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| with pytest.raises(MpesaApiException) as exc: | ||||||||||||||||||||||
| client.get("/timeout") | ||||||||||||||||||||||
|
|
@@ -176,8 +172,8 @@ def test_get_timeout(client): | |||||||||||||||||||||
| def test_get_connection_error(client): | ||||||||||||||||||||||
| """Test GET request raises MpesaApiException on connection error.""" | ||||||||||||||||||||||
| with patch( | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.requests.get", | ||||||||||||||||||||||
| side_effect=requests.ConnectionError, | ||||||||||||||||||||||
| "mpesakit.http_client.mpesa_http_client.httpx.Client.get", | ||||||||||||||||||||||
| side_effect=httpx.ConnectError, | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| with pytest.raises(MpesaApiException) as exc: | ||||||||||||||||||||||
| client.get("/conn") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conflicting border definitions on
.featureCard.Lines 617 and 618 both set
border, but with different colors:rgba(0, 0, 0, 0.1)(dark) andrgba(255, 255, 255, 0.1)(light). The second declaration will override the first, making the first line ineffective.Clarify the intended border style and apply a single border definition:
.featureCard { background: var(--card-bg); border-radius: 20px; padding: 2.5rem; - border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(255, 255, 255, 0.1); transition: all 0.3s ease;Alternatively, if the intent is to use a dark border for light mode, remove line 618 and keep line 617.
📝 Committable suggestion
🧰 Tools
🪛 Biome (2.1.2)
[error] 618-618: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
border is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🤖 Prompt for AI Agents