diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 72f55101..bb93da66 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -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/codecov-action@v2.0.2 \ No newline at end of file + - name: 📤 Upload coverage report + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} # required \ No newline at end of file diff --git a/custom_components/mail_and_packages/__init__.py b/custom_components/mail_and_packages/__init__.py index fd3486df..86ec0cc6 100644 --- a/custom_components/mail_and_packages/__init__.py +++ b/custom_components/mail_and_packages/__init__.py @@ -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 diff --git a/custom_components/mail_and_packages/const.py b/custom_components/mail_and_packages/const.py index 4701b885..708e8b19 100644 --- a/custom_components/mail_and_packages/const.py +++ b/custom_components/mail_and_packages/const.py @@ -194,6 +194,15 @@ ], "subject": ["Your Daily Digest"], }, + "usps_mail_delivered": { + "email": [ + "USPSInformedDelivery@usps.gov", + "USPSInformeddelivery@email.informeddelivery.usps.com", + "USPSInformeddelivery@informeddelivery.usps.com", + "USPS Informed Delivery", + ], + "subject": ["Your Mail Was Delivered"], + }, # UPS "ups_delivered": { "email": ["mcinfo@ups.com"], diff --git a/custom_components/mail_and_packages/helpers.py b/custom_components/mail_and_packages/helpers.py index da77814b..6fb8e3d9 100644 --- a/custom_components/mail_and_packages/helpers.py +++ b/custom_components/mail_and_packages/helpers.py @@ -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 @@ -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 @@ -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 @@ -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. @@ -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 @@ -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 @@ -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 @@ -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. @@ -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, @@ -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, @@ -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] @@ -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() @@ -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, diff --git a/tests/test_helpers.py b/tests/test_helpers.py index a75c8d0d..a727abb9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -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 @@ -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 @@ -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 @@ -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" @@ -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"] @@ -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"] @@ -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"] @@ -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 == {} @@ -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 @@ -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" @@ -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