private-attribute-cpp 2.1.24__tar.gz → 2.2.0__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 (26) hide show
  1. {private_attribute_cpp-2.1.24/private_attribute_cpp.egg-info → private_attribute_cpp-2.2.0}/PKG-INFO +4 -2
  2. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/README.md +3 -1
  3. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/private_attribute.cpp +213 -162
  4. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/private_attribute.pyi +9 -1
  5. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0/private_attribute_cpp.egg-info}/PKG-INFO +4 -2
  6. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/private_attribute_cpp.egg-info/SOURCES.txt +3 -1
  7. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/setup.py +1 -1
  8. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_attr_classify_owner.py +6 -6
  9. private_attribute_cpp-2.2.0/tests/test_init_subclass.py +85 -0
  10. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_private_inheritance.py +42 -20
  11. private_attribute_cpp-2.2.0/tests/test_use_parents.py +129 -0
  12. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/LICENSE +0 -0
  13. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/MANIFEST.in +0 -0
  14. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/picosha2.h +0 -0
  15. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/private_attribute_cpp.egg-info/dependency_links.txt +0 -0
  16. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/private_attribute_cpp.egg-info/not-zip-safe +0 -0
  17. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/private_attribute_cpp.egg-info/top_level.txt +0 -0
  18. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/setup.cfg +0 -0
  19. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_class_private_attrs.py +0 -0
  20. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_gc_collect.py +0 -0
  21. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_private_abc.py +0 -0
  22. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_private_attrs.py +0 -0
  23. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_private_func.py +0 -0
  24. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_private_method.py +0 -0
  25. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_private_wrap.py +0 -0
  26. {private_attribute_cpp-2.1.24 → private_attribute_cpp-2.2.0}/tests/test_slots_cycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: private_attribute_cpp
3
- Version: 2.1.24
3
+ Version: 2.2.0
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
@@ -253,7 +253,9 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
253
253
  - One class defined in another class cannot use another class's private attribute.
254
254
  - One parent class defined an attribute which not in `__private_attrs__` or not a `PrivateAttrType` instance, the child class shouldn't contain the attribute in its `__private_attrs__`.
255
255
  - Since 2.1.0: the code of a subclass can no longer access the private attributes of its parent classes - a parent's private attribute is only reachable from the parent's own code, or from a class that declares the same name in its own `__private_attrs__`.
