pythonnative 0.25.0__py3-none-any.whl → 0.26.0__py3-none-any.whl

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.
Files changed (44) hide show
  1. pythonnative/__init__.py +1 -1
  2. pythonnative/cli/pn.py +172 -12
  3. pythonnative/diagnostics.py +27 -0
  4. pythonnative/native_modules/app_state.py +2 -1
  5. pythonnative/native_modules/battery.py +3 -2
  6. pythonnative/native_modules/biometrics.py +2 -1
  7. pythonnative/native_modules/camera.py +16 -7
  8. pythonnative/native_modules/clipboard.py +3 -2
  9. pythonnative/native_modules/file_system.py +3 -2
  10. pythonnative/native_modules/haptics.py +7 -6
  11. pythonnative/native_modules/linking.py +62 -6
  12. pythonnative/native_modules/location.py +11 -8
  13. pythonnative/native_modules/net_info.py +2 -1
  14. pythonnative/native_modules/notifications.py +97 -6
  15. pythonnative/native_modules/permissions.py +3 -2
  16. pythonnative/native_modules/share.py +2 -1
  17. pythonnative/native_views/android.py +142 -122
  18. pythonnative/native_views/desktop.py +50 -49
  19. pythonnative/native_views/ios.py +228 -160
  20. pythonnative/project/android.py +15 -3
  21. pythonnative/project/builder.py +172 -70
  22. pythonnative/project/config.py +28 -6
  23. pythonnative/project/devices.py +291 -0
  24. pythonnative/project/doctor.py +5 -4
  25. pythonnative/project/ios.py +51 -77
  26. pythonnative/project/permissions.py +19 -1
  27. pythonnative/project/runtime_assets.py +78 -166
  28. pythonnative/templates/android_template/app/build.gradle +5 -0
  29. pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +1 -0
  30. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +88 -12
  31. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +41 -17
  32. pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
  33. pythonnative/templates/ios_template/ios_template/Info.plist +0 -2
  34. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +397 -0
  35. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +31 -20
  36. pythonnative/templates/ios_template/ios_template/ViewController.swift +77 -198
  37. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +72 -47
  38. {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/METADATA +1 -1
  39. {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/RECORD +43 -41
  40. pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -24
  41. {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/WHEEL +0 -0
  42. {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/entry_points.txt +0 -0
  43. {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/licenses/LICENSE +0 -0
  44. {pythonnative-0.25.0.dist-info → pythonnative-0.26.0.dist-info}/top_level.txt +0 -0
@@ -3,11 +3,16 @@
3
3
  iOS apps can't rely on a system Python, so PythonNative bundles a copy of
4
4
  CPython built for iOS by the excellent
5
5
  [Python-Apple-support](https://github.com/beeware/Python-Apple-support)
6
- project. This module downloads the pinned release asset, verifies it,
7
- extracts it once (cached under the build directory), and exposes the
8
- paths the [`ios`][pythonnative.project.ios] configurator needs:
9
- ``Python.xcframework``, the simulator ``Python.framework``, the standard
10
- library, and the simulator headers/static lib.
6
+ project. This module downloads the pinned release asset for the
7
+ project's ``app.python_version``, verifies its checksum, extracts it
8
+ once (cached under the build directory), and exposes the path to
9
+ ``Python.xcframework``.
10
+
11
+ The xcframework is linked and embedded by the bundled Xcode template at
12
+ build time; its ``build/utils.sh`` helper (shipped inside the framework
13
+ by BeeWare) installs the standard library and converts binary modules
14
+ into signed frameworks during the Xcode build. There is no post-build
15
+ copy step.
11
16
 
12
17
  Android doesn't need any of this: Chaquopy ships its own CPython via
13
18
  Gradle, so there's no Android equivalent here.
@@ -16,23 +21,34 @@ Gradle, so there's no Android equivalent here.
16
21
  from __future__ import annotations
17
22
 
18
23
  import hashlib
19
- import json
20
- import os
21
24
  import tarfile
22
25
  import urllib.request
23
26
  from dataclasses import dataclass
24
27
  from pathlib import Path
25
- from typing import Callable, List, Optional
26
-
27
- # Pinned, checksum-verified asset for the supported iOS Python version.
28
- _PINNED_ASSETS = {
28
+ from typing import Callable, Optional
29
+
30
+ # Pinned, checksum-verified Python-Apple-support assets. Every version in
31
+ # ``config.SUPPORTED_PYTHON_VERSIONS`` must have an entry here; iOS builds
32
+ # refuse to run against an unpinned, unverified runtime.
33
+ PINNED_ASSETS = {
34
+ "3.10": (
35
+ "3.10-b14",
36
+ "Python-3.10-iOS-support.b14.tar.gz",
37
+ "a6da479a67be74569813af77179dc8ac83a5e685324408110bfc04632166e404",
38
+ ),
29
39
  "3.11": (
30
- "Python-3.11-iOS-support.b7.tar.gz",
31
- "2b7d8589715b9890e8dd7e1bce91c210bb5287417e17b9af120fc577675ed28e",
40
+ "3.11-b9",
41
+ "Python-3.11-iOS-support.b9.tar.gz",
42
+ "56810335d2b73558f7a16b6b2f7ad855b88c3007b200cbaecae9d6a25a2d1ecc",
43
+ ),
44
+ "3.12": (
45
+ "3.12-b9",
46
+ "Python-3.12-iOS-support.b9.tar.gz",
47
+ "a3be9e278c742911db54dd3045bd7451928813508771c9acf14b4af75294edd2",
32
48
  ),
33
49
  }
34
50
 
35
- _RELEASES_API = "https://api.github.com/repos/beeware/Python-Apple-support/releases?per_page=100"
51
+ _DOWNLOAD_URL = "https://github.com/beeware/Python-Apple-support/releases/download/{tag}/{name}"
36
52
  _USER_AGENT = "pythonnative-cli"
37
53
 
38
54
  Logger = Callable[[str], None]
@@ -40,98 +56,20 @@ Logger = Callable[[str], None]
40
56
 
41
57
  @dataclass
42
58
  class IOSRuntime:
43
- """Resolved paths to an extracted iOS CPython support package.
59
+ """A resolved, extracted iOS CPython support package.
44
60
 
45
61
  Attributes:
46
62
  python_version: The CPython ``major.minor`` version.
47
- xcframework_dir: Path to ``Python.xcframework``.
48
- simulator_framework: Path to the simulator-slice
49
- ``Python.framework`` (embedded into the simulator ``.app``).
50
- stdlib_dir: Path to the simulator standard library directory.
51
- simulator_headers: Path to the simulator-slice ``Headers``
52
- directory (used when the project links the static lib).
53
- simulator_static_lib: Path to the simulator ``libPythonX.Y.a``.
54
- device_framework: Path to the device-slice ``Python.framework``
55
- (embedded when archiving for a real device).
56
- device_stdlib: Path to the device standard library directory.
63
+ xcframework_dir: Path to the extracted ``Python.xcframework``.
57
64
  """
58
65
 
59
66
  python_version: str
60
67
  xcframework_dir: Path
61
- simulator_framework: Optional[Path]
62
- stdlib_dir: Optional[Path]
63
- simulator_headers: Optional[Path]
64
- simulator_static_lib: Optional[Path]
65
- device_framework: Optional[Path] = None
66
- device_stdlib: Optional[Path] = None
67
-
68
- def framework_for(self, destination: str) -> Optional[Path]:
69
- """Return the ``Python.framework`` for a build destination.
70
-
71
- Args:
72
- destination: ``"simulator"`` or ``"device"``.
73
-
74
- Returns:
75
- The matching framework path, or ``None`` if unavailable.
76
- """
77
- return self.device_framework if destination == "device" else self.simulator_framework
78
-
79
- def stdlib_for(self, destination: str) -> Optional[Path]:
80
- """Return the standard library directory for a build destination.
81
-
82
- Args:
83
- destination: ``"simulator"`` or ``"device"``.
84
-
85
- Returns:
86
- The matching stdlib path, or ``None`` if unavailable.
87
- """
88
- return self.device_stdlib if destination == "device" else self.stdlib_dir
89
-
90
-
91
- def _github_json(url: str) -> object:
92
- headers = {"User-Agent": _USER_AGENT}
93
- token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
94
- if token:
95
- headers["Authorization"] = f"Bearer {token}"
96
- req = urllib.request.Request(url, headers=headers)
97
- with urllib.request.urlopen(req) as response:
98
- return json.loads(response.read().decode("utf-8"))
99
-
100
-
101
- def resolve_asset_url(python_version: str, preferred_name: Optional[str] = None) -> Optional[str]:
102
- """Resolve a download URL for a Python-Apple-support iOS release asset.
103
-
104
- Prefers an exact ``preferred_name`` match across all releases, then
105
- falls back to the newest asset whose name contains
106
- ``Python-<version>-iOS-support`` and ends in ``.tar.gz``.
107
-
108
- Args:
109
- python_version: CPython ``major.minor`` (e.g., ``"3.11"``).
110
- preferred_name: Exact asset filename to prefer.
111
-
112
- Returns:
113
- A ``browser_download_url``, or ``None`` if resolution fails.
114
- """
115
- try:
116
- releases = _github_json(_RELEASES_API)
117
- except Exception:
118
- return None
119
- if not isinstance(releases, list):
120
- return None
121
-
122
- if preferred_name:
123
- for release in releases:
124
- for asset in release.get("assets", []) or []:
125
- if asset.get("name") == preferred_name:
126
- return asset.get("browser_download_url")
127
68
 
128
- needle = f"Python-{python_version}-iOS-support"
129
- for release in releases:
130
- for asset in release.get("assets", []) or []:
131
- name = asset.get("name") or ""
132
- if needle in name and name.endswith(".tar.gz"):
133
- return asset.get("browser_download_url")
134
- return None
69
+ @property
70
+ def install_script(self) -> Path:
71
+ """Path to BeeWare's ``utils.sh`` build helper inside the framework."""
72
+ return self.xcframework_dir / "build" / "utils.sh"
135
73
 
136
74
 
137
75
  def _sha256(path: Path) -> str:
@@ -159,60 +97,31 @@ def _safe_extract(tar_path: Path, dest: Path) -> None:
159
97
  tar.extractall(dest)
160
98
 
161
99
 
162
- def _first_existing(candidates: List[Path]) -> Optional[Path]:
163
- for candidate in candidates:
164
- if candidate.exists():
165
- return candidate
166
- return None
167
-
168
-
169
100
  def _locate_runtime(extract_root: Path, python_version: str) -> IOSRuntime:
170
- xc_candidates = [
171
- extract_root / "Python.xcframework",
172
- extract_root / "support" / "Python.xcframework",
173
- ]
174
- xcframework = _first_existing(xc_candidates)
175
- if xcframework is None:
101
+ xcframework = extract_root / "Python.xcframework"
102
+ if not xcframework.is_dir():
176
103
  raise RuntimeError("Python.xcframework not found in extracted Python-Apple-support package.")
177
-
178
- sim_slice = xcframework / "ios-arm64_x86_64-simulator"
179
- simulator_framework = _first_existing([sim_slice / "Python.framework"])
180
- stdlib_dir = _first_existing([sim_slice / "lib" / f"python{python_version}"])
181
- simulator_headers = _first_existing([sim_slice / "Headers"])
182
- simulator_static_lib = _first_existing(
183
- [
184
- sim_slice / f"libPython{python_version}.a",
185
- sim_slice / "libpython.a",
186
- ]
187
- )
188
-
189
- device_slice = xcframework / "ios-arm64"
190
- device_framework = _first_existing([device_slice / "Python.framework"])
191
- device_stdlib = _first_existing([device_slice / "lib" / f"python{python_version}"])
192
-
193
- return IOSRuntime(
194
- python_version=python_version,
195
- xcframework_dir=xcframework,
196
- simulator_framework=simulator_framework,
197
- stdlib_dir=stdlib_dir,
198
- simulator_headers=simulator_headers,
199
- simulator_static_lib=simulator_static_lib,
200
- device_framework=device_framework,
201
- device_stdlib=device_stdlib,
202
- )
104
+ runtime = IOSRuntime(python_version=python_version, xcframework_dir=xcframework)
105
+ if not runtime.install_script.is_file():
106
+ raise RuntimeError(
107
+ "The extracted Python.xcframework is missing build/utils.sh; the support "
108
+ "package layout is older than PythonNative expects. Delete the "
109
+ "build/ios_runtime cache and re-run to fetch the pinned asset."
110
+ )
111
+ return runtime
203
112
 
204
113
 
205
114
  def prepare_ios_runtime(
206
115
  cache_dir: Path,
207
- python_version: str = "3.11",
116
+ python_version: str = "3.12",
208
117
  *,
209
118
  log: Optional[Logger] = None,
210
119
  ) -> IOSRuntime:
211
120
  """Download (if needed), verify, and extract the iOS CPython package.
212
121
 
213
122
  The download and extraction are cached under ``cache_dir`` so repeat
214
- builds are fast. For the pinned version the tarball checksum is
215
- verified; for other versions the checksum is skipped with a warning.
123
+ builds are fast. Only pinned, checksum-verified versions are
124
+ accepted; there is no unverified fallback.
216
125
 
217
126
  Args:
218
127
  cache_dir: Directory to store downloads and extractions in.
@@ -223,15 +132,20 @@ def prepare_ios_runtime(
223
132
  A resolved [`IOSRuntime`][pythonnative.project.runtime_assets.IOSRuntime].
224
133
 
225
134
  Raises:
226
- RuntimeError: If the asset URL can't be resolved, the checksum
227
- doesn't match, or the package layout is unexpected.
135
+ RuntimeError: If the version has no pinned asset, the download
136
+ fails, the checksum doesn't match, or the package layout is
137
+ unexpected.
228
138
  """
229
139
  emit: Logger = log or (lambda _message: None)
230
140
  cache_dir.mkdir(parents=True, exist_ok=True)
231
141
 
232
- pinned = _PINNED_ASSETS.get(python_version)
233
- preferred_name = pinned[0] if pinned else None
234
- expected_sha = pinned[1] if pinned else None
142
+ pinned = PINNED_ASSETS.get(python_version)
143
+ if pinned is None:
144
+ supported = ", ".join(sorted(PINNED_ASSETS))
145
+ raise RuntimeError(
146
+ f"No pinned iOS runtime for Python {python_version}. " f"Set app.python_version to one of: {supported}."
147
+ )
148
+ tag, asset_name, expected_sha = pinned
235
149
 
236
150
  extract_root = cache_dir / f"python-{python_version}"
237
151
  if extract_root.is_dir():
@@ -241,30 +155,28 @@ def prepare_ios_runtime(
241
155
  # Stale/partial extraction: re-extract below.
242
156
  pass
243
157
 
244
- url = resolve_asset_url(python_version, preferred_name=preferred_name)
245
- if not url:
246
- raise RuntimeError(
247
- f"Could not resolve a Python-Apple-support iOS asset for Python {python_version}. "
248
- "Check your network connection or set GITHUB_TOKEN to avoid rate limits."
249
- )
250
-
251
- tar_path = cache_dir / os.path.basename(url)
252
- if not tar_path.exists():
253
- emit(f"Downloading embedded Python runtime ({python_version} iOS): {os.path.basename(url)}")
158
+ url = _DOWNLOAD_URL.format(tag=tag, name=asset_name)
159
+ tar_path = cache_dir / asset_name
160
+ if not tar_path.exists() or _sha256(tar_path) != expected_sha:
161
+ emit(f"Downloading embedded Python runtime ({python_version} iOS): {asset_name}")
254
162
  req = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT})
255
- with urllib.request.urlopen(req) as response, open(tar_path, "wb") as handle:
256
- handle.write(response.read())
257
-
258
- if expected_sha:
259
- actual = _sha256(tar_path)
260
- if actual != expected_sha:
163
+ try:
164
+ with urllib.request.urlopen(req) as response, open(tar_path, "wb") as handle:
165
+ handle.write(response.read())
166
+ except OSError as exc:
261
167
  tar_path.unlink(missing_ok=True)
262
168
  raise RuntimeError(
263
- f"Checksum mismatch for {tar_path.name}: expected {expected_sha}, got {actual}. "
264
- "The download may be corrupt; re-run to try again."
265
- )
266
- else:
267
- emit(f"Warning: no pinned checksum for Python {python_version}; skipping verification.")
169
+ f"Could not download the iOS Python runtime from {url}: {exc}. "
170
+ "Check your network connection and re-run."
171
+ ) from exc
172
+
173
+ actual = _sha256(tar_path)
174
+ if actual != expected_sha:
175
+ tar_path.unlink(missing_ok=True)
176
+ raise RuntimeError(
177
+ f"Checksum mismatch for {asset_name}: expected {expected_sha}, got {actual}. "
178
+ "The download may be corrupt; re-run to try again."
179
+ )
268
180
 
269
181
  emit("Extracting embedded Python runtime...")
270
182
  extract_root.mkdir(parents=True, exist_ok=True)
@@ -27,6 +27,11 @@ android {
27
27
  }
28
28
  }
29
29
 
30
+ buildFeatures {
31
+ // MainActivity gates dev-only behavior (hot reload) on BuildConfig.DEBUG.
32
+ buildConfig true
33
+ }
34
+
30
35
  buildTypes {
31
36
  release {
32
37
  minifyEnabled false
@@ -15,6 +15,7 @@
15
15
  <activity
16
16
  android:name=".MainActivity"
17
17
  android:exported="true"
18
+ android:launchMode="singleTask"
18
19
  android:windowSoftInputMode="adjustResize">
19
20
  <intent-filter>
20
21
  <action android:name="android.intent.action.MAIN" />
@@ -1,39 +1,115 @@
1
1
  package com.pythonnative.android_template
2
2
 
3
- import androidx.appcompat.app.AppCompatActivity
3
+ import android.content.Intent
4
+ import android.graphics.Color
5
+ import android.graphics.Typeface
4
6
  import android.os.Bundle
5
7
  import android.util.Log
8
+ import android.util.TypedValue
9
+ import android.widget.ScrollView
6
10
  import android.widget.TextView
11
+ import androidx.appcompat.app.AppCompatActivity
7
12
  import com.chaquo.python.Python
8
13
  import com.chaquo.python.android.AndroidPlatform
9
14
 
10
15
  class MainActivity : AppCompatActivity() {
11
16
  private val TAG = javaClass.simpleName
17
+ private var pythonReady = false
12
18
 
13
19
  override fun onCreate(savedInstanceState: Bundle?) {
14
20
  super.onCreate(savedInstanceState)
15
21
  Log.d(TAG, "onCreate() called")
16
22
 
17
- // Initialize Chaquopy
18
23
  if (!Python.isStarted()) {
19
24
  Python.start(AndroidPlatform(this))
20
25
  }
21
26
  try {
22
- // Set content view to the NavHost layout; the initial screen loads via nav_graph startDestination
27
+ // Set content view to the NavHost layout; the initial screen
28
+ // loads via nav_graph startDestination.
23
29
  setContentView(R.layout.activity_main)
24
- // Optionally, bootstrap Python so first fragment can create the initial screen onCreate
25
30
  val py = Python.getInstance()
26
- py.getModule("pythonnative.hot_reload").callAttr(
27
- "configure_dev_environment",
28
- filesDir.absolutePath
29
- )
30
- // Touch module to ensure bundled Python code is available; actual instantiation happens in ScreenFragment
31
+ if (BuildConfig.DEBUG) {
32
+ // Dev-only writable source overlay for hot reload.
33
+ py.getModule("pythonnative.hot_reload").callAttr(
34
+ "configure_dev_environment",
35
+ filesDir.absolutePath
36
+ )
37
+ }
38
+ // Import the entry module now so a broken app fails here,
39
+ // with a full traceback, instead of inside the first fragment.
31
40
  py.getModule("app.main")
41
+ pythonReady = true
32
42
  } catch (e: Exception) {
33
43
  Log.e("PythonNative", "Bootstrap failed", e)
34
- val tv = TextView(this)
35
- tv.text = "Hello from PythonNative (Android template)"
36
- setContentView(tv)
44
+ showBootstrapError(e)
45
+ return
46
+ }
47
+
48
+ // A cold start from a deep link carries the URL on the launch intent.
49
+ intent?.dataString?.let { dispatchUrl(it) }
50
+ }
51
+
52
+ override fun onNewIntent(intent: Intent) {
53
+ super.onNewIntent(intent)
54
+ setIntent(intent)
55
+ intent.dataString?.let { dispatchUrl(it) }
56
+ }
57
+
58
+ // MARK: - AppState forwarding
59
+
60
+ override fun onResume() {
61
+ super.onResume()
62
+ dispatchAppState("active")
63
+ }
64
+
65
+ override fun onPause() {
66
+ super.onPause()
67
+ dispatchAppState("inactive")
68
+ }
69
+
70
+ override fun onStop() {
71
+ super.onStop()
72
+ dispatchAppState("background")
73
+ }
74
+
75
+ private fun dispatchAppState(state: String) {
76
+ if (!pythonReady) return
77
+ try {
78
+ Python.getInstance()
79
+ .getModule("pythonnative.native_modules.app_state")
80
+ .callAttr("dispatch_app_state", state)
81
+ } catch (e: Exception) {
82
+ Log.e("PythonNative", "dispatch_app_state($state) failed", e)
37
83
  }
38
84
  }
85
+
86
+ // MARK: - Deep links
87
+
88
+ private fun dispatchUrl(url: String) {
89
+ if (!pythonReady) return
90
+ try {
91
+ Python.getInstance()
92
+ .getModule("pythonnative.native_modules.linking")
93
+ .callAttr("dispatch_url", url)
94
+ } catch (e: Exception) {
95
+ Log.e("PythonNative", "dispatch_url failed", e)
96
+ }
97
+ }
98
+
99
+ // MARK: - Bootstrap error UI
100
+
101
+ private fun showBootstrapError(error: Exception) {
102
+ val text = TextView(this)
103
+ text.text = "PythonNative could not start\n\n" + Log.getStackTraceString(error)
104
+ text.setTextColor(Color.WHITE)
105
+ text.setBackgroundColor(Color.rgb(191, 26, 26))
106
+ text.typeface = Typeface.MONOSPACE
107
+ text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
108
+ val pad = (16 * resources.displayMetrics.density).toInt()
109
+ text.setPadding(pad, pad * 3, pad, pad * 2)
110
+ val scroll = ScrollView(this)
111
+ scroll.setBackgroundColor(Color.rgb(191, 26, 26))
112
+ scroll.addView(text)
113
+ setContentView(scroll)
114
+ }
39
115
  }
@@ -2,35 +2,59 @@
2
2
  // AppDelegate.swift
3
3
  // ios_template
4
4
  //
5
- // Created by Owen Carey on 6/19/23.
5
+ // Application-level callbacks. Remote-notification registration
6
+ // results are forwarded to pythonnative.native_modules.notifications,
7
+ // which resolves the awaitable returned by
8
+ // Notifications.get_device_token().
6
9
  //
7
10
 
8
11
  import UIKit
9
12
 
10
13
  @main
11
14
  class AppDelegate: UIResponder, UIApplicationDelegate {
12
-
13
-
14
-
15
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16
- // Override point for customization after application launch.
15
+ func application(
16
+ _ application: UIApplication,
17
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
18
+ ) -> Bool {
17
19
  return true
18
20
  }
19
21
 
20
- // MARK: UISceneSession Lifecycle
21
-
22
- func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23
- // Called when a new scene session is being created.
24
- // Use this method to select a configuration to create the new scene with.
25
- return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
22
+ // MARK: - Remote notifications (APNs)
23
+
24
+ func application(
25
+ _ application: UIApplication,
26
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
27
+ ) {
28
+ let token = deviceToken.map { String(format: "%02x", $0) }.joined()
29
+ guard PythonRuntime.shared.started else { return }
30
+ PythonRuntime.shared.notify(
31
+ module: "pythonnative.native_modules.notifications",
32
+ function: "dispatch_device_token",
33
+ token
34
+ )
26
35
  }
27
36
 
28
- func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
29
- // Called when the user discards a scene session.
30
- // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31
- // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
37
+ func application(
38
+ _ application: UIApplication,
39
+ didFailToRegisterForRemoteNotificationsWithError error: Error
40
+ ) {
41
+ guard PythonRuntime.shared.started else { return }
42
+ PythonRuntime.shared.notify(
43
+ module: "pythonnative.native_modules.notifications",
44
+ function: "dispatch_device_token_error",
45
+ String(describing: error)
46
+ )
32
47
  }
33
48
 
49
+ // MARK: - UISceneSession lifecycle
34
50
 
35
- }
51
+ func application(
52
+ _ application: UIApplication,
53
+ configurationForConnecting connectingSceneSession: UISceneSession,
54
+ options: UIScene.ConnectionOptions
55
+ ) -> UISceneConfiguration {
56
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
57
+ }
36
58
 
59
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}
60
+ }
@@ -0,0 +1,14 @@
1
+ //
2
+ // BridgingHeader.h
3
+ // ios_template
4
+ //
5
+ // Exposes the embedded CPython C API to Swift. The header resolves
6
+ // through the linked Python.xcframework's framework search path.
7
+ //
8
+
9
+ #ifndef ios_template_BridgingHeader_h
10
+ #define ios_template_BridgingHeader_h
11
+
12
+ #include <Python/Python.h>
13
+
14
+ #endif /* ios_template_BridgingHeader_h */
@@ -15,8 +15,6 @@
15
15
  <string>Default Configuration</string>
16
16
  <key>UISceneDelegateClassName</key>
17
17
  <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
18
- <key>UISceneStoryboardFile</key>
19
- <string>Main</string>
20
18
  </dict>
21
19
  </array>
22
20
  </dict>