pyxpo-application 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vishal Chaubey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyxpo-application
3
+ Version: 0.1.0a1
4
+ Summary: App identity for pyxpo apps: name, id, version, build, install time (expo-application shape).
5
+ Author: Vishal Chaubey
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pyxpo/pyxpo/tree/main/packages/application
8
+ Project-URL: Issues, https://github.com/pyxpo/pyxpo/issues
9
+ Keywords: pyxpo,pyxpo-module,mobile,android,ios
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: User Interfaces
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: pyxpo<0.2,>=0.1.0a2
18
+ Dynamic: license-file
19
+
20
+ # pyxpo-application
21
+
22
+ The installed app's own identity for [pyxpo](https://github.com/pyxpo/pyxpo) apps: name, id, version,
23
+ build number, install time and signing details. The API follows
24
+ [expo-application](https://docs.expo.dev/versions/latest/sdk/application/).
25
+
26
+ ```sh
27
+ pip install pyxpo-application
28
+ ```
29
+
30
+ ```toml
31
+ [tool.pyxpo]
32
+ modules = ["application"]
33
+ ```
34
+
35
+ ```python
36
+ import pyxpo_application as Application
37
+
38
+ print(Application.application_name, Application.application_id)
39
+ print(Application.native_application_version, Application.native_build_version)
40
+ installed = await Application.get_installation_time_async() # timezone-aware datetime (UTC)
41
+ ```
42
+
43
+ Every name also has its expo-application spelling (`Application.applicationId`,
44
+ `Application.getInstallationTimeAsync`).
45
+
46
+ ## Constants
47
+
48
+ Read once at app start. `None` on the desktop.
49
+
50
+ - `application_name`: the name on the home screen
51
+ - `application_id`: bundle identifier (iOS) or package name (Android)
52
+ - `native_application_version`: `CFBundleShortVersionString` / `versionName`, for example `"1.2.0"`
53
+ - `native_build_version`: `CFBundleVersion` / `versionCode`, as a string
54
+
55
+ ## Functions
56
+
57
+ | Function | Platforms | Returns |
58
+ | --- | --- | --- |
59
+ | `get_installation_time_async()` | iOS, Android | `datetime` (UTC). iOS: creation time of the Documents folder |
60
+ | `get_last_update_time_async()` | Android | `datetime` (UTC) of the last install or update |
61
+ | `get_android_id()` | Android | `ANDROID_ID`: unique per signing key, user and device; changes on factory reset |
62
+ | `get_install_referrer_async()` | Android | Google Play install referrer. Fails without the Play Store (most emulators) |
63
+ | `get_ios_id_for_vendor_async()` | iOS | `identifierForVendor`, or None before the first unlock |
64
+ | `get_ios_application_release_type_async()` | iOS | `ApplicationReleaseType`: `SIMULATOR`, `DEVELOPMENT`, `AD_HOC`, `ENTERPRISE`, `APP_STORE` |
65
+ | `get_ios_push_notification_service_environment_async()` | iOS | `"development"`, `"production"` or None |
66
+
67
+ Calling a function on a platform it does not list raises `NotSupportedError`; so does every function on
68
+ the desktop.
69
+
70
+ On Android the module adds the `com.android.installreferrer:installreferrer` library.
71
+
72
+ ## Permissions
73
+
74
+ None.
@@ -0,0 +1,55 @@
1
+ # pyxpo-application
2
+
3
+ The installed app's own identity for [pyxpo](https://github.com/pyxpo/pyxpo) apps: name, id, version,
4
+ build number, install time and signing details. The API follows
5
+ [expo-application](https://docs.expo.dev/versions/latest/sdk/application/).
6
+
7
+ ```sh
8
+ pip install pyxpo-application
9
+ ```
10
+
11
+ ```toml
12
+ [tool.pyxpo]
13
+ modules = ["application"]
14
+ ```
15
+
16
+ ```python
17
+ import pyxpo_application as Application
18
+
19
+ print(Application.application_name, Application.application_id)
20
+ print(Application.native_application_version, Application.native_build_version)
21
+ installed = await Application.get_installation_time_async() # timezone-aware datetime (UTC)
22
+ ```
23
+
24
+ Every name also has its expo-application spelling (`Application.applicationId`,
25
+ `Application.getInstallationTimeAsync`).
26
+
27
+ ## Constants
28
+
29
+ Read once at app start. `None` on the desktop.
30
+
31
+ - `application_name`: the name on the home screen
32
+ - `application_id`: bundle identifier (iOS) or package name (Android)
33
+ - `native_application_version`: `CFBundleShortVersionString` / `versionName`, for example `"1.2.0"`
34
+ - `native_build_version`: `CFBundleVersion` / `versionCode`, as a string
35
+
36
+ ## Functions
37
+
38
+ | Function | Platforms | Returns |
39
+ | --- | --- | --- |
40
+ | `get_installation_time_async()` | iOS, Android | `datetime` (UTC). iOS: creation time of the Documents folder |
41
+ | `get_last_update_time_async()` | Android | `datetime` (UTC) of the last install or update |
42
+ | `get_android_id()` | Android | `ANDROID_ID`: unique per signing key, user and device; changes on factory reset |
43
+ | `get_install_referrer_async()` | Android | Google Play install referrer. Fails without the Play Store (most emulators) |
44
+ | `get_ios_id_for_vendor_async()` | iOS | `identifierForVendor`, or None before the first unlock |
45
+ | `get_ios_application_release_type_async()` | iOS | `ApplicationReleaseType`: `SIMULATOR`, `DEVELOPMENT`, `AD_HOC`, `ENTERPRISE`, `APP_STORE` |
46
+ | `get_ios_push_notification_service_environment_async()` | iOS | `"development"`, `"production"` or None |
47
+
48
+ Calling a function on a platform it does not list raises `NotSupportedError`; so does every function on
49
+ the desktop.
50
+
51
+ On Android the module adds the `com.android.installreferrer:installreferrer` library.
52
+
53
+ ## Permissions
54
+
55
+ None.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyxpo-application"
7
+ dynamic = ["version"]
8
+ description = "App identity for pyxpo apps: name, id, version, build, install time (expo-application shape)."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Vishal Chaubey" }]
13
+ keywords = ["pyxpo", "pyxpo-module", "mobile", "android", "ios"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Software Development :: User Interfaces",
19
+ ]
20
+ requires-python = ">=3.9"
21
+ dependencies = ["pyxpo>=0.1.0a2,<0.2"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/pyxpo/pyxpo/tree/main/packages/application"
25
+ Issues = "https://github.com/pyxpo/pyxpo/issues"
26
+
27
+ # How `pyxpo build` finds this module: the entry point name is the module name apps list in
28
+ # `[tool.pyxpo] modules`, its value the import package holding pyxpo-module.json.
29
+ [project.entry-points."pyxpo.modules"]
30
+ application = "pyxpo_application"
31
+
32
+ [tool.setuptools]
33
+ packages = ["pyxpo_application"]
34
+
35
+ [tool.setuptools.dynamic]
36
+ version = { attr = "pyxpo_application.__version__" }
37
+
38
+ [tool.setuptools.package-data]
39
+ pyxpo_application = ["pyxpo-module.json", "py.typed", "ios/**/*", "android/**/*"]
40
+
41
+ [tool.setuptools.exclude-package-data]
42
+ "*" = ["__pycache__", "*.pyc", ".DS_Store"]
@@ -0,0 +1,132 @@
1
+ """The app's own identity: the expo-application API.
2
+
3
+ import pyxpo_application as Application
4
+
5
+ Application.application_name, Application.application_id # or Application.applicationName ...
6
+ Application.native_application_version, Application.native_build_version
7
+ installed = await Application.get_installation_time_async() # datetime (UTC)
8
+
9
+ The constants are read once at startup from the installed app (Info.plist, PackageInfo). On the desktop
10
+ they are None and the functions raise `NotSupportedError`, as there is no installed app.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from datetime import datetime, timezone
15
+ from enum import IntEnum
16
+ from typing import Any, Optional
17
+
18
+ from pyxpo.module_api import NotSupportedError, call, constants, platform, require_linked, require_supported
19
+
20
+ __version__ = "0.1.0a1"
21
+
22
+ _FIELDS = {
23
+ "applicationName": "application_name", "applicationId": "application_id",
24
+ "nativeApplicationVersion": "native_application_version", "nativeBuildVersion": "native_build_version",
25
+ }
26
+ _BY_SNAKE = {snake: camel for camel, snake in _FIELDS.items()}
27
+
28
+
29
+ class ApplicationReleaseType(IntEnum):
30
+ """How an iOS build was signed, from its embedded provisioning profile."""
31
+ UNKNOWN = 0
32
+ SIMULATOR = 1
33
+ ENTERPRISE = 2
34
+ DEVELOPMENT = 3
35
+ AD_HOC = 4
36
+ APP_STORE = 5
37
+
38
+
39
+ def _native(feature: str) -> None:
40
+ if platform() == "desktop":
41
+ raise NotSupportedError("%s needs an installed app (iOS or Android)" % feature)
42
+ require_linked("Application", "application", package="pyxpo-application")
43
+
44
+
45
+ def _const(name: str) -> Any:
46
+ if platform() == "desktop":
47
+ return None
48
+ require_linked("Application", "application", package="pyxpo-application")
49
+ return constants("Application").get(name)
50
+
51
+
52
+ def __getattr__(name: str) -> Any:
53
+ # Constants resolve on access: the startup env is not there yet when this module is imported.
54
+ camel = name if name in _FIELDS else _BY_SNAKE.get(name)
55
+ if camel is None:
56
+ raise AttributeError("module 'pyxpo_application' has no attribute %r" % name)
57
+ return _const(camel)
58
+
59
+
60
+ def _time(ms: Any) -> datetime:
61
+ return datetime.fromtimestamp(float(ms) / 1000.0, tz=timezone.utc)
62
+
63
+
64
+ def get_android_id() -> str:
65
+ """Android: the 64-bit hex id unique per app signing key, user and device (`Settings.Secure.ANDROID_ID`).
66
+ It changes on factory reset. Raises `NotSupportedError` on iOS and the desktop."""
67
+ require_supported("get_android_id", ios=False)
68
+ _native("get_android_id")
69
+ return str(_const("androidId"))
70
+
71
+
72
+ async def get_installation_time_async() -> datetime:
73
+ """When the app was installed (UTC). iOS: creation time of the app's Documents folder; Android:
74
+ `PackageInfo.firstInstallTime`."""
75
+ _native("get_installation_time_async")
76
+ return _time(await call("Application", "getInstallationTime", reply=True))
77
+
78
+
79
+ async def get_last_update_time_async() -> datetime:
80
+ """Android: when the app was last installed or updated (UTC, `PackageInfo.lastUpdateTime`)."""
81
+ require_supported("get_last_update_time_async", ios=False)
82
+ _native("get_last_update_time_async")
83
+ return _time(await call("Application", "getLastUpdateTime", reply=True))
84
+
85
+
86
+ async def get_install_referrer_async() -> str:
87
+ """Android: the Google Play install referrer string (`utm_source=...`). Fails where the Play Store is
88
+ missing, for example on emulators without Google Play."""
89
+ require_supported("get_install_referrer_async", ios=False)
90
+ _native("get_install_referrer_async")
91
+ return str(await call("Application", "getInstallReferrer", reply=True))
92
+
93
+
94
+ async def get_ios_id_for_vendor_async() -> Optional[str]:
95
+ """iOS: `identifierForVendor`, the same for all apps of one vendor on the device. None right after a
96
+ restart before the first unlock."""
97
+ require_supported("get_ios_id_for_vendor_async", android=False)
98
+ _native("get_ios_id_for_vendor_async")
99
+ return await call("Application", "getIdForVendor", reply=True)
100
+
101
+
102
+ async def get_ios_application_release_type_async() -> ApplicationReleaseType:
103
+ """iOS: how this build was signed (`SIMULATOR`, `DEVELOPMENT`, `AD_HOC`, `ENTERPRISE`, `APP_STORE`)."""
104
+ require_supported("get_ios_application_release_type_async", android=False)
105
+ _native("get_ios_application_release_type_async")
106
+ v = await call("Application", "getReleaseType", reply=True)
107
+ return ApplicationReleaseType(v) if v in ApplicationReleaseType._value2member_map_ else ApplicationReleaseType.UNKNOWN
108
+
109
+
110
+ async def get_ios_push_notification_service_environment_async() -> Optional[str]:
111
+ """iOS: the `aps-environment` entitlement ("development" or "production"), None when the app has none
112
+ (the Simulator, or no push capability)."""
113
+ require_supported("get_ios_push_notification_service_environment_async", android=False)
114
+ _native("get_ios_push_notification_service_environment_async")
115
+ return await call("Application", "getPushEnvironment", reply=True)
116
+
117
+
118
+ getAndroidId = get_android_id
119
+ getInstallationTimeAsync = get_installation_time_async
120
+ getLastUpdateTimeAsync = get_last_update_time_async
121
+ getInstallReferrerAsync = get_install_referrer_async
122
+ getIosIdForVendorAsync = get_ios_id_for_vendor_async
123
+ getIosApplicationReleaseTypeAsync = get_ios_application_release_type_async
124
+ getIosPushNotificationServiceEnvironmentAsync = get_ios_push_notification_service_environment_async
125
+
126
+ __all__ = [
127
+ "ApplicationReleaseType", "get_android_id", "get_installation_time_async", "get_last_update_time_async",
128
+ "get_install_referrer_async", "get_ios_id_for_vendor_async", "get_ios_application_release_type_async",
129
+ "get_ios_push_notification_service_environment_async", "getAndroidId", "getInstallationTimeAsync",
130
+ "getLastUpdateTimeAsync", "getInstallReferrerAsync", "getIosIdForVendorAsync",
131
+ "getIosApplicationReleaseTypeAsync", "getIosPushNotificationServiceEnvironmentAsync",
132
+ ] + list(_FIELDS) + list(_FIELDS.values())
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,79 @@
1
+ package dev.pyxpo.modules.application
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.content.pm.PackageInfo
6
+ import android.content.pm.PackageManager
7
+ import android.os.Build
8
+ import android.provider.Settings
9
+ import com.android.installreferrer.api.InstallReferrerClient
10
+ import com.android.installreferrer.api.InstallReferrerStateListener
11
+ import dev.pyxpo.moduleapi.PxModule
12
+ import dev.pyxpo.moduleapi.PxModuleContext
13
+ import dev.pyxpo.moduleapi.PxReply
14
+ import org.json.JSONArray
15
+
16
+ /** expo-application on Android: `PackageInfo`, `ANDROID_ID` and the Play install referrer. */
17
+ class ApplicationModule : PxModule() {
18
+ override val name = "Application"
19
+
20
+ private fun info(c: Context): PackageInfo =
21
+ if (Build.VERSION.SDK_INT >= 33) c.packageManager.getPackageInfo(c.packageName, PackageManager.PackageInfoFlags.of(0))
22
+ else @Suppress("DEPRECATION") c.packageManager.getPackageInfo(c.packageName, 0)
23
+
24
+ @SuppressLint("HardwareIds")
25
+ override fun constants(context: Context): Map<String, Any?> {
26
+ val p = info(context)
27
+ val build = if (Build.VERSION.SDK_INT >= 28) p.longVersionCode else @Suppress("DEPRECATION") p.versionCode.toLong()
28
+ return mapOf(
29
+ "applicationName" to context.applicationInfo.loadLabel(context.packageManager).toString(),
30
+ "applicationId" to context.packageName,
31
+ "nativeApplicationVersion" to p.versionName,
32
+ "nativeBuildVersion" to build.toString(),
33
+ "androidId" to Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID),
34
+ )
35
+ }
36
+
37
+ override fun call(ctx: PxModuleContext, method: String, args: JSONArray, reply: PxReply) {
38
+ val c = ctx.activity
39
+ when (method) {
40
+ "getInstallationTime" -> reply(info(c).firstInstallTime, null)
41
+ "getLastUpdateTime" -> reply(info(c).lastUpdateTime, null)
42
+ "getInstallReferrer" -> referrer(c, reply)
43
+ else -> reply(null, "Application has no method '$method'")
44
+ }
45
+ }
46
+
47
+ private fun referrer(c: Context, reply: PxReply) {
48
+ val client = InstallReferrerClient.newBuilder(c.applicationContext).build()
49
+ var done = false
50
+ fun finish(value: Any?, error: String?) {
51
+ if (done) return
52
+ done = true
53
+ try { client.endConnection() } catch (_: Exception) {}
54
+ reply(value, error)
55
+ }
56
+ try {
57
+ client.startConnection(object : InstallReferrerStateListener {
58
+ override fun onInstallReferrerSetupFinished(code: Int) {
59
+ when (code) {
60
+ InstallReferrerClient.InstallReferrerResponse.OK ->
61
+ try { finish(client.installReferrer.installReferrer, null) }
62
+ catch (e: Exception) { finish(null, "install referrer failed: ${e.message}") }
63
+ InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED ->
64
+ finish(null, "install referrer is not supported (no Google Play Store)")
65
+ InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE ->
66
+ finish(null, "install referrer service is unavailable")
67
+ else -> finish(null, "install referrer failed with code $code")
68
+ }
69
+ }
70
+
71
+ override fun onInstallReferrerServiceDisconnected() {
72
+ finish(null, "install referrer service disconnected")
73
+ }
74
+ })
75
+ } catch (e: Exception) {
76
+ finish(null, "install referrer failed: ${e.message}")
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,61 @@
1
+ import UIKit
2
+
3
+ /// expo-application on iOS. Constants come from the main bundle's Info.plist; signing details from the
4
+ /// embedded provisioning profile (absent in App Store and Simulator builds).
5
+ final class ApplicationModule: PxModule {
6
+ static let moduleName = "Application"
7
+
8
+ required init() {}
9
+
10
+ static var constants: [String: Any] {
11
+ let info = Bundle.main.infoDictionary ?? [:]
12
+ let name = info["CFBundleDisplayName"] ?? info["CFBundleName"]
13
+ return [
14
+ "applicationName": name ?? NSNull(),
15
+ "applicationId": Bundle.main.bundleIdentifier ?? NSNull(),
16
+ "nativeApplicationVersion": info["CFBundleShortVersionString"] ?? NSNull(),
17
+ "nativeBuildVersion": info["CFBundleVersion"] ?? NSNull(),
18
+ ]
19
+ }
20
+
21
+ /// The plist inside embedded.mobileprovision (a CMS envelope around plain XML).
22
+ private static var profile: [String: Any]? {
23
+ guard let url = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision"),
24
+ let data = try? Data(contentsOf: url),
25
+ let start = data.range(of: Data("<?xml".utf8)),
26
+ let end = data.range(of: Data("</plist>".utf8), in: start.lowerBound..<data.endIndex) else { return nil }
27
+ let xml = data.subdata(in: start.lowerBound..<end.upperBound)
28
+ return (try? PropertyListSerialization.propertyList(from: xml, format: nil)) as? [String: Any]
29
+ }
30
+
31
+ private static var releaseType: Int {
32
+ #if targetEnvironment(simulator)
33
+ return 1
34
+ #else
35
+ guard let p = profile else { return 5 } // no profile: App Store (or TestFlight)
36
+ if p["ProvisionsAllDevices"] as? Bool == true { return 2 }
37
+ let ent = p["Entitlements"] as? [String: Any] ?? [:]
38
+ if p["ProvisionedDevices"] != nil { return ent["get-task-allow"] as? Bool == true ? 3 : 4 }
39
+ return 5
40
+ #endif
41
+ }
42
+
43
+ func call(_ ctx: PxModuleContext, _ method: String, _ args: [Any], _ reply: @escaping PxReply) {
44
+ switch method {
45
+ case "getInstallationTime":
46
+ let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
47
+ guard let attrs = try? FileManager.default.attributesOfItem(atPath: docs.path),
48
+ let date = attrs[.creationDate] as? Date else { reply(nil, "could not read the install time"); return }
49
+ reply(date.timeIntervalSince1970 * 1000, nil)
50
+ case "getIdForVendor":
51
+ reply(UIDevice.current.identifierForVendor?.uuidString ?? NSNull(), nil)
52
+ case "getReleaseType":
53
+ reply(Self.releaseType, nil)
54
+ case "getPushEnvironment":
55
+ let ent = Self.profile?["Entitlements"] as? [String: Any]
56
+ reply(ent?["aps-environment"] as? String ?? NSNull(), nil)
57
+ default:
58
+ reply(nil, "Application has no method '\(method)'")
59
+ }
60
+ }
61
+ }
File without changes
@@ -0,0 +1,15 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "nativeContract": 2,
4
+ "name": "application",
5
+ "native": "Application",
6
+ "ios": {
7
+ "classes": ["ApplicationModule"]
8
+ },
9
+ "android": {
10
+ "namespace": "dev.pyxpo.modules.application",
11
+ "classes": ["dev.pyxpo.modules.application.ApplicationModule"],
12
+ "dependencies": ["com.android.installreferrer:installreferrer:2.2"],
13
+ "minSdk": 24
14
+ }
15
+ }
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyxpo-application
3
+ Version: 0.1.0a1
4
+ Summary: App identity for pyxpo apps: name, id, version, build, install time (expo-application shape).
5
+ Author: Vishal Chaubey
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pyxpo/pyxpo/tree/main/packages/application
8
+ Project-URL: Issues, https://github.com/pyxpo/pyxpo/issues
9
+ Keywords: pyxpo,pyxpo-module,mobile,android,ios
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: User Interfaces
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: pyxpo<0.2,>=0.1.0a2
18
+ Dynamic: license-file
19
+
20
+ # pyxpo-application
21
+
22
+ The installed app's own identity for [pyxpo](https://github.com/pyxpo/pyxpo) apps: name, id, version,
23
+ build number, install time and signing details. The API follows
24
+ [expo-application](https://docs.expo.dev/versions/latest/sdk/application/).
25
+
26
+ ```sh
27
+ pip install pyxpo-application
28
+ ```
29
+
30
+ ```toml
31
+ [tool.pyxpo]
32
+ modules = ["application"]
33
+ ```
34
+
35
+ ```python
36
+ import pyxpo_application as Application
37
+
38
+ print(Application.application_name, Application.application_id)
39
+ print(Application.native_application_version, Application.native_build_version)
40
+ installed = await Application.get_installation_time_async() # timezone-aware datetime (UTC)
41
+ ```
42
+
43
+ Every name also has its expo-application spelling (`Application.applicationId`,
44
+ `Application.getInstallationTimeAsync`).
45
+
46
+ ## Constants
47
+
48
+ Read once at app start. `None` on the desktop.
49
+
50
+ - `application_name`: the name on the home screen
51
+ - `application_id`: bundle identifier (iOS) or package name (Android)
52
+ - `native_application_version`: `CFBundleShortVersionString` / `versionName`, for example `"1.2.0"`
53
+ - `native_build_version`: `CFBundleVersion` / `versionCode`, as a string
54
+
55
+ ## Functions
56
+
57
+ | Function | Platforms | Returns |
58
+ | --- | --- | --- |
59
+ | `get_installation_time_async()` | iOS, Android | `datetime` (UTC). iOS: creation time of the Documents folder |
60
+ | `get_last_update_time_async()` | Android | `datetime` (UTC) of the last install or update |
61
+ | `get_android_id()` | Android | `ANDROID_ID`: unique per signing key, user and device; changes on factory reset |
62
+ | `get_install_referrer_async()` | Android | Google Play install referrer. Fails without the Play Store (most emulators) |
63
+ | `get_ios_id_for_vendor_async()` | iOS | `identifierForVendor`, or None before the first unlock |
64
+ | `get_ios_application_release_type_async()` | iOS | `ApplicationReleaseType`: `SIMULATOR`, `DEVELOPMENT`, `AD_HOC`, `ENTERPRISE`, `APP_STORE` |
65
+ | `get_ios_push_notification_service_environment_async()` | iOS | `"development"`, `"production"` or None |
66
+
67
+ Calling a function on a platform it does not list raises `NotSupportedError`; so does every function on
68
+ the desktop.
69
+
70
+ On Android the module adds the `com.android.installreferrer:installreferrer` library.
71
+
72
+ ## Permissions
73
+
74
+ None.
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ pyxpo_application/__init__.py
5
+ pyxpo_application/py.typed
6
+ pyxpo_application/pyxpo-module.json
7
+ pyxpo_application.egg-info/PKG-INFO
8
+ pyxpo_application.egg-info/SOURCES.txt
9
+ pyxpo_application.egg-info/dependency_links.txt
10
+ pyxpo_application.egg-info/entry_points.txt
11
+ pyxpo_application.egg-info/requires.txt
12
+ pyxpo_application.egg-info/top_level.txt
13
+ pyxpo_application/android/src/main/AndroidManifest.xml
14
+ pyxpo_application/android/src/main/java/dev/pyxpo/modules/application/ApplicationModule.kt
15
+ pyxpo_application/ios/ApplicationModule.swift
@@ -0,0 +1,2 @@
1
+ [pyxpo.modules]
2
+ application = pyxpo_application
@@ -0,0 +1 @@
1
+ pyxpo<0.2,>=0.1.0a2
@@ -0,0 +1 @@
1
+ pyxpo_application
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+