Skip to content
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

feat: First try to fetch data #2

Merged
merged 67 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
4a57124
feat: First try to fetch data
hectorzin Nov 14, 2024
d33dbed
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 14, 2024
ebc7ed7
fix code: to meet Pre-commit requirements
hectorzin Nov 15, 2024
34174fe
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
28c2a94
fix: pre-commit guidelines, step 2
hectorzin Nov 15, 2024
44f6fc3
fix: pre-commit guidelines, step 3
hectorzin Nov 15, 2024
518feae
fix: pre-commit errors step 4
hectorzin Nov 15, 2024
06b6c27
fix: pre-commit step 5
hectorzin Nov 15, 2024
75ad37e
fix: PR-commit 😡, try 6
hectorzin Nov 15, 2024
0e050ad
fix: pre-commit step 7, vamooooooooosss
hectorzin Nov 15, 2024
e72ae0a
fix: pre-commit step 8
hectorzin Nov 15, 2024
a2bc916
fix: pre-commit step 9
hectorzin Nov 15, 2024
038c029
fix: pre-commit, sorting imports and froms 🥴, step 10
hectorzin Nov 15, 2024
5fbbe67
fix: pre-commit formatting froms
hectorzin Nov 15, 2024
706c5a1
fix: pre-commit, como sea esto me cagontó porque la línea no era tan …
hectorzin Nov 15, 2024
511d57e
fix: pre-commit step 11
hectorzin Nov 15, 2024
dedc9e8
fix: pre-commit step 12
hectorzin Nov 15, 2024
a9462b1
feat: adding isort to pre-commit
hectorzin Nov 15, 2024
5de78b9
me rindoooooooooooooooo
hectorzin Nov 15, 2024
f501c3b
fix: adding issort to pyproject.toml
hectorzin Nov 15, 2024
aaf113d
Fix imports
MiguelAngelLV Nov 15, 2024
98dad81
Fix imports
MiguelAngelLV Nov 15, 2024
f7694df
Merge remote-tracking branch 'origin/feat/call_api_to_get_orders' int…
MiguelAngelLV Nov 15, 2024
af04177
venga, a ver mezclando imports con froms
hectorzin Nov 15, 2024
d0d89e2
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
57d38d7
Poetry en pre-commit
MiguelAngelLV Nov 15, 2024
49f7ccd
Updated version
MiguelAngelLV Nov 15, 2024
994384e
try again
hectorzin Nov 15, 2024
7cd1d4c
poetry
hectorzin Nov 15, 2024
59da074
poetry
hectorzin Nov 15, 2024
745a9d3
poetry
hectorzin Nov 15, 2024
4c8575c
poetry
hectorzin Nov 15, 2024
f3d9d51
Update pre-commit
MiguelAngelLV Nov 15, 2024
def5c34
Merge remote-tracking branch 'origin/feat/call_api_to_get_orders' int…
MiguelAngelLV Nov 15, 2024
2897817
fuera ci
hectorzin Nov 15, 2024
20ca101
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
5795811
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2024
6d1b99a
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
580ebe3
pre-commit, fighting again
hectorzin Nov 15, 2024
36f2001
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2024
72fcd93
😭
hectorzin Nov 15, 2024
12d99e2
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
c692fae
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2024
4d59388
the final one?
hectorzin Nov 15, 2024
40c8ab3
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
7e3e0c3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2024
efc16bc
another try
hectorzin Nov 15, 2024
cbf2305
Merge branch 'feat/call_api_to_get_orders' of https://github.com/hect…
hectorzin Nov 15, 2024
5c23a3a
bug: missing DOMAIN in ConfigFlow
hectorzin Nov 15, 2024
aee1e65
Update sensors as stats.
MiguelAngelLV Nov 15, 2024
b930834
Merge remote-tracking branch 'origin/feat/call_api_to_get_orders' int…
MiguelAngelLV Nov 15, 2024
73e5bb6
Update pre-commit
MiguelAngelLV Nov 15, 2024
e5d9345
Fix values
MiguelAngelLV Nov 16, 2024
3cafe2a
patch: add aliexpress_api inside the project
hectorzin Nov 26, 2024
4865a30
patch
hectorzin Nov 27, 2024
2a52c12
patch
hectorzin Nov 27, 2024
9120952
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 27, 2024
2a7e6b9
include api
hectorzin Nov 27, 2024
f0f0272
include api
hectorzin Nov 27, 2024
d746ff1
remove precommit from api
hectorzin Nov 27, 2024
91276bf
ignore pre-commit for api
hectorzin Nov 27, 2024
1838af5
feat: use internal API handler (#4)
hectorzin Dec 22, 2024
d9a43ff
Update Coordinator and reorder code
MiguelAngelLV Dec 25, 2024
9798ad2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 25, 2024
fd95335
Pre-Commit fixs
MiguelAngelLV Dec 25, 2024
1bdaf31
Merge remote-tracking branch 'origin/feat/call_api_to_get_orders' int…
MiguelAngelLV Dec 25, 2024
954a704
Pre-Commit fixs
MiguelAngelLV Dec 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/pylint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,15 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache: "poetry"

- name: 🔄 Synchronize Poetry Lock File
run: poetry lock --no-update || poetry lock

- name: 🏗 Install Python dependencies
run: poetry install --no-interaction
run: poetry install --with dev --no-interaction

- name: 🚀 Run Pylint
run: poetry run pylint custom_components/aliexpress_openplatform
run: poetry run pylint custom_components/aliexpress_openplatform --ignore=iop --disable=import-error,too-few-public-methods

- name: 🚀 Run Mypy
run: |
poetry run mypy custom_components/aliexpress_openplatform --exclude '^custom_components/aliexpress_openplatform/iop/'
4 changes: 4 additions & 0 deletions .github/workflows/ruff.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache: "poetry"

- name: Regenerate poetry.lock if needed
run: |
poetry lock --no-update || poetry lock

- name: 🏗 Install Python dependencies
run: poetry install --no-interaction

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
__pycache__
.idea
/.run/HASS.run.xml
/node_modules/.cache/
custom_components/test.py
100 changes: 36 additions & 64 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,73 +1,45 @@
---
minimum_pre_commit_version: "3.0.4"
repos:
- repo: local
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.2
hooks:
- id: ruff-check
name: 🐶 Ruff Linter
language: system
types: [python]
entry: poetry run ruff check --fix
require_serial: true
stages: [commit, push, manual]
- id: ruff
args:
- --fix
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- id: ruff-format
name: 🐶 Ruff Formatter
language: system
types: [python]
entry: poetry run ruff format
require_serial: true
stages: [commit, push, manual]
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- repo: "https://github.com/pre-commit/pre-commit-hooks"
rev: "v4.5.0"
hooks:
- id: end-of-file-fixer
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- id: trailing-whitespace
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- id: check-json
name: { Check JSON files
language: system
types: [json]
entry: poetry run check-json
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- id: check-toml
name: ✅ Check TOML files
language: system
types: [toml]
entry: poetry run check-toml
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- id: check-yaml
name: ✅ Check YAML files
language: system
types: [yaml]
entry: poetry run check-yaml
- id: check-merge-conflict
name: 💥 Check for merge conflicts
language: system
types: [text]
entry: poetry run check-merge-conflict
- id: check-symlinks
name: 🔗 Check for broken symlinks
language: system
types: [symlink]
entry: poetry run check-symlinks
- id: end-of-file-fixer
name: ⮐ Fix End of Files
language: system
types: [text]
entry: poetry run end-of-file-fixer
stages: [commit, push, manual]
- id: no-commit-to-branch
name: 🛑 Don't commit to main branch
language: system
entry: poetry run no-commit-to-branch
pass_filenames: false
always_run: true
args:
- --branch=main
- id: poetry
name: 📜 Check pyproject with Poetry
language: system
entry: poetry check
pass_filenames: false
always_run: true
- id: pylint
name: 🌟 Starring code with pylint
language: system
types: [python]
entry: poetry run pylint
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
rev: v4.0.0-alpha.8
hooks:
- id: prettier
name: 🎨 Format using prettier
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- repo: https://github.com/PyCQA/pylint
rev: "v3.0.0"
hooks:
- id: pylint
args:
- --ignore=custom_components/aliexpress_openplatform/iop
- --disable=import-error,too-few-public-methods
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
- repo: "https://github.com/pre-commit/mirrors-mypy"
rev: "v1.13.0"
hooks:
- id: "mypy"
name: "Check type hints (mypy)"
args: [--ignore-missing-imports]
verbose: true
exclude: ^(node_modules/|custom_components/aliexpress_openplatform/iop/)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# ha-aliexpress-openplatform

A custom Home Assistant integration to track AliExpress affiliate purchases in real-time

This component enables Home Assistant users to monitor and display affiliate purchases made through AliExpress, leveraging the AliExpress Affiliate API. The integration provides dynamically updated sensors for tracking purchase details, including product names, quantities, prices, and order statuses. Users can easily visualize affiliate sales data within their Home Assistant dashboards and set up custom notifications or automations based on purchase activity.
49 changes: 38 additions & 11 deletions custom_components/aliexpress_openplatform/__init__.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,63 @@
"""Aliexpress OpenPlatform."""
"""Aliexpress OpenPlatform Integration.

This module sets up the integration and handles its configuration.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from homeassistant.const import Platform

from .aliexpress_coordinator import AliexpressOpenPlatformCoordinator
from .const import DOMAIN

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Initialise entry configuration."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(_async_update_options))
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Aliexpress OpenPlatform from a config entry."""
# Check if the coordinator already exists to avoid double setup

if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}

if config_entry.entry_id in hass.data[DOMAIN]:
return False

# Initialize the coordinator
coordinator = AliexpressOpenPlatformCoordinator(hass, config_entry)
hass.data[DOMAIN][config_entry.entry_id] = coordinator

# Forward the setup to the sensor platform
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
await coordinator.async_config_entry_first_refresh()

# Register the listener for option updates
config_entry.async_on_unload(
config_entry.add_update_listener(_async_update_options)
)

return True


async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Remove entry after unload component."""
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
"""Unload an Aliexpress OpenPlatform config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok


async def _async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Handle options update."""
# update entry replacing data with new options
# Update entry replacing data with new options
hass.config_entries.async_update_entry(
config_entry, data={**config_entry.data, **config_entry.options}
)
Expand Down
146 changes: 146 additions & 0 deletions custom_components/aliexpress_openplatform/aliexpress_api_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""Module to handle interactions with the AliExpress API."""

from __future__ import annotations

import hashlib
import hmac
import logging
import time
from typing import Any

import requests # type: ignore[import-untyped]

from .iop import IopClient, IopRequest

# Base URL for the AliExpress API
ALIEXPRESS_API_URL = "https://api-sg.aliexpress.com/sync"
_LOGGER = logging.getLogger(__name__)


def get_order_list(
app_key: str,
app_secret: str,
query_params: dict[str, Any],
pagination: dict[str, int] | None = None,
) -> dict[str, Any]:
"""Make an API call to `aliexpress.affiliate.order.list` using the AliExpress SDK."""
client = IopClient(ALIEXPRESS_API_URL, app_key, app_secret)

request = IopRequest("aliexpress.affiliate.order.list", "POST")
for key, value in query_params.items():
request.add_api_param(key, value)
for key, value in (pagination or {}).items():
request.add_api_param(key, str(value))

response = client.execute(request)

if not response:
message = response.message if response else "Unknown error"
code = response.code if response else "Unknown code"
error_message = f"API returned an error: {message} (Code: {code})"
raise ValueError(error_message)

data = response.body
main_key = next((key for key in data if key.endswith("_response")), None)
if not main_key or "resp_result" not in data[main_key]:
error_message = f"Unexpected API response format: {data}"
raise ValueError(error_message)

return data[main_key]["resp_result"].get("result", {})


def generate_signature(secret: str, params: dict[str, Any]) -> str:
"""Generate the HMAC-SHA256 signature for API authentication.

Args:
----
secret (str): API secret key provided by AliExpress.
params (dict): Parameters to be signed.

Returns:
-------
str: HMAC-SHA256 signature in hexadecimal format.

"""
sorted_params = sorted((k, v) for k, v in params.items() if k != "sign")
concatenated_params = "".join(f"{k}{v}" for k, v in sorted_params)
return (
hmac.new(
secret.encode("utf-8"),
concatenated_params.encode("utf-8"),
hashlib.sha256,
)
.hexdigest()
.upper()
)


def get_order_list_http_request(
app_key: str,
app_secret: str,
query_params: dict[str, Any],
pagination: dict[str, int] | None = None,
) -> dict[str, Any]:
"""Make an API call to `aliexpress.affiliate.order.list` to fetch orders.

Args:
----
app_key (str): API key provided by AliExpress.
app_secret (str): API secret key provided by AliExpress.
query_params (dict): Query parameters for the API request.
pagination (dict | None): Dictionary with `page_no` and `page_size`.

Returns:
-------
dict: Processed data from the API response.

Raises:
------
ValueError: If the API response has an unexpected format.
requests.RequestException: If an HTTP request error occurs.

"""
pagination = pagination or {"page_no": 1, "page_size": 50}

# Build the query parameters
params = {
"app_key": app_key,
"timestamp": str(int(time.time() * 1000)),
"sign_method": "hmac-sha256",
"method": "aliexpress.affiliate.order.list",
"time_type": "Payment Completed Time",
**query_params,
**pagination,
}
params["sign"] = generate_signature(app_secret, params)

try:
response = requests.get(ALIEXPRESS_API_URL, params=params, timeout=10)
response.raise_for_status()
data = response.json()

# Locate the main key in the response
main_key = next((key for key in data if key.endswith("_response")), None)
if not main_key or "resp_result" not in data[main_key]:
_LOGGER.error("Unexpected API response: %s", data)
raise_format_error("The API response does not contain the expected data.")
else:
# Process results within `resp_result`
return data[main_key]["resp_result"].get("result", {})

except requests.RequestException as e:
handle_request_exception(e)
except ValueError as e:
raise_format_error(str(e))
return {}


def raise_format_error(message: str) -> None:
"""Raise a format error with a standard message."""
raise ValueError(message)


def handle_request_exception(e: requests.RequestException) -> None:
"""Raise a request error with additional context."""
error_message = f"HTTP request error to AliExpress API: {e}"
raise requests.RequestException(error_message) from e
Loading
Loading