256
- - Since 2.1.0: if a subclass defines an attribute with the same name as a parent's private attribute, they are stored separately (instance attributes per declaring class, class-level attributes in `all_type_subclass_attr[parent][subclass]`). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched. Such a same-name definition does NOT grant the subclass's own code access to the name.
256
+ - Since 2.1.0: if a subclass declares a name in its own `__private_attrs__` that is also private to a parent, the two are stored separately (instance attributes per declaring class, class-level attributes per class). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched.
257
+ - Since 2.2.0: a subclass must NOT define an attribute in its namespace (class body) whose name is private to a parent class, unless that name is declared in its own `__private_attrs__` or the class opts in with `use_parents=True`.
258
+ - Since 2.2.0: pass `use_parents=True` in the class definition to inherit all parent classes' private attribute names into the subclass's own private-attribute set. The subclass's code can then read/write those names, but the values are stored in the subclass's own storage (they never overwrite the parent's own values). The Python-level `__private_attrs__` tuple is left unchanged; only the internal private-attribute name set is merged. Default is `False`.
257
259
  - CPython may change "tp_getattro", "tp_setattro" and so on when you change the attribute "\_\_getattribute\_\_", "\_\_setattr\_\_" and so on. If you are fear about it, you can use `ensure_type` to reset those tp slots. For the other metaclasses, you can use `ensure_metaclass` to reset those tp slots. Also, don't set those methods on these classes in your code.
258
260
  - `private_attribute.register_metaclass` must be called with the metaclass which supports weakref.
259
261
  - Don't set `__static_attributes__` in private attribute class, or it will be removed.
@@ -232,7 +232,9 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
232
232
  - One class defined in another class cannot use another class's private attribute.
233
233
  - One parent class defined an attribute which not in `__private_attrs__` or not a `PrivateAttrType` instance, the child class shouldn't contain the attribute in its `__private_attrs__`.
234
234
  - Since 2.1.0: the code of a subclass can no longer access the private attributes of its parent classes - a parent's private attribute is only reachable from the parent's own code, or from a class that declares the same name in its own `__private_attrs__`.
235
- - Since 2.1.0: if a subclass defines an attribute with the same name as a parent's private attribute, they are stored separately (instance attributes per declaring class, class-level attributes in `all_type_subclass_attr[parent][subclass]`). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched. Such a same-name definition does NOT grant the subclass's own code access to the name.
235
+ - Since 2.1.0: if a subclass declares a name in its own `__private_attrs__` that is also private to a parent, the two are stored separately (instance attributes per declaring class, class-level attributes per class). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched.
236
+ - Since 2.2.0: a subclass must NOT define an attribute in its namespace (class body) whose name is private to a parent class, unless that name is declared in its own `__private_attrs__` or the class opts in with `use_parents=True`.
237
+ - Since 2.2.0: pass `use_parents=True` in the class definition to inherit all parent classes' private attribute names into the subclass's own private-attribute set. The subclass's code can then read/write those names, but the values are stored in the subclass's own storage (they never overwrite the parent's own values). The Python-level `__private_attrs__` tuple is left unchanged; only the internal private-attribute name set is merged. Default is `False`.
236
238
  - CPython may change "tp_getattro", "tp_setattro" and so on when you change the attribute "\_\_getattribute\_\_", "\_\_setattr\_\_" and so on. If you are fear about it, you can use `ensure_type` to reset those tp slots. For the other metaclasses, you can use `ensure_metaclass` to reset those tp slots. Also, don't set those methods on these classes in your code.
237
239
  - `private_attribute.register_metaclass` must be called with the metaclass which supports weakref.
238
240
  - Don't set `__static_attributes__` in private attribute class, or it will be removed.
@@ -328,9 +328,10 @@ struct FinalObject
328
328
 
329
329
  static TwoStringTuple get_string_hash_tuple2(const std::string& name) noexcept;
330
330
  static PyCodeObject* get_now_code() noexcept;
331
- static uintptr_t type_set_attr_long_long_guidance(uintptr_t type, const std::string& name) noexcept;
331
+ static bool need_analyse_type(PyObject* type) noexcept;
332
+ static const std::vector<uintptr_t>& type_get_parent_ids(uintptr_t type_id) noexcept;
332
333
  static bool type_private_attr(uintptr_t type, const std::string& name) noexcept;
333
- static FinalObject type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept;
334
+ static FinalObject type_get_final_attr(uintptr_t main_type, uintptr_t target_type, const std::string& name) noexcept;
334
335
 
335
336
  // Result of classifying an attribute access.
336
337
  // - type_id: the node on the MRO chain (a class) whose code is currently running
@@ -377,8 +378,8 @@ attr_classify(uintptr_t typ_id, const std::string& name, PyCodeObject* code) noe
377
378
  // Build the MRO chain: the actual class first, then its parents.
378
379
  std::vector<uintptr_t> chain;
379
380
  chain.push_back(typ_id);
380
- if (::AllData::all_type_parent_id.find(typ_id) != ::AllData::all_type_parent_id.end()) {
381
- auto& parent_ids = ::AllData::all_type_parent_id[typ_id];
381
+ {
382
+ auto& parent_ids = type_get_parent_ids(typ_id);
382
383
  chain.insert(chain.end(), parent_ids.begin(), parent_ids.end());
383
384
  }
384
385
 
@@ -603,7 +604,7 @@ id_getattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
603
604
  // Class-level fallback resolves per-subject (the instance's type): a
604
605
  // subclass's separately-stored shadow of a parent's private name is
605
606
  // visible through the per-subject walk in type_get_final_attr.
606
- FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
607
+ FinalObject final_object = type_get_final_attr(final_id, (uintptr_t)typ, attr_name);
607
608
  if (final_object.status == -2) {
608
609
  return NULL;
609
610
  }
@@ -693,10 +694,10 @@ id_getattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
693
694
  }
694
695
 
695
696
  static PyObject*
696
- type_getattr(PyObject* typ, const std::string& attr_name) noexcept
697
+ type_getattr(uintptr_t main_type, PyObject* typ, const std::string& attr_name) noexcept
697
698
  {
698
699
  uintptr_t typ_id = (uintptr_t)typ;
699
- FinalObject final_object = type_get_final_attr(typ_id, attr_name);
700
+ FinalObject final_object = type_get_final_attr(main_type, typ_id, attr_name);
700
701
  if (final_object.status == -2) {
701
702
  return NULL;
702
703
  }
@@ -730,7 +731,7 @@ id_setattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
730
731
  {
731
732
  uintptr_t obj_id = (uintptr_t) obj;
732
733
  // Class-level fallback resolves per-subject (the instance's type).
733
- FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
734
+ FinalObject final_object = type_get_final_attr(final_id, (uintptr_t)typ, attr_name);
734
735
  if (final_object.status == -2) {
735
736
  return -1;
736
737
  }
@@ -780,50 +781,45 @@ id_setattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
780
781
  return 0;
781
782
  }
782
783
 
783
- static int type_delattr(PyObject* typ, const std::string& attr_name) noexcept;
784
+ static int type_delattr(uintptr_t main_type, PyObject* typ, const std::string& attr_name) noexcept;
784
785
 
785
786
  static int
786
- type_setattr(PyObject* typ, const std::string& attr_name, PyObject* value) noexcept
787
+ type_setattr(uintptr_t main_type, PyObject* typ, const std::string& attr_name, PyObject* value) noexcept
787
788
  {
788
789
  if (!value) {
789
- return type_delattr(typ, attr_name);
790
+ return type_delattr(main_type, typ, attr_name);
790
791
  }
791
- uintptr_t typ_id = (uintptr_t) typ;
792
- uintptr_t final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
792
+ uintptr_t target_type = (uintptr_t) typ;
793
793
  std::string final_key;
794
794
  PyObject* type_need_call;
795
- if (::AllData::type_need_call.find(typ_id) != ::AllData::type_need_call.end()) {
796
- type_need_call = ::AllData::type_need_call[typ_id];
795
+ if (::AllData::type_need_call.find(target_type) != ::AllData::type_need_call.end()) {
796
+ type_need_call = ::AllData::type_need_call[target_type];
797
797
  } else {
798
798
  type_need_call = NULL;
799
799
  }
800
800
  if (type_need_call) {
801
- auto private_name_result = custom_random_string(typ_id, attr_name, type_need_call);
801
+ auto private_name_result = custom_random_string(target_type, attr_name, type_need_call);
802
802
  if (!private_name_result.ok) {
803
803
  return -1;
804
804
  }
805
805
  final_key = private_name_result.value;
806
806
  } else {
807
- final_key = default_random_string(typ_id, attr_name);
808
- }
809
- if (final_id == 0) {
810
- PyErr_SetString(PyExc_TypeError, "type not found");
811
- return -1;
807
+ final_key = default_random_string(target_type, attr_name);
812
808
  }
813
- if (final_id == typ_id) {
814
- type_create_mutex(typ_id);
809
+ if (main_type == target_type) {
810
+ type_create_mutex(target_type);
815
811
  {
816
- std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[typ_id]);
817
- ::AllData::type_attr_dict[typ_id][final_key] = value;
812
+ std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[target_type]);
813
+ ::AllData::type_attr_dict[target_type][final_key] = value;
818
814
  }
819
815
  return 0;
820
816
  } else {
821
817
  // The accessed class is a subclass that stores its own value for a
822
- // parent's private name separately: all_type_subclass_attr[final_id][typ_id].
823
- type_parenttype_create_mutex(final_id, typ_id);
818
+ // parent's private name separately: all_type_subclass_attr[main_type][target_type].
819
+ type_parenttype_create_mutex(main_type, target_type);
824
820
  {
825
- std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[final_id][typ_id]);
826
- ::AllData::all_type_subclass_attr[final_id][typ_id][final_key] = value;
821
+ std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[main_type][target_type]);
822
+ ::AllData::all_type_subclass_attr[main_type][target_type][final_key] = value;
827
823
  return 0;
828
824
  }
829
825
  }
@@ -834,7 +830,7 @@ id_delattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
834
830
  {
835
831
  uintptr_t obj_id = (uintptr_t) obj;
836
832
  // Class-level fallback resolves per-subject (the instance's type).
837
- FinalObject final_object = type_get_final_attr((uintptr_t)typ, attr_name);
833
+ FinalObject final_object = type_get_final_attr(final_id, (uintptr_t)typ, attr_name);
838
834
  if (final_object.status == -2) {
839
835
  return -1;
840
836
  }
@@ -892,34 +888,29 @@ id_delattr(uintptr_t final_id, const std::string& attr_name, PyObject* obj, PyOb
892
888
  }
893
889
 
894
890
  static int
895
- type_delattr(PyObject* typ, const std::string& attr_name) noexcept
891
+ type_delattr(uintptr_t main_type, PyObject* typ, const std::string& attr_name) noexcept
896
892
  {
897
- uintptr_t typ_id = (uintptr_t) typ;
898
- uintptr_t final_id = type_set_attr_long_long_guidance(typ_id, attr_name);
893
+ uintptr_t target_type = (uintptr_t) typ;
899
894
  std::string final_key;
900
895
  PyObject* type_need_call;
901
- if (::AllData::type_need_call.find(typ_id) != ::AllData::type_need_call.end()) {
902
- type_need_call = ::AllData::type_need_call[typ_id];
896
+ if (::AllData::type_need_call.find(target_type) != ::AllData::type_need_call.end()) {
897
+ type_need_call = ::AllData::type_need_call[target_type];
903
898
  } else {
904
899
  type_need_call = NULL;
905
900
  }
906
901
  if (type_need_call) {
907
- auto private_name_result = custom_random_string(typ_id, attr_name, type_need_call);
902
+ auto private_name_result = custom_random_string(target_type, attr_name, type_need_call);
908
903
  if (!private_name_result.ok) {
909
904
  return -1;
910
905
  }
911
906
  final_key = private_name_result.value;
912
907
  } else {
913
- final_key = default_random_string(typ_id, attr_name);
914
- }
915
- if (final_id == 0) {
916
- PyErr_SetString(PyExc_TypeError, "type not found");
917
- return -1;
908
+ final_key = default_random_string(target_type, attr_name);
918
909
  }
919
- if (typ_id == final_id) {
920
- type_create_mutex(typ_id);
921
- std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[typ_id]);
922
- if (::AllData::type_attr_dict[typ_id].find(final_key) == ::AllData::type_attr_dict[typ_id].end()) {
910
+ if (main_type == target_type) {
911
+ type_create_mutex(target_type);
912
+ std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[target_type]);
913
+ if (::AllData::type_attr_dict[target_type].find(final_key) == ::AllData::type_attr_dict[target_type].end()) {
923
914
  const char* type_name = get_name_from_tp_name((PyTypeObject*)typ);
924
915
  if (type_name == NULL) {
925
916
  return -1;
@@ -927,11 +918,11 @@ type_delattr(PyObject* typ, const std::string& attr_name) noexcept
927
918
  PyErr_Format(PyExc_AttributeError, "type object '%s' has no attribute '%s'", type_name, attr_name.c_str());
928
919
  return -1;
929
920
  }
930
- ::AllData::type_attr_dict[typ_id].erase(final_key);
921
+ ::AllData::type_attr_dict[target_type].erase(final_key);
931
922
  } else {
932
- type_parenttype_create_mutex(final_id, typ_id);
933
- std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[final_id][typ_id]);
934
- if (::AllData::all_type_subclass_attr[final_id][typ_id].find(final_key) == ::AllData::all_type_subclass_attr[final_id][typ_id].end()) {
923
+ type_parenttype_create_mutex(main_type, target_type);
924
+ std::unique_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[main_type][target_type]);
925
+ if (::AllData::all_type_subclass_attr[main_type][target_type].find(final_key) == ::AllData::all_type_subclass_attr[main_type][target_type].end()) {
935
926
  const char* type_name = get_name_from_tp_name((PyTypeObject*)typ);
936
927
  if (type_name == NULL) {
937
928
  return -1;
@@ -939,7 +930,7 @@ type_delattr(PyObject* typ, const std::string& attr_name) noexcept
939
930
  PyErr_Format(PyExc_AttributeError, "type object '%s' has no attribute '%s'", type_name, attr_name.c_str());
940
931
  return -1;
941
932
  }
942
- ::AllData::all_type_subclass_attr[final_id][typ_id].erase(final_key);
933
+ ::AllData::all_type_subclass_attr[main_type][target_type].erase(final_key);
943
934
  }
944
935
  return 0;
945
936
  }
@@ -2470,123 +2461,122 @@ get_string_hash_tuple2(const std::string& name) noexcept
2470
2461
  }
2471
2462
 
2472
2463
  static FinalObject
2473
- type_get_final_attr(uintptr_t type_id, const std::string& name) noexcept
2464
+ type_get_final_attr(uintptr_t main_type, uintptr_t target_type, const std::string& name) noexcept
2474
2465
  {
2475
- // type_id is the SUBJECT: the class whose attribute is being accessed.
2476
- // The walk resolves per-subject: a subclass's separately stored value
2477
- // (all_type_subclass_attr[parent][subclass]) shadows the parent's own
2478
- // class-level value, satisfying class-level separate storage.
2479
- TwoStringTuple hash_tuple = get_string_hash_tuple2(name);
2480
- if (::AllData::all_type_attr_set.find(type_id) != ::AllData::all_type_attr_set.end()) {
2481
- if (::AllData::all_type_attr_set[type_id].find(hash_tuple) != ::AllData::all_type_attr_set[type_id].end()) {
2482
- PyObject* type_need_call = NULL;
2483
- if (::AllData::type_need_call.find(type_id) != ::AllData::type_need_call.end()) {
2484
- type_need_call = ::AllData::type_need_call[type_id];
2485
- }
2486
- std::string key;
2487
- if (type_need_call != NULL) {
2488
- auto private_name_result = custom_random_string(type_id, name, type_need_call);
2489
- if (!private_name_result.ok) {
2490
- return -2; // -2 means exception
2491
- }
2492
- key = private_name_result.value;
2493
- } else {
2494
- key = default_random_string(type_id, name);
2466
+ // main_type is the code owner (the class declaring the attribute private);
2467
+ // target_type is the object's type whose attribute is being accessed. Only
2468
+ // type_attr_dict[main_type] and all_type_subclass_attr[main_type][*] are
2469
+ // analysed. Each visited type's own type_need_call drives its key.
2470
+ std::vector<uintptr_t> chain;
2471
+ chain.push_back(target_type);
2472
+ if (::AllData::all_type_parent_id.find(target_type) != ::AllData::all_type_parent_id.end()) {
2473
+ auto& parent_ids = ::AllData::all_type_parent_id[target_type];
2474
+ chain.insert(chain.end(), parent_ids.begin(), parent_ids.end());
2475
+ }
2476
+ for (auto& type : chain) {
2477
+ std::string key;
2478
+ PyObject* need_call = NULL;
2479
+ if (::AllData::type_need_call.find(type) != ::AllData::type_need_call.end()) {
2480
+ need_call = ::AllData::type_need_call[type];
2481
+ }
2482
+ if (need_call != NULL) {
2483
+ auto private_name_result = custom_random_string(type, name, need_call);
2484
+ if (!private_name_result.ok) {
2485
+ return -2; // -2 means exception
2495
2486
  }
2496
- type_create_mutex(type_id);
2497
- std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[type_id]);
2498
- auto& item_set = ::AllData::type_attr_dict[type_id];
2487
+ key = private_name_result.value;
2488
+ } else {
2489
+ key = default_random_string(type, name);
2490
+ }
2491
+ if (type == main_type) {
2492
+ type_create_mutex(main_type);
2493
+ std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[main_type]);
2494
+ auto& item_set = ::AllData::type_attr_dict[main_type];
2499
2495
  if (item_set.find(key) != item_set.end()) {
2500
2496
  PyObject* obj = item_set[key];
2501
2497
  return obj;
2502
2498
  }
2503
- }
2504
- }
2505
- std::vector<uintptr_t> now_visited = {type_id};
2506
- if (::AllData::all_type_parent_id.find(type_id) != ::AllData::all_type_parent_id.end()) {
2507
- auto& parent_ids = ::AllData::all_type_parent_id[type_id];
2508
- for (auto& parent_id: parent_ids) {
2509
- if (::AllData::all_type_attr_set.find(parent_id) != ::AllData::all_type_attr_set.end()) {
2510
- auto& item_set = ::AllData::all_type_attr_set[parent_id];
2511
- if (item_set.find(hash_tuple) != item_set.end()) {
2512
- if (::AllData::all_type_subclass_attr.find(parent_id) != ::AllData::all_type_subclass_attr.end()) {
2513
- auto& now_mro_dict = ::AllData::all_type_subclass_attr[parent_id];
2514
- for (auto& now_visited_id: now_visited) {
2515
- if (now_mro_dict.find(now_visited_id) != now_mro_dict.end()) {
2516
- std::string key;
2517
- if (::AllData::type_need_call.find(now_visited_id) != ::AllData::type_need_call.end()) {
2518
- PyObject* func = ::AllData::type_need_call[now_visited_id];
2519
- if (func != NULL) {
2520
- auto private_name_result = custom_random_string(now_visited_id, name, func);
2521
- if (!private_name_result.ok) {
2522
- return -2; // -2 means exception
2523
- }
2524
- key = private_name_result.value;
2525
- } else {
2526
- key = default_random_string(now_visited_id, name);
2527
- }
2528
- } else {
2529
- key = default_random_string(now_visited_id, name);
2530
- }
2531
- type_parenttype_create_mutex(parent_id, now_visited_id);
2532
- std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[parent_id][now_visited_id]);
2533
- if (now_mro_dict[now_visited_id].find(key) != now_mro_dict[now_visited_id].end()) {
2534
- PyObject* obj = now_mro_dict[now_visited_id][key];
2535
- return obj;
2536
- }
2537
- }
2538
- }
2539
- }
2540
- std::string key;
2541
- if (::AllData::type_need_call.find(parent_id) != ::AllData::type_need_call.end()) {
2542
- PyObject* func = ::AllData::type_need_call[parent_id];
2543
- if (func != NULL) {
2544
- auto private_name_result = custom_random_string(parent_id, name, func);
2545
- if (!private_name_result.ok) {
2546
- return -2; // -2 means exception
2547
- }
2548
- key = private_name_result.value;
2549
- } else {
2550
- key = default_random_string(parent_id, name);
2551
- }
2552
- } else {
2553
- key = default_random_string(parent_id, name);
2554
- }
2555
- type_create_mutex(parent_id);
2556
- auto& item_set = ::AllData::type_attr_dict[parent_id];
2557
- std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_mutex[parent_id]);
2558
- if (item_set.find(key) != item_set.end()) {
2559
- PyObject* obj = item_set[key];
2560
- return obj;
2561
- }
2499
+ return -1; // main_type has no such attribute -> not found
2500
+ }
2501
+ if (::AllData::all_type_subclass_attr.find(main_type) != ::AllData::all_type_subclass_attr.end()) {
2502
+ auto& now_mro_dict = ::AllData::all_type_subclass_attr[main_type];
2503
+ if (now_mro_dict.find(type) != now_mro_dict.end()) {
2504
+ type_parenttype_create_mutex(main_type, type);
2505
+ std::shared_lock<std::shared_mutex> lock(*::AllData::all_type_subclass_mutex[main_type][type]);
2506
+ if (now_mro_dict[type].find(key) != now_mro_dict[type].end()) {
2507
+ PyObject* obj = now_mro_dict[type][key];
2508
+ return obj;
2562
2509
  }
2563
2510
  }
2564
- now_visited.push_back(parent_id);
2565
2511
  }
2566
2512
  }
2567
2513
  return -1; // -1 means not found
2568
2514
  }
2569
2515
 
2570
- static uintptr_t
2571
- type_set_attr_long_long_guidance(uintptr_t type_id, const std::string& name) noexcept
2516
+ static bool
2517
+ need_analyse_type(PyObject* type) noexcept;
2518
+
2519
+ static bool
2520
+ type_private_attr_declared(uintptr_t type_id, const std::string& name) noexcept
2572
2521
  {
2573
- TwoStringTuple hash_tuple = get_string_hash_tuple2(name);
2574
- if (::AllData::all_type_attr_set.find(type_id) != ::AllData::all_type_attr_set.end()) {
2575
- auto& item_set = ::AllData::all_type_attr_set[type_id];
2576
- if (item_set.find(hash_tuple) != item_set.end()) {
2577
- return type_id;
2522
+ // Fallback used while a type is still being created (e.g. inside
2523
+ // __init_subclass__), when all_type_attr_set[type_id] has not been set yet.
2524
+ // Read '__private_attrs__' from the type's tp_dict and check membership
2525
+ // via Python tuple equality.
2526
+ KeepPythonException _;
2527
+ PyTypeObject* tp = (PyTypeObject*)type_id;
2528
+ if (!PyType_Check(tp)) {
2529
+ return false;
2530
+ }
2531
+ PyObject* private_attrs = PyDict_GetItemString(tp->tp_dict, "__private_attrs__");
2532
+ if (!private_attrs || !PySequence_Check(private_attrs)) {
2533
+ return false;
2534
+ }
2535
+ PyObjectStorage hash_tuple = PyObjectStorage::from_new_ref(get_string_hash_tuple(name));
2536
+ if (!hash_tuple) {
2537
+ return false;
2538
+ }
2539
+ Py_ssize_t len = PySequence_Length(private_attrs);
2540
+ for (Py_ssize_t i = 0; i < len; i++) {
2541
+ PyObjectStorage item = PyObjectStorage::from_new_ref(PySequence_GetItem(private_attrs, i));
2542
+ if (!item) {
2543
+ continue;
2544
+ }
2545
+ int eq = PyObject_RichCompareBool(item, hash_tuple, Py_EQ);
2546
+ if (eq < 0) {
2547
+ PyErr_Clear();
2548
+ return false;
2549
+ }
2550
+ if (eq) {
2551
+ return true;
2578
2552
  }
2579
2553
  }
2580
- if (::AllData::all_type_parent_id.find(type_id) != ::AllData::all_type_parent_id.end()) {
2581
- auto& parent_id_list = ::AllData::all_type_parent_id[type_id];
2582
- for (auto& parent_id: parent_id_list) {
2583
- auto& item_set = ::AllData::all_type_attr_set[parent_id];
2584
- if (item_set.find(hash_tuple) != item_set.end()) {
2585
- return parent_id;
2554
+ return false;
2555
+ }
2556
+
2557
+ static const std::vector<uintptr_t>&
2558
+ type_get_parent_ids(uintptr_t type_id) noexcept
2559
+ {
2560
+ auto it = ::AllData::all_type_parent_id.find(type_id);
2561
+ if (it != ::AllData::all_type_parent_id.end()) {
2562
+ return it->second;
2563
+ }
2564
+ // The type has not been fully registered yet (e.g. inside __init_subclass__):
2565
+ // build the MRO chain on the fly from tp_mro.
2566
+ std::vector<uintptr_t> mro_vector;
2567
+ PyTypeObject* tp = (PyTypeObject*)type_id;
2568
+ if (PyType_Check(tp) && tp->tp_mro) {
2569
+ Py_ssize_t mro_size = PyTuple_GET_SIZE(tp->tp_mro);
2570
+ for (Py_ssize_t i = 1; i < mro_size; i++) {
2571
+ PyObject* item = PyTuple_GET_ITEM(tp->tp_mro, i);
2572
+ if (!item || !PyType_Check(item) || !need_analyse_type(item)) {
2573
+ continue;
2586
2574
  }
2575
+ mro_vector.push_back((uintptr_t)item);
2587
2576
  }
2588
2577
  }
2589
- return 0; // 0 means not found
2578
+ ::AllData::all_type_parent_id[type_id] = mro_vector;
2579
+ return ::AllData::all_type_parent_id[type_id];
2590
2580
  }
2591
2581
 
2592
2582
  static bool
@@ -2598,14 +2588,18 @@ type_private_attr(uintptr_t type_id, const std::string& name) noexcept
2598
2588
  if (item_set.find(hash_tuple) != item_set.end()) {
2599
2589
  return true;
2600
2590
  }
2591
+ } else if (type_private_attr_declared(type_id, name)) {
2592
+ return true;
2601
2593
  }
2602
- if (::AllData::all_type_parent_id.find(type_id) != ::AllData::all_type_parent_id.end()) {
2603
- auto& parent_id_list = ::AllData::all_type_parent_id[type_id];
2604
- for (auto& parent_id: parent_id_list) {
2594
+ auto& parent_id_list = type_get_parent_ids(type_id);
2595
+ for (auto& parent_id: parent_id_list) {
2596
+ if (::AllData::all_type_attr_set.find(parent_id) != ::AllData::all_type_attr_set.end()) {
2605
2597
  auto& item_set = ::AllData::all_type_attr_set[parent_id];
2606
2598
  if (item_set.find(hash_tuple) != item_set.end()) {
2607
2599
  return true;
2608
2600
  }
2601
+ } else if (type_private_attr_declared(parent_id, name)) {
2602
+ return true;
2609
2603
  }
2610
2604
  }
2611
2605
  return false;
@@ -2758,6 +2752,7 @@ struct PrivateAttrCreationData
2758
2752
  PyObject* name = nullptr;
2759
2753
  PyObject* bases = nullptr;
2760
2754
  PyObject* attrs = nullptr;
2755
+ bool use_parents = false;
2761
2756
  bool cleared = false;
2762
2757
 
2763
2758
  void clear() noexcept {
@@ -2857,6 +2852,21 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
2857
2852
  return false;
2858
2853
  }
2859
2854
 
2855
+ if (kwds && PyDict_Check(kwds)) {
2856
+ int has_use_parents = PyDict_ContainsString(kwds, "use_parents");
2857
+ if (has_use_parents < 0) {
2858
+ return false;
2859
+ }
2860
+ if (has_use_parents) {
2861
+ PyObject* use_parents_obj = PyDict_GetItemString(kwds, "use_parents");
2862
+ int truth = PyObject_IsTrue(use_parents_obj);
2863
+ if (truth < 0) {
2864
+ return false;
2865
+ }
2866
+ data.use_parents = (truth == 1);
2867
+ }
2868
+ }
2869
+
2860
2870
  PyObjectStorage __private_attrs__;
2861
2871
  if (PyDict_ContainsString(data.attrs, "__private_attrs__")) {
2862
2872
  __private_attrs__ = PyDict_GetItemString(data.attrs, "__private_attrs__");
@@ -2865,6 +2875,10 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
2865
2875
  __private_attrs__ = PyObjectStorage::from_new_ref(PyTuple_New(0));
2866
2876
  }
2867
2877
 
2878
+ if (!__private_attrs__) {
2879
+ return false;
2880
+ }
2881
+
2868
2882
  if (PyUnicode_Check(__private_attrs__)) {
2869
2883
  PyObject* new___private_attrs__ = PyTuple_New(1);
2870
2884
  if (!new___private_attrs__) {
@@ -2985,10 +2999,31 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
2985
2999
  data.all_need_analyse_base.push_back((uintptr_t)base);
2986
3000
  }
2987
3001
 
3002
+ if (data.use_parents) {
3003
+ // 2.2.0: when use_parents=True, merge every ancestor's private attribute
3004
+ // names into this class's own private-attr set (the TwoStringTuple set).
3005
+ // The Python-level '__private_attrs__' tuple is left untouched; only
3006
+ // AllData::all_type_attr_set[this] gains the inherited names.
3007
+ std::vector<uintptr_t> all_ancestors;
3008
+ for (auto& base : data.all_need_analyse_base) {
3009
+ all_ancestors.push_back(base);
3010
+ if (::AllData::all_type_parent_id.find(base) != ::AllData::all_type_parent_id.end()) {
3011
+ auto& ancestor_ids = ::AllData::all_type_parent_id[base];
3012
+ all_ancestors.insert(all_ancestors.end(), ancestor_ids.begin(), ancestor_ids.end());
3013
+ }
3014
+ }
3015
+ for (auto& ancestor : all_ancestors) {
3016
+ if (::AllData::all_type_attr_set.find(ancestor) != ::AllData::all_type_attr_set.end()) {
3017
+ auto& ancestor_set = ::AllData::all_type_attr_set[ancestor];
3018
+ data.private_attrs_set.insert(ancestor_set.begin(), ancestor_set.end());
3019
+ }
3020
+ }
3021
+ }
3022
+
2988
3023
  std::function<uintptr_t(std::string)> need_remove_to_subclass = [&data](std::string attr_name){
2989
3024
  for (auto& base: data.all_need_analyse_base) {
2990
3025
  if (type_private_attr(base, attr_name)) {
2991
- return type_set_attr_long_long_guidance(base, attr_name);
3026
+ return base;
2992
3027
  }
2993
3028
  }
2994
3029
  return (uintptr_t)0;
@@ -3027,13 +3062,19 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
3027
3062
  uintptr_t need_remove_subclass_id = need_remove_to_subclass(attr_name);
3028
3063
  if (need_remove_subclass_id) {
3029
3064
  // The class body defines an attribute whose name is private to a
3030
- // parent class. It is stored separately (class-level separate
3031
- // storage) in all_type_subclass_attr[need_remove_subclass_id][this].
3032
- if (data.need_remove_subclass.find(need_remove_subclass_id) == data.need_remove_subclass.end()) {
3033
- data.need_remove_subclass[need_remove_subclass_id] = {};
3065
+ // parent class and is not declared in this class's own
3066
+ // '__private_attrs__'.
3067
+ if (!data.use_parents) {
3068
+ // 2.2.0: forbidden unless the parent private attribute is
3069
+ // explicitly inherited via use_parents=True.
3070
+ PyErr_Format(PyExc_TypeError,
3071
+ "cannot define '%s' in class '%s': it is private to a parent class and not declared in this class's '__private_attrs__' (set use_parents=True to inherit parent private attributes)",
3072
+ attr_name.c_str(), data.class_name.c_str());
3073
+ return false;
3034
3074
  }
3035
- // PyObjectStorage assignment INCREFs (RAII); no manual INCREF.
3036
- data.need_remove_subclass[need_remove_subclass_id][attr_name] = need_value;
3075
+ // use_parents=True: store the value into this class's own private
3076
+ // attribute storage (type_attr_dict[this]) instead of the parent's.
3077
+ data.need_remove_itself[attr_name] = need_value;
3037
3078
  if (PyDict_DelItem(data.attrs_copy, key)) return false;
3038
3079
  continue;
3039
3080
  }
@@ -3057,6 +3098,9 @@ PrivateAttrType_preprocess(PyObject* args, PyObject* kwds, PrivateAttrCreationDa
3057
3098
  if (has_private_func) {
3058
3099
  if (PyDict_DelItemString(data.base_kwds, "private_func") < 0) return false;
3059
3100
  }
3101
+ if (PyDict_ContainsString(kwds, "use_parents")) {
3102
+ if (PyDict_DelItemString(data.base_kwds, "use_parents") < 0) return false;
3103
+ }
3060
3104
  }
3061
3105
 
3062
3106
  return true;
@@ -3269,8 +3313,15 @@ PrivateAttrType_postprocess(PyObject* new_type, PrivateAttrCreationData& data) n
3269
3313
  if (::AllData::all_type_subclass_mutex.find(i) == ::AllData::all_type_subclass_mutex.end()) {
3270
3314
  ::AllData::all_type_subclass_mutex[i].clear();
3271
3315
  }
3272
- ::AllData::all_type_subclass_attr[i][type_id] = {};
3273
- ::AllData::all_type_subclass_mutex[i][type_id] = std::make_unique<std::shared_mutex>();
3316
+ // Do not wipe a parent's shadow already written for this class during
3317
+ // __init_subclass__ (e.g. `cls._value = ...` inside the parent's
3318
+ // __init_subclass__ runs inside tp_new, before postprocess).
3319
+ if (::AllData::all_type_subclass_attr[i].find(type_id) == ::AllData::all_type_subclass_attr[i].end()) {
3320
+ ::AllData::all_type_subclass_attr[i][type_id] = {};
3321
+ }
3322
+ if (::AllData::all_type_subclass_mutex[i].find(type_id) == ::AllData::all_type_subclass_mutex[i].end()) {
3323
+ ::AllData::all_type_subclass_mutex[i][type_id] = std::make_unique<std::shared_mutex>();
3324
+ }
3274
3325
  }
3275
3326
 
3276
3327
  for (auto& [key, value]: data.need_remove_itself) {
@@ -3418,7 +3469,7 @@ PrivateAttrType_getattr(PyObject* cls, PyObject* name) noexcept
3418
3469
  return NULL;
3419
3470
  }
3420
3471
  if (classify.type_id != 0) {
3421
- return type_getattr(cls, name_str);
3472
+ return type_getattr(classify.type_id, cls, name_str);
3422
3473
  }
3423
3474
  }
3424
3475
  }
@@ -3766,7 +3817,7 @@ PrivateAttrType_setattr(PyObject* cls, PyObject* name, PyObject* value) noexcept
3766
3817
  return -1;
3767
3818
  }
3768
3819
  if (classify.type_id != 0) {
3769
- return type_setattr(cls, name_str, value);
3820
+ return type_setattr(classify.type_id, cls, name_str, value);
3770
3821
  }
3771
3822
  // owner is 0: not private for this access context -> normal attribute
3772
3823
  }
@@ -99,7 +99,8 @@ class PrivateAttrType(type):
99
99
  "metaclass for private attributes"
100
100
  def __new__(cls, name: str, bases: tuple,
101
101
  attrs: _PrivateAttrDict, /, *,
102
- private_func: Callable[[int, str], str]|None = None) -> PrivateAttrType: ...
102
+ private_func: Callable[[int, str], str]|None = None,
103
+ use_parents: bool = False) -> PrivateAttrType: ...
103
104
 
104
105
  class PrivateAttrBase(metaclass=PrivateAttrType):
105
106
  "The class to help creating private attribute. It does not have any special behavior."
@@ -146,6 +147,13 @@ def prepare(name: str, bases: tuple, attrs: _PrivateAttrDict, /, **kwds) -> _Pri
146
147
 
147
148
  private_attribute.register_metaclass(PrivateAbcMeta)
148
149
  ```
150
+
151
+ Optional keyword arguments:
152
+ - private_func: custom hidden-name generator `(obj_id, attr_name) -> str`.
153
+ - use_parents: when True, merge every parent class's private attribute names
154
+ into this class's own private-attribute set, so the subclass's code may
155
+ read/write those names (stored in the subclass's own storage). Default
156
+ False.
149
157
  """
150
158
  ...
151
159
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: private_attribute_cpp
3
- Version: 2.1.24
3
+ Version: 2.2.0
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
@@ -253,7 +253,9 @@ TypeError: Can't instantiate abstract class MyClass without an implementation fo
253
253
  - One class defined in another class cannot use another class's private attribute.
254
254
  - One parent class defined an attribute which not in `__private_attrs__` or not a `PrivateAttrType` instance, the child class shouldn't contain the attribute in its `__private_attrs__`.
255
255
  - Since 2.1.0: the code of a subclass can no longer access the private attributes of its parent classes - a parent's private attribute is only reachable from the parent's own code, or from a class that declares the same name in its own `__private_attrs__`.
256
- - Since 2.1.0: if a subclass defines an attribute with the same name as a parent's private attribute, they are stored separately (instance attributes per declaring class, class-level attributes in `all_type_subclass_attr[parent][subclass]`). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched. Such a same-name definition does NOT grant the subclass's own code access to the name.
256
+ - Since 2.1.0: if a subclass declares a name in its own `__private_attrs__` that is also private to a parent, the two are stored separately (instance attributes per declaring class, class-level attributes per class). Class-level resolution is per-subject through the parent's code: reading the name on a subclass subject returns the subclass's own value, and the parent's own value is untouched.
257
+ - Since 2.2.0: a subclass must NOT define an attribute in its namespace (class body) whose name is private to a parent class, unless that name is declared in its own `__private_attrs__` or the class opts in with `use_parents=True`.
258
+ - Since 2.2.0: pass `use_parents=True` in the class definition to inherit all parent classes' private attribute names into the subclass's own private-attribute set. The subclass's code can then read/write those names, but the values are stored in the subclass's own storage (they never overwrite the parent's own values). The Python-level `__private_attrs__` tuple is left unchanged; only the internal private-attribute name set is merged. Default is `False`.
257
259
  - CPython may change "tp_getattro", "tp_setattro" and so on when you change the attribute "\_\_getattribute\_\_", "\_\_setattr\_\_" and so on. If you are fear about it, you can use `ensure_type` to reset those tp slots. For the other metaclasses, you can use `ensure_metaclass` to reset those tp slots. Also, don't set those methods on these classes in your code.
258
260
  - `private_attribute.register_metaclass` must be called with the metaclass which supports weakref.
259
261
  - Don't set `__static_attributes__` in private attribute class, or it will be removed.
@@ -13,10 +13,12 @@ private_attribute_cpp.egg-info/top_level.txt
13
13
  tests/test_attr_classify_owner.py
14
14
  tests/test_class_private_attrs.py
15
15
  tests/test_gc_collect.py
16
+ tests/test_init_subclass.py
16
17
  tests/test_private_abc.py
17
18
  tests/test_private_attrs.py
18
19
  tests/test_private_func.py
19
20
  tests/test_private_inheritance.py
20
21
  tests/test_private_method.py
21
22
  tests/test_private_wrap.py
22
- tests/test_slots_cycle.py
23
+ tests/test_slots_cycle.py
24
+ tests/test_use_parents.py
@@ -24,7 +24,7 @@ readme = open('README.md').read()
24
24
 
25
25
  setup(
26
26
  name='private_attribute_cpp',
27
- version='2.1.24',
27
+ version='2.2.0',
28
28
  author="HuangHaoHua",
29
29
  author_email="13140752715@example.com",
30
30
  description='A Python package that provides a way to define private attributes in C++ implementation.',
@@ -80,24 +80,24 @@ class TestCodeSwapOwnership(unittest.TestCase):
80
80
 
81
81
 
82
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."""
83
+ """Parent code always reads the parent's own storage, even when a subclass
84
+ shadows the name in its own class-level storage."""
85
85
 
86
- def test_unwritten_read_falls_back_per_subject(self):
86
+ def test_unwritten_read_uses_parent_storage(self):
87
87
  class FreshChild(Parent):
88
88
  __private_attrs__ = ["_n"]
89
89
  _n = 100
90
90
  f = Parent.inc
91
91
 
92
92
  c = FreshChild()
93
- self.assertEqual(c.peek(), 100)
93
+ self.assertEqual(c.peek(), 0)
94
94
  v1 = c.f()
95
95
  self.assertEqual(v1, c.peek())
96
96
 
97
97
 
98
98
  class TestNormalUsageRegression(unittest.TestCase):
99
99
  """Without tampering, nothing changes: child's own code uses child
100
- storage, parent's code keeps its per-subject view."""
100
+ storage, parent's code reads the parent's own storage (0)."""
101
101
 
102
102
  def test_child_own_code_uses_child_storage(self):
103
103
  class NormChild(Parent):
@@ -110,7 +110,7 @@ class TestNormalUsageRegression(unittest.TestCase):
110
110
 
111
111
  c = NormChild()
112
112
  self.assertEqual(c.own_inc(), 101)
113
- self.assertEqual(c.peek(), 100)
113
+ self.assertEqual(c.peek(), 0)
114
114
  self.assertEqual(Parent().peek(), 0)
115
115
 
116
116
 
@@ -0,0 +1,85 @@
1
+ import private_attribute
2
+ import unittest
3
+
4
+
5
+ class TestInitSubclassWrite(unittest.TestCase):
6
+ """A parent may write a private attribute into a subclass (e.g. via
7
+ __init_subclass__); later, the parent's own code reading the subclass (or
8
+ an instance of it) must see the parent-stored value, while the subclass's
9
+ own same-name private storage stays independent."""
10
+
11
+ def test_init_subclass_write_visible_to_parent_code(self):
12
+ class Parent(private_attribute.PrivateAttrBase):
13
+ __private_attrs__ = ["_value"]
14
+ _value = "parent_own"
15
+
16
+ def __init_subclass__(cls, **kw):
17
+ super().__init_subclass__(**kw)
18
+ cls._value = "from_parent"
19
+
20
+ @classmethod
21
+ def get_cls_value(cls):
22
+ return cls._value
23
+
24
+ def get_value(self):
25
+ return self._value
26
+
27
+ class Child(Parent):
28
+ __private_attrs__ = ["_value"]
29
+
30
+ def __init__(self):
31
+ super().__init__()
32
+ self._value = "child_own"
33
+
34
+ def get_own(self):
35
+ return self._value
36
+
37
+ # parent code reading the subclass class / instance sees the
38
+ # parent-stored value
39
+ self.assertEqual(Child.get_cls_value(), "from_parent")
40
+ self.assertEqual(Child().get_value(), "from_parent")
41
+ # the subclass's own code reads/writes its own independent storage
42
+ self.assertEqual(Child().get_own(), "child_own")
43
+ # the parent's own storage is untouched
44
+ self.assertEqual(Parent().get_value(), "parent_own")
45
+ # private attribute stays hidden from outside
46
+ with self.assertRaises(AttributeError):
47
+ Child()._value
48
+ with self.assertRaises(AttributeError):
49
+ Child._value
50
+
51
+ def test_parent_writes_child_after_creation(self):
52
+ class Parent(private_attribute.PrivateAttrBase):
53
+ __private_attrs__ = ["_value"]
54
+ _value = "parent_own"
55
+
56
+ @classmethod
57
+ def write_child(cls, child_cls, v):
58
+ child_cls._value = v
59
+
60
+ def get_value(self):
61
+ return self._value
62
+
63
+ @classmethod
64
+ def get_cls_value(cls):
65
+ return cls._value
66
+
67
+ class Child(Parent):
68
+ __private_attrs__ = ["_value"]
69
+
70
+ def __init__(self):
71
+ super().__init__()
72
+ self._value = "child_own"
73
+
74
+ def get_own(self):
75
+ return self._value
76
+
77
+ Parent.write_child(Child, "from_parent")
78
+ self.assertEqual(Child.get_cls_value(), "from_parent")
79
+ self.assertEqual(Child().get_value(), "from_parent")
80
+ self.assertEqual(Child().get_own(), "child_own")
81
+ self.assertEqual(Parent().get_value(), "parent_own")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ unittest.main()
@@ -88,11 +88,9 @@ class TestPrivateInheritance(unittest.TestCase):
88
88
  with self.assertRaises(AttributeError):
89
89
  obj._value
90
90
 
91
- def test_same_name_class_attrs_stored_separately(self):
92
- # A class-body attribute reusing a parent's private name is stored
93
- # separately (all_type_subclass_attr[parent][child]) and resolves
94
- # per-subject through the PARENT's code. It does NOT grant the
95
- # subclass's own code any access to the name.
91
+ def test_same_name_class_attrs_require_use_parents(self):
92
+ # 2.2.0: a class body may not define a name that is private to a parent
93
+ # unless it declares the name itself or opts in via use_parents=True.
96
94
  class Parent(private_attribute.PrivateAttrBase):
97
95
  __private_attrs__ = ["_value"]
98
96
  _value = 10
@@ -100,7 +98,24 @@ class TestPrivateInheritance(unittest.TestCase):
100
98
  def get_value(self):
101
99
  return self._value
102
100
 
103
- class Child(Parent):
101
+ with self.assertRaises(TypeError):
102
+ class Child(Parent):
103
+ __private_attrs__ = []
104
+ _value = 20
105
+
106
+ def test_use_parents_class_attrs(self):
107
+ # 2.2.0: use_parents=True lets a subclass define a parent's private
108
+ # name in its own body; the value is stored in the subclass's own
109
+ # storage and the parent's value is untouched. Parent code always
110
+ # reads the parent's own storage.
111
+ class Parent(private_attribute.PrivateAttrBase):
112
+ __private_attrs__ = ["_value"]
113
+ _value = 10
114
+
115
+ def get_value(self):
116
+ return self._value
117
+
118
+ class Child(Parent, use_parents=True):
104
119
  __private_attrs__ = []
105
120
  _value = 20
106
121
 
@@ -109,19 +124,20 @@ class TestPrivateInheritance(unittest.TestCase):
109
124
 
110
125
  self.assertEqual(Parent().get_value(), 10)
111
126
  obj = Child()
112
- # parent's code resolves per-subject: the child's own value
113
- self.assertEqual(obj.get_value(), 20)
114
- # the subclass's own code is NOT allowed to touch the name
115
- with self.assertRaises(AttributeError):
116
- obj.get_child_value()
127
+ # parent's code reads the parent's own storage, not the child's shadow
128
+ self.assertEqual(obj.get_value(), 10)
129
+ # the subclass's own code reads its own value
130
+ self.assertEqual(obj.get_child_value(), 20)
117
131
  with self.assertRaises(AttributeError):
118
132
  obj._value
119
133
  with self.assertRaises(AttributeError):
120
134
  Child._value
121
135
 
122
- def test_same_name_class_attrs_write_separately(self):
123
- # Writes through the parent's code go to the subject's own storage;
124
- # the subclass's own code cannot write the name at all.
136
+ def test_use_parents_class_attrs_write_separately(self):
137
+ # 2.2.0: with use_parents=True, parent code (set_value/get_value) writes
138
+ # and reads the parent's own shadow of the child's storage
139
+ # (all_type_subclass_attr[Parent][Child]); the subclass's own code
140
+ # operates on the child's own storage (type_attr_dict[Child]).
125
141
  class Parent(private_attribute.PrivateAttrBase):
126
142
  __private_attrs__ = ["_value"]
127
143
  _value = 1
@@ -134,7 +150,7 @@ class TestPrivateInheritance(unittest.TestCase):
134
150
  def get_value(cls):
135
151
  return cls._value
136
152
 
137
- class Child(Parent):
153
+ class Child(Parent, use_parents=True):
138
154
  __private_attrs__ = []
139
155
  _value = 2
140
156
 
@@ -142,16 +158,22 @@ class TestPrivateInheritance(unittest.TestCase):
142
158
  def set_own(cls, v):
143
159
  cls._value = v
144
160
 
161
+ @classmethod
162
+ def get_own(cls):
163
+ return cls._value
164
+
145
165
  self.assertEqual(Parent.get_value(), 1)
146
- self.assertEqual(Child.get_value(), 2)
147
- # parent's classmethod called on the child writes the child's own storage
166
+ # parent code reads the parent's shadow (empty -> falls back to parent storage)
167
+ self.assertEqual(Child.get_value(), 1)
168
+ # parent's classmethod called on the child writes the parent's shadow
148
169
  Child.set_value(99)
149
170
  self.assertEqual(Child.get_value(), 99)
150
171
  self.assertEqual(Parent.get_value(), 1)
151
- # the subclass's own code cannot write the parent's private name
152
- with self.assertRaises(AttributeError):
153
- Child.set_own(7)
172
+ # the subclass's own code writes its own storage
173
+ Child.set_own(7)
174
+ self.assertEqual(Child.get_own(), 7)
154
175
  self.assertEqual(Child.get_value(), 99)
176
+ self.assertEqual(Parent.get_value(), 1)
155
177
  # parent's own storage still writable through the parent
156
178
  Parent.set_value(5)
157
179
  self.assertEqual(Parent.get_value(), 5)
@@ -0,0 +1,129 @@
1
+ import private_attribute
2
+ import unittest
3
+
4
+
5
+ class TestUseParents(unittest.TestCase):
6
+ def test_forbidden_parent_private_in_namespace(self):
7
+ # 2.2.0: a subclass must not write a parent-private attribute into its
8
+ # own namespace unless it is declared in __private_attrs__ or
9
+ # use_parents=True is set.
10
+ class Parent(private_attribute.PrivateAttrBase):
11
+ __private_attrs__ = ["_secret"]
12
+
13
+ def __init__(self):
14
+ self._secret = 1
15
+
16
+ with self.assertRaises(TypeError):
17
+ class Child(Parent):
18
+ __private_attrs__ = []
19
+ _secret = 999
20
+
21
+ def test_use_parents_instance_storage(self):
22
+ # 2.2.0: use_parents=True lets the subclass code read/write the
23
+ # parent's private name, but the value lives in the subclass's own
24
+ # storage; the parent's own storage is untouched.
25
+ class Parent(private_attribute.PrivateAttrBase):
26
+ __private_attrs__ = ["_secret"]
27
+
28
+ def __init__(self):
29
+ self._secret = 1
30
+
31
+ def get_secret(self):
32
+ return self._secret
33
+
34
+ class Child(Parent, use_parents=True):
35
+ __private_attrs__ = []
36
+
37
+ def __init__(self):
38
+ super().__init__()
39
+ self._secret = 99
40
+
41
+ def read_own(self):
42
+ return self._secret
43
+
44
+ def write_own(self, v):
45
+ self._secret = v
46
+
47
+ obj = Child()
48
+ # parent's code sees the parent's own value
49
+ self.assertEqual(obj.get_secret(), 1)
50
+ # child's code sees its own value
51
+ self.assertEqual(obj.read_own(), 99)
52
+ # child writes into its own storage only
53
+ obj.write_own(7)
54
+ self.assertEqual(obj.read_own(), 7)
55
+ self.assertEqual(obj.get_secret(), 1)
56
+ with self.assertRaises(AttributeError):
57
+ obj._secret
58
+
59
+ def test_use_parents_class_body_value(self):
60
+ # 2.2.0: with use_parents=True a subclass may define a parent-private
61
+ # name in its own body; it is stored in the subclass's own storage.
62
+ # Parent code always reads the parent's own storage.
63
+ class Parent(private_attribute.PrivateAttrBase):
64
+ __private_attrs__ = ["_value"]
65
+ _value = 10
66
+
67
+ def get_value(self):
68
+ return self._value
69
+
70
+ class Child(Parent, use_parents=True):
71
+ __private_attrs__ = []
72
+ _value = 20
73
+
74
+ def get_own(self):
75
+ return self._value
76
+
77
+ self.assertEqual(Parent().get_value(), 10)
78
+ obj = Child()
79
+ self.assertEqual(obj.get_value(), 10)
80
+ self.assertEqual(obj.get_own(), 20)
81
+ with self.assertRaises(AttributeError):
82
+ obj._value
83
+
84
+ def test_use_parents_not_inherited_by_grandchild(self):
85
+ # 2.2.0: use_parents merges the ancestor sets only into the class that
86
+ # opts in; a grandchild without use_parents neither sees nor may
87
+ # define the grandparent's private name.
88
+ class GrandParent(private_attribute.PrivateAttrBase):
89
+ __private_attrs__ = ["_g"]
90
+
91
+ class Parent(GrandParent, use_parents=True):
92
+ __private_attrs__ = []
93
+ _g = 1
94
+
95
+ # Child (no use_parents) cannot define the grandparent-private name.
96
+ with self.assertRaises(TypeError):
97
+ class Child(Parent):
98
+ __private_attrs__ = []
99
+ _g = 2
100
+
101
+ class Child2(Parent):
102
+ __private_attrs__ = []
103
+
104
+ def read_g(self):
105
+ return self._g
106
+
107
+ c = Child2()
108
+ with self.assertRaises(AttributeError):
109
+ c.read_g()
110
+
111
+ def test_use_parents_false_is_default(self):
112
+ class Parent(private_attribute.PrivateAttrBase):
113
+ __private_attrs__ = ["_secret"]
114
+
115
+ # default (no kwarg) behaves like use_parents=False
116
+ with self.assertRaises(TypeError):
117
+ class Child(Parent):
118
+ __private_attrs__ = []
119
+ _secret = 1
120
+
121
+ # explicit False behaves the same
122
+ with self.assertRaises(TypeError):
123
+ class Child2(Parent, use_parents=False):
124
+ __private_attrs__ = []
125
+ _secret = 1
126
+
127
+
128
+ if __name__ == "__main__":
129
+ unittest.main()