private-attribute-cpp 2.1.2__tar.gz → 2.1.3__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 (22) hide show
  1. {private_attribute_cpp-2.1.2/private_attribute_cpp.egg-info → private_attribute_cpp-2.1.3}/PKG-INFO +1 -1
  2. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/private_attribute.cpp +4 -4
  3. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3/private_attribute_cpp.egg-info}/PKG-INFO +1 -1
  4. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/private_attribute_cpp.egg-info/SOURCES.txt +1 -0
  5. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/setup.py +1 -1
  6. private_attribute_cpp-2.1.3/tests/test_attr_classify_owner.py +137 -0
  7. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/LICENSE +0 -0
  8. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/MANIFEST.in +0 -0
  9. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/README.md +0 -0
  10. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/picosha2.h +0 -0
  11. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/private_attribute.pyi +0 -0
  12. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
  13. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
  14. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/private_attribute_cpp.egg-info/top_level.txt +0 -0
  15. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/setup.cfg +0 -0
  16. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_class_private_attrs.py +0 -0
  17. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_private_abc.py +0 -0
  18. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_private_attrs.py +0 -0
  19. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_private_func.py +0 -0
  20. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_private_inheritance.py +0 -0
  21. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_private_method.py +0 -0
  22. {private_attribute_cpp-2.1.2 → private_attribute_cpp-2.1.3}/tests/test_private_wrap.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: private_attribute_cpp
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: A Python package that provides a way to define private attributes in C++ implementation.
5
5
  Home-page: https://github.com/Locked-chess-official/private_attribute_cpp
6
6
  Author: HuangHaoHua
@@ -317,11 +317,11 @@ attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noe
317
317
  chain.insert(chain.end(), parent_ids.begin(), parent_ids.end());
318
318
  }
319
319
 
320
- // Find the class whose code is currently running (the code owner).
320
+ // Find the class whose code is currently running (the code owner, parent first).
321
321
  uintptr_t code_class = 0;
