Skip to content

Commit b03f237

Browse files
authored
Merge pull request #5 from davisagli/buildout-fixes
Buildout fixes
2 parents 4336871 + 08712c9 commit b03f237

File tree

9 files changed

+139
-26
lines changed

9 files changed

+139
-26
lines changed

demo_buildout/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/.installed.cfg
2+
/.venv
3+
/bin
4+
/develop-eggs
5+
/eggs
6+
/parts
7+
/var

demo_buildout/Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.PHONY: all
2+
all: bin/instance
3+
bin/instance run test.py
4+
5+
bin/instance: .venv/bin/buildout
6+
.venv/bin/buildout -D
7+
8+
.venv/bin/pip:
9+
uv venv --seed .venv
10+
11+
.venv/bin/buildout: .venv/bin/pip
12+
.venv/bin/pip install -r requirements.txt
13+
.venv/bin/pip install -e ..
14+
15+
.PHONY: clean
16+
clean:
17+
rm -rf .installed.cfg .venv bin develop-eggs eggs parts var

demo_buildout/buildout.cfg

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[buildout]
2+
extends =
3+
https://dist.plone.org/release/6.1.2/versions.cfg
4+
5+
parts =
6+
instance
7+
test
8+
9+
eggs =
10+
Plone
11+
12+
[instance]
13+
recipe = plone.recipe.zope2instance
14+
user = admin:admin
15+
eggs = ${buildout:eggs}
16+
17+
[test]
18+
recipe = zc.recipe.testrunner
19+
eggs = ${buildout:eggs}

demo_buildout/constraints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-r https://dist.plone.org/release/6.1.2/requirements.txt

demo_buildout/requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-c constraints.txt
2+
3+
zc.buildout
4+
wheel
5+
horse-with-no-namespace

demo_buildout/test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import zc.buildout # noqa: F401
2+
import zc.lockfile # noqa: F401
3+
4+
print("")
5+
print("Testing imports...")
6+
print("")
7+
8+
print(zc.buildout)
9+
print(zc.lockfile)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# SPDX-FileCopyrightText: 2025 David Glick <[email protected]>
22
#
33
# SPDX-License-Identifier: MIT
4-
__version__ = "20250626.0"
4+
__version__ = "20250704.0"

src/horse_with_no_namespace/__init__.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
# but the site module sorts the files before processing them,
1313
# and that hasn't changed recently.)
1414

15-
import pkgutil
15+
import importlib
1616
import sys
1717

1818
logged = False
19+
BOLD = "\033[1m"
20+
RESET = "\033[0m"
1921

2022

2123
def apply():
@@ -25,37 +27,26 @@ def apply():
2527
global logged
2628
if not logged:
2729
print(
28-
"🐎 This Python uses horse-with-no-namespace "
29-
"to make pkg_resources namespace packages compatible "
30-
"with PEP 420 namespace packages.",
30+
f"🐎 This Python ({BOLD}{sys.executable}{RESET}) uses "
31+
"horse-with-no-namespace to make pkg_resources namespace "
32+
"packages compatible with PEP 420 namespace packages.",
3133
file=sys.stderr,
3234
)
3335
logged = True
3436

35-
# Only patch pkg_resources if it is installed...
36-
# (To do: at some point pkg_resources will be removed from setuptools.
37-
# Then we might have to add our own fake module.)
38-
try:
39-
import pkg_resources
40-
except ImportError:
41-
pass
42-
else:
43-
# Patch pkg_resources.declare_namespace
44-
# to update __path__ using pkgutil.extend_path instead
45-
def declare_namespace(packageName):
46-
parent_locals = sys._getframe(1).f_locals
47-
# Sometimes declare_namespace is called from pkg_resources itself;
48-
# then there is no __path__ which needs to be updated.
49-
if "__path__" in parent_locals:
50-
parent_locals["__path__"] = pkgutil.extend_path(
51-
parent_locals["__path__"], packageName
52-
)
53-
54-
pkg_resources.declare_namespace = declare_namespace
55-
5637
# Remove existing namespace package modules that were already created
5738
# by other .pth files, possibly with an incomplete __path__
5839
for name, module in list(sys.modules.items()):
5940
loader = getattr(module, "__loader__", None)
6041
if loader and loader.__class__.__name__ == "NamespaceLoader":
6142
del sys.modules[name]
43+
44+
# We want to patch pkg_resources.declare_namespace,
45+
# but we don't want to import it too early,
46+
# because that would initialize the pkg_resources working set
47+
# before sys.path is finalized.
48+
# So, let's put a fake pkg_resources module is sys.modules,
49+
# which will replace itself once it is accessed.
50+
sys.modules["pkg_resources"] = importlib.import_module(
51+
"horse_with_no_namespace.pkg_resources"
52+
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import pkgutil
2+
import sys
3+
4+
5+
def declare_namespace(packageName):
6+
# We'll patch pkg_resources to replace its declare_namespace function.
7+
# The replacement bypasses all the pkg_resources namespace package
8+
# machinery, and instead makes the calling module's __path__
9+
# get dynamically looked up using pkgutil.extend_path.
10+
11+
# Get the locals from our caller
12+
parent_locals = sys._getframe(1).f_locals
13+
14+
# Sometimes declare_namespace is called from pkg_resources itself;
15+
# then there is no __path__ which needs to be updated.
16+
if "__path__" not in parent_locals:
17+
return
18+
19+
orig_path = parent_locals["__path__"]
20+
del parent_locals["__path__"]
21+
if "__getattr__" in parent_locals:
22+
# The module already has its own __getattr__;
23+
# there's nothing we can do.
24+
return
25+
26+
# Get the module __path__ dynamically.
27+
# (We can't just set it to a static value,
28+
# because it needs to handle updates to sys.path)
29+
def __getattr__(name):
30+
if name == "__path__":
31+
return pkgutil.extend_path(orig_path, packageName)
32+
raise AttributeError(name)
33+
34+
parent_locals["__getattr__"] = __getattr__
35+
36+
37+
# The rest of this is to replace this module
38+
# with the real pkg_resources once it is accessed.
39+
40+
_pkg_resources = None
41+
42+
43+
def _lazy_load_pkg_resources():
44+
global _pkg_resources
45+
if _pkg_resources is not None:
46+
return
47+
# This is called when something from pkg_resources is accessed.
48+
# We import it here to avoid importing it at the top of the file,
49+
# which would cause a circular import.
50+
del sys.modules["pkg_resources"]
51+
import pkg_resources
52+
53+
pkg_resources.declare_namespace = declare_namespace
54+
_pkg_resources = pkg_resources
55+
56+
57+
def __getattr__(name):
58+
_lazy_load_pkg_resources()
59+
return getattr(_pkg_resources, name)
60+
61+
62+
def __dir__():
63+
_lazy_load_pkg_resources()
64+
return dir(_pkg_resources)

0 commit comments

Comments
 (0)