openepd 7.1.0__tar.gz → 7.3.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 (152) hide show
  1. {openepd-7.1.0 → openepd-7.3.0}/PKG-INFO +23 -13
  2. {openepd-7.1.0 → openepd-7.3.0}/README.md +19 -8
  3. {openepd-7.1.0 → openepd-7.3.0}/pyproject.toml +36 -15
  4. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/__version__.py +1 -1
  5. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/average_dataset/generic_estimate_sync_api.py +2 -1
  6. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/average_dataset/industry_epd_sync_api.py +2 -1
  7. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/base_sync_client.py +10 -8
  8. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/common.py +17 -11
  9. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/dto/common.py +1 -1
  10. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/epd/sync_api.py +2 -1
  11. openepd-7.3.0/src/openepd/api/org/sync_api.py +79 -0
  12. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/pcr/sync_api.py +35 -0
  13. openepd-7.3.0/src/openepd/api/plant/sync_api.py +79 -0
  14. openepd-7.3.0/src/openepd/api/standard/sync_api.py +79 -0
  15. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/sync_client.py +27 -0
  16. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/bundle/base.py +47 -6
  17. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/bundle/model.py +1 -0
  18. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/bundle/reader.py +35 -10
  19. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/bundle/writer.py +21 -12
  20. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/m49/__init__.py +2 -0
  21. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/m49/const.py +1 -1
  22. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/m49/utils.py +16 -10
  23. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/base.py +20 -15
  24. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/common.py +10 -5
  25. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/declaration.py +2 -2
  26. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/epd.py +2 -1
  27. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/factory.py +5 -3
  28. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/generic_estimate.py +4 -0
  29. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/lcia.py +10 -10
  30. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/org.py +14 -7
  31. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/pcr.py +2 -2
  32. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/__init__.py +37 -0
  33. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/asphalt.py +3 -3
  34. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/base.py +2 -1
  35. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/enums.py +9 -1
  36. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/__init__.py +5 -3
  37. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/cmu.py +0 -2
  38. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/concrete.py +25 -2
  39. openepd-7.3.0/src/openepd/model/specs/range/exterior_improvements.py +47 -0
  40. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/finishes.py +19 -40
  41. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/manufacturing_inputs.py +17 -5
  42. openepd-7.3.0/src/openepd/model/specs/range/mixins/__init__.py +15 -0
  43. openepd-7.3.0/src/openepd/model/specs/range/mixins/access_flooring_mixin.py +43 -0
  44. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/steel.py +18 -9
  45. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/wood.py +4 -6
  46. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/__init__.py +119 -2
  47. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/aluminium.py +2 -1
  48. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/concrete.py +25 -1
  49. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/deprecated/__init__.py +1 -1
  50. openepd-7.3.0/src/openepd/model/specs/singular/exterior_improvements.py +47 -0
  51. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/finishes.py +3 -59
  52. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/manufacturing_inputs.py +13 -1
  53. openepd-7.3.0/src/openepd/model/specs/singular/mixins/__init__.py +15 -0
  54. openepd-7.3.0/src/openepd/model/specs/singular/mixins/access_flooring_mixin.py +55 -0
  55. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/steel.py +10 -2
  56. openepd-7.3.0/src/openepd/model/validation/__init__.py +15 -0
  57. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/validation/common.py +10 -6
  58. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/validation/enum.py +4 -2
  59. openepd-7.3.0/src/openepd/model/validation/numbers.py +15 -0
  60. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/validation/quantity.py +13 -6
  61. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/versioning.py +8 -6
  62. {openepd-7.1.0 → openepd-7.3.0}/LICENSE +0 -0
  63. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/__init__.py +0 -0
  64. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/__init__.py +0 -0
  65. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/average_dataset/__init__.py +0 -0
  66. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/category/__init__.py +0 -0
  67. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/category/dto.py +0 -0
  68. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/category/sync_api.py +0 -0
  69. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/dto/__init__.py +0 -0
  70. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/dto/base.py +0 -0
  71. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/dto/meta.py +0 -0
  72. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/dto/mf.py +0 -0
  73. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/dto/params.py +0 -0
  74. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/epd/__init__.py +0 -0
  75. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/epd/dto.py +0 -0
  76. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/errors.py +0 -0
  77. {openepd-7.1.0/src/openepd/api/pcr → openepd-7.3.0/src/openepd/api/org}/__init__.py +0 -0
  78. {openepd-7.1.0/src/openepd/api/test → openepd-7.3.0/src/openepd/api/pcr}/__init__.py +0 -0
  79. {openepd-7.1.0/src/openepd/bundle → openepd-7.3.0/src/openepd/api/plant}/__init__.py +0 -0
  80. {openepd-7.1.0/src/openepd/model → openepd-7.3.0/src/openepd/api/standard}/__init__.py +0 -0
  81. {openepd-7.1.0/src/openepd/model/specs/singular/mixins → openepd-7.3.0/src/openepd/api/test}/__init__.py +0 -0
  82. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/api/utils.py +0 -0
  83. {openepd-7.1.0/src/openepd/model/validation → openepd-7.3.0/src/openepd/bundle}/__init__.py +0 -0
  84. openepd-7.1.0/src/openepd/model/validation/numbers.py → openepd-7.3.0/src/openepd/model/__init__.py +0 -0
  85. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/category.py +0 -0
  86. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/geography.py +0 -0
  87. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/industry_epd.py +0 -0
  88. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/README.md +0 -0
  89. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/concrete.py +0 -0
  90. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/accessories.py +1 -1
  91. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/aggregates.py +0 -0
  92. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/aluminium.py +1 -1
  93. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/asphalt.py +0 -0
  94. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/bulk_materials.py +0 -0
  95. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/cast_decks_and_underlayment.py +0 -0
  96. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/cladding.py +10 -10
  97. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/conveying_equipment.py +2 -2
  98. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/electrical.py +18 -18
  99. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +1 -1
  100. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/electricity.py +0 -0
  101. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/fire_and_smoke_protection.py +3 -3
  102. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/furnishings.py +7 -7
  103. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/grouting.py +0 -0
  104. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/masonry.py +1 -1
  105. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/material_handling.py +0 -0
  106. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/mechanical.py +6 -6
  107. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/mechanical_insulation.py +0 -0
  108. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/network_infrastructure.py +3 -3
  109. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/openings.py +17 -17
  110. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/other_electrical_equipment.py +0 -0
  111. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/other_materials.py +4 -4
  112. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/plumbing.py +5 -5
  113. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/precast_concrete.py +2 -2
  114. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/sheathing.py +0 -0
  115. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/thermal_moisture_protection.py +12 -12
  116. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/utility_piping.py +0 -0
  117. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/range/wood_joists.py +0 -0
  118. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/accessories.py +0 -0
  119. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/aggregates.py +0 -0
  120. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/asphalt.py +0 -0
  121. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/bulk_materials.py +0 -0
  122. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/cast_decks_and_underlayment.py +0 -0
  123. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/cladding.py +0 -0
  124. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/cmu.py +0 -0
  125. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/common.py +0 -0
  126. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/conveying_equipment.py +0 -0
  127. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/deprecated/concrete.py +0 -0
  128. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/deprecated/steel.py +0 -0
  129. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/electrical.py +0 -0
  130. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/electrical_transmission_and_distribution_equipment.py +0 -0
  131. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/electricity.py +0 -0
  132. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/fire_and_smoke_protection.py +0 -0
  133. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/furnishings.py +0 -0
  134. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/grouting.py +0 -0
  135. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/masonry.py +0 -0
  136. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/material_handling.py +0 -0
  137. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/mechanical.py +0 -0
  138. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/mechanical_insulation.py +0 -0
  139. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/mixins/conduit_mixin.py +0 -0
  140. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/network_infrastructure.py +0 -0
  141. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/openings.py +0 -0
  142. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/other_electrical_equipment.py +0 -0
  143. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/other_materials.py +0 -0
  144. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/plumbing.py +0 -0
  145. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/precast_concrete.py +0 -0
  146. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/sheathing.py +0 -0
  147. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/thermal_moisture_protection.py +0 -0
  148. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/utility_piping.py +0 -0
  149. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/wood.py +0 -0
  150. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/specs/singular/wood_joists.py +0 -0
  151. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/model/standard.py +0 -0
  152. {openepd-7.1.0 → openepd-7.3.0}/src/openepd/py.typed +0 -0
@@ -1,15 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: openepd
3
- Version: 7.1.0
3
+ Version: 7.3.0
4
4
  Summary: Python library to work with OpenEPD format
5
- Home-page: https://github.com/cchangelabs/openepd
6
5
  License: Apache-2.0
7
6
  Author: C-Change Labs
8
7
  Author-email: support@c-change-labs.com
9
8
  Maintainer: C-Change Labs
10
9
  Maintainer-email: open-source@c-change-labs.com
11
10
  Requires-Python: >=3.11,<4.0
12
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 4 - Beta
13
12
  Classifier: Intended Audience :: Developers
14
13
  Classifier: License :: OSI Approved :: Apache Software License
15
14
  Classifier: Operating System :: OS Independent
@@ -21,7 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
20
  Provides-Extra: api-client
22
21
  Requires-Dist: email-validator (>=1.3.1)
23
22
  Requires-Dist: idna (>=3.7)
24
- Requires-Dist: open-xpd-uuid (>=0.2.1,<0.3.0)
23
+ Requires-Dist: open-xpd-uuid (>=0.2.1,<2)
25
24
  Requires-Dist: openlocationcode (>=1.0.1)
26
25
  Requires-Dist: pydantic (>=2.0,<3)
27
26
  Requires-Dist: requests (>=2.0) ; extra == "api-client"
