This release is a pre-release and may not be stable for production use.
pyxpo-device
Facts about the device a pyxpo app runs on, written in Python: brand, model, OS and version, memory, CPU architectures, phone or tablet, uptime and Android system features. The names follow expo-device, in Python style.
import pyxpo_device as Device
print(Device.brand, Device.model_name, Device.os_name, Device.os_version) # "Google", "sdk_gphone64_arm64", "Android", "15"
if Device.device_type == Device.DeviceType.TABLET:
...
uptime_ms = await Device.get_uptime_async()
Features
- Constants with no bridge call: brand, model, OS, memory and the rest are read once at startup and are plain attributes of the module.
- Phone, tablet, TV or desktop:
Device.device_typeis aDeviceTypeenum, worked out from the platform's own idiom and the screen size. - Runs on the desktop too: the web page and the desktop window fill the same fields from Python's
platformmodule, so layout code can run the same everywhere. - Android extras: the runtime memory limit, the list of system features, a feature check
(
android.hardware.camera) and whether the app may install other apps. - Honest values: a value a platform does not have is
None, not a guess. The rooted check is documented as easy to fool. - No permissions: nothing here needs a prompt, an entitlement or a manifest entry.
- Typed: ships
py.typed. Every name also has its expo-device spelling (Device.modelName,Device.getUptimeAsync).
Contents
| If you are... | Start here |
|---|---|
| Showing the model in a support screen | Quick start → Constants |
| Switching layouts for tablets | Device type |
| Checking for a camera or other hardware | Android system features |
| Wondering whether it is an emulator | is_device |
| Looking for exact signatures | API reference · Errors |
| Seeing it in a real app | Recipe: a diagnostics line for support |
| Stuck | Testing · Troubleshooting · FAQ |
Platform support
| Target | What works |
|---|---|
| Android, API 24+ | Everything. |
| iOS | Everything except the Android-only values, which are None or an empty result. |
Web page and desktop window (pyxpo run web / pyxpo run desktop) |
Constants from Python's platform module, device_type is DESKTOP, uptime from the monotonic clock. |
Requires Python 3.9+ and pyxpo>=0.1.0a2.
Install
pip install pyxpo-device
Add the module to your app's pyproject.toml:
[tool.pyxpo]
modules = ["device"]
The module has native code, so rebuild the app after adding it (pyxpo run android or pyxpo run ios).
Quick start
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"),
)
Tip: the constants resolve when you read them, not when the module is imported, because the startup environment is not there yet at import time. Read them inside a component or a function. Do not copy them into module-level variables at import.
Constants
Read once at app start. None where a platform has no value.
| Python name | expo name | iOS | Android |
|---|---|---|---|
is_device |
isDevice |
False on the Simulator | False on an emulator (best-effort check) |
brand, manufacturer |
brand, manufacturer |
"Apple" |
Build.BRAND, Build.MANUFACTURER |
model_name |
modelName |
Marketing name for known iPhones, else the identifier | Build.MODEL |
model_id |
modelId |
Identifier, for example "iPhone17,3" |
None |
design_name, product_name |
designName, productName |
None |
Build.DEVICE, Build.PRODUCT |
device_year_class |
deviceYearClass |
Release year of known iPhones | None |
total_memory |
totalMemory |
Bytes of RAM | Bytes of RAM |
supported_cpu_architectures |
supportedCpuArchitectures |
["arm64"] |
Build.SUPPORTED_ABIS |
os_name |
osName |
"iOS" or "iPadOS" |
"Android" |
os_version |
osVersion |
For example "26.0" |
For example "15" |
os_build_id, os_internal_build_id |
osBuildId, osInternalBuildId |
Kernel OS build | Build.DISPLAY, Build.ID |
os_build_fingerprint, platform_api_level |
osBuildFingerprint, platformApiLevel |
None |
Build.FINGERPRINT, the API level |
device_name |
deviceName |
UIDevice.name (a generic name on iOS 16+) |
The device name setting, else the model |
device_type |
deviceType |
DeviceType from the interface idiom |
DeviceType from TV mode and screen width (600dp and up is a tablet) |
is_device and emulators
is_device is False on the iOS Simulator and, on a best-effort basis, on the Android emulator. Use it to skip
features that need real hardware (haptics, camera). Do not use it for security.
Device type
if Device.device_type == Device.DeviceType.TABLET:
columns = 3
DeviceType |
Value | Meaning |
|---|---|---|
UNKNOWN |
0 | The platform could not tell. |
PHONE |
1 | A phone. |
TABLET |
2 | A tablet (an iPad, or Android 600dp and wider). |
DESKTOP |
3 | The web page or the desktop window. |
TV |
4 | Android TV mode. |
await Device.get_device_type_async() returns the same value as the constant.
Memory and uptime
total = Device.total_memory # bytes of RAM
limit = await Device.get_max_memory_async() # Android: the runtime limit; elsewhere MAX_SAFE_INTEGER
ms = await Device.get_uptime_async() # milliseconds since boot, not counting deep sleep
MAX_SAFE_INTEGER (2 ** 53 - 1) is what expo-device returns when memory has no inherent limit, which is the case
on iOS and the desktop.
Android system features
if await Device.has_platform_feature_async("android.hardware.camera"):
show_camera_button()
names = await Device.get_platform_features_async() # every feature the device reports
has_platform_feature_async(feature) takes a non-empty string and raises ValueError otherwise. On iOS and the
desktop it returns False, and get_platform_features_async() returns [].
is_side_loading_enabled_async() tells whether this app may install other apps. It needs the
REQUEST_INSTALL_PACKAGES permission on Android 8 and later, and is always False on iOS and the desktop.
is_rooted_experimental_async() is a best-effort jailbreak or root check. It is easy to fool, so never use it as
a security decision.
Errors
| Error | When | What to do |
|---|---|---|
ValueError |
has_platform_feature_async gets an empty or non-string feature. |
Pass a feature name such as "android.hardware.camera". |
ModuleNotLinkedError (from pyxpo.module_api) |
The app was built without "device" in modules. |
Add it and rebuild. The desktop is exempt. |
AttributeError |
A name that is not a constant, for example a typo. | Check the Constants table. |
Config
There are no module options and no config plugin. The only native setup is the modules entry.
Web and desktop
The same names work. Values come from Python's platform module (uname()), total_memory from sysconf where
available, device_type is DESKTOP, brand and model are None, and get_uptime_async() uses the monotonic
clock. is_rooted_experimental_async(), is_side_loading_enabled_async() and has_platform_feature_async()
return False.
Recipe: a diagnostics line for support
One line the user can paste into a support email.
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)
Production checklist
- No decision about security or licensing depends on
is_rooted_experimental_async()oris_device. - Layouts do not assume a phone:
DeviceType.TABLETandDESKTOPare handled. - Android-only values (
platform_api_level,model_idon iOS) areNone-checked. - A feature that needs hardware checks
has_platform_feature_asyncbefore showing its button. - Device names (
device_name) are not sent to your server without the user knowing.
Testing
Constants are read through pyxpo.module_api. Patch _all to control them:
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
On the desktop test host the values come from Python's platform module, and device_type is DESKTOP.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
brand and model_name are None |
You are on the desktop or the web page. They only exist on a phone. |
model_id is None on Android |
It is iOS only. Use product_name or design_name. |
device_year_class is None |
Unknown iPhone model, or Android. |
get_max_memory_async() returns a huge number |
iOS and the desktop have no runtime limit, so it is MAX_SAFE_INTEGER. |
is_side_loading_enabled_async() is always False |
Add REQUEST_INSTALL_PACKAGES to the Android manifest, and note iOS is always False. |
ModuleNotLinkedError |
Add "device" to [tool.pyxpo] modules and rebuild. |
FAQ
Is device_name the user's name? It is the name in the device settings. On iOS 16 and later Apple returns a
generic name such as "iPhone" unless the app has a special entitlement.
Does it need any permission? No.
Is the tablet check reliable? On iOS it follows the interface idiom (iPad is a tablet). On Android it follows TV mode and a 600dp screen width.
Why total_memory and not free memory? It is the installed RAM. Free memory changes every second and is not
something the system promises to report.
API reference
Constants
is_device, brand, manufacturer, model_name, model_id, design_name, product_name, device_year_class,
total_memory, supported_cpu_architectures, os_name, os_version, os_build_id, os_internal_build_id,
os_build_fingerprint, platform_api_level, device_name, device_type. Each has a camelCase twin.
Functions
| Function (expo name) | Platforms | Returns |
|---|---|---|
get_device_type_async() (getDeviceTypeAsync) |
all | DeviceType |
get_uptime_async() (getUptimeAsync) |
all | int milliseconds |
get_max_memory_async() (getMaxMemoryAsync) |
all | int bytes, MAX_SAFE_INTEGER when unlimited |
is_rooted_experimental_async() (isRootedExperimentalAsync) |
all | bool |
is_side_loading_enabled_async() (isSideLoadingEnabledAsync) |
Android | bool, False elsewhere |
get_platform_features_async() (getPlatformFeaturesAsync) |
Android | list[str], [] elsewhere |
has_platform_feature_async(feature) (hasPlatformFeatureAsync) |
Android | bool, False elsewhere |
Types
| Name | Description |
|---|---|
DeviceType |
IntEnum: UNKNOWN, PHONE, TABLET, DESKTOP, TV. |
MAX_SAFE_INTEGER |
2 ** 53 - 1. |
License
MIT
Metadata
Release files for pyxpo-device 0.1.0a2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyxpo_device-0.1.0a2.tar.gz | 19.0 kB | With provenance | Details for pyxpo_device-0.1.0a2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyxpo_device-0.1.0a2-py3-none-any.whl | Python 3 | none | any | Details for pyxpo_device-0.1.0a2-py3-none-any.whl |
Total release size: 33.8 kB
Release files / pyxpo_device-0.1.0a2.tar.gz
| Download URL | pyxpo_device-0.1.0a2.tar.gz |
|---|---|
| Size | 19.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2a4e10b328224ee3a82f5e0b4cb7e97ae194912204f1db2d7466f1a8316f7ff5
|
|
BLAKE2b-256 checksum How to use checksums |
b1a4dbad9a79517b6d47d7cac4735360d14f9fd54a208bb09fc6546fd23f6d58
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / pyxpo_device-0.1.0a2-py3-none-any.whl
| Download URL | pyxpo_device-0.1.0a2-py3-none-any.whl |
|---|---|
| Size | 14.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9d8ed2647651bb9bfb73142a15c79898f5a7e7640ef6581a57b1eeb580c00d25
|
|
BLAKE2b-256 checksum How to use checksums |
a78a76eca1f53638f19180f881bc52d4ca3460a05e0fffe7ef291d31d4044e29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for pyxpo-device 0.1.0a2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pyxpo_device-0.1.0a2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.