packages

Device

Read the device's brand, model, OS, memory and CPU architectures as plain constants, tell a phone from a tablet, TV or desktop, get the uptime, and on Android check system features like android.hardware.camera. The web page and the desktop window fill the same names from Python's platform module, so layout code runs the same everywhere. A value a platform does not have is None, not a guess. No permissions.

  • pyxpo-device
  • v0.1.0a1
  • Native: Swift + Kotlin
  • Like expo-device
Brand, model, OS, memory and device type on Android and iOS.
Brand, model, OS, memory and device type on Android and iOS.

Install

Install the package into the same environment as pyxpo.

$pip install pyxpo-device

Add device to modules in your app config, then import it in your code.

pyproject.toml
[tool.pyxpo]
modules = ["device"]
python
import pyxpo_device as Device

Needs Python 3.9+ and pyxpo>=0.1.0a2.

This module has native code. After you add it, run pyxpo run or pyxpo build again so it is compiled into the app. Hot reload alone is not enough.

Platforms

iOSEverything except the Android-only values, which are None or empty.
AndroidAPI 24+. Everything.
Desktop previewConstants from Python's platform module; device_type is DESKTOP.
WebSame as the desktop.

Quick start

app/index.py
import pyxpo_device as Device
from pyxpo import Text, View, component, use_async


@component
def DeviceInfo():
    uptime = use_async(Device.get_uptime_async, [])     # (loading, value, error)
    hours = (uptime.value or 0) / 3_600_000
    return View(
        Text("%s %s" % (Device.brand or "", Device.model_name or "")),
        Text("%s %s" % (Device.os_name, Device.os_version)),
        Text("Up for %.1f hours" % hours),
        Text("Tablet" if Device.device_type == Device.DeviceType.TABLET else "Phone"),
    )

Permissions

pyxpo adds these to Info.plist and AndroidManifest.xml for you when the module is listed.

iOSNone.
AndroidNone. is_side_loading_enabled_async() needs REQUEST_INSTALL_PACKAGES on Android 8+ to return True.

API reference

Constants

Read once at app start. They resolve when you read them, so read them inside a component or function, not into module-level variables at import. Each has a camelCase twin such as Device.modelName.

is_device→ bool

False on the iOS Simulator and, best effort, on the Android emulator. Not for security.

brand, manufacturer→ str or None

"Apple" on iOS; Build.BRAND and Build.MANUFACTURER on Android.

model_name→ str or None

Marketing name for known iPhones, else the identifier; Build.MODEL.

model_id→ str or None

iOS. For example "iPhone17,3".

design_name, product_name→ str or None

Android. Build.DEVICE and Build.PRODUCT.

device_year_class→ int or None

Release year of known iPhones.

total_memory→ int

Bytes of installed RAM.

supported_cpu_architectures→ list[str]

["arm64"] on iOS; Build.SUPPORTED_ABIS.

os_name, os_version→ str

"iOS" or "iPadOS", or "Android"; for example "26.0" or "15".

os_build_id, os_internal_build_id, os_build_fingerprint→ str or None

Kernel OS build on iOS; Build.DISPLAY, Build.ID and Build.FINGERPRINT on Android.

platform_api_level→ int or None

Android API level.

device_name→ str or None

The name in the device settings. iOS 16+ returns a generic name such as "iPhone".

device_type→ DeviceType

From the interface idiom on iOS; from TV mode and a 600dp width on Android.

Functions

await get_device_type_async()→ DeviceType

Same as the constant.

await get_uptime_async()→ int

Milliseconds since boot, not counting deep sleep.

await get_max_memory_async()→ int

Android: the runtime memory limit. iOS and the desktop: MAX_SAFE_INTEGER (no limit).

await is_rooted_experimental_async()→ bool

A best-effort jailbreak or root check. Easy to fool; never use it as a security decision.

await is_side_loading_enabled_async()→ bool

Android: may this app install other apps. False elsewhere.

await get_platform_features_async()→ list[str]

Android: every system feature. [] elsewhere.

await has_platform_feature_async(feature)→ bool

Android, for example "android.hardware.camera". False elsewhere. An empty or non-string feature raises ValueError.

Types

Results and options are plain Python dicts with these keys.

DeviceType (IntEnum)

KeyTypeDescription
UNKNOWN0The platform could not tell.
PHONE1A phone.
TABLET2An iPad, or Android 600dp and wider.
DESKTOP3The web page or the desktop window.
TV4Android TV mode.

Constants and errors

KeyTypeDescription
MAX_SAFE_INTEGERint2 ** 53 - 1, what get_max_memory_async returns with no limit.
ValueErrorerrorhas_platform_feature_async got an empty or non-string feature.
ModuleNotLinkedErrorerror"device" is missing from modules. Add it and rebuild. The desktop is exempt.

Examples

Tablet layout and hardware checks

Pick a column count from the device type, and show a camera button only where Android reports a camera.

Device constants on Android and iOS.
Device constants on Android and iOS.
python
import pyxpo_device as Device


async def layout():
    columns = 3 if Device.device_type == Device.DeviceType.TABLET else 1
    has_camera = await Device.has_platform_feature_async("android.hardware.camera")
    return columns, has_camera

Recipe: a diagnostics line for support

One line the user can paste into a support email.

python
import pyxpo_application as Application
import pyxpo_device as Device


def diagnostics() -> str:
    gb = (Device.total_memory or 0) / 1024 ** 3
    parts = [
        "%s %s" % (Device.brand or "?", Device.model_name or "?"),
        "%s %s" % (Device.os_name, Device.os_version),
        "%.1f GB" % gb,
        "app %s" % (Application.native_application_version or "dev"),
    ]
    if Device.platform_api_level:
        parts.append("API %s" % Device.platform_api_level)
    if not Device.is_device:
        parts.append("emulator")
    return " | ".join(parts)

Testing

Patch _all to control the constants in a unit test.

python
import pyxpo_device as Device


def test_tablet_layout(monkeypatch):
    monkeypatch.setattr(Device, "_all", lambda: {**{k: None for k in Device._FIELDS}, "deviceType": 2})
    assert Device.device_type == Device.DeviceType.TABLET

Good to know

  • No module options and no config plugin.
  • On the web and desktop, brand and model are None, total_memory comes from sysconf where available, and the uptime uses the monotonic clock.
  • total_memory is installed RAM. Free memory changes every second and is not something the system promises to report.
  • Do not send device_name to your server without the user knowing.