Skip to content

fix(deps): make uamqp optional on ARM macOS to fix installation failure#18

Open
marcodalessandro wants to merge 11 commits intoAzure:mainfrom
marcodalessandro:fix/uamqp-macos-compatibility
Open

fix(deps): make uamqp optional on ARM macOS to fix installation failure#18
marcodalessandro wants to merge 11 commits intoAzure:mainfrom
marcodalessandro:fix/uamqp-macos-compatibility

Conversation

@marcodalessandro
Copy link
Member

This pull request adds robust support for environments where the uamqp dependency (required for AMQP-based Cloud-to-Device (C2D) messaging) cannot be installed, specifically ARM macOS (Apple Silicon). It makes uamqp optional on those platforms, provides clear error handling and installation instructions when C2D messaging is attempted without it, and documents the new behavior in the README. Comprehensive tests are also added to verify correct SDK behavior when uamqp is absent. Other SDK features (device/module CRUD, twin operations, etc.) remain fully functional without uamqp.

Dependency and Platform Support

  • Updated setup.py to make uamqp an optional dependency on ARM macOS (Apple Silicon), with an opt-in amqp extra for users who can build it in their environment.
  • Bumped SDK version to 2.8.0 to reflect these changes.

Error Handling and Fallbacks

  • Added runtime checks throughout the SDK to raise a clear ImportError with installation instructions when C2D messaging is used without uamqp. AMQP client classes also raise on construction if uamqp is missing. [1] [2] [3] [4] [5] [6]
  • Introduced a fallback TransportType enum for use when uamqp is not installed, ensuring API compatibility.

Documentation Updates

  • Expanded the README.md to document the new installation workflow, platform-specific behavior, and how to opt in to C2D messaging on ARM macOS. Also clarified which SDK features require uamqp and which do not.

Testing

  • Added a new test suite (tests/test_no_uamqp.py) that simulates the absence of uamqp and verifies that REST operations work, C2D messaging fails with a clear error, and fallback behaviors are correct.
  • Updated existing tests to skip AMQP-specific tests when uamqp is not available.uamqp fails to build on Apple Silicon due to stricter clang type checking. Exclude it from install_requires on ARM macOS via PEP 508 marker while keeping it for Windows, Linux, and Intel Mac. Add [amqp] extra for opt-in. Gracefully handle missing uamqp at runtime so all non-C2D functionality works without it.

uamqp fails to build on Apple Silicon due to stricter clang type
checking. Exclude it from install_requires on ARM macOS via PEP 508
marker while keeping it for Windows, Linux, and Intel Mac. Add [amqp]
extra for opt-in. Gracefully handle missing uamqp at runtime so all
non-C2D functionality works without it.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Makes uamqp optional on ARM macOS (Apple Silicon) to prevent installation failures while preserving non-AMQP SDK functionality, and provides clearer runtime errors + docs for opting into C2D AMQP messaging.

Changes:

  • Adds runtime handling when uamqp is missing (fallback TransportType, graceful init, and explicit ImportErrors for C2D usage).
  • Updates packaging to exclude uamqp on ARM macOS by default and introduces an [amqp] extra.
  • Adds/updates tests and README to validate and document the new behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/test_no_uamqp.py New tests to validate SDK behavior when uamqp is absent.
tests/test_iothub_amqp_client.py Skips AMQP tests when uamqp is not installed.
src/azure/iot/hub/iothub_registry_manager.py Adds fallback TransportType, avoids failing construction without uamqp, and errors clearly on send_c2d_message.
src/azure/iot/hub/iothub_amqp_client.py Makes uamqp import optional; raises clear ImportError when AMQP paths are used without it.
src/azure/iot/hub/constant.py Bumps package version to 2.8.0.
setup.py Uses PEP 508 marker to exclude uamqp on ARM macOS and adds [amqp] extra.
README.md Documents platform behavior, opt-in install, and C2D AMQP usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Replace bare except ImportError with explicit HAS_UAMQP check
- Use IntEnum for fallback TransportType for better API compatibility
- Remove unused imports in test_no_uamqp.py
- Strengthen test_delete_device assertion with exact call args
@marcodalessandro
Copy link
Member Author

All review feedback has been addressed in commit d038f75:

1. Silent except ImportError: pass (iothub_registry_manager.py, two locations)
Replaced bare except ImportError: pass with an explicit if iothub_amqp_client.HAS_UAMQP: guard before constructing the AMQP client. self.amqp_svc_client is initialized to None before the branch, so it's always defined.

2. Use IntEnum instead of Enum for fallback TransportType
Changed from Enum to IntEnum for better API compatibility with uamqp.TransportType.

3. README markdown table double pipes
Reviewed the table — it uses single pipes and renders correctly. No change needed (false positive).

4-5. Unused imports and standalone string in test_no_uamqp.py
Removed unused patch and MagicMock imports. Changed """---Constants---""" standalone string to a # ---Constants--- comment.

6. test_delete_device assertion
Strengthened to assert_called_once_with(fake_device_id, '"*"') to verify exact call arguments.

All 185 tests pass.

@marcodalessandro marcodalessandro marked this pull request as ready for review March 6, 2026 00:14
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@marcodalessandro marcodalessandro marked this pull request as draft March 6, 2026 00:19
six is a Python 2 compatibility library that is not reliably available
as a transitive dependency on all Python versions, causing CI failures.
The existing test file imported TransportType directly from uamqp,
which fails on ARM macOS where uamqp is not installed. Import from
the registry manager module which has a fallback enum.
- Deduplicate ImportError message using shared _UAMQP_MISSING_ERROR constant
- Fix mutable default argument properties={} in send_c2d_message
- Improve fallback IntEnum test to exercise actual runtime fallback via reload
@marcodalessandro
Copy link
Member Author

