Skip to content

Commit

Permalink
fix: better fix for io blocking (#921)
Browse files Browse the repository at this point in the history
* fix: better fix for io blocking

* formatting

* update tests config
  • Loading branch information
firstof9 authored Jun 22, 2024
1 parent 449b293 commit 130c5c9
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 64 deletions.
46 changes: 26 additions & 20 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
@@ -1,51 +1,57 @@
name: Pytest

on:
push:
pull_request:
schedule:
- cron: "0 7 1-28/7 * *"
push:
branches:
- dev

jobs:
build:
runs-on: ubuntu-latest
tests:
runs-on: "ubuntu-latest"
name: Run tests
strategy:
matrix:
python-version:
# - "3.10"
# - "3.11"
- "3.12"

steps:
- uses: actions/checkout@v2
- name: 📥 Checkout the repository
uses: actions/checkout@v4
- name: 🛠️ Set up Python
uses: actions/setup-python@v5
with:
fetch-depth: 2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2.2.2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
- name: 📦 Install requirements
run: |
sudo apt-get update
sudo apt-get -y install language-pack-it
pip install tox tox-gh-actions
- name: Test with tox
- name: 🏃 Test with tox
run: tox
- name: Upload coverage data
uses: "actions/upload-artifact@v2.2.4"
- name: 📤 Upload coverage to Codecov
uses: "actions/upload-artifact@v4"
with:
name: coverage-data
path: "coverage.xml"

coverage:
runs-on: ubuntu-latest
needs: build
needs: tests
steps:
- name: Check out the repository
uses: actions/checkout@v2.3.4
- name: 📥 Checkout the repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Download coverage data
uses: actions/download-artifact@v2.0.10
- name: 📥 Download coverage data
uses: actions/download-artifact@v4
with:
name: coverage-data
- name: Upload coverage report
uses: codecov/[email protected]
- name: 📤 Upload coverage report
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # required
4 changes: 3 additions & 1 deletion custom_components/mail_and_packages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ async def _async_update_data(self):
"""Fetch data."""
async with asyncio.timeout(self.timeout):
try:
data = await process_emails(self.hass, self.config)
data = await self.hass.async_add_executor_job(
process_emails, self.hass, self.config
)
except Exception as error:
_LOGGER.error("Problem updating sensors: %s", error)
raise UpdateFailed(error) from error
Expand Down
9 changes: 9 additions & 0 deletions custom_components/mail_and_packages/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@
],
"subject": ["Your Daily Digest"],
},
"usps_mail_delivered": {
"email": [
"[email protected]",
"[email protected]",
"[email protected]",
"USPS Informed Delivery",
],
"subject": ["Your Mail Was Delivered"],
},
# UPS
"ups_delivered": {
"email": ["[email protected]"],
Expand Down
50 changes: 19 additions & 31 deletions custom_components/mail_and_packages/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def default_image_path(
return "custom_components/mail_and_packages/images/"


async def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict:
def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict:
"""Process emails and return value.
Returns dict containing sensor data
Expand Down Expand Up @@ -197,12 +197,12 @@ async def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict:
_image = {}

# USPS Mail Image name
image_name = await image_file_name(hass, config)
image_name = image_file_name(hass, config)
_LOGGER.debug("Image name: %s", image_name)
_image[ATTR_IMAGE_NAME] = image_name

# Amazon delivery image name
image_name = await image_file_name(hass, config, True)
image_name = image_file_name(hass, config, True)
_LOGGER.debug("Amazon Image Name: %s", image_name)
_image[ATTR_AMAZON_IMAGE] = image_name

Expand All @@ -213,11 +213,11 @@ async def process_emails(hass: HomeAssistant, config: ConfigEntry) -> dict:

# Only update sensors we're intrested in
for sensor in resources:
await fetch(hass, config, account, data, sensor)
fetch(hass, config, account, data, sensor)

# Copy image file to www directory if enabled
if config.get(CONF_ALLOW_EXTERNAL):
await hass.async_add_executor_job(copy_images, hass, config)
copy_images(hass, config)

return data

Expand Down Expand Up @@ -253,7 +253,7 @@ def copy_images(hass: HomeAssistant, config: ConfigEntry) -> None:
return


async def image_file_name(
def image_file_name(
hass: HomeAssistant, config: ConfigEntry, amazon: bool = False
) -> str:
"""Determine if filename is to be changed or not.
Expand Down Expand Up @@ -287,7 +287,7 @@ async def image_file_name(

# SHA1 file hash check
try:
sha1 = await hass.async_add_executor_job(hash_file, mail_none)
sha1 = hash_file(mail_none)
except OSError as err:
_LOGGER.error("Problem accessing file: %s, error returned: %s", mail_none, err)
return image_name
Expand All @@ -310,13 +310,7 @@ async def image_file_name(
_LOGGER.debug("Created: %s, Today: %s", created, today)
# If image isn't mail_none and not created today,
# return a new filename
if (
sha1
!= await hass.async_add_executor_job(
hash_file, os.path.join(path, file)
)
and today != created
):
if sha1 != hash_file(os.path.join(path, file)) and today != created:
image_name = f"{str(uuid.uuid4())}{ext}"
else:
image_name = file
Expand All @@ -329,9 +323,7 @@ async def image_file_name(
# Insert place holder image
_LOGGER.debug("Copying %s to %s", mail_none, os.path.join(path, image_name))

await hass.async_add_executor_job(
copyfile, mail_none, os.path.join(path, image_name)
)
copyfile(mail_none, os.path.join(path, image_name))

return image_name

Expand All @@ -357,7 +349,7 @@ def hash_file(filename: str) -> str:
return the_hash.hexdigest()


async def fetch(
def fetch(
hass: HomeAssistant, config: ConfigEntry, account: Any, data: dict, sensor: str
) -> int:
"""Fetch data for a single sensor, including any sensors it depends on.
Expand All @@ -383,8 +375,7 @@ async def fetch(
count = {}

if sensor == "usps_mail":
count[sensor] = await hass.async_add_executor_job(
get_mails,
count[sensor] = get_mails(
account,
img_out_path,
gif_duration,
Expand All @@ -393,15 +384,13 @@ async def fetch(
nomail,
)
elif sensor == AMAZON_PACKAGES:
count[sensor] = await hass.async_add_executor_job(
get_items,
count[sensor] = get_items(
account,
ATTR_COUNT,
amazon_fwds,
amazon_days,
)
count[AMAZON_ORDER] = await hass.async_add_executor_job(
get_items,
count[AMAZON_ORDER] = get_items(
account,
ATTR_ORDER,
amazon_fwds,
Expand All @@ -417,12 +406,12 @@ async def fetch(
count[AMAZON_EXCEPTION_ORDER] = info[ATTR_ORDER]
elif "_packages" in sensor:
prefix = sensor.replace("_packages", "")
delivering = await fetch(hass, config, account, data, f"{prefix}_delivering")
delivered = await fetch(hass, config, account, data, f"{prefix}_delivered")
delivering = fetch(hass, config, account, data, f"{prefix}_delivering")
delivered = fetch(hass, config, account, data, f"{prefix}_delivered")
count[sensor] = delivering + delivered
elif "_delivering" in sensor:
prefix = sensor.replace("_delivering", "")
delivered = await fetch(hass, config, account, data, f"{prefix}_delivered")
delivered = fetch(hass, config, account, data, f"{prefix}_delivered")
info = get_count(account, sensor, True)
count[sensor] = max(0, info[ATTR_COUNT] - delivered)
count[f"{prefix}_tracking"] = info[ATTR_TRACKING]
Expand All @@ -431,13 +420,13 @@ async def fetch(
for shipper in SHIPPERS:
delivered = f"{shipper}_delivered"
if delivered in data and delivered != sensor:
count[sensor] += await fetch(hass, config, account, data, delivered)
count[sensor] += fetch(hass, config, account, data, delivered)
elif sensor == "zpackages_transit":
total = 0
for shipper in SHIPPERS:
delivering = f"{shipper}_delivering"
if delivering in data and delivering != sensor:
total += await fetch(hass, config, account, data, delivering)
total += fetch(hass, config, account, data, delivering)
count[sensor] = max(0, total)
elif sensor == "mail_updated":
count[sensor] = update_time()
Expand Down Expand Up @@ -1123,8 +1112,7 @@ def amazon_search(
if server_response == "OK" and data[0] is not None:
count += len(data[0].split())
_LOGGER.debug("Amazon delivered email(s) found: %s", count)
hass.async_add_executor_job(
get_amazon_image,
get_amazon_image(
data[0],
account,
image_path,
Expand Down
24 changes: 12 additions & 12 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async def test_process_emails(
assert "/testing_config/custom_components/mail_and_packages/images/" in state.state
state = hass.states.get(MAIL_IMAGE_URL_ENTITY)
assert state.state == "unknown"
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert isinstance(result["mail_updated"], datetime.datetime)
assert result["zpackages_delivered"] == 0
assert result["zpackages_transit"] == 0
Expand Down Expand Up @@ -140,7 +140,7 @@ async def test_process_emails_external(
assert "/testing_config/custom_components/mail_and_packages/images/" in state.state
state = hass.states.get(MAIL_IMAGE_URL_ENTITY)
assert state.state == "unknown"
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert isinstance(result["mail_updated"], datetime.datetime)
assert result["zpackages_delivered"] == 0
assert result["zpackages_transit"] == 0
Expand Down Expand Up @@ -178,7 +178,7 @@ async def test_process_emails_external_error(
config = entry.data.copy()
with patch("os.makedirs") as mock_osmakedirs:
mock_osmakedirs.side_effect = OSError
await process_emails(hass, config)
process_emails(hass, config)

assert "Problem creating:" in caplog.text

Expand Down Expand Up @@ -234,7 +234,7 @@ async def test_process_emails_non_random(
entry = integration

config = entry.data
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert result["image_name"] == "testfile.gif"


Expand All @@ -254,7 +254,7 @@ async def test_process_emails_random(
entry = integration

config = entry.data
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert ".gif" in result["image_name"]


Expand All @@ -274,7 +274,7 @@ async def test_process_nogif(
entry = integration

config = entry.data
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert ".gif" in result["image_name"]


Expand All @@ -294,7 +294,7 @@ async def test_process_old_image(
entry = integration

config = entry.data
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert ".gif" in result["image_name"]


Expand All @@ -317,7 +317,7 @@ async def test_process_folder_error(
with patch(
"custom_components.mail_and_packages.helpers.selectfolder", return_value=False
):
result = await process_emails(hass, config)
result = process_emails(hass, config)
assert result == {}


Expand Down Expand Up @@ -989,7 +989,7 @@ async def test_image_file_name_path_error(hass, caplog):
with patch("os.path.exists", return_value=False), patch(
"os.makedirs", side_effect=OSError
):
result = await image_file_name(hass, config)
result = image_file_name(hass, config)
assert result == "mail_none.gif"
assert "Problem creating:" in caplog.text

Expand All @@ -1003,7 +1003,7 @@ async def test_image_file_name_amazon(
with patch("os.path.exists", return_value=True), patch(
"os.makedirs", return_value=True
):
result = await image_file_name(hass, config, True)
result = image_file_name(hass, config, True)
assert result == "testfile.jpg"


Expand All @@ -1016,13 +1016,13 @@ async def test_image_file_name(
with patch("os.path.exists", return_value=True), patch(
"os.makedirs", return_value=True
):
result = await image_file_name(hass, config)
result = image_file_name(hass, config)
assert ".gif" in result
assert not result == "mail_none.gif"

# Test custom image settings
config = FAKE_CONFIG_DATA_CUSTOM_IMG
result = await image_file_name(hass, config)
result = image_file_name(hass, config)
assert ".gif" in result
assert not result == "mail_none.gif"
assert len(mock_copyfile.mock_calls) == 2
Expand Down

0 comments on commit 130c5c9

Please sign in to comment.