flet-cli 0.84.0.dev0__tar.gz → 0.85.0.dev1__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.
Files changed (49) hide show
  1. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/PKG-INFO +2 -2
  2. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/pyproject.toml +2 -2
  3. flet_cli-0.85.0.dev1/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py +16 -0
  4. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/build_base.py +31 -12
  5. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/pack.py +48 -6
  6. flet_cli-0.85.0.dev1/src/flet_cli/utils/project_dependencies.py +133 -0
  7. flet_cli-0.85.0.dev1/src/flet_cli/version.py +1 -0
  8. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli.egg-info/PKG-INFO +2 -2
  9. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli.egg-info/SOURCES.txt +2 -1
  10. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli.egg-info/requires.txt +1 -1
  11. flet_cli-0.85.0.dev1/tests/test_project_dependencies.py +241 -0
  12. flet_cli-0.84.0.dev0/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py +0 -9
  13. flet_cli-0.84.0.dev0/src/flet_cli/utils/project_dependencies.py +0 -133
  14. flet_cli-0.84.0.dev0/src/flet_cli/version.py +0 -1
  15. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/README.md +0 -0
  16. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/setup.cfg +0 -0
  17. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/__pyinstaller/__init__.py +0 -0
  18. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/__pyinstaller/config.py +0 -0
  19. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/__pyinstaller/hook-flet.py +0 -0
  20. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/__pyinstaller/macos_utils.py +0 -0
  21. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/__pyinstaller/utils.py +0 -0
  22. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/__pyinstaller/win_utils.py +0 -0
  23. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/cli.py +0 -0
  24. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/base.py +0 -0
  25. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/build.py +0 -0
  26. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/create.py +0 -0
  27. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/debug.py +0 -0
  28. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/devices.py +0 -0
  29. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/doctor.py +0 -0
  30. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/emulators.py +0 -0
  31. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/flutter_base.py +0 -0
  32. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/options.py +0 -0
  33. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/publish.py +0 -0
  34. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/run.py +0 -0
  35. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/commands/serve.py +0 -0
  36. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/android_sdk.py +0 -0
  37. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/cli.py +0 -0
  38. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/distros.py +0 -0
  39. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/flutter.py +0 -0
  40. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/hash_stamp.py +0 -0
  41. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/jdk.py +0 -0
  42. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/merge.py +0 -0
  43. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/plist.py +0 -0
  44. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/processes.py +0 -0
  45. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli/utils/pyproject_toml.py +0 -0
  46. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli.egg-info/dependency_links.txt +0 -0
  47. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli.egg-info/entry_points.txt +0 -0
  48. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/src/flet_cli.egg-info/top_level.txt +0 -0
  49. {flet_cli-0.84.0.dev0 → flet_cli-0.85.0.dev1}/tests/test_plist.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-cli
3
- Version: 0.84.0.dev0
3
+ Version: 0.85.0.dev1
4
4
  Summary: Flet CLI
5
5
  Author-email: "Appveyor Systems Inc." <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/flet-dev/flet
9
9
  Project-URL: Documentation, https://flet.dev/docs
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: flet==0.84.0.dev0
12
+ Requires-Dist: flet==0.85.0.dev1
13
13
  Requires-Dist: watchdog>=4.0.0
14
14
  Requires-Dist: packaging>=25.0
15
15
  Requires-Dist: qrcode>=7.4.2
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "flet-cli"
3
- version = "0.84.0.dev0"
3
+ version = "0.85.0.dev1"
4
4
  description = "Flet CLI"
5
5
  authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }]
6
6
  license = "Apache-2.0"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.10"
9
9
  dependencies = [
10
- "flet==0.84.0.dev0",
10
+ "flet==0.85.0.dev1",
11
11
  "watchdog >=4.0.0",
12
12
  "packaging >=25.0",
13
13
  "qrcode >=7.4.2",
@@ -0,0 +1,16 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+
5
+ logger = logging.getLogger("flet")
6
+
7
+
8
+ logger.info("Running PyInstaller runtime hook for Flet...")
9
+
10
+ os.environ["FLET_SERVER_IP"] = "127.0.0.1"
11
+
12
+ # On Windows, set AppUserModelID so the taskbar associates the Flet client window
13
+ # with the parent executable (a PyInstaller bundle in this case) rather than the cached
14
+ # flet.exe. This ensures taskbar pins and shortcuts point to the correct executable.
15
+ if sys.platform == "win32" and "FLET_APP_USER_MODEL_ID" not in os.environ:
16
+ os.environ["FLET_APP_USER_MODEL_ID"] = os.path.abspath(sys.executable)
@@ -2272,7 +2272,13 @@ class BaseBuildCommand(BaseFlutterCommand):
2272
2272
  hash: HashStamp,
2273
2273
  ):
2274
2274
  """
2275
- Find first matching image file by base name and queue it for copy.
2275
+ Find the best matching image file for the current target platform.
2276
+
2277
+ When multiple files share the same base name (e.g. `icon.icns`,
2278
+ `icon.ico`, `icon.png`), the method filters out formats that are
2279
+ incompatible with the build target before selecting the first match.
2280
+ For example, `.icns` is skipped on Windows builds because
2281
+ `flutter_launcher_icons` cannot decode it.
2276
2282
 
2277
2283
  Args:
2278
2284
  src_path: Source assets directory.
@@ -2285,17 +2291,30 @@ class BaseBuildCommand(BaseFlutterCommand):
2285
2291
  File name of matched image, or `None` if not found.
2286
2292
  """
2287
2293
 
2288
- images = glob.glob(str(src_path.joinpath(f"{image_name}.*")))
2289
- if len(images) > 0:
2290
- if self.verbose > 0:
2291
- console.log(
2292
- f'Found "{image_name}" image at {images[0]}', style=verbose1_style
2293
- )
2294
- copy_ops.append((images[0], dest_path))
2295
- hash.update(images[0])
2296
- hash.update(Path(images[0]).stat().st_mtime)
2297
- return Path(images[0]).name
2298
- return None
2294
+ # .icns is macOS-only and .ico is Windows-only; filter out
2295
+ # incompatible formats so flutter_launcher_icons gets a decodable file.
2296
+ images = list(
2297
+ filter(
2298
+ lambda p: not (
2299
+ (ext := Path(p).suffix.lower()) == ".icns"
2300
+ and self.target_platform != "macos"
2301
+ or ext == ".ico"
2302
+ and self.target_platform != "windows"
2303
+ ),
2304
+ glob.glob(str(src_path.joinpath(f"{image_name}.*"))),
2305
+ )
2306
+ )
2307
+
2308
+ if not images:
2309
+ return None
2310
+
2311
+ best = images[0]
2312
+ if self.verbose > 0:
2313
+ console.log(f'Found "{image_name}" image at {best}', style=verbose1_style)
2314
+ copy_ops.append((best, dest_path))
2315
+ hash.update(best)
2316
+ hash.update(Path(best).stat().st_mtime)
2317
+ return Path(best).name
2299
2318
 
2300
2319
  def run(self, args, cwd, env: Optional[dict] = None, capture_output=True):
2301
2320
  """
@@ -2,10 +2,12 @@ import argparse
2
2
  import os
3
3
  import shutil
4
4
  import sys
5
+ import tarfile
6
+ import zipfile
5
7
  from pathlib import Path
6
8
 
7
9
  import flet_cli.__pyinstaller.config as hook_config
8
- from flet.utils import is_macos, is_windows
10
+ from flet.utils import is_linux, is_macos, is_windows
9
11
  from flet_cli.commands.base import BaseCommand
10
12
 
11
13
 
@@ -152,6 +154,31 @@ class Command(BaseCommand):
152
154
  help="Enable non-interactive mode. All prompts will be skipped",
153
155
  )
154
156
 
157
+ def compress_flet_client_dir(self, temp_bin_dir: str, archive_name: str) -> None:
158
+ """Compress the flet/ directory into an archive and remove the original.
159
+
160
+ Args:
161
+ temp_bin_dir: Path to the temporary directory containing the flet/
162
+ subdirectory with client binaries.
163
+ archive_name: Target archive filename. Uses zip for `.zip`
164
+ extensions and gzipped tar for everything else.
165
+ """
166
+ flet_dir = os.path.join(temp_bin_dir, "flet")
167
+ if not os.path.isdir(flet_dir):
168
+ return
169
+ archive_path = os.path.join(temp_bin_dir, archive_name)
170
+ if archive_name.endswith(".zip"): # windows
171
+ with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zf:
172
+ for root, _dirs, files in os.walk(flet_dir):
173
+ for f in files:
174
+ full = os.path.join(root, f)
175
+ arcname = os.path.relpath(full, temp_bin_dir)
176
+ zf.write(full, arcname)
177
+ else:
178
+ with tarfile.open(archive_path, "w:gz") as tar:
179
+ tar.add(flet_dir, arcname="flet")
180
+ shutil.rmtree(flet_dir)
181
+
155
182
  def handle(self, options: argparse.Namespace) -> None:
156
183
  """
157
184
  Package the app into a standalone desktop artifact.
@@ -293,6 +320,12 @@ class Command(BaseCommand):
293
320
 
294
321
  pyi_args.extend(["--version-file", version_info_path])
295
322
 
323
+ # Compress the patched flet/ directory into flet-windows.zip
324
+ # so ensure_client_cached() finds it at runtime.
325
+ self.compress_flet_client_dir(
326
+ hook_config.temp_bin_dir, "flet-windows.zip"
327
+ )
328
+
296
329
  elif is_macos():
297
330
  from flet_cli.__pyinstaller.macos_utils import (
298
331
  assemble_app_bundle,
@@ -341,12 +374,12 @@ class Command(BaseCommand):
341
374
  copyright=options.copyright,
342
375
  )
343
376
 
344
- # assemble
377
+ # Compress the patched .app bundle back into flet-macos.tar.gz so
378
+ # ensure_client_cached() finds it at runtime.
345
379
  assemble_app_bundle(app_path, tar_path)
346
380
 
347
- # Remove everything except the tar.gz so
348
- # PyInstaller doesn't try to process loose
349
- # framework binaries.
381
+ # Remove everything except the tar.gz so PyInstaller doesn't try
382
+ # to process loose framework binaries.
350
383
  for entry in os.listdir(hook_config.temp_bin_dir):
351
384
  entry_path = os.path.join(hook_config.temp_bin_dir, entry)
352
385
  if entry_path == tar_path:
@@ -356,7 +389,16 @@ class Command(BaseCommand):
356
389
  else:
357
390
  os.remove(entry_path)
358
391
 
359
- # run PyInstaller!
392
+ elif is_linux():
393
+ from flet_desktop import get_artifact_filename
394
+
395
+ # Compress the flet/ directory into a tar.gz
396
+ # so ensure_client_cached() finds it at runtime.
397
+ self.compress_flet_client_dir(
398
+ hook_config.temp_bin_dir, get_artifact_filename()
399
+ )
400
+
401
+ # run PyInstaller
360
402
  print("Running PyInstaller:", pyi_args)
361
403
  PyInstaller.__main__.run(pyi_args)
362
404
 
@@ -0,0 +1,133 @@
1
+ """Convert a pyproject.toml file to a requirements.txt file."""
2
+
3
+ # Based on: https://pypi.org/project/toml-to-requirements/
4
+
5
+ import re
6
+ from typing import Any, Optional
7
+
8
+ from packaging.requirements import Requirement
9
+
10
+
11
+ def _windows_safe(req_str: str) -> str:
12
+ """Insert a space before bare `<` or `>` so Windows cmd.exe does not
13
+ interpret them as shell redirection when the string is passed via `-r`
14
+ to a `.BAT` subprocess."""
15
+ return re.sub(r"(?<=[^ ])([<>])", r" \1", req_str)
16
+
17
+
18
+ def _poetry_version_to_pep440(version: str) -> str:
19
+ """Convert a Poetry version constraint to PEP 440 syntax.
20
+
21
+ - `^1.2.3` → `>=1.2.3`
22
+ - `~1.2.3` → `~=1.2.3` (`~=` passes through unchanged)
23
+ - `*` → `""` (no constraint)
24
+ - `1.2.3` (bare version) → `==1.2.3`
25
+ - Anything else is returned as-is (already PEP 440).
26
+ """
27
+ version = version.replace(" ", "")
28
+ if not version or version == "*":
29
+ return ""
30
+ if version.startswith("^"):
31
+ return f">={version[1:]}"
32
+ if version.startswith("~") and not version.startswith("~="):
33
+ return f"~={version[1:]}"
34
+ # Bare version number → pin with ==
35
+ if version[0].isdigit():
36
+ return f"=={version}"
37
+ return version
38
+
39
+
40
+ def _poetry_dep_to_pep508(name: str, value: Any) -> str:
41
+ """Convert a single Poetry dependency entry to a PEP 508 requirement string."""
42
+ suffix = ""
43
+
44
+ if isinstance(value, dict):
45
+ version = value.get("version")
46
+ if version:
47
+ specifier = _poetry_version_to_pep440(version)
48
+ markers = value.get("markers")
49
+ if markers is not None:
50
+ suffix = f"; {markers}"
51
+ if specifier:
52
+ return f"{name}{specifier}{suffix}"
53
+ return f"{name}{suffix}"
54
+
55
+ git_url = value.get("git")
56
+ if git_url:
57
+ url = f"git+{git_url}" if not git_url.startswith("git@") else git_url
58
+ rev = value.get("branch") or value.get("rev") or value.get("tag")
59
+ if rev:
60
+ url = f"{url}@{rev}"
61
+ subdirectory = value.get("subdirectory")
62
+ if subdirectory:
63
+ url = f"{url}#subdirectory={subdirectory}"
64
+ markers = value.get("markers")
65
+ if markers is not None:
66
+ suffix = f"; {markers}"
67
+ return f"{name} @ {url}{suffix}"
68
+
69
+ path = value.get("path")
70
+ if path:
71
+ return path
72
+
73
+ url = value.get("url")
74
+ if url:
75
+ return url
76
+
77
+ raise ValueError(f"Unsupported dependency specification: {name} = {value}")
78
+
79
+ # String value
80
+ specifier = _poetry_version_to_pep440(value)
81
+ if specifier:
82
+ return f"{name}{specifier}"
83
+ return name
84
+
85
+
86
+ def get_poetry_dependencies(
87
+ poetry_dependencies: Optional[dict[str, Any]] = None,
88
+ ) -> Optional[list[str]]:
89
+ """
90
+ Convert Poetry dependency declarations into pip-style requirement strings.
91
+
92
+ Args:
93
+ poetry_dependencies: Value from `tool.poetry.dependencies`.
94
+
95
+ Returns:
96
+ Sorted requirement strings or `None` when `poetry_dependencies` is `None`.
97
+ """
98
+ if poetry_dependencies is None:
99
+ return None
100
+
101
+ dependencies: set[str] = {
102
+ _windows_safe(_poetry_dep_to_pep508(dep, ver))
103
+ for dep, ver in poetry_dependencies.items()
104
+ if dep != "python"
105
+ }
106
+
107
+ return sorted(dependencies)
108
+
109
+
110
+ def get_project_dependencies(
111
+ project_dependencies: Optional[list[str]] = None,
112
+ ) -> Optional[list[str]]:
113
+ """
114
+ Normalize PEP 621 `project.dependencies` into a sorted unique list.
115
+
116
+ Args:
117
+ project_dependencies: Value from `project.dependencies`.
118
+
119
+ Returns:
120
+ Sorted dependency strings, or `None` when input is `None`.
121
+ """
122
+ if project_dependencies is None:
123
+ return None
124
+
125
+ dependencies: set[str] = set()
126
+ for dep in project_dependencies:
127
+ try:
128
+ req = Requirement(dep)
129
+ dependencies.add(_windows_safe(str(req)))
130
+ except Exception:
131
+ dependencies.add(_windows_safe(dep))
132
+
133
+ return sorted(dependencies)
@@ -0,0 +1 @@
1
+ version = "0.85.0.dev1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-cli
3
- Version: 0.84.0.dev0
3
+ Version: 0.85.0.dev1
4
4
  Summary: Flet CLI
5
5
  Author-email: "Appveyor Systems Inc." <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/flet-dev/flet
9
9
  Project-URL: Documentation, https://flet.dev/docs
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: flet==0.84.0.dev0
12
+ Requires-Dist: flet==0.85.0.dev1
13
13
  Requires-Dist: watchdog>=4.0.0
14
14
  Requires-Dist: packaging>=25.0
15
15
  Requires-Dist: qrcode>=7.4.2
@@ -40,4 +40,5 @@ src/flet_cli/utils/plist.py
40
40
  src/flet_cli/utils/processes.py
41
41
  src/flet_cli/utils/project_dependencies.py
42
42
  src/flet_cli/utils/pyproject_toml.py
43
- tests/test_plist.py
43
+ tests/test_plist.py
44
+ tests/test_project_dependencies.py
@@ -1,4 +1,4 @@
1
- flet==0.84.0.dev0
1
+ flet==0.85.0.dev1
2
2
  watchdog>=4.0.0
3
3
  packaging>=25.0
4
4
  qrcode>=7.4.2
@@ -0,0 +1,241 @@
1
+ """Tests for project_dependencies module."""
2
+
3
+ import pytest
4
+
5
+ from flet_cli.utils.project_dependencies import (
6
+ get_poetry_dependencies,
7
+ get_project_dependencies,
8
+ )
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # get_poetry_dependencies
12
+ # ---------------------------------------------------------------------------
13
+
14
+
15
+ class TestGetPoetryDependencies:
16
+ def test_none_returns_none(self):
17
+ assert get_poetry_dependencies(None) is None
18
+
19
+ def test_empty_dict(self):
20
+ assert get_poetry_dependencies({}) == []
21
+
22
+ def test_python_excluded(self):
23
+ assert get_poetry_dependencies({"python": "^3.10"}) == []
24
+
25
+ def test_exact_version(self):
26
+ result = get_poetry_dependencies({"packaging": "26.0"})
27
+ assert result == ["packaging==26.0"]
28
+
29
+ def test_caret(self):
30
+ result = get_poetry_dependencies({"urllib3": "^2.6.0"})
31
+ assert result == ["urllib3 >=2.6.0"]
32
+
33
+ def test_tilde(self):
34
+ result = get_poetry_dependencies({"setuptools": "~82.0.0"})
35
+ assert result == ["setuptools~=82.0.0"]
36
+
37
+ def test_tilde_equals_passthrough(self):
38
+ result = get_poetry_dependencies({"setuptools": "~=82.0.0"})
39
+ assert result == ["setuptools~=82.0.0"]
40
+
41
+ def test_wildcard(self):
42
+ result = get_poetry_dependencies({"boto3": "*"})
43
+ assert result == ["boto3"]
44
+
45
+ def test_less_than(self):
46
+ result = get_poetry_dependencies({"chardet": "<6"})
47
+ assert result == ["chardet <6"]
48
+
49
+ def test_less_than_equal(self):
50
+ result = get_poetry_dependencies({"requests": "<=2.32.4"})
51
+ assert result == ["requests <=2.32.4"]
52
+
53
+ def test_greater_than_equal(self):
54
+ result = get_poetry_dependencies({"certifi": ">=2026.1.4"})
55
+ assert result == ["certifi >=2026.1.4"]
56
+
57
+ def test_range_constraint(self):
58
+ result = get_poetry_dependencies({"pydantic": ">=2.9.0,<3.0.0"})
59
+ # _windows_safe ensures spaces before < and >
60
+ assert "pydantic" in result[0]
61
+ assert " >=" in result[0]
62
+ assert " <" in result[0]
63
+
64
+ def test_not_equal_combined(self):
65
+ result = get_poetry_dependencies({"pandas": ">=2.3,!=2.3.3"})
66
+ assert "pandas" in result[0]
67
+ assert " >=" in result[0]
68
+ assert "!=" in result[0]
69
+
70
+ def test_spaces_in_version_stripped(self):
71
+ result = get_poetry_dependencies({"chardet": " < 6 "})
72
+ assert result == ["chardet <6"]
73
+
74
+ def test_dict_version(self):
75
+ result = get_poetry_dependencies(
76
+ {"scipy": {"version": "^1.16", "optional": True}}
77
+ )
78
+ assert result == ["scipy >=1.16"]
79
+
80
+ def test_dict_version_with_markers(self):
81
+ result = get_poetry_dependencies(
82
+ {"pywin32": {"version": ">=310", "markers": "sys_platform == 'win32'"}}
83
+ )
84
+ assert result == ["pywin32 >=310; sys_platform == 'win32'"]
85
+
86
+ def test_dict_git(self):
87
+ result = get_poetry_dependencies(
88
+ {"numpy": {"git": "https://github.com/numpy/numpy.git", "branch": "main"}}
89
+ )
90
+ assert result == ["numpy @ git+https://github.com/numpy/numpy.git@main"]
91
+
92
+ def test_dict_git_ssh(self):
93
+ result = get_poetry_dependencies(
94
+ {"mylib": {"git": "git@github.com:org/repo.git", "tag": "v1.0"}}
95
+ )
96
+ assert result == ["mylib @ git@github.com:org/repo.git@v1.0"]
97
+
98
+ def test_dict_git_subdirectory(self):
99
+ result = get_poetry_dependencies(
100
+ {
101
+ "mylib": {
102
+ "git": "https://github.com/org/mono.git",
103
+ "branch": "main",
104
+ "subdirectory": "packages/mylib",
105
+ }
106
+ }
107
+ )
108
+ assert "subdirectory=packages/mylib" in result[0]
109
+
110
+ def test_dict_path(self):
111
+ result = get_poetry_dependencies({"mylib": {"path": "../mylib"}})
112
+ assert result == ["../mylib"]
113
+
114
+ def test_dict_url(self):
115
+ result = get_poetry_dependencies(
116
+ {"mylib": {"url": "https://example.com/mylib.tar.gz"}}
117
+ )
118
+ assert result == ["https://example.com/mylib.tar.gz"]
119
+
120
+ def test_dict_unsupported_raises(self):
121
+ with pytest.raises(ValueError, match="Unsupported"):
122
+ get_poetry_dependencies({"bad": {"extras": ["foo"]}})
123
+
124
+ def test_sorted_output(self):
125
+ result = get_poetry_dependencies({"zlib": "1.0", "aiohttp": "3.0"})
126
+ assert result == ["aiohttp==3.0", "zlib==1.0"]
127
+
128
+ def test_multiple_deps(self):
129
+ deps = {
130
+ "python": "^3.10",
131
+ "boto3": "*",
132
+ "chardet": "<6",
133
+ "packaging": "26.0",
134
+ }
135
+ result = get_poetry_dependencies(deps)
136
+ assert "boto3" in result
137
+ assert any("chardet" in r for r in result)
138
+ assert any("packaging" in r for r in result)
139
+ # python should be excluded
140
+ assert not any("python" in r for r in result)
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # get_project_dependencies
145
+ # ---------------------------------------------------------------------------
146
+
147
+
148
+ class TestGetProjectDependencies:
149
+ def test_none_returns_none(self):
150
+ assert get_project_dependencies(None) is None
151
+
152
+ def test_empty_list(self):
153
+ assert get_project_dependencies([]) == []
154
+
155
+ def test_simple_dep(self):
156
+ result = get_project_dependencies(["boto3"])
157
+ assert result == ["boto3"]
158
+
159
+ def test_exact_version(self):
160
+ result = get_project_dependencies(["packaging==26.0"])
161
+ assert result == ["packaging==26.0"]
162
+
163
+ def test_gte(self):
164
+ result = get_project_dependencies(["flet>=0.82.0"])
165
+ assert result == ["flet >=0.82.0"]
166
+
167
+ def test_lt_gets_space(self):
168
+ result = get_project_dependencies(["chardet<6"])
169
+ assert result == ["chardet <6"]
170
+
171
+ def test_lte_gets_space(self):
172
+ result = get_project_dependencies(["requests<=2.32.4"])
173
+ assert result == ["requests <=2.32.4"]
174
+
175
+ def test_gt_gets_space(self):
176
+ result = get_project_dependencies(["chardet>3"])
177
+ assert result == ["chardet >3"]
178
+
179
+ def test_combined_constraints(self):
180
+ result = get_project_dependencies(["pydantic>=2.9.0,<3.0.0"])
181
+ assert len(result) == 1
182
+ dep = result[0]
183
+ assert "pydantic" in dep
184
+ assert " <" in dep or " >" in dep
185
+
186
+ def test_not_equal(self):
187
+ result = get_project_dependencies(["pandas>=2.3,!=2.3.3"])
188
+ assert len(result) == 1
189
+ assert " >=" in result[0]
190
+ assert "!=" in result[0]
191
+
192
+ def test_compatible_release(self):
193
+ result = get_project_dependencies(["setuptools~=82.0.0"])
194
+ assert result == ["setuptools~=82.0.0"]
195
+
196
+ def test_extras(self):
197
+ result = get_project_dependencies(["uvicorn[standard]>=0.42.0"])
198
+ dep = result[0]
199
+ assert "uvicorn" in dep
200
+ assert "[standard]" in dep
201
+ assert " >=" in dep
202
+
203
+ def test_markers_preserved(self):
204
+ result = get_project_dependencies(['pywin32>=310; sys_platform == "win32"'])
205
+ assert len(result) == 1
206
+ assert "sys_platform" in result[0]
207
+ assert "win32" in result[0]
208
+ assert " >=" in result[0]
209
+
210
+ def test_url_dep(self):
211
+ result = get_project_dependencies(
212
+ ["numpy @ git+https://github.com/numpy/numpy.git@main"]
213
+ )
214
+ assert len(result) == 1
215
+ assert "git+https://github.com/numpy/numpy.git@main" in result[0]
216
+
217
+ def test_extra_spaces_normalized(self):
218
+ result = get_project_dependencies(["chardet < 6"])
219
+ assert result == ["chardet <6"]
220
+
221
+ def test_sorted_and_deduplicated(self):
222
+ result = get_project_dependencies(["zlib>=1.0", "aiohttp>=3.0", "zlib>=1.0"])
223
+ assert result[0].startswith("aiohttp")
224
+ assert len(result) == 2
225
+
226
+ def test_downstream_compatible(self):
227
+ """Output must be parseable by packaging.requirements.Requirement,
228
+ since build_base.py does Requirement(dep).name on our output."""
229
+ from packaging.requirements import Requirement
230
+
231
+ deps = [
232
+ "flet>=0.82.0",
233
+ "chardet<6",
234
+ "pydantic>=2.9.0,<3.0.0",
235
+ 'pywin32>=310; sys_platform == "win32"',
236
+ "uvicorn[standard]>=0.42.0",
237
+ ]
238
+ result = get_project_dependencies(deps)
239
+ for dep in result:
240
+ req = Requirement(dep)
241
+ assert req.name # must parse without error
@@ -1,9 +0,0 @@
1
- import logging
2
- import os
3
-
4
- logger = logging.getLogger("flet")
5
-
6
-
7
- logger.info("Running PyInstaller runtime hook for Flet...")
8
-
9
- os.environ["FLET_SERVER_IP"] = "127.0.0.1"
@@ -1,133 +0,0 @@
1
- """Convert a pyproject.toml file to a requirements.txt file."""
2
-
3
- # Based on: https://pypi.org/project/toml-to-requirements/
4
-
5
- from typing import Any, Optional
6
-
7
-
8
- def get_poetry_dependencies(
9
- poetry_dependencies: Optional[dict[str, Any]] = None,
10
- ) -> Optional[list[str]]:
11
- """
12
- Convert Poetry dependency declarations into pip-style requirement strings.
13
-
14
- Args:
15
- poetry_dependencies: Value from `tool.poetry.dependencies`.
16
-
17
- Returns:
18
- Sorted requirement strings or `None` when `poetry_dependencies` is `None`.
19
- """
20
-
21
- if poetry_dependencies is None:
22
- return None
23
-
24
- def format_dependency_version(dependency_name: str, dependency_value: Any):
25
- """
26
- Format a single Poetry dependency entry as a requirement specifier.
27
-
28
- Supports version constraints, git dependencies (including branch/rev/tag
29
- and subdirectory), path/url dependencies, and optional environment markers.
30
-
31
- Args:
32
- dependency_name: Dependency key in Poetry configuration.
33
- dependency_value: String or mapping that describes the dependency.
34
-
35
- Returns:
36
- A requirement string consumable by pip-style tooling.
37
-
38
- Raises:
39
- ValueError: If the dependency mapping uses an unsupported shape.
40
- """
41
-
42
- sep = "@"
43
- value = ""
44
- suffix = ""
45
-
46
- if isinstance(dependency_value, dict):
47
- version = dependency_value.get("version")
48
- if version:
49
- sep = "=="
50
- value = version
51
- else:
52
- git_url = dependency_value.get("git")
53
- if git_url:
54
- value = (
55
- f"git+{git_url}" if not git_url.startswith("git@") else git_url
56
- )
57
- rev = (
58
- dependency_value.get("branch")
59
- or dependency_value.get("rev")
60
- or dependency_value.get("tag")
61
- )
62
- if rev:
63
- value = f"{value}@{rev}"
64
- subdirectory = dependency_value.get("subdirectory")
65
- if subdirectory:
66
- value = f"{value}#subdirectory={subdirectory}"
67
- else:
68
- path = dependency_value.get("path")
69
- if path:
70
- value = path
71
- dependency_name = ""
72
- sep = ""
73
- else:
74
- url = dependency_value.get("url")
75
- if url:
76
- value = url
77
- dependency_name = ""
78
- sep = ""
79
- else:
80
- raise ValueError(
81
- "Unsupported dependency specification: "
82
- f"{dependency_name} = {dependency_value}"
83
- )
84
-
85
- # markers - common for all
86
- markers = dependency_value.get("markers")
87
- if markers is not None:
88
- suffix = f";{markers}"
89
- else:
90
- value = dependency_value
91
- sep = "=="
92
-
93
- if value.startswith("^"):
94
- sep = ">="
95
- value = value[1:]
96
- elif value.startswith("~"):
97
- sep = "~="
98
- value = value[1:]
99
- return f"{dependency_name}~={value[1:]}"
100
- elif "<" in value or ">" in value:
101
- sep = ""
102
- value = value.replace(" ", "")
103
-
104
- return f"{dependency_name}{sep}{value}{suffix}"
105
-
106
- dependencies: set[str] = {
107
- format_dependency_version(dependency, version)
108
- for dependency, version in poetry_dependencies.items()
109
- if dependency != "python"
110
- }
111
-
112
- return sorted(dependencies)
113
-
114
-
115
- def get_project_dependencies(
116
- project_dependencies: Optional[dict[str, Any]] = None,
117
- ) -> Optional[list[str]]:
118
- """
119
- Normalize PEP 621 `project.dependencies` into a sorted unique list.
120
-
121
- Args:
122
- project_dependencies: Value from `project.dependencies`.
123
-
124
- Returns:
125
- Sorted dependency strings, or `None` when input is `None`.
126
- """
127
-
128
- if project_dependencies is None:
129
- return None
130
-
131
- dependencies = set(project_dependencies)
132
-
133
- return sorted(dependencies)
@@ -1 +0,0 @@
1
- version = "0.84.0.dev0"
File without changes
File without changes