Second round of review feedback addressed in commit f2e5db3:

1. Duplicated ImportError message (iothub_registry_manager.py:962)
Replaced the inline error string in send_c2d_message with iothub_amqp_client._UAMQP_MISSING_ERROR to keep a single source of truth.

2. Non-ASCII characters need encoding cookie (test_no_uamqp.py:44)
Not applicable. Python 3 defaults to UTF-8 source encoding (PEP 3120), and this package's dependencies (azure-core>=1.10.0) already require Python 3. No change needed.

3. Fallback IntEnum test should exercise actual runtime fallback (test_no_uamqp.py:217)
Replaced the local class test with one that patches builtins.__import__ to block uamqp, reloads the registry manager module, and asserts the actual fallback TransportType is an IntEnum with the correct values.

4. Mutable default argument properties={} (iothub_registry_manager.py:963)
Changed to properties=None with if properties is None: properties = {} inside the method body.

All 185 tests pass locally. CI should confirm shortly.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Revert six.moves.urllib back to maintain Python 2 compatibility
declared in setup.py metadata. Add six as explicit install_requires
since it is used directly. Trim CI matrix to match classifiers (3.9-3.10).
@marcodalessandro
Copy link
Member Author

Third round of review feedback addressed:

Already resolved by previous commits:

  • CI matrix vs classifiers (#2893169665) — Trimmed to Python 3.9-3.10 to match classifiers (commit e692e44)
  • urllib.parse is Python 3-only (#2893169698, #2893169709) — Reverted to six.moves.urllib and added six>=1.12.0 as explicit install_requires (commit e692e44)

Resolved in this round:

  • Docstrings reference uamqp.TransportType (#2893169683) — Updated :type transport_type: annotations to reference TransportType instead of uamqp.TransportType (commit 0ce44e7)

Not changing (by design):

  • Error message always mentions ARM macOS (#2893169645) — ARM macOS is the only platform where uamqp is excluded from auto-install. On other platforms uamqp installs automatically, so users won't encounter this error unless they manually uninstall it. The install command (pip install azure-iot-hub[amqp]) is correct regardless of platform. Adding platform detection adds complexity for a scenario that shouldn't occur in normal usage.
  • Tests hard-require "ARM macOS" in error (#2893169660) — The test validates the message as designed. Coupled to the above.

All 185 tests pass. CI green on all 6 matrix jobs (3 OS x 2 Python versions).

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

assert FallbackTransportType.Amqp == 1
assert FallbackTransportType.AmqpOverWebsocket == 3

# Restore original module state
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this test, the final importlib.reload(registry_module) happens while builtins.__import__ is still monkeypatched to raise for uamqp, so the module can remain in the fallback (no-uamqp) state for the rest of the test session. Restore builtins.__import__ (or stop the monkeypatch) before the final reload, or avoid reloading here and instead rely on pytest’s teardown to restore the import hook.

Suggested change
# Restore original module state
# Restore original module state: first restore __import__, then reload.
monkeypatch.setattr(builtins, "__import__", real_import)

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +45
# ──────────────────────────────────────────────────────────────────────
# Fixtures
# ──────────────────────────────────────────────────────────────────────


Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test module includes non-ASCII characters (e.g., box-drawing separators / em dashes) and imports the builtins module directly. If the project’s stated Python 2.7 support is still intended (see setup.py classifiers / python_requires), this file will fail to parse/import on Python 2.7. Consider using ASCII-only separators and importing builtins via six.moves.builtins (or a small compatibility shim) to keep the test suite importable under 2.7.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +57
| Platform | C2D messaging support | Notes |
|---|---|---|
| Windows | Included automatically | `uamqp` is installed as part of `pip install azure-iot-hub` |
| Linux | Included automatically | `uamqp` is installed as part of `pip install azure-iot-hub` |
| macOS (Intel) | Included automatically | `uamqp` is installed as part of `pip install azure-iot-hub` |
| macOS (Apple Silicon / ARM) | **Not installed automatically** | Recent versions of Xcode/clang enforce stricter C type checking that breaks the `uamqp` build. See below. |

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Markdown table under “C2D messaging and uamqp” uses double leading pipes (||) in the separator and data rows, which breaks table rendering in common Markdown parsers. Use single | to start each row (e.g., |---|---|---| and | Windows | ... | ... |).

Copilot uses AI. Check for mistakes.
@marcodalessandro
Copy link
Member Author

Fourth round of review feedback addressed in commit 28424d6:

Fixed:

  • Restore reload runs with monkeypatched import (#2893244017) — The importlib.reload to restore module state was running while builtins.__import__ was still patched. Now restores the real import before reloading.

Not changing (same reasons as before):

  • Non-ASCII characters / builtins for Python 2.7 (#2893244028) — Python 3 defaults to UTF-8; dependencies require Python 3.
  • README double pipes (#2893244043) — Table renders correctly with single pipes. This has been flagged three times now and reviewed each time — it's a false positive.
  • IntEnum unavailable on Python 2.7 (#2893244048) — The fallback only triggers when uamqp is absent, and all declared dependencies require Python 3.6+.

@marcodalessandro marcodalessandro marked this pull request as ready for review March 6, 2026 01:51
Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants