reyk 0.0.1__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 (33) hide show
  1. reyk-0.0.1/.gitignore +36 -0
  2. reyk-0.0.1/LICENSE +21 -0
  3. reyk-0.0.1/Makefile +46 -0
  4. reyk-0.0.1/PKG-INFO +76 -0
  5. reyk-0.0.1/README.md +51 -0
  6. reyk-0.0.1/pyproject.toml +129 -0
  7. reyk-0.0.1/reyk/__init__.py +0 -0
  8. reyk-0.0.1/reyk/caller_finder.py +52 -0
  9. reyk-0.0.1/reyk/isolator.py +46 -0
  10. reyk-0.0.1/reyk/stdlib_finder.py +28 -0
  11. reyk-0.0.1/reyk/vendor_importer.py +292 -0
  12. reyk-0.0.1/reyk/vendored_sys_modules.py +108 -0
  13. reyk-0.0.1/reyk/version.py +1 -0
  14. reyk-0.0.1/tests/__init__.py +0 -0
  15. reyk-0.0.1/tests/blackbox/__init__.py +0 -0
  16. reyk-0.0.1/tests/blackbox/conftest.py +81 -0
  17. reyk-0.0.1/tests/blackbox/example_project/__init__.py +0 -0
  18. reyk-0.0.1/tests/blackbox/example_project_file_manager.py +78 -0
  19. reyk-0.0.1/tests/blackbox/libraries_manager.py +33 -0
  20. reyk-0.0.1/tests/blackbox/predefined_tests/__init__.py +0 -0
  21. reyk-0.0.1/tests/blackbox/predefined_tests/import_path_extractor.py +6 -0
  22. reyk-0.0.1/tests/blackbox/predefined_tests/test_isolator_blackbox.py +523 -0
  23. reyk-0.0.1/tests/blackbox/predefined_tests/variable_access_statement_generator.py +129 -0
  24. reyk-0.0.1/tests/blackbox/project_paths.py +10 -0
  25. reyk-0.0.1/tests/blackbox/specific_libraries/__init__.py +0 -0
  26. reyk-0.0.1/tests/blackbox/specific_libraries/conftest.py +3 -0
  27. reyk-0.0.1/tests/blackbox/specific_libraries/pyproject.toml +21 -0
  28. reyk-0.0.1/tests/blackbox/specific_libraries/test_isolator_blackbox_specific_libraries.py +130 -0
  29. reyk-0.0.1/tests/blackbox/specific_libraries/uv.lock +1468 -0
  30. reyk-0.0.1/tests/blackbox/test_distributions_finder.py +42 -0
  31. reyk-0.0.1/tests/conftest.py +9 -0
  32. reyk-0.0.1/tests/internals/__init__.py +0 -0
  33. reyk-0.0.1/tests/internals/test_internals.py +43 -0
reyk-0.0.1/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ # Byte-compiled
2
+ __pycache__/
3
+
4
+ # Distribution / packaging
5
+ build/
6
+ dist/
7
+
8
+ # Unit test / coverage reports
9
+ htmlcov/
10
+ .tox/
11
+ .nox/
12
+ .coverage
13
+ .coverage.*
14
+ .cache
15
+ nosetests.xml
16
+ coverage.xml
17
+ *.cover
18
+ *.py,cover
19
+ .hypothesis/
20
+ .pytest_cache/
21
+ coverage/
22
+ coverage.lcov
23
+
24
+ # Environments
25
+ .venv/
26
+
27
+ # PyPI configuration file
28
+ .pypirc
29
+
30
+ # Tests
31
+ tests/blackbox/locked_files_pending_deletion/
32
+ tests/blackbox/specific_libraries/test_libs
33
+ tests/blackbox/specific_libraries/vendor.lock
34
+
35
+ # Docs
36
+ .vercel
reyk-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Reyk's authors
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.
reyk-0.0.1/Makefile ADDED
@@ -0,0 +1,46 @@
1
+ # Can be installed on Windows with GNUWin32 or Chocolatey
2
+
3
+ specific_libraries_test := tests/blackbox/specific_libraries
4
+ specific_libraries_test_libs := $(specific_libraries_test)/test_libs
5
+
6
+ uv:
7
+ @uv -V || echo 'uv is not installed. Install via: https://docs.astral.sh/uv/getting-started/installation/'
8
+
9
+ install-hooks: uv
10
+ uv run pre-commit install --install-hooks
11
+
12
+ install: uv
13
+ uv sync --frozen --all-groups --all-extras --all-packages
14
+
15
+ install-test-libs: $(specific_libraries_test_libs)
16
+
17
+ $(specific_libraries_test_libs):
18
+ CLI_PATH="$$(pwd)/reyk-cli"; \
19
+ cd $(specific_libraries_test); \
20
+ uvx "$$CLI_PATH" sync
21
+
22
+ test-library: install install-test-libs
23
+ uv run coverage run $(COVERAGE_FLAGS) -m pytest -vv tests/
24
+
25
+ test-library-memray: install install-test-libs
26
+ uv run pytest -v tests/ --memray
27
+
28
+ test-cli: install install-test-libs
29
+ uv run coverage run $(COVERAGE_FLAGS) -m pytest -vv reyk-cli/tests/
30
+
31
+ # The --append flag is needed to combine the coverage data from both test-library and test-cli
32
+ test: install install-test-libs
33
+ mkdir -p coverage
34
+ rm -f coverage/.coverage.*
35
+ "$(MAKE)" COVERAGE_FLAGS=--append test-library
36
+ "$(MAKE)" COVERAGE_FLAGS=--append test-cli
37
+
38
+ testcov: test
39
+ uv run coverage report
40
+ @echo "building coverage lcov"
41
+ uv run coverage lcov
42
+ @echo "building coverage html"
43
+ uv run coverage html
44
+
45
+ update-dependencies: uv
46
+ uv lock --upgrade
reyk-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: reyk
3
+ Version: 0.0.1
4
+ Summary: Run conflicting Python dependencies in one process — with vendored isolation.
5
+ Project-URL: Homepage, https://reyk.dev
6
+ Project-URL: Source, https://github.com/eyalohn/reyk
7
+ Author-email: Eyal Ohn <eyal.ohn@gmail.com>, Ofek Ashery <ofek.ashery@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: conflicting dependencies,dependency isolation,python import system,vendoring,virtual environment
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Reyk
27
+
28
+ [![Tests](https://github.com/eyalohn/reyk/actions/workflows/test.yml/badge.svg)](https://github.com/eyalohn/reyk/actions/workflows/test.yml)
29
+ [![Coverage](https://coverage-badge.samuelcolvin.workers.dev/eyalohn/reyk.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/eyalohn/reyk)
30
+ [![Latest Version](https://img.shields.io/pypi/v/reyk.svg)](https://pypi.python.org/pypi/reyk/)
31
+ [![Python Versions](https://img.shields.io/pypi/pyversions/reyk.svg)](https://pypi.python.org/pypi/reyk/)
32
+ [![llms.txt](https://img.shields.io/badge/llms.txt-green)](https://reyk.dev/llms-full.txt)
33
+
34
+ Run conflicting Python dependencies in one process — with vendored isolation.
35
+
36
+ [Reyk](https://reyk.dev) lets you **vendor Python dependencies into your project and isolate them by design**, without changing how you write imports. We leverage Python's import system internals to achieve transparent dependency isolation.
37
+
38
+ ## Features
39
+
40
+ - **Zero Configuration**: Enable isolation with a single function call
41
+ - **Dependency Isolation**: Avoid conflicts by shipping libraries with your package
42
+ - **Version Flexibility**: Run multiple versions of the same library in one process
43
+ - **Transparent Imports**: Keep your public imports unchanged (`import requests` still works)
44
+ - **Lightweight**: Minimal overhead with a tiny import hook
45
+
46
+ # Installation
47
+
48
+ You can install Reyk from PyPI:
49
+
50
+ ```bash
51
+ uv add reyk
52
+ ```
53
+
54
+ Enable isolation from your package's `__init__.py`:
55
+
56
+ ```python
57
+ from reyk.isolator import isolate_package
58
+
59
+ isolate_package()
60
+ ```
61
+
62
+ Then, vendor your dependencies with Reyk's CLI:
63
+
64
+ ```bash
65
+ uvx reyk-cli add requests==2.25.1
66
+ ```
67
+
68
+ **That's it!** Inside `your_package`, `import requests` resolves to `your_package.libs.requests`. Outside `your_package`, `import requests` continues to resolve to the global installation.
69
+
70
+ ## Documentation
71
+
72
+ For detailed documentation, examples, and API reference, visit the documentation site at https://reyk.dev.
73
+
74
+ ## Limitations
75
+
76
+ - Multi-threaded applications are not supported.
reyk-0.0.1/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Reyk
2
+
3
+ [![Tests](https://github.com/eyalohn/reyk/actions/workflows/test.yml/badge.svg)](https://github.com/eyalohn/reyk/actions/workflows/test.yml)
4
+ [![Coverage](https://coverage-badge.samuelcolvin.workers.dev/eyalohn/reyk.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/eyalohn/reyk)
5
+ [![Latest Version](https://img.shields.io/pypi/v/reyk.svg)](https://pypi.python.org/pypi/reyk/)
6
+ [![Python Versions](https://img.shields.io/pypi/pyversions/reyk.svg)](https://pypi.python.org/pypi/reyk/)
7
+ [![llms.txt](https://img.shields.io/badge/llms.txt-green)](https://reyk.dev/llms-full.txt)
8
+
9
+ Run conflicting Python dependencies in one process — with vendored isolation.
10
+
11
+ [Reyk](https://reyk.dev) lets you **vendor Python dependencies into your project and isolate them by design**, without changing how you write imports. We leverage Python's import system internals to achieve transparent dependency isolation.
12
+
13
+ ## Features
14
+
15
+ - **Zero Configuration**: Enable isolation with a single function call
16
+ - **Dependency Isolation**: Avoid conflicts by shipping libraries with your package
17
+ - **Version Flexibility**: Run multiple versions of the same library in one process
18
+ - **Transparent Imports**: Keep your public imports unchanged (`import requests` still works)
19
+ - **Lightweight**: Minimal overhead with a tiny import hook
20
+
21
+ # Installation
22
+
23
+ You can install Reyk from PyPI:
24
+
25
+ ```bash
26
+ uv add reyk
27
+ ```
28
+
29
+ Enable isolation from your package's `__init__.py`:
30
+
31
+ ```python
32
+ from reyk.isolator import isolate_package
33
+
34
+ isolate_package()
35
+ ```
36
+
37
+ Then, vendor your dependencies with Reyk's CLI:
38
+
39
+ ```bash
40
+ uvx reyk-cli add requests==2.25.1
41
+ ```
42
+
43
+ **That's it!** Inside `your_package`, `import requests` resolves to `your_package.libs.requests`. Outside `your_package`, `import requests` continues to resolve to the global installation.
44
+
45
+ ## Documentation
46
+
47
+ For detailed documentation, examples, and API reference, visit the documentation site at https://reyk.dev.
48
+
49
+ ## Limitations
50
+
51
+ - Multi-threaded applications are not supported.
@@ -0,0 +1,129 @@
1
+ [project]
2
+ name = "reyk"
3
+ description = "Run conflicting Python dependencies in one process — with vendored isolation."
4
+ authors = [
5
+ { name = 'Eyal Ohn', email = 'eyal.ohn@gmail.com' },
6
+ { name = 'Ofek Ashery', email = 'ofek.ashery@gmail.com' },
7
+ ]
8
+ readme = "README.md"
9
+ license = 'MIT'
10
+ license-files = ['LICENSE']
11
+ keywords = [
12
+ "vendoring",
13
+ "dependency isolation",
14
+ "conflicting dependencies",
15
+ "virtual environment",
16
+ "python import system",
17
+ ]
18
+ classifiers = [
19
+ 'Development Status :: 4 - Beta',
20
+ 'Programming Language :: Python',
21
+ 'Programming Language :: Python :: Implementation :: CPython',
22
+ 'Programming Language :: Python :: 3',
23
+ 'Programming Language :: Python :: 3.9',
24
+ 'Programming Language :: Python :: 3.10',
25
+ 'Programming Language :: Python :: 3.11',
26
+ 'Programming Language :: Python :: 3.12',
27
+ 'Programming Language :: Python :: 3.13',
28
+ 'Programming Language :: Python :: 3.14',
29
+ 'Intended Audience :: Developers',
30
+ 'Operating System :: OS Independent',
31
+ ]
32
+ requires-python = ">=3.9"
33
+ dependencies = []
34
+ dynamic = ["version"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://reyk.dev"
38
+ Source = "https://github.com/eyalohn/reyk"
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [dependency-groups]
45
+ dev = ["coverage[toml]>=7.10.7", "pre-commit>=4.3.0", "pytest>=8.4.2"]
46
+ testing-extra = [
47
+ "pytest-memray>=1.8.0; platform_python_implementation == 'CPython' and platform_system != 'Windows'",
48
+ ]
49
+
50
+ [tool.hatch.version]
51
+ path = "reyk/version.py"
52
+
53
+ [tool.hatch.build.targets.sdist]
54
+ include = [
55
+ "/README.md",
56
+ "/Makefile",
57
+ "/reyk",
58
+ "/tests",
59
+ ]
60
+
61
+ [tool.uv.workspace]
62
+ members = ["reyk-cli"]
63
+
64
+ [tool.ruff]
65
+ line-length = 120
66
+ exclude = ["test_libs"]
67
+
68
+ [tool.ruff.lint]
69
+ select = ["ALL"]
70
+ ignore = [
71
+ "W293",
72
+ "D101",
73
+ "D102",
74
+ "D103",
75
+ "D104",
76
+ "D105",
77
+ "D100",
78
+ "T201",
79
+ "I001",
80
+ "INP001",
81
+ "EM102",
82
+ "TRY003",
83
+ "S101",
84
+ "FA102",
85
+ "UP036",
86
+ "EM101",
87
+ "G004",
88
+ "D107",
89
+ "FA100",
90
+ "COM812",
91
+ "TC006",
92
+ "SIM118",
93
+ "D205",
94
+ "D212",
95
+ "D401",
96
+ "D400",
97
+ "D415",
98
+ "UP040",
99
+ "D203",
100
+ "TC003",
101
+ ]
102
+
103
+ [tool.ruff.lint.per-file-ignores]
104
+ "tests/**/test_*.py" = ["SLF001", "PLC0415", "F401"]
105
+ "tests/**/conftest.py" = ["SLF001", "PLC0415", "F401"]
106
+ "tests/blackbox/distributions_finder.py" = ["SLF001", "PLC0415", "F401"]
107
+ "reyk/vendor_importer.py" = ["A002"]
108
+
109
+ [tool.pyright]
110
+ include = ["reyk", "tests"]
111
+ exclude = ["tests/blackbox/specific_libraries/test_libs", ".venv"]
112
+ venvPath = "."
113
+ venv = ".venv"
114
+
115
+ [tool.codespell]
116
+ skip = ".git,env*,uv.lock,**/package-lock.json,./tests/blackbox/specific_libraries/test_libs,node_modules,htmlcov"
117
+ ignore-words-list = ["reyk"]
118
+
119
+ [tool.coverage.run]
120
+ source = ["reyk", "reyk-cli/src"]
121
+ relative_files = true
122
+ branch = true
123
+ data_file = "coverage/.coverage"
124
+
125
+ [tool.coverage.lcov]
126
+ output = "coverage/coverage.lcov"
127
+
128
+ [tool.coverage.html]
129
+ directory = "coverage/htmlcov/coverage.lcov"
File without changes
@@ -0,0 +1,52 @@
1
+ import logging
2
+ import sys
3
+ from collections.abc import Iterator
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from reyk.stdlib_finder import is_part_of_stdlib
8
+
9
+ LOGGER = logging.getLogger(__name__)
10
+ MY_PACKAGE_NAME = __package__
11
+
12
+
13
+ class NoCallerOutsideLibFoundError(RuntimeError): ...
14
+
15
+
16
+ @dataclass
17
+ class StackFrame:
18
+ filename: Path
19
+ module_name: str
20
+
21
+
22
+ def get_caller_frame_outside_reyk() -> StackFrame:
23
+ for frame in _iterate_over_stack():
24
+ if is_part_of_stdlib(frame.module_name):
25
+ continue
26
+
27
+ if frame.module_name == MY_PACKAGE_NAME or frame.module_name.startswith(f"{MY_PACKAGE_NAME}."):
28
+ # This function
29
+ continue
30
+
31
+ return frame
32
+
33
+ raise NoCallerOutsideLibFoundError("Failed to find caller outside builtin")
34
+
35
+
36
+ def _iterate_over_stack() -> Iterator[StackFrame]:
37
+ current_frame = sys._getframe(1) # noqa: SLF001
38
+ while current_frame is not None:
39
+ yield StackFrame(
40
+ filename=Path(current_frame.f_code.co_filename),
41
+ module_name=current_frame.f_globals["__name__"],
42
+ )
43
+ current_frame = current_frame.f_back
44
+
45
+
46
+ def is_caller_part_of_library(library_name: str) -> bool:
47
+ try:
48
+ caller_frame = get_caller_frame_outside_reyk()
49
+ except NoCallerOutsideLibFoundError:
50
+ return False
51
+ caller_module_name = caller_frame.module_name
52
+ return (caller_module_name == library_name) or (caller_module_name.startswith(f"{library_name}."))
@@ -0,0 +1,46 @@
1
+ import builtins
2
+ import importlib
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import Optional, cast
6
+
7
+ from reyk.caller_finder import get_caller_frame_outside_reyk
8
+ from reyk.vendor_importer import BuiltinsImporter, VendorImporter
9
+
10
+ LOGGER = logging.getLogger(__name__)
11
+
12
+
13
+ def isolate_package(package_path: Optional[Path] = None, vendorized_libs_directory_import_path: str = "libs") -> None:
14
+ vendor_importer = create_vendor_importer(package_path, vendorized_libs_directory_import_path)
15
+ LOGGER.debug(f"Isolating library: {vendor_importer.package_name} ({vendorized_libs_directory_import_path=})")
16
+ vendor_importer.install()
17
+
18
+
19
+ def create_vendor_importer(
20
+ package_path: Optional[Path] = None,
21
+ vendorized_libs_directory_import_path: str = "libs",
22
+ ) -> VendorImporter:
23
+ """
24
+ Isolates a package dependencies - everything imported from the specified package_path
25
+ will prefer to import libraries inside the specified vendorized_libs_directory_import_path
26
+ instead of the default PYTHONPATH/site-packages.
27
+ The package_path can be unspecified/None to default to the caller's parent package.
28
+ vendorized_libs_directory_import_path should be the path to import the dependencies
29
+ from the package.
30
+ For example if the dependencies are in 'libs' it should be 'libs' as well as we need to perform:
31
+ `import libs` but if it's a subdirectory like 'my/libs' it should be 'my.libs' for
32
+ `import my.libs`.
33
+ """
34
+ if package_path is None:
35
+ package_path = get_caller_frame_outside_reyk().filename.parent
36
+
37
+ package_name = package_path.name
38
+ vendorized_libs_path = package_path / vendorized_libs_directory_import_path
39
+ return VendorImporter(
40
+ package_name,
41
+ vendorized_libs_directory_import_path,
42
+ vendorized_libs_path,
43
+ # Cast as for some reason the definition of __import__ thinks fromlist is not nullable
44
+ cast(BuiltinsImporter, builtins.__import__),
45
+ importlib.import_module,
46
+ )
@@ -0,0 +1,28 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+ from functools import cache
5
+ from collections.abc import Iterator
6
+
7
+
8
+ @cache
9
+ def list_stdlib_modules() -> list[str]:
10
+ return list(_list_stdlib_modules_iter())
11
+
12
+
13
+ def _list_stdlib_modules_iter() -> Iterator[str]:
14
+ standard_lib_dir = Path(os.__file__).parent
15
+ for std_lib_file in standard_lib_dir.iterdir():
16
+ if _is_package(std_lib_file) or (std_lib_file.suffix == ".py"):
17
+ yield std_lib_file.stem
18
+
19
+ yield from sys.builtin_module_names
20
+
21
+
22
+ def _is_package(path: Path) -> bool:
23
+ return path.is_dir() and (path / "__init__.py").exists()
24
+
25
+
26
+ def is_part_of_stdlib(module_name: str) -> bool:
27
+ library_name, _, _ = module_name.partition(".")
28
+ return library_name in list_stdlib_modules()