@@ -45,6 +44,24 @@ Description-Content-Type: text/markdown
45
44
 
46
45
  This library is a Python library to work with OpenEPD format.
47
46
 
47
+ > ⚠️ **Version Warning**
48
+ >
49
+ > This application is currently developed in **two major versions** in parallel:
50
+ >
51
+ > - **v6.x (>=6.0.0)** — Stable and production-ready. Maintains support for Pydantic v1 and v2 through a compatibility layer.
52
+ > - **v7.x (>=7.0.0)** — Public beta. Fully functional, with native support for Pydantic v2. Still experimental and may introduce breaking changes in **internal and integration interfaces**.
53
+ >
54
+ > ⚠️ No breaking changes are expected in the **public standard or data model**, only in internal APIs and integration points.
55
+ >
56
+ > Both versions currently offer the same set of features.
57
+ > We recommend using **v6** for most production use cases as the more mature and stable option.
58
+ > **v7** is suitable for production environments that can tolerate some level of interface instability and want to adopt the latest internals.
59
+ >
60
+ > 💡 Only the **latest version of v7** is guaranteed to contain all the features and updates from v6. Earlier v7 releases may lack some recent improvements.
61
+ >
62
+ > Once **v7 is promoted to stable**, all earlier **pre-stable (beta) v7 releases** will be **marked as yanked** to prevent accidental usage in production.
63
+ >
64
+
48
65
  ## About OpenEPD
49
66
 
50
67
  [openEPD](https://www.buildingtransparency.org/programs/openepd/) is an open data format for passing digital
@@ -60,17 +77,10 @@ including uniqueness of organizations/plants, precise PCR references, and dated
60
77
  The openEPD format is **extensible**. Standard extensions exist for concrete products, and for
61
78
  documenting supply-chain specific data.
62
79
 
63
- [Read More about OpenEPD format here](https://www.buildingtransparency.org/programs/openepd/).
80
+ [Read More about OpenEPD format here](https://www.open-epd-forum.org).
64
81
 
65
82
  ## Usage
66
83
 
67
- **❗ ATTENTION**: Pick the right version. The cornerstone of this library models package representing openEPD models.
68
- Models are defined with Pydantic library which is a dependency for openepd package. If you use Pydantic in your project
69
- carefully pick the version:
70
-
71
- * Use version **below** `2.0.0` if your project uses Pydantic version below `2.0.0`
72
- * Use version `2.x.x` or higher if your project uses Pydantic version `2.0.0` or above
73
-
74
84
  ### Models
75
85
 
76
86
  The library provides the Pydantic models for all the OpenEPD entities. The models are available in the `openepd.models`
@@ -15,6 +15,24 @@
15
15
 
16
16
  This library is a Python library to work with OpenEPD format.
17
17
 
18
+ > ⚠️ **Version Warning**
19
+ >
20
+ > This application is currently developed in **two major versions** in parallel:
21
+ >
22
+ > - **v6.x (>=6.0.0)** — Stable and production-ready. Maintains support for Pydantic v1 and v2 through a compatibility layer.
23
+ > - **v7.x (>=7.0.0)** — Public beta. Fully functional, with native support for Pydantic v2. Still experimental and may introduce breaking changes in **internal and integration interfaces**.
24
+ >
25
+ > ⚠️ No breaking changes are expected in the **public standard or data model**, only in internal APIs and integration points.
26
+ >
27
+ > Both versions currently offer the same set of features.
28
+ > We recommend using **v6** for most production use cases as the more mature and stable option.
29
+ > **v7** is suitable for production environments that can tolerate some level of interface instability and want to adopt the latest internals.
30
+ >
31
+ > 💡 Only the **latest version of v7** is guaranteed to contain all the features and updates from v6. Earlier v7 releases may lack some recent improvements.
32
+ >
33
+ > Once **v7 is promoted to stable**, all earlier **pre-stable (beta) v7 releases** will be **marked as yanked** to prevent accidental usage in production.
34
+ >
35
+
18
36
  ## About OpenEPD
19
37
 
20
38
  [openEPD](https://www.buildingtransparency.org/programs/openepd/) is an open data format for passing digital
@@ -30,17 +48,10 @@ including uniqueness of organizations/plants, precise PCR references, and dated
30
48
  The openEPD format is **extensible**. Standard extensions exist for concrete products, and for
31
49
  documenting supply-chain specific data.
32
50
 
33
- [Read More about OpenEPD format here](https://www.buildingtransparency.org/programs/openepd/).
51
+ [Read More about OpenEPD format here](https://www.open-epd-forum.org).
34
52
 
35
53
  ## Usage
36
54
 
37
- **❗ ATTENTION**: Pick the right version. The cornerstone of this library models package representing openEPD models.
38
- Models are defined with Pydantic library which is a dependency for openepd package. If you use Pydantic in your project
39
- carefully pick the version:
40
-
41
- * Use version **below** `2.0.0` if your project uses Pydantic version below `2.0.0`
42
- * Use version `2.x.x` or higher if your project uses Pydantic version `2.0.0` or above
43
-
44
55
  ### Models
45
56
 
46
57
  The library provides the Pydantic models for all the OpenEPD entities. The models are available in the `openepd.models`
@@ -1,11 +1,6 @@
1
- [tool.ruff]
2
- line-length = 120
3
- target-version = "py311"
4
- exclude = [".*pyi"]
5
-
6
1
  [tool.poetry]
7
2
  name = "openepd"
8
- version = "7.1.0"
3
+ version = "7.3.0"
9
4
  license = "Apache-2.0"
10
5
  description = "Python library to work with OpenEPD format"
11
6
  authors = ["C-Change Labs <support@c-change-labs.com>"]
@@ -13,7 +8,7 @@ maintainers = ["C-Change Labs <open-source@c-change-labs.com>"]
13
8
  repository = "https://github.com/cchangelabs/openepd"
14
9
  keywords = []
15
10
  classifiers = [
16
- "Development Status :: 3 - Alpha",
11
+ "Development Status :: 4 - Beta",
17
12
  "Intended Audience :: Developers",
18
13
  "License :: OSI Approved :: Apache Software License",
19
14
  "Operating System :: OS Independent",
@@ -23,6 +18,7 @@ classifiers = [
23
18
  readme = "README.md"
24
19
  packages = [{ include = "openepd", from = "src" }]
25
20
  exclude = ["**/test_*.py", "**/tests/**"]
21
+ requires-poetry = ">=2.0.0"
26
22
 
27
23
  [tool.poetry.dependencies]
28
24
  python = "^3.11"
@@ -30,13 +26,13 @@ pydantic = ">=2.0,<3"
30
26
  email-validator = ">=1.3.1"
31
27
  requests = { version = ">=2.0", optional = true }
32
28
  idna = ">=3.7"
33
- open-xpd-uuid = "~=0.2.1"
29
+ open-xpd-uuid = ">=0.2.1,<2"
34
30
  openlocationcode = ">=1.0.1"
35
31
 
36
32
  # Optional dependencies
37
33
  # lxml = { version = "~=4.9.2", optional = true }
38
34
 
39
- [tool.poetry.dev-dependencies]
35
+ [tool.poetry.group.dev.dependencies]
40
36
  # Unit tests
41
37
  coverage = { version = "=6.5", extras = ["toml"] }
42
38
  pytest = "~=7.2"
@@ -47,12 +43,8 @@ wheel = "~=0.40.0"
47
43
  click = "~=8.1.7"
48
44
 
49
45
  # Dev tools
50
- black = "~=24.3"
46
+ ruff = ">=0.11.8"
51
47
  licenseheaders = "~=0.8"
52
- flake8 = "~=4.0"
53
- flake8-import-graph = "~=0.1.3"
54
- flake8-docstrings = "~=1.7.0"
55
- isort = "~=5.11"
56
48
  mypy = ">=1.0.1"
57
49
  pre-commit = "~=2.19"
58
50
  commitizen = "~=3.16.0"
@@ -71,7 +63,6 @@ jinja2 = ">=3.1.4"
71
63
  [tool.poetry.extras]
72
64
  api_client = ["requests"]
73
65
 
74
-
75
66
  [tool.commitizen]
76
67
  version_provider = "poetry"
77
68
  bump_version = "bump: version $current_version → $new_version"
@@ -151,3 +142,33 @@ follow_imports = "skip"
151
142
  [[tool.mypy.overrides]]
152
143
  module = "openlocationcode.*"
153
144
  ignore_missing_imports = true
145
+
146
+ [tool.ruff]
147
+ line-length = 120
148
+ target-version = "py311"
149
+ exclude = [".*pyi", "tools/**.py"]
150
+
151
+ [tool.ruff.lint.isort]
152
+ force-sort-within-sections = true
153
+
154
+ [tool.ruff.lint]
155
+ extend-ignore = [
156
+ "S101", # Use of assert statement. We have a lot of asserts for mypy type checking.
157
+ "W291", # W291 trailing whitespace
158
+ "W391", # W391 blank line at end of file
159
+ "E501", # E501: line too long
160
+ "E203", # E704: Multiple statements on one line (def)
161
+ "F403", #F403 'from module import *' used; unable to detect undefined names (F403)
162
+ ##### DOCSTRINGS #####
163
+ "D100", # Missing docstring in public module
164
+ "D101", # Missing docstring in public class
165
+ "D102", # Docstring of prublic method
166
+ "D107", # Missing docstring in __init__
167
+ "D105", # Missing docstring in magic method
168
+ "D104", # Missing docstring in public package
169
+ "D106", # Missing docstring in public nested class
170
+ "D202", # D202 No blank lines allowed after function docstring
171
+ "D203", # We want to have blank line before class
172
+ "D212", # We want to require second line for multiline docstrings
173
+ ]
174
+ extend-select = ["S", "E", "B", "A", "EM", "UP", "LOG", "G", "I", "D"]
@@ -13,4 +13,4 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- VERSION = "7.1.0"
16
+ VERSION = "7.3.0"
@@ -135,7 +135,8 @@ class GenericEstimateApi(BaseApiMethodGroup):
135
135
  """
136
136
  ge_id = ge.id
137
137
  if not ge_id:
138
- raise ValueError("The ID must be set to edit a GenericEstimate.")
138
+ msg = "The ID must be set to edit a GenericEstimate."
139
+ raise ValueError(msg)
139
140
 
140
141
  response = self._client.do_request(
141
142
  "put",
@@ -90,7 +90,8 @@ class IndustryEpdApi(BaseApiMethodGroup):
90
90
  """
91
91
  iepd_id = iepd.id
92
92
  if not iepd_id:
93
- raise ValueError("The ID must be set to edit a IndustryEpd.")
93
+ msg = "The ID must be set to edit a IndustryEpd."
94
+ raise ValueError(msg)
94
95
 
95
96
  response = self._client.do_request(
96
97
  "put",
@@ -14,14 +14,15 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __all__ = (
17
- "HttpStreamReader",
18
- "SyncHttpClient",
17
+ "USER_AGENT_DEFAULT",
18
+ "BaseApiMethodGroup",
19
19
  "DoRequest",
20
+ "HttpStreamReader",
20
21
  "RetryHandler",
21
- "BaseApiMethodGroup",
22
- "USER_AGENT_DEFAULT",
22
+ "SyncHttpClient",
23
23
  )
24
24
 
25
+ from collections.abc import Callable
25
26
  import datetime
26
27
  from functools import partial, wraps
27
28
  from io import IOBase
@@ -29,7 +30,7 @@ import logging
29
30
  import random
30
31
  import shutil
31
32
  import time
32
- from typing import IO, Any, BinaryIO, Callable, Final, NamedTuple
33
+ from typing import IO, Any, BinaryIO, Final, NamedTuple
33
34
 
34
35
  import requests
35
36
  from requests import PreparedRequest, Response, Session, Timeout
@@ -180,7 +181,7 @@ class SyncHttpClient:
180
181
  self._throttler = Throttler(rate_per_sec=requests_per_sec)
181
182
  self._throttle_retry_timeout: float = (
182
183
  float(throttle_retry_timeout)
183
- if isinstance(throttle_retry_timeout, (float, int))
184
+ if isinstance(throttle_retry_timeout, float | int)
184
185
  else throttle_retry_timeout.total_seconds()
185
186
  )
186
187
  self.user_agent = user_agent
@@ -412,7 +413,8 @@ class SyncHttpClient:
412
413
 
413
414
  response.raise_for_status()
414
415
  # This can't be handled by static checker because of the dynamic nature of the raise_for_status method
415
- raise RuntimeError("This line should never be reached")
416
+ msg = "This line should never be reached"
417
+ raise RuntimeError(msg)
416
418
 
417
419
  def _get_url_for_request(self, path_or_url: str) -> str:
418
420
  """
@@ -471,7 +473,7 @@ class SyncHttpClient:
471
473
  exception = e
472
474
 
473
475
  if exception or response.status_code == requests_codes.service_unavailable:
474
- secs = random.randint(60, 60 * 5)
476
+ secs = random.randint(60, 60 * 5) # noqa: S311
475
477
  logger.warning(
476
478
  "%s %s is unavailable. Attempts left: %s. Waiting %s seconds...",
477
479
  method,
@@ -13,12 +13,12 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from collections.abc import Iterable
16
+ from collections.abc import Callable, Iterable, Iterator
17
17
  from contextlib import contextmanager
18
18
  from datetime import datetime, timedelta
19
19
  import threading
20
20
  from time import sleep
21
- from typing import Callable, Generic, Iterator, cast
21
+ from typing import Generic, cast
22
22
 
23
23
  from requests import Response
24
24
 
@@ -126,20 +126,26 @@ class StreamingListResponse(Iterable[TOpenEpdObject], Generic[TOpenEpdObject]):
126
126
  :return: list of items on the page
127
127
  """
128
128
  if page_num <= 0:
129
- raise ValueError("Page number must be positive")
129
+ msg = "Page number must be positive"
130
+ raise ValueError(msg)
130
131
  if self.__current_page != page_num or force_reload:
131
132
  self.__recent_response = self.__fetch_handler(page_num, self.__page_size)
132
133
  self.__current_page = page_num
133
134
  if self.__recent_response is None:
134
- raise RuntimeError("Response is empty, this should not happen, check if fetch_handler is compatible")
135
+ msg = "Response is empty, this should not happen, check if fetch_handler is compatible"
136
+ raise RuntimeError(msg)
135
137
  if self.__recent_response.payload is None:
136
- raise ValueError("Response does not contain payload")
138
+ msg = "Response does not contain payload"
139
+ raise ValueError(msg)
137
140
  if not isinstance(self.__recent_response.payload, list):
138
- raise ValueError("Response does not contain a list")
141
+ msg = "Response does not contain a list"
142
+ raise ValueError(msg)
139
143
  if self.__recent_response.meta is None:
140
- raise ValueError("Response does not contain meta")
144
+ msg = "Response does not contain meta"
145
+ raise ValueError(msg)
141
146
  if not isinstance(self.__recent_response.meta, PagingMetaMixin):
142
- raise ValueError("Response does not contain paging meta")
147
+ msg = "Response does not contain paging meta"
148
+ raise ValueError(msg)
143
149
  return self.__recent_response.payload
144
150
 
145
151
  def get_paging_meta(self) -> PagingMeta:
@@ -152,7 +158,8 @@ class StreamingListResponse(Iterable[TOpenEpdObject], Generic[TOpenEpdObject]):
152
158
  """
153
159
  paging_meta = cast(PagingMetaMixin, self.get_meta()).paging
154
160
  if paging_meta is None:
155
- raise ValueError("Response does not contain paging meta")
161
+ msg = "Response does not contain paging meta"
162
+ raise ValueError(msg)
156
163
  return paging_meta
157
164
 
158
165
  def get_meta(self) -> MetaCollectionDto:
@@ -206,8 +213,7 @@ class StreamingListResponse(Iterable[TOpenEpdObject], Generic[TOpenEpdObject]):
206
213
  self.goto_page(start_from_page)
207
214
  while True:
208
215
  items = self.goto_page(self.current_page)
209
- for x in items:
210
- yield x
216
+ yield from items
211
217
  if not self.has_next_page():
212
218
  return # no more pages
213
219
  else:
@@ -63,7 +63,7 @@ TMetaExtension = TypeVar("TMetaExtension", bound=MetaExtensionBase)
63
63
 
64
64
  class MetaCollectionDto(BaseOpenEpdApiModel, Generic[TMetaExtension]):
65
65
  """
66
- This class is intended to be used as a container for different meta objects.
66
+ Use this class as a container for different meta objects.
67
67
 
68
68
  From a specific controller, you should return a specific subclass of MetaCollectionDto and appropriate mixins. This
69
69
  would allow to retain type information to generate schema for meta section of response.
@@ -173,7 +173,8 @@ class EpdApi(BaseApiMethodGroup):
173
173
  """
174
174
  epd_id = epd.id
175
175
  if not epd_id:
176
- raise ValueError("The EPD ID must be set to edit an EPD.")
176
+ msg = "The EPD ID must be set to edit an EPD."
177
+ raise ValueError(msg)
177
178
  response = self._client.do_request(
178
179
  "put",
179
180
  f"/epds/{encode_path_param(epd_id)}",
@@ -0,0 +1,79 @@
1
+ #
2
+ # Copyright 2025 by C Change Labs Inc. www.c-change-labs.com
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ from typing import Literal, overload
17
+
18
+ from requests import Response
19
+
20
+ from openepd.api.base_sync_client import BaseApiMethodGroup
21
+ from openepd.api.utils import encode_path_param
22
+ from openepd.model.org import Org, OrgRef
23
+
24
+
25
+ class OrgApi(BaseApiMethodGroup):
26
+ """API methods for Orgs."""
27
+
28
+ @overload
29
+ def create(self, to_create: Org, with_response: Literal[True]) -> tuple[OrgRef, Response]: ...
30
+
31
+ @overload
32
+ def create(self, to_create: Org, with_response: Literal[False] = False) -> OrgRef: ...
33
+
34
+ def create(self, to_create: Org, with_response: bool = False) -> OrgRef | tuple[OrgRef, Response]:
35
+ """
36
+ Create a new organization.
37
+
38
+ :param to_create: Organization to create
39
+ :param with_response: if True, return a tuple of (OrgRef, Response), otherwise return only OrgRef
40
+ :return: Organization reference or Organization reference with HTTP Response object depending on parameter
41
+ :raise ValidationError: if given object Org is invalid
42
+ """
43
+ response = self._client.do_request("post", "/orgs", json=to_create.to_serializable())
44
+ content = response.json()
45
+ ref = OrgRef.model_validate(content)
46
+ if with_response:
47
+ return ref, response
48
+ return ref
49
+
50
+ @overload
51
+ def edit(self, to_edit: Org, with_response: Literal[True]) -> tuple[OrgRef, Response]: ...
52
+
53
+ @overload
54
+ def edit(self, to_edit: Org, with_response: Literal[False] = False) -> OrgRef: ...
55
+
56
+ def edit(self, to_edit: Org, with_response: bool = False) -> OrgRef | tuple[OrgRef, Response]:
57
+ """
58
+ Edit an organization.
59
+
60
+ :param to_edit: Organization to edit
61
+ :param with_response: if True, return a tuple of (OrgRef, Response), otherwise return only Org
62
+ :return: Organization reference or Organization reference with HTTP Response object depending on parameter
63
+ :raise ValueError: if the organization web_domain is not set
64
+ """
65
+ entity_id = to_edit.web_domain
66
+ if not entity_id:
67
+ msg = "The organization web_domain must be set to edit an organization."
68
+ raise ValueError(msg)
69
+ response = self._client.do_request(
70
+ "put",
71
+ f"/orgs/{encode_path_param(entity_id)}",
72
+ json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
73
+ )
74
+ response.raise_for_status()
75
+ content = response.json()
76
+ ref = OrgRef.model_validate(content)
77
+ if with_response:
78
+ return ref, response
79
+ return ref
@@ -13,7 +13,12 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ from typing import Literal, overload
17
+
18
+ from requests import Response
19
+
16
20
  from openepd.api.base_sync_client import BaseApiMethodGroup
21
+ from openepd.api.utils import encode_path_param
17
22
  from openepd.model.pcr import Pcr, PcrRef
18
23
 
19
24
 
@@ -42,3 +47,33 @@ class PcrApi(BaseApiMethodGroup):
42
47
  """
43
48
  pcr_ref_obj = self._client.do_request("post", "/pcrs", json=pcr.to_serializable()).json()
44
49
  return PcrRef.model_validate(pcr_ref_obj)
50
+
51
+ @overload
52
+ def edit(self, to_edit: Pcr, with_response: Literal[True]) -> tuple[PcrRef, Response]: ...
53
+
54
+ @overload
55
+ def edit(self, to_edit: Pcr, with_response: Literal[False] = False) -> PcrRef: ...
56
+
57
+ def edit(self, to_edit: Pcr, with_response: bool = False) -> PcrRef | tuple[PcrRef, Response]:
58
+ """
59
+ Edit a pcr.
60
+
61
+ :param to_edit: Pcr to edit
62
+ :param with_response: if True, return a tuple of (PcrRef, Response), otherwise return only PcrRef
63
+ :return: Pcr reference or Pcr reference with HTTP Response object depending on parameter
64
+ :raise ValueError: if the pcr ID is not set
65
+ """
66
+ entity_id = to_edit.id
67
+ if not entity_id:
68
+ msg = "The pcr ID must be set to edit a pcr."
69
+ raise ValueError(msg)
70
+ response = self._client.do_request(
71
+ "put",
72
+ f"/pcrs/{encode_path_param(entity_id)}",
73
+ json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
74
+ )
75
+ content = response.json()
76
+ ref = PcrRef.model_validate(content)
77
+ if with_response:
78
+ return ref, response
79
+ return ref
@@ -0,0 +1,79 @@
1
+ #
2
+ # Copyright 2025 by C Change Labs Inc. www.c-change-labs.com
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ from typing import Literal, overload
17
+
18
+ from requests import Response
19
+
20
+ from openepd.api.base_sync_client import BaseApiMethodGroup
21
+ from openepd.api.utils import encode_path_param
22
+ from openepd.model.org import Plant, PlantRef
23
+
24
+
25
+ class PlantApi(BaseApiMethodGroup):
26
+ """API methods for Plants."""
27
+
28
+ @overload
29
+ def create(self, to_create: Plant, with_response: Literal[True]) -> tuple[PlantRef, Response]: ...
30
+
31
+ @overload
32
+ def create(self, to_create: Plant, with_response: Literal[False] = False) -> PlantRef: ...
33
+
34
+ def create(self, to_create: Plant, with_response: bool = False) -> PlantRef | tuple[PlantRef, Response]:
35
+ """
36
+ Create a new plant.
37
+
38
+ :param to_create: Plant to create
39
+ :param with_response: if True, return a tuple of (PlantRef, Response), otherwise return only PlantRef
40
+ :return: Plant reference or Plant reference with HTTP Response object depending on parameter
41
+ :raise ValidationError: if given object Plant is invalid
42
+ """
43
+ response = self._client.do_request("post", "/plants", json=to_create.to_serializable())
44
+ content = response.json()
45
+ ref = PlantRef.model_validate(content)
46
+ if with_response:
47
+ return ref, response
48
+ return ref
49
+
50
+ @overload
51
+ def edit(self, to_edit: Plant, with_response: Literal[True]) -> tuple[PlantRef, Response]: ...
52
+
53
+ @overload
54
+ def edit(self, to_edit: Plant, with_response: Literal[False] = False) -> PlantRef: ...
55
+
56
+ def edit(self, to_edit: Plant, with_response: bool = False) -> PlantRef | tuple[PlantRef, Response]:
57
+ """
58
+ Edit a plant.
59
+
60
+ :param to_edit: Plant to edit
61
+ :param with_response: if True, return a tuple of (PlantRef, Response), otherwise return only PlantRef
62
+ :return: Plant reference or Plant reference with HTTP Response object depending on parameter
63
+ :raise ValueError: if the plant ID is not set
64
+ """
65
+ entity_id = to_edit.id
66
+ if not entity_id:
67
+ msg = "The plant ID must be set to edit a plant."
68
+ raise ValueError(msg)
69
+ response = self._client.do_request(
70
+ "put",
71
+ f"/plants/{encode_path_param(entity_id)}",
72
+ json=to_edit.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
73
+ )
74
+ response.raise_for_status()
75
+ content = response.json()
76
+ ref = PlantRef.model_validate(content)
77
+ if with_response:
78
+ return ref, response
79
+ return ref