frequenz-quantities 1.0.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.
- frequenz-quantities-1.0.0/LICENSE +21 -0
- frequenz-quantities-1.0.0/MANIFEST.in +13 -0
- frequenz-quantities-1.0.0/PKG-INFO +68 -0
- frequenz-quantities-1.0.0/README.md +36 -0
- frequenz-quantities-1.0.0/RELEASE_NOTES.md +14 -0
- frequenz-quantities-1.0.0/pyproject.toml +180 -0
- frequenz-quantities-1.0.0/setup.cfg +4 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/__init__.py +112 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_apparent_power.py +234 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_current.py +131 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_energy.py +188 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_frequency.py +115 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_percentage.py +66 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_power.py +244 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_quantity.py +541 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_reactive_power.py +235 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_temperature.py +39 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/_voltage.py +148 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/conftest.py +13 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/experimental/__init__.py +11 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/experimental/marshmallow.py +271 -0
- frequenz-quantities-1.0.0/src/frequenz/quantities/py.typed +0 -0
- frequenz-quantities-1.0.0/src/frequenz_quantities.egg-info/PKG-INFO +68 -0
- frequenz-quantities-1.0.0/src/frequenz_quantities.egg-info/SOURCES.txt +25 -0
- frequenz-quantities-1.0.0/src/frequenz_quantities.egg-info/dependency_links.txt +1 -0
- frequenz-quantities-1.0.0/src/frequenz_quantities.egg-info/requires.txt +53 -0
- frequenz-quantities-1.0.0/src/frequenz_quantities.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2024 Frequenz Energy-as-a-Service GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
exclude .cookiecutter-replay.json
|
|
2
|
+
exclude .editorconfig
|
|
3
|
+
exclude .gitignore
|
|
4
|
+
exclude CODEOWNERS
|
|
5
|
+
exclude CONTRIBUTING.md
|
|
6
|
+
exclude mkdocs.yml
|
|
7
|
+
exclude noxfile.py
|
|
8
|
+
exclude src/conftest.py
|
|
9
|
+
recursive-exclude .github *
|
|
10
|
+
recursive-exclude benchmarks *
|
|
11
|
+
recursive-exclude docs *
|
|
12
|
+
recursive-exclude tests *
|
|
13
|
+
recursive-include py *.pyi
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: frequenz-quantities
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Types for holding quantities with units
|
|
5
|
+
Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-quantities-python/
|
|
8
|
+
Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-quantities-python/releases
|
|
9
|
+
Project-URL: Issues, https://github.com/frequenz-floss/frequenz-quantities-python/issues
|
|
10
|
+
Project-URL: Repository, https://github.com/frequenz-floss/frequenz-quantities-python
|
|
11
|
+
Project-URL: Support, https://github.com/frequenz-floss/frequenz-quantities-python/discussions/categories/support
|
|
12
|
+
Keywords: frequenz,python,lib,library,quantities,unit,conversion
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: <4,>=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Provides-Extra: dev-flake8
|
|
23
|
+
Provides-Extra: dev-formatting
|
|
24
|
+
Provides-Extra: dev-mkdocs
|
|
25
|
+
Provides-Extra: dev-mypy
|
|
26
|
+
Provides-Extra: dev-noxfile
|
|
27
|
+
Provides-Extra: dev-pylint
|
|
28
|
+
Provides-Extra: dev-pytest
|
|
29
|
+
Provides-Extra: marshmallow
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
|
|
33
|
+
# Frequenz Quantities Library
|
|
34
|
+
|
|
35
|
+
[](https://github.com/frequenz-floss/frequenz-quantities-python/actions/workflows/ci.yaml)
|
|
36
|
+
[](https://pypi.org/project/frequenz-quantities/)
|
|
37
|
+
[](https://frequenz-floss.github.io/frequenz-quantities-python/)
|
|
38
|
+
|
|
39
|
+
## Introduction
|
|
40
|
+
|
|
41
|
+
This library provide types for holding quantities with units. The main goal is
|
|
42
|
+
to avoid mistakes while working with different types of quantities, for example
|
|
43
|
+
avoiding adding a length to a time.
|
|
44
|
+
|
|
45
|
+
It also prevents mistakes when operating between the same quantity but in
|
|
46
|
+
different units, like adding a power in Joules to a power in Watts without
|
|
47
|
+
converting one of them.
|
|
48
|
+
|
|
49
|
+
Quantities store the value in a base unit, and then provide methods to get that
|
|
50
|
+
quantity as a particular unit.
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
For more information on how to use this library and examples, please check the
|
|
55
|
+
[Documentation website](https://frequenz-floss.github.io/frequenz-quantities-python/).
|
|
56
|
+
|
|
57
|
+
## Supported Platforms
|
|
58
|
+
|
|
59
|
+
The following platforms are officially supported (tested):
|
|
60
|
+
|
|
61
|
+
- **Python:** 3.11
|
|
62
|
+
- **Operating System:** Ubuntu Linux 20.04
|
|
63
|
+
- **Architectures:** amd64, arm64
|
|
64
|
+
|
|
65
|
+
## Contributing
|
|
66
|
+
|
|
67
|
+
If you want to know how to build this project and contribute to it, please
|
|
68
|
+
check out the [Contributing Guide](CONTRIBUTING.md).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Frequenz Quantities Library
|
|
2
|
+
|
|
3
|
+
[](https://github.com/frequenz-floss/frequenz-quantities-python/actions/workflows/ci.yaml)
|
|
4
|
+
[](https://pypi.org/project/frequenz-quantities/)
|
|
5
|
+
[](https://frequenz-floss.github.io/frequenz-quantities-python/)
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
This library provide types for holding quantities with units. The main goal is
|
|
10
|
+
to avoid mistakes while working with different types of quantities, for example
|
|
11
|
+
avoiding adding a length to a time.
|
|
12
|
+
|
|
13
|
+
It also prevents mistakes when operating between the same quantity but in
|
|
14
|
+
different units, like adding a power in Joules to a power in Watts without
|
|
15
|
+
converting one of them.
|
|
16
|
+
|
|
17
|
+
Quantities store the value in a base unit, and then provide methods to get that
|
|
18
|
+
quantity as a particular unit.
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
For more information on how to use this library and examples, please check the
|
|
23
|
+
[Documentation website](https://frequenz-floss.github.io/frequenz-quantities-python/).
|
|
24
|
+
|
|
25
|
+
## Supported Platforms
|
|
26
|
+
|
|
27
|
+
The following platforms are officially supported (tested):
|
|
28
|
+
|
|
29
|
+
- **Python:** 3.11
|
|
30
|
+
- **Operating System:** Ubuntu Linux 20.04
|
|
31
|
+
- **Architectures:** amd64, arm64
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
If you want to know how to build this project and contribute to it, please
|
|
36
|
+
check out the [Contributing Guide](CONTRIBUTING.md).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Frequenz Quantities Library Release Notes
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
This first stable release of `frequenz-quantities`!
|
|
6
|
+
|
|
7
|
+
The code is based on the quantities code in the [`frequenz-sdk` v1.0.0rc601](https://github.com/frequenz-floss/frequenz-sdk-python/releases/tag/v1.0.0-rc601) but with some new features and improvements.
|
|
8
|
+
|
|
9
|
+
## New Features
|
|
10
|
+
|
|
11
|
+
- Added support for `__round__` (`round(quantity)`), `__pos__` (`+quantity`) and `__mod__` (`quantity % quantity`) operators.
|
|
12
|
+
- Add `ReactivePower` quantity.
|
|
13
|
+
- Add `ApparentPower` quantity.
|
|
14
|
+
- Add an **experimental** marshmallow module available when adding `[marshmallow]` to the requirements. This module provides `marshmallow` fields for quantities and a `QuantitySchema` to use as a base schema that supports loading and dumping quantities.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = [
|
|
6
|
+
"setuptools == 68.1.0",
|
|
7
|
+
"setuptools_scm[toml] == 7.1.0",
|
|
8
|
+
"frequenz-repo-config[lib] == 0.10.0",
|
|
9
|
+
]
|
|
10
|
+
build-backend = "setuptools.build_meta"
|
|
11
|
+
|
|
12
|
+
[project]
|
|
13
|
+
name = "frequenz-quantities"
|
|
14
|
+
description = "Types for holding quantities with units"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
license = { text = "MIT" }
|
|
17
|
+
keywords = [
|
|
18
|
+
"frequenz",
|
|
19
|
+
"python",
|
|
20
|
+
"lib",
|
|
21
|
+
"library",
|
|
22
|
+
"quantities",
|
|
23
|
+
"unit",
|
|
24
|
+
"conversion",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 3 - Alpha",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
"Topic :: Software Development :: Libraries",
|
|
33
|
+
"Typing :: Typed",
|
|
34
|
+
]
|
|
35
|
+
requires-python = ">= 3.11, < 4"
|
|
36
|
+
dependencies = ["typing-extensions >= 4.5.0, < 5"]
|
|
37
|
+
dynamic = ["version"]
|
|
38
|
+
|
|
39
|
+
[[project.authors]]
|
|
40
|
+
name = "Frequenz Energy-as-a-Service GmbH"
|
|
41
|
+
email = "floss@frequenz.com"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev-flake8 = [
|
|
45
|
+
"flake8 == 7.1.1",
|
|
46
|
+
"flake8-docstrings == 1.7.0",
|
|
47
|
+
"flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml
|
|
48
|
+
"pydoclint == 0.5.9",
|
|
49
|
+
"pydocstyle == 6.3.0",
|
|
50
|
+
]
|
|
51
|
+
dev-formatting = ["black == 24.10.0", "isort == 5.13.2"]
|
|
52
|
+
dev-mkdocs = [
|
|
53
|
+
"Markdown == 3.7",
|
|
54
|
+
"black == 24.10.0",
|
|
55
|
+
"mike == 2.1.3",
|
|
56
|
+
"mkdocs-gen-files == 0.5.0",
|
|
57
|
+
"mkdocs-literate-nav == 0.6.1",
|
|
58
|
+
"mkdocs-macros-plugin == 1.3.7",
|
|
59
|
+
"mkdocs-material == 9.5.43",
|
|
60
|
+
"mkdocstrings[python] == 0.26.2",
|
|
61
|
+
"mkdocstrings-python == 1.12.2",
|
|
62
|
+
"frequenz-repo-config[lib] == 0.10.0",
|
|
63
|
+
]
|
|
64
|
+
dev-mypy = [
|
|
65
|
+
"mypy == 1.13.0",
|
|
66
|
+
"types-Markdown == 3.7.0.20240822",
|
|
67
|
+
# For checking the noxfile, docs/ script, and tests
|
|
68
|
+
"frequenz-quantities[dev-mkdocs,dev-noxfile,dev-pytest,marshmallow]",
|
|
69
|
+
]
|
|
70
|
+
dev-noxfile = ["nox == 2024.10.9", "frequenz-repo-config[lib] == 0.10.0"]
|
|
71
|
+
dev-pylint = [
|
|
72
|
+
# dev-pytest already defines a dependency to pylint because of the examples
|
|
73
|
+
# For checking the noxfile, docs/ script, and tests
|
|
74
|
+
"frequenz-quantities[dev-mkdocs,dev-noxfile,dev-pytest,marshmallow]",
|
|
75
|
+
]
|
|
76
|
+
dev-pytest = [
|
|
77
|
+
"pytest == 8.3.3",
|
|
78
|
+
"pylint == 3.3.1", # We need this to check for the examples
|
|
79
|
+
"frequenz-repo-config[extra-lint-examples] == 0.10.0",
|
|
80
|
+
"pytest-mock == 3.14.0",
|
|
81
|
+
"pytest-asyncio == 0.24.0",
|
|
82
|
+
"async-solipsism == 0.7",
|
|
83
|
+
"hypothesis == 6.116.0",
|
|
84
|
+
"frequenz-quantities[marshmallow]",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
marshmallow = [
|
|
88
|
+
"marshmallow >= 3.0.0, < 4",
|
|
89
|
+
"marshmallow-dataclass >= 8.0.0, < 9",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
dev = [
|
|
93
|
+
"frequenz-quantities[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest,marshmallow]",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[project.urls]
|
|
97
|
+
Documentation = "https://frequenz-floss.github.io/frequenz-quantities-python/"
|
|
98
|
+
Changelog = "https://github.com/frequenz-floss/frequenz-quantities-python/releases"
|
|
99
|
+
Issues = "https://github.com/frequenz-floss/frequenz-quantities-python/issues"
|
|
100
|
+
Repository = "https://github.com/frequenz-floss/frequenz-quantities-python"
|
|
101
|
+
Support = "https://github.com/frequenz-floss/frequenz-quantities-python/discussions/categories/support"
|
|
102
|
+
|
|
103
|
+
[tool.black]
|
|
104
|
+
line-length = 88
|
|
105
|
+
target-version = ['py311']
|
|
106
|
+
include = '\.pyi?$'
|
|
107
|
+
|
|
108
|
+
[tool.isort]
|
|
109
|
+
profile = "black"
|
|
110
|
+
line_length = 88
|
|
111
|
+
src_paths = ["benchmarks", "examples", "src", "tests"]
|
|
112
|
+
|
|
113
|
+
[tool.flake8]
|
|
114
|
+
# We give some flexibility to go over 88, there are cases like long URLs or
|
|
115
|
+
# code in documenation that have extra indentation. Black will still take care
|
|
116
|
+
# of making everything that can be 88 wide, 88 wide.
|
|
117
|
+
max-line-length = 100
|
|
118
|
+
extend-ignore = [
|
|
119
|
+
"E203", # Whitespace before ':' (conflicts with black)
|
|
120
|
+
"W503", # Line break before binary operator (conflicts with black)
|
|
121
|
+
]
|
|
122
|
+
# pydoclint options
|
|
123
|
+
style = "google"
|
|
124
|
+
check-return-types = false
|
|
125
|
+
check-yield-types = false
|
|
126
|
+
arg-type-hints-in-docstring = false
|
|
127
|
+
arg-type-hints-in-signature = true
|
|
128
|
+
allow-init-docstring = true
|
|
129
|
+
|
|
130
|
+
[tool.pylint.similarities]
|
|
131
|
+
ignore-comments = ['yes']
|
|
132
|
+
ignore-docstrings = ['yes']
|
|
133
|
+
ignore-imports = ['no']
|
|
134
|
+
min-similarity-lines = 40
|
|
135
|
+
|
|
136
|
+
[tool.pylint.messages_control]
|
|
137
|
+
disable = [
|
|
138
|
+
"too-few-public-methods",
|
|
139
|
+
"too-many-return-statements",
|
|
140
|
+
# disabled because it conflicts with isort
|
|
141
|
+
"wrong-import-order",
|
|
142
|
+
"ungrouped-imports",
|
|
143
|
+
# pylint's unsubscriptable check is buggy and is not needed because
|
|
144
|
+
# it is a type-check, for which we already have mypy.
|
|
145
|
+
"unsubscriptable-object",
|
|
146
|
+
# Checked by mypy
|
|
147
|
+
"no-member",
|
|
148
|
+
# Checked by flake8
|
|
149
|
+
"f-string-without-interpolation",
|
|
150
|
+
"line-too-long",
|
|
151
|
+
"missing-function-docstring",
|
|
152
|
+
"redefined-outer-name",
|
|
153
|
+
"unnecessary-lambda-assignment",
|
|
154
|
+
"unused-import",
|
|
155
|
+
"unused-variable",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
[tool.pytest.ini_options]
|
|
159
|
+
testpaths = ["tests", "src"]
|
|
160
|
+
asyncio_mode = "auto"
|
|
161
|
+
required_plugins = ["pytest-asyncio", "pytest-mock"]
|
|
162
|
+
|
|
163
|
+
[tool.mypy]
|
|
164
|
+
explicit_package_bases = true
|
|
165
|
+
namespace_packages = true
|
|
166
|
+
# This option disables mypy cache, and it is sometimes useful to enable it if
|
|
167
|
+
# you are getting weird intermittent error, or error in the CI but not locally
|
|
168
|
+
# (or vice versa). In particular errors saying that type: ignore is not
|
|
169
|
+
# used but getting the original ignored error when removing the type: ignore.
|
|
170
|
+
# See for example: https://github.com/python/mypy/issues/2960
|
|
171
|
+
#no_incremental = true
|
|
172
|
+
packages = ["frequenz.quantities"]
|
|
173
|
+
strict = true
|
|
174
|
+
|
|
175
|
+
[[tool.mypy.overrides]]
|
|
176
|
+
module = ["mkdocs_macros.*", "sybil", "sybil.*"]
|
|
177
|
+
ignore_missing_imports = true
|
|
178
|
+
|
|
179
|
+
[tool.setuptools_scm]
|
|
180
|
+
version_scheme = "post-release"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
|
|
4
|
+
"""Types for holding quantities with units.
|
|
5
|
+
|
|
6
|
+
This library provide types for holding quantities with units. The main goal is to avoid
|
|
7
|
+
mistakes while working with different types of quantities, for example avoiding adding
|
|
8
|
+
a length to a time.
|
|
9
|
+
|
|
10
|
+
It also prevents mistakes when operating between the same quantity but in different
|
|
11
|
+
units, like adding a power in Joules to a power in Watts without converting one of them.
|
|
12
|
+
|
|
13
|
+
Quantities store the value in a base unit, and then provide methods to get that quantity
|
|
14
|
+
as a particular unit. They can only be constructed using special constructors with the
|
|
15
|
+
form `Quantity.from_<unit>`, for example
|
|
16
|
+
[`Power.from_watts(10.0)`][frequenz.quantities.Power.from_watts].
|
|
17
|
+
|
|
18
|
+
Internally quantities store values as `float`s, so regular [float issues and limitations
|
|
19
|
+
apply](https://docs.python.org/3/tutorial/floatingpoint.html), although some of them are
|
|
20
|
+
tried to be mitigated.
|
|
21
|
+
|
|
22
|
+
Quantities are also immutable, so operations between quantities return a new instance of
|
|
23
|
+
the quantity.
|
|
24
|
+
|
|
25
|
+
This library provides the following types:
|
|
26
|
+
|
|
27
|
+
- [ApparentPower][frequenz.quantities.ApparentPower]: A quantity representing apparent
|
|
28
|
+
power.
|
|
29
|
+
- [Current][frequenz.quantities.Current]: A quantity representing an electric current.
|
|
30
|
+
- [Energy][frequenz.quantities.Energy]: A quantity representing energy.
|
|
31
|
+
- [Frequency][frequenz.quantities.Frequency]: A quantity representing frequency.
|
|
32
|
+
- [Percentage][frequenz.quantities.Percentage]: A quantity representing a percentage.
|
|
33
|
+
- [Power][frequenz.quantities.Power]: A quantity representing power.
|
|
34
|
+
- [ReactivePower][frequenz.quantities.ReactivePower]: A quantity representing reactive
|
|
35
|
+
power.
|
|
36
|
+
- [Temperature][frequenz.quantities.Temperature]: A quantity representing temperature.
|
|
37
|
+
- [Voltage][frequenz.quantities.Voltage]: A quantity representing electric voltage.
|
|
38
|
+
|
|
39
|
+
There is also the unitless [Quantity][frequenz.quantities.Quantity] class. All
|
|
40
|
+
quantities are subclasses of this class and it can be used as a base to create new
|
|
41
|
+
quantities. Using the `Quantity` class directly is discouraged, as it doesn't provide
|
|
42
|
+
any unit conversion methods.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
```python
|
|
46
|
+
from datetime import timedelta
|
|
47
|
+
from frequenz.quantities import Power, Voltage, Current, Energy
|
|
48
|
+
|
|
49
|
+
# Create a power quantity
|
|
50
|
+
power = Power.from_watts(230.0)
|
|
51
|
+
|
|
52
|
+
# Printing uses a unit to make the string as short as possible
|
|
53
|
+
print(f"Power: {power}") # Power: 230.0 W
|
|
54
|
+
# The precision can be changed
|
|
55
|
+
print(f"Power: {power:0.3}") # Power: 230.000 W
|
|
56
|
+
# The conversion methods can be used to get the value in a particular unit
|
|
57
|
+
print(f"Power in MW: {power.as_megawatt()}") # Power in MW: 0.00023 MW
|
|
58
|
+
|
|
59
|
+
# Create a voltage quantity
|
|
60
|
+
voltage = Voltage.from_volts(230.0)
|
|
61
|
+
|
|
62
|
+
# Calculate the current
|
|
63
|
+
current = power / voltage
|
|
64
|
+
assert isinstance(current, Current)
|
|
65
|
+
print(f"Current: {current}") # Current: 1.0 A
|
|
66
|
+
assert current.isclose(Current.from_amperes(1.0))
|
|
67
|
+
|
|
68
|
+
# Calculate the energy
|
|
69
|
+
energy = power * timedelta(hours=1)
|
|
70
|
+
assert isinstance(energy, Energy)
|
|
71
|
+
print(f"Energy: {energy}") # Energy: 230.0 Wh
|
|
72
|
+
print(f"Energy in kWh: {energy.as_kilowatt_hours()}") # Energy in kWh: 0.23
|
|
73
|
+
|
|
74
|
+
# Invalid operations are not permitted
|
|
75
|
+
# (when using a type hinting linter like mypy, this will be caught at linting time)
|
|
76
|
+
try:
|
|
77
|
+
power + voltage
|
|
78
|
+
except TypeError as e:
|
|
79
|
+
print(f"Error: {e}") # Error: unsupported operand type(s) for +: 'Power' and 'Voltage'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This library also provides an [**experimental** module with marshmallow fields and
|
|
83
|
+
a base schema][frequenz.quantities.experimental.marshmallow] to serialize and
|
|
84
|
+
deserialize quantities using the marshmallow library. To use it, you need to make sure
|
|
85
|
+
to install this package with the `marshmallow` optional dependencies (e.g.
|
|
86
|
+
`pip install frequenz-quantities[marshmallow]`).
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
from ._apparent_power import ApparentPower
|
|
91
|
+
from ._current import Current
|
|
92
|
+
from ._energy import Energy
|
|
93
|
+
from ._frequency import Frequency
|
|
94
|
+
from ._percentage import Percentage
|
|
95
|
+
from ._power import Power
|
|
96
|
+
from ._quantity import Quantity
|
|
97
|
+
from ._reactive_power import ReactivePower
|
|
98
|
+
from ._temperature import Temperature
|
|
99
|
+
from ._voltage import Voltage
|
|
100
|
+
|
|
101
|
+
__all__ = [
|
|
102
|
+
"ApparentPower",
|
|
103
|
+
"Current",
|
|
104
|
+
"Energy",
|
|
105
|
+
"Frequency",
|
|
106
|
+
"Percentage",
|
|
107
|
+
"Power",
|
|
108
|
+
"Quantity",
|
|
109
|
+
"ReactivePower",
|
|
110
|
+
"Temperature",
|
|
111
|
+
"Voltage",
|
|
112
|
+
]
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# License: MIT
|
|
2
|
+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
|
|
3
|
+
|
|
4
|
+
"""Types for holding apparent power quantities with units."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Self, overload
|
|
9
|
+
|
|
10
|
+
from ._quantity import NoDefaultConstructible, Quantity
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ._current import Current
|
|
14
|
+
from ._percentage import Percentage
|
|
15
|
+
from ._voltage import Voltage
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ApparentPower(
|
|
19
|
+
Quantity,
|
|
20
|
+
metaclass=NoDefaultConstructible,
|
|
21
|
+
exponent_unit_map={
|
|
22
|
+
-3: "mVA",
|
|
23
|
+
0: "VA",
|
|
24
|
+
3: "kVA",
|
|
25
|
+
6: "MVA",
|
|
26
|
+
},
|
|
27
|
+
):
|
|
28
|
+
"""A apparent power quantity.
|
|
29
|
+
|
|
30
|
+
Objects of this type are wrappers around `float` values and are immutable.
|
|
31
|
+
|
|
32
|
+
The constructors accept a single `float` value, the `as_*()` methods return a
|
|
33
|
+
`float` value, and each of the arithmetic operators supported by this type are
|
|
34
|
+
actually implemented using floating-point arithmetic.
|
|
35
|
+
|
|
36
|
+
So all considerations about floating-point arithmetic apply to this type as well.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_volt_amperes(cls, value: float) -> Self:
|
|
41
|
+
"""Initialize a new apparent power quantity.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
value: The apparent power in volt-amperes (VA).
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A new apparent power quantity.
|
|
48
|
+
"""
|
|
49
|
+
return cls._new(value)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_milli_volt_amperes(cls, mva: float) -> Self:
|
|
53
|
+
"""Initialize a new apparent power quantity.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
mva: The apparent power in millivolt-amperes (mVA).
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A new apparent power quantity.
|
|
60
|
+
"""
|
|
61
|
+
return cls._new(mva, exponent=-3)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_kilo_volt_amperes(cls, kva: float) -> Self:
|
|
65
|
+
"""Initialize a new apparent power quantity.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
kva: The apparent power in kilovolt-amperes (kVA).
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A new apparent power quantity.
|
|
72
|
+
"""
|
|
73
|
+
return cls._new(kva, exponent=3)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_mega_volt_amperes(cls, mva: float) -> Self:
|
|
77
|
+
"""Initialize a new apparent power quantity.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
mva: The apparent power in megavolt-amperes (MVA).
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A new apparent power quantity.
|
|
84
|
+
"""
|
|
85
|
+
return cls._new(mva, exponent=6)
|
|
86
|
+
|
|
87
|
+
def as_volt_amperes(self) -> float:
|
|
88
|
+
"""Return the apparent power in volt-amperes (VA).
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The apparent power in volt-amperes (VA).
|
|
92
|
+
"""
|
|
93
|
+
return self._base_value
|
|
94
|
+
|
|
95
|
+
def as_milli_volt_amperes(self) -> float:
|
|
96
|
+
"""Return the apparent power in millivolt-amperes (mVA).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The apparent power in millivolt-amperes (mVA).
|
|
100
|
+
"""
|
|
101
|
+
return self._base_value * 1e3
|
|
102
|
+
|
|
103
|
+
def as_kilo_volt_amperes(self) -> float:
|
|
104
|
+
"""Return the apparent power in kilovolt-amperes (kVA).
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The apparent power in kilovolt-amperes (kVA).
|
|
108
|
+
"""
|
|
109
|
+
return self._base_value / 1e3
|
|
110
|
+
|
|
111
|
+
def as_mega_volt_amperes(self) -> float:
|
|
112
|
+
"""Return the apparent power in megavolt-amperes (MVA).
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
The apparent power in megavolt-amperes (MVA).
|
|
116
|
+
"""
|
|
117
|
+
return self._base_value / 1e6
|
|
118
|
+
|
|
119
|
+
@overload
|
|
120
|
+
def __mul__(self, scalar: float, /) -> Self:
|
|
121
|
+
"""Scale this power by a scalar.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
scalar: The scalar by which to scale this power.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The scaled power.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
@overload
|
|
131
|
+
def __mul__(self, percent: Percentage, /) -> Self:
|
|
132
|
+
"""Scale this power by a percentage.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
percent: The percentage by which to scale this power.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The scaled power.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __mul__(self, other: float | Percentage, /) -> Self:
|
|
142
|
+
"""Return a power or energy from multiplying this power by the given value.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
other: The scalar, percentage or duration to multiply by.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
A power or energy.
|
|
149
|
+
"""
|
|
150
|
+
from ._percentage import Percentage # pylint: disable=import-outside-toplevel
|
|
151
|
+
|
|
152
|
+
match other:
|
|
153
|
+
case float() | Percentage():
|
|
154
|
+
return super().__mul__(other)
|
|
155
|
+
case _:
|
|
156
|
+
return NotImplemented
|
|
157
|
+
|
|
158
|
+
# We need the ignore here because otherwise mypy will give this error:
|
|
159
|
+
# > Overloaded operator methods can't have wider argument types in overrides
|
|
160
|
+
# The problem seems to be when the other type implements an **incompatible**
|
|
161
|
+
# __rmul__ method, which is not the case here, so we should be safe.
|
|
162
|
+
# Please see this example:
|
|
163
|
+
# https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55
|
|
164
|
+
# And a discussion in a mypy issue here:
|
|
165
|
+
# https://github.com/python/mypy/issues/4985#issuecomment-389692396
|
|
166
|
+
@overload # type: ignore[override]
|
|
167
|
+
def __truediv__(self, other: float, /) -> Self:
|
|
168
|
+
"""Divide this power by a scalar.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
other: The scalar to divide this power by.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The divided power.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
@overload
|
|
178
|
+
def __truediv__(self, other: Self, /) -> float:
|
|
179
|
+
"""Return the ratio of this power to another.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
other: The other power.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
The ratio of this power to another.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
@overload
|
|
189
|
+
def __truediv__(self, current: Current, /) -> Voltage:
|
|
190
|
+
"""Return a voltage from dividing this power by the given current.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
current: The current to divide by.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A voltage from dividing this power by the a current.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
@overload
|
|
200
|
+
def __truediv__(self, voltage: Voltage, /) -> Current:
|
|
201
|
+
"""Return a current from dividing this power by the given voltage.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
voltage: The voltage to divide by.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
A current from dividing this power by a voltage.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
def __truediv__(
|
|
211
|
+
self, other: float | Self | Current | Voltage, /
|
|
212
|
+
) -> Self | float | Voltage | Current:
|
|
213
|
+
"""Return a current or voltage from dividing this power by the given value.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
other: The scalar, power, current or voltage to divide by.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
A current or voltage from dividing this power by the given value.
|
|
220
|
+
"""
|
|
221
|
+
from ._current import Current # pylint: disable=import-outside-toplevel
|
|
222
|
+
from ._voltage import Voltage # pylint: disable=import-outside-toplevel
|
|
223
|
+
|
|
224
|
+
match other:
|
|
225
|
+
case float():
|
|
226
|
+
return super().__truediv__(other)
|
|
227
|
+
case ApparentPower():
|
|
228
|
+
return self._base_value / other._base_value
|
|
229
|
+
case Current():
|
|
230
|
+
return Voltage._new(self._base_value / other._base_value)
|
|
231
|
+
case Voltage():
|
|
232
|
+
return Current._new(self._base_value / other._base_value)
|
|
233
|
+
case _:
|
|
234
|
+
return NotImplemented
|