322
- for (auto& node : chain) {
323
- if (is_class_code(node, code)) {
324
- code_class = node;
322
+ for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
323
+ if (is_class_code(*it, code)) {
324
+ code_class = *it; // first hit from the base-most end
325
325
  break;
326
326
  }
327
327
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: private_attribute_cpp
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: A Python package that provides a way to define private attributes in C++ implementation.
5
5
  Home-page: https://github.com/Locked-chess-official/private_attribute_cpp
6
6
  Author: HuangHaoHua
@@ -10,6 +10,7 @@ private_attribute_cpp.egg-info/SOURCES.txt
10
10
  private_attribute_cpp.egg-info/dependency_links.txt
11
11
  private_attribute_cpp.egg-info/not-zip-safe
12
12
  private_attribute_cpp.egg-info/top_level.txt
13
+ tests/test_attr_classify_owner.py
13
14
  tests/test_class_private_attrs.py
14
15
  tests/test_private_abc.py
15
16
  tests/test_private_attrs.py
@@ -18,7 +18,7 @@ readme = open('README.md').read()
18
18
 
19
19
  setup(
20
20
  name='private_attribute_cpp',
21
- version='2.1.2',
21
+ version='2.1.3',
22
22
  author="HuangHaoHua",
23
23
  author_email="13140752715@example.com",
24
24
  description='A Python package that provides a way to define private attributes in C++ implementation.',
@@ -0,0 +1,137 @@
1
+ """attr_classify ownership rule (v2.1.3).
2
+
3
+ When a code object is owned by several classes on the MRO chain (e.g. a
4
+ subclass aliases a parent method with `f = g`, or copies a parent method's
5
+ bytecode with `f.__code__ = g.__code__`), attr_classify must attribute the
6
+ code to the base-most (declaring, parent) class. Otherwise the parent code's
7
+ private attribute accesses get split across two storages: the tampered method
8
+ writes the subclass's storage while the untampered parent method reads the
9
+ parent's storage, corrupting the parent's behavior.
10
+
11
+ Key invariant: parent methods inc() (tampered by the child) and peek() (not
12
+ tampered) must ALWAYS see the same storage on a child instance.
13
+ """
14
+ import unittest
15
+
16
+ import private_attribute
17
+
18
+
19
+ class Parent(private_attribute.PrivateAttrBase):
20
+ __private_attrs__ = ["_n"]
21
+ _n = 0
22
+
23
+ def inc(self):
24
+ self._n += 1
25
+ return self._n
26
+
27
+ def peek(self):
28
+ return self._n
29
+
30
+
31
+ class TestAliasCodeOwnership(unittest.TestCase):
32
+ """`f = g` in the class body registers the same function object with both
33
+ classes; the aliased and the untampered parent methods must stay
34
+ consistent on a child instance."""
35
+
36
+ def test_alias_method_sees_same_storage_as_untampered(self):
37
+ class AliasChild(Parent):
38
+ __private_attrs__ = ["_n"]
39
+ _n = 100
40
+ f = Parent.inc # same function object in both class dicts
41
+
42
+ c = AliasChild()
43
+ v1 = c.f() # parent code, double-owned
44
+ v2 = c.peek() # parent code, single-owned
45
+ self.assertEqual(v1, v2)
46
+ c.f()
47
+ self.assertEqual(c.peek(), v2 + 1)
48
+ self.assertEqual(Parent().peek(), 0)
49
+
50
+ def test_alias_without_shadow_works(self):
51
+ class AliasChild2(Parent):
52
+ __private_attrs__ = []
53
+ f = Parent.inc
54
+
55
+ c = AliasChild2()
56
+ v1 = c.f()
57
+ self.assertEqual(v1, c.peek())
58
+
59
+
60
+ class TestCodeSwapOwnership(unittest.TestCase):
61
+ """`f.__code__ = g.__code__` inside the class body; the swapped method
62
+ must behave exactly like the parent's method (same storage)."""
63
+
64
+ def test_code_swap_sees_same_storage_as_untampered(self):
65
+ class CodeChild(Parent):
66
+ __private_attrs__ = ["_n"]
67
+ _n = 100
68
+
69
+ def f(self):
70
+ return None
71
+
72
+ f.__code__ = Parent.inc.__code__
73
+
74
+ c = CodeChild()
75
+ v1 = c.f()
76
+ v2 = c.peek()
77
+ self.assertEqual(v1, v2)
78
+ c.f()
79
+ self.assertEqual(c.peek(), v2 + 1)
80
+
81
+
82
+ class TestFallbackSemantics(unittest.TestCase):
83
+ """Per-subject fallback is preserved: an unwritten read resolves to the
84
+ subject's own class-level default, consistently for both methods."""
85
+
86
+ def test_unwritten_read_falls_back_per_subject(self):
87
+ class FreshChild(Parent):
88
+ __private_attrs__ = ["_n"]
89
+ _n = 100
90
+ f = Parent.inc
91
+
92
+ c = FreshChild()
93
+ self.assertEqual(c.peek(), 100)
94
+ v1 = c.f()
95
+ self.assertEqual(v1, c.peek())
96
+
97
+
98
+ class TestNormalUsageRegression(unittest.TestCase):
99
+ """Without tampering, nothing changes: child's own code uses child
100
+ storage, parent's code keeps its per-subject view."""
101
+
102
+ def test_child_own_code_uses_child_storage(self):
103
+ class NormChild(Parent):
104
+ __private_attrs__ = ["_n"]
105
+ _n = 100
106
+
107
+ def own_inc(self):
108
+ self._n += 1
109
+ return self._n
110
+
111
+ c = NormChild()
112
+ self.assertEqual(c.own_inc(), 101)
113
+ self.assertEqual(c.peek(), 100)
114
+ self.assertEqual(Parent().peek(), 0)
115
+
116
+
117
+ class TestIsolationIntact(unittest.TestCase):
118
+ """The v2.1.0 isolation rule still holds: the subclass's own code must
119
+ not touch a parent's private attribute."""
120
+
121
+ def test_subclass_own_code_denied(self):
122
+ class IsoParent(private_attribute.PrivateAttrBase):
123
+ __private_attrs__ = ["_secret"]
124
+ _secret = 1
125
+
126
+ class IsoChild(IsoParent):
127
+ __private_attrs__ = []
128
+
129
+ def read_secret(self):
130
+ return self._secret
131
+
132
+ with self.assertRaises(AttributeError):
133
+ IsoChild().read_secret()
134
+
135
+
136
+ if __name__ == "__main__":
137
+ unittest.main()