[+] update pr34

1. add -U to UV_ARGS,
    ignore it in venv;
  2. generate .whl;
  3. update m.py for pr34;
This commit is contained in:
Siarhei Siniak 2025-09-11 13:51:35 +03:00
parent f4f579b8f1
commit aa6b407fe7
3 changed files with 317 additions and 51 deletions

@ -1,5 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import glob import glob
import importlib
import json
import io import io
import tempfile import tempfile
import dataclasses import dataclasses
@ -8,15 +10,21 @@ import sys
import subprocess import subprocess
import os import os
import logging import logging
import typing
from typing import ( from typing import (
Optional, Optional,
Any, Any,
cast,
Type,
TypeVar,
Callable,
) )
from typing_extensions import ( from typing_extensions import (
Self, Self,
BinaryIO, BinaryIO,
overload,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -24,17 +32,23 @@ logger = logging.getLogger(__name__)
def toml_load(f: BinaryIO) -> Any: def toml_load(f: BinaryIO) -> Any:
try: try:
import tomllib tomllib = importlib.import_module('tomllib')
return tomllib.load(f) return cast(
except: Callable[[Any], Any],
getattr(
tomllib,
'load',
),
)(f)
except ModuleNotFoundError:
pass pass
try: try:
import tomli import tomli
return tomli.load(f) return tomli.load(f)
except: except ModuleNotFoundError:
pass pass
raise NotImplementedError raise NotImplementedError
@ -42,14 +56,136 @@ def toml_load(f: BinaryIO) -> Any:
@dataclasses.dataclass @dataclasses.dataclass
class PyProject: class PyProject:
@dataclasses.dataclass
class Module:
name: str
meson: Optional[pathlib.Path] = None
tool: dict[str, Any] = dataclasses.field(default_factory=lambda: dict())
path: pathlib.Path path: pathlib.Path
dependencies: dict[str, list[str]] dependencies: dict[str, list[str]]
early_features: Optional[list[str]] = None early_features: Optional[list[str]] = None
pip_find_links: Optional[list[pathlib.Path]] = None pip_find_links: Optional[list[pathlib.Path]] = None
runtime_libdirs: Optional[list[pathlib.Path]] = None runtime_libdirs: Optional[list[pathlib.Path]] = None
runtime_preload: Optional[list[pathlib.Path]] = None runtime_preload: Optional[list[pathlib.Path]] = None
@dataclasses.dataclass
class ThirdPartyRoot:
package: Optional[str] = None
module_root: Optional[str] = None
path: Optional[str] = None
third_party_roots: list[ThirdPartyRoot] = dataclasses.field(
default_factory=lambda: [],
)
requirements: dict[str, pathlib.Path] = dataclasses.field(default_factory=lambda: dict()) requirements: dict[str, pathlib.Path] = dataclasses.field(default_factory=lambda: dict())
modules: list[Module] = dataclasses.field(
default_factory=lambda: [],
)
tool: dict[str, Any] = dataclasses.field(
default_factory=lambda: dict(),
)
Key = TypeVar('Key')
Value = TypeVar('Value')
@overload
def check_dict(
value: Any,
KT: Type[Key],
VT: Type[Value],
) -> dict[Key, Value]: ...
@overload
def check_dict(
value: Any,
KT: Type[Key],
) -> dict[Key, Any]: ...
def check_dict(
value: Any,
KT: Type[Key],
VT: Optional[Type[Value]] = None,
) -> dict[Key, Value]:
assert isinstance(value, dict)
value2 = cast(dict[Any, Any], value)
VT_class: Optional[type[Any]] = None
if not VT is None:
if not typing.get_origin(VT) is None:
VT_class = cast(type[Any], typing.get_origin(VT))
else:
VT_class = VT
assert all([isinstance(k, KT) and (VT_class is None or isinstance(v, VT_class)) for k, v in value2.items()])
if VT is None:
return cast(
dict[Key, Any],
value,
)
else:
return cast(
dict[Key, Value],
value,
)
@overload
def check_list(
value: Any,
VT: Type[Value],
) -> list[Value]: ...
@overload
def check_list(
value: Any,
) -> list[Any]: ...
def check_list(
value: Any,
VT: Optional[Type[Value]] = None,
) -> list[Value] | list[Any]:
assert isinstance(value, list)
value2 = cast(list[Any], value)
assert all([(VT is None or isinstance(o, VT)) for o in value2])
if VT is None:
return cast(
list[Any],
value,
)
else:
return cast(
list[Value],
value,
)
def check_type(
value: Any,
VT: Type[Value],
attribute_name: Optional[str] = None,
) -> Value:
if attribute_name:
attribute_value = getattr(value, attribute_name)
assert isinstance(attribute_value, VT)
return attribute_value
else:
assert isinstance(value, VT)
return value
def pyproject_load( def pyproject_load(
d: pathlib.Path, d: pathlib.Path,
@ -66,9 +202,21 @@ def pyproject_load(
if 'optional-dependencies' in content['project']: if 'optional-dependencies' in content['project']:
assert isinstance(content['project']['optional-dependencies'], dict) assert isinstance(content['project']['optional-dependencies'], dict)
for k, v in content['project']['optional-dependencies'].items(): for k, v in check_dict(
assert isinstance(v, list) check_dict(
assert isinstance(k, str) check_dict(
content,
str,
# Any,
)['project'],
str,
# Any,
)['optional-dependencies'],
str,
list[Any],
).items():
# assert isinstance(v, list)
# assert isinstance(k, str)
dependencies[k] = v dependencies[k] = v
@ -79,36 +227,88 @@ def pyproject_load(
tool_name = 'online.fxreader.pr34'.replace('.', '-') tool_name = 'online.fxreader.pr34'.replace('.', '-')
if 'tool' in content:
res.tool = check_dict(
content['tool'],
str,
)
if 'tool' in content and isinstance(content['tool'], dict) and tool_name in content['tool'] and isinstance(content['tool'][tool_name], dict): if 'tool' in content and isinstance(content['tool'], dict) and tool_name in content['tool'] and isinstance(content['tool'][tool_name], dict):
if 'early_features' in content['tool'][tool_name]: pr34_tool = check_dict(
res.early_features = content['tool'][tool_name]['early_features'] check_dict(
content['tool'],
str,
)[tool_name],
str,
)
if 'pip_find_links' in content['tool'][tool_name]: if 'early_features' in pr34_tool:
res.pip_find_links = [d.parent / pathlib.Path(o) for o in content['tool'][tool_name]['pip_find_links']] res.early_features = pr34_tool['early_features']
if 'runtime_libdirs' in content['tool'][tool_name]: if 'pip_find_links' in pr34_tool:
res.pip_find_links = [d.parent / pathlib.Path(o) for o in pr34_tool['pip_find_links']]
if 'runtime_libdirs' in pr34_tool:
res.runtime_libdirs = [ res.runtime_libdirs = [
d.parent / pathlib.Path(o) d.parent / pathlib.Path(o)
# pathlib.Path(o) # pathlib.Path(o)
for o in content['tool'][tool_name]['runtime_libdirs'] for o in check_list(pr34_tool['runtime_libdirs'], str)
] ]
if 'runtime_preload' in content['tool'][tool_name]: if 'runtime_preload' in pr34_tool:
res.runtime_preload = [ res.runtime_preload = [
d.parent / pathlib.Path(o) d.parent / pathlib.Path(o)
# pathlib.Path(o) # pathlib.Path(o)
for o in content['tool'][tool_name]['runtime_preload'] for o in check_list(pr34_tool['runtime_preload'], str)
] ]
if 'requirements' in content['tool'][tool_name]: if 'third_party_roots' in pr34_tool:
assert isinstance(content['tool'][tool_name]['requirements'], dict) for o in check_list(pr34_tool['third_party_roots']):
o2 = check_dict(o, str, str)
assert all([k in {'package', 'module_root', 'path'} for k in o2])
res.third_party_roots.append(
PyProject.ThirdPartyRoot(
package=o2.get('package'),
module_root=o2.get('module_root'),
path=o2.get('path'),
)
)
if 'requirements' in pr34_tool:
res.requirements = { res.requirements = {
k: d.parent / pathlib.Path(v) k: d.parent / pathlib.Path(v)
# pathlib.Path(o) # pathlib.Path(o)
for k, v in content['tool'][tool_name]['requirements'].items() for k, v in check_dict(pr34_tool['requirements'], str, str).items()
} }
if 'modules' in pr34_tool:
modules = check_list(pr34_tool['modules'])
# res.modules = []
for o in modules:
assert isinstance(o, dict)
assert 'name' in o and isinstance(o['name'], str)
module = PyProject.Module(
name=o['name'],
)
if 'meson' in o:
assert 'meson' in o and isinstance(o['meson'], str)
module.meson = pathlib.Path(o['meson'])
if 'tool' in o:
module.tool.update(
check_dict(
o['tool'],
str,
)
)
res.modules.append(module)
return res return res
@ -127,10 +327,13 @@ class BootstrapSettings:
), ),
).strip() ).strip()
) )
pip_check_conflicts: Optional[bool] = dataclasses.field(
default_factory=lambda: os.environ.get('PIP_CHECK_CONFLICTS', json.dumps(True)) in [json.dumps(True)],
)
uv_args: list[str] = dataclasses.field( uv_args: list[str] = dataclasses.field(
default_factory=lambda: os.environ.get( default_factory=lambda: os.environ.get(
'UV_ARGS', 'UV_ARGS',
'--offline', '--offline -U',
).split(), ).split(),
) )
@ -142,7 +345,12 @@ class BootstrapSettings:
if base_dir is None: if base_dir is None:
base_dir = pathlib.Path.cwd() base_dir = pathlib.Path.cwd()
env_path: Optional[pathlib.Path] = None
if 'ENV_PATH' in os.environ:
env_path = pathlib.Path(os.environ['ENV_PATH'])
else:
env_path = base_dir / '.venv' env_path = base_dir / '.venv'
python_path = env_path / 'bin' / 'python3' python_path = env_path / 'bin' / 'python3'
return cls( return cls(
@ -152,6 +360,47 @@ class BootstrapSettings:
) )
class requirements_name_get_t:
@dataclasses.dataclass
class res_t:
not_compiled: pathlib.Path
compiled: pathlib.Path
name: str
def requirements_name_get(
source_dir: pathlib.Path,
python_version: Optional[str],
features: list[str],
requirements: dict[str, pathlib.Path],
) -> requirements_name_get_t.res_t:
requirements_python_version: Optional[str] = None
if not python_version is None:
requirements_python_version = python_version.replace('.', '_')
requirements_name = '_'.join(sorted(features))
if requirements_python_version:
requirements_name += '_' + requirements_python_version
requirements_path: Optional[pathlib.Path] = None
if requirements_name in requirements:
requirements_path = requirements[requirements_name]
else:
requirements_path = source_dir / 'requirements.txt'
requirements_path_in = requirements_path.parent / (requirements_path.stem + '.in')
requirements_in: list[str] = []
return requirements_name_get_t.res_t(
not_compiled=requirements_path_in,
compiled=requirements_path,
name=requirements_name,
)
def env_bootstrap( def env_bootstrap(
bootstrap_settings: BootstrapSettings, bootstrap_settings: BootstrapSettings,
pyproject: PyProject, pyproject: PyProject,
@ -169,7 +418,7 @@ def env_bootstrap(
] ]
for o in pip_find_links for o in pip_find_links
], ],
[], cast(list[str], []),
) )
features: list[str] = [] features: list[str] = []
@ -177,31 +426,24 @@ def env_bootstrap(
if pyproject.early_features: if pyproject.early_features:
features.extend(pyproject.early_features) features.extend(pyproject.early_features)
requirements_python_version: Optional[str] = None requirements_name_get_res = requirements_name_get(
if not bootstrap_settings.python_version is None: python_version=bootstrap_settings.python_version,
requirements_python_version = bootstrap_settings.python_version.replace('.', '_') features=features,
requirements=pyproject.requirements,
requirements_name = '_'.join(sorted(features)) source_dir=pyproject.path.parent,
)
if requirements_python_version: requirements_path = requirements_name_get_res.compiled
requirements_name += '_' + requirements_python_version
requirements_path: Optional[pathlib.Path] = None
if requirements_name in pyproject.requirements:
requirements_path = pyproject.requirements[requirements_name]
else:
requirements_path = pyproject.path.parent / 'requirements.txt'
requirements_in: list[str] = [] requirements_in: list[str] = []
requirements_in.extend(['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11']) requirements_in.extend(['uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11'])
if pyproject.early_features: if pyproject.early_features:
early_dependencies = sum([pyproject.dependencies[o] for o in pyproject.early_features], []) early_dependencies = sum([pyproject.dependencies[o] for o in pyproject.early_features], cast(list[str], []))
logger.info( logger.info(
dict( dict(
requirements_name_get_res=requirements_name_get_res,
early_dependencies=early_dependencies, early_dependencies=early_dependencies,
) )
) )
@ -218,6 +460,25 @@ def env_bootstrap(
# *early_dependencies, # *early_dependencies,
# ]) # ])
uv_python_version: list[str] = []
venv_python_version: list[str] = []
if not bootstrap_settings.python_version is None:
uv_python_version.extend(
[
# '-p',
'--python-version',
bootstrap_settings.python_version,
]
)
venv_python_version.extend(
[
'-p',
# '--python-version',
bootstrap_settings.python_version,
]
)
if not requirements_path.exists(): if not requirements_path.exists():
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
mode='w', mode='w',
@ -232,6 +493,7 @@ def env_bootstrap(
'uv', 'uv',
'pip', 'pip',
'compile', 'compile',
*uv_python_version,
'--generate-hashes', '--generate-hashes',
*pip_find_links_args, *pip_find_links_args,
# '-p', # '-p',
@ -243,24 +505,14 @@ def env_bootstrap(
] ]
) )
uv_python_version: list[str] = []
if not bootstrap_settings.python_version is None:
uv_python_version.extend(
[
'-p',
bootstrap_settings.python_version,
]
)
subprocess.check_call( subprocess.check_call(
[ [
'uv', 'uv',
*[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']],
'venv', 'venv',
*uv_python_version, *venv_python_version,
*pip_find_links_args, *pip_find_links_args,
# '--seed', # '--seed',
*bootstrap_settings.uv_args,
str(bootstrap_settings.env_path), str(bootstrap_settings.env_path),
] ]
) )
@ -270,6 +522,7 @@ def env_bootstrap(
'uv', 'uv',
'pip', 'pip',
'install', 'install',
*uv_python_version,
*pip_find_links_args, *pip_find_links_args,
'-p', '-p',
bootstrap_settings.python_path, bootstrap_settings.python_path,
@ -280,6 +533,16 @@ def env_bootstrap(
] ]
) )
if bootstrap_settings.pip_check_conflicts:
subprocess.check_call(
[
bootstrap_settings.python_path,
'-m',
'online.fxreader.pr34.commands',
'pip_check_conflicts',
]
)
def paths_equal(a: pathlib.Path | str, b: pathlib.Path | str) -> bool: def paths_equal(a: pathlib.Path | str, b: pathlib.Path | str) -> bool:
return os.path.abspath(str(a)) == os.path.abspath(str(b)) return os.path.abspath(str(a)) == os.path.abspath(str(b))

@ -333,7 +333,7 @@ class BootstrapSettings:
uv_args: list[str] = dataclasses.field( uv_args: list[str] = dataclasses.field(
default_factory=lambda: os.environ.get( default_factory=lambda: os.environ.get(
'UV_ARGS', 'UV_ARGS',
'--offline', '--offline -U',
).split(), ).split(),
) )
@ -508,11 +508,11 @@ def env_bootstrap(
subprocess.check_call( subprocess.check_call(
[ [
'uv', 'uv',
*[o for o in bootstrap_settings.uv_args if not o in ['-U', '--upgrade']],
'venv', 'venv',
*venv_python_version, *venv_python_version,
*pip_find_links_args, *pip_find_links_args,
# '--seed', # '--seed',
*bootstrap_settings.uv_args,
str(bootstrap_settings.env_path), str(bootstrap_settings.env_path),
] ]
) )

BIN
releases/whl/online_fxreader_pr34-0.1.5.28-py3-none-any.whl (Stored with Git LFS) Normal file

Binary file not shown.