psr-graf 2.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.
@@ -0,0 +1,13 @@
1
+ venv
2
+ .venv
3
+ __pycache__
4
+ *.pyc
5
+ *.csv
6
+ !tests/test_data/*.csv
7
+ *.parquet
8
+ *.html
9
+ *.log
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ build/
13
+ dist/
psr_graf-2.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 PSR
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,336 @@
1
+ Metadata-Version: 2.5
2
+ Name: psr-graf
3
+ Version: 2.3.0
4
+ Summary: Utility module to read PSR SDDP's hdr/bin result file pairs.
5
+ Project-URL: Homepage, https://github.com/psrenergy/pygraf
6
+ Project-URL: Issues, https://github.com/psrenergy/pygraf/issues
7
+ Author-email: PSR <d.parrini@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Provides-Extra: pandas
23
+ Requires-Dist: numpy; extra == 'pandas'
24
+ Requires-Dist: pandas; extra == 'pandas'
25
+ Description-Content-Type: text/markdown
26
+
27
+ PyGraf
28
+ ======
29
+
30
+
31
+ Utility module to read SDDP hdr/bin result file pairs. Some [examples](#usage-samples) to convert it to
32
+ popular formats are available.
33
+
34
+
35
+ Installing
36
+ ----------
37
+
38
+ Install the latest version from [PyPI](https://pypi.org/project/psr-graf/)
39
+
40
+ ```
41
+ pip install psr-graf
42
+ ```
43
+
44
+ To use `load_as_dataframe`, install it with the optional `pandas` dependency:
45
+
46
+ ```
47
+ pip install "psr-graf[pandas]"
48
+ ```
49
+
50
+ Or download this repository contents.
51
+
52
+
53
+ Usage
54
+ -----
55
+
56
+ Start by importing `psr.graf` module. It's possible to read data directly using `open_bin` and `open_csv` functions
57
+ or `load_as_dataframe` function if `pandas` package is available.
58
+
59
+
60
+ The example below shows how to load data directly into a `pandas.DataFrame` and prints the first 5 lines of data.
61
+
62
+ ```python
63
+ import psr.graf
64
+
65
+ df = psr.graf.load_as_dataframe("sample_data/gerter.hdr")
66
+ print(df.head())
67
+ ```
68
+
69
+ The output is:
70
+ ```
71
+ Thermal 1 Thermal 2 Thermal 3
72
+ stage scenario block
73
+ 1 1 1 7.440000 0.744 0.368069
74
+ 2 1 6.437624 0.000 0.000000
75
+ 3 1 7.440000 0.744 0.576140
76
+ 4 1 7.440000 0.744 2.994997
77
+ 5 1 7.440000 0.744 0.916644
78
+ ```
79
+
80
+ Alternatively, `open_bin` and `open_csv` functions can be used for direct data access as shown in the example next.
81
+
82
+ ```python
83
+ import psr.graf
84
+
85
+ with psr.graf.open_bin("sample_data/gerter.hdr") as graf_file:
86
+ print("Stages:", graf_file.stages)
87
+ print("Scenarios:", graf_file.scenarios)
88
+ print("Agents:", graf_file.agents)
89
+ print(f"Initial date: {graf_file.initial_year:04d}/{graf_file.initial_stage:02d}")
90
+ print("Units:", graf_file.units)
91
+ stage = 2
92
+ print(f"Number of blocks at stage {stage}: {graf_file.blocks(stage)}")
93
+ scenario = 10
94
+ block = 1
95
+ print(
96
+ f"Data at stage {stage}, scenario {scenario}, block {block}:",
97
+ graf_file.read(stage, scenario, block),
98
+ )
99
+ ```
100
+
101
+ The output is:
102
+ ```
103
+ Stages: 12
104
+ Scenarios: 50
105
+ Agents: ('Thermal 1', 'Thermal 2', 'Thermal 3')
106
+ Initial date: 2013/01
107
+ Units: GWh
108
+ Number of blocks at stage 2: 1
109
+ Data at stage 2, scenario 10, block 1: (7.440000057220459, 0.7440000176429749, 0.3680693209171295)
110
+ ```
111
+
112
+ Requirements
113
+ ------------
114
+
115
+ * Python 3.10 or newer.
116
+ * (Optional) `pandas` package to use `load_as_dataframe` function, installed with the `pandas` extra.
117
+
118
+
119
+ File Formats
120
+ ------------
121
+
122
+ | File Extension | Description |
123
+ |:--------------:|:---------------------------------|
124
+ | .hdr or .bin | Binary .hdr and .bin pair |
125
+ | .dat | Single-binary file |
126
+ | .csv | CSV file with specific structure |
127
+
128
+ * `load_as_dataframe` supports all of them and will determine which reader will be used based on the file extension.
129
+ * `open_bin` supports only .hdr/bin pairs or single-binary files.
130
+ * `open_csv` supports only CSV.
131
+
132
+
133
+ Both `open_bin`, `open_csv`, and `load_as_dataframe` functions accept `encoding` parameter to specify the encoding of the strings in file. The default is `utf-8`.
134
+
135
+
136
+ DataFrame options
137
+ -----------------
138
+
139
+
140
+ ### MultiIndex or single index
141
+
142
+ `load_as_dataframe` accepts an optional keyword argument `multi_index` (default `True`) to specify if the
143
+ returned `pandas.DataFrame` should use `pandas.MultiIndex` or not. If `False`, the returned `pandas.DataFrame` will have a
144
+ single automatic index and the columns 'stage', 'scenario', 'block' will appear before the agents' data.
145
+
146
+ Example:
147
+ ```python
148
+ import psr.graf
149
+
150
+ df = psr.graf.load_as_dataframe("sample_data/gerter.hdr", multi_index=False)
151
+ print(df.head())
152
+ print("Column names:", df.columns.values)
153
+ ```
154
+
155
+ The output is:
156
+ ```
157
+ stage scenario block Thermal 1 Thermal 2 Thermal 3
158
+ 0 1 1 1 7.440000 0.744 0.368069
159
+ 1 1 2 1 6.437624 0.000 0.000000
160
+ 2 1 3 1 7.440000 0.744 0.576140
161
+ 3 1 4 1 7.440000 0.744 2.994997
162
+ 4 1 5 1 7.440000 0.744 0.916644
163
+ Column names: ['stage' 'scenario' 'block' 'Thermal 1' 'Thermal 2' 'Thermal 3']
164
+ ```
165
+
166
+ On the other hand,
167
+
168
+ ```python
169
+ import psr.graf
170
+
171
+ df = psr.graf.load_as_dataframe("sample_data/gerter.hdr", multi_index=True)
172
+ print(df.head())
173
+ print("Column names:", df.columns.values)
174
+ ```
175
+
176
+ Will produce the following output:
177
+ ```
178
+ Thermal 1 Thermal 2 Thermal 3
179
+ stage scenario block
180
+ 1 1 1 7.440000 0.744 0.368069
181
+ 2 1 6.437624 0.000 0.000000
182
+ 3 1 7.440000 0.744 0.576140
183
+ 4 1 7.440000 0.744 2.994997
184
+ 5 1 7.440000 0.744 0.916644
185
+ Column names: ['Thermal 1' 'Thermal 2' 'Thermal 3']
186
+ ```
187
+
188
+ ### Index formats
189
+
190
+ The `index_format` specifies the format of index columns of the returned `pandas.Dataframe`. It accepts the following
191
+ values:
192
+
193
+ | Index Format | Columns |
194
+ |:------------:|:---------------------------------------------|
195
+ | `default` | stage, scenario, block or hour |
196
+ | `period` | year, month or week, scenario, block or hour |
197
+
198
+ * `default` creates a `pandas.DataFrame` with the columns as they are stored in the original file.
199
+ * `period` converts `stage` into `year`, `month` or `week` depending on the stage type of the file and the
200
+ initial year and stage.
201
+
202
+
203
+ ### Filter by stage, scenario, block and/or agent
204
+
205
+ The `load_as_dataframe` function accepts the following optional keyword arguments to filter data:
206
+
207
+ | Argument | Description |
208
+ |:------------------:|:------------------------------------------------------------------------------|
209
+ | `filter_stages` | List of stages to be included |
210
+ | `filter_blocks` | List of blocks to be included |
211
+ | `filter_scenarios` | List of scenarios to be included |
212
+ | `filter_agents` | List of agents to be included. The name is case insensitive. |
213
+
214
+ An empty list or unspecified means all stages, blocks, scenarios or agents will be included.
215
+
216
+ Example:
217
+ ```python
218
+ import psr.graf
219
+
220
+ df = psr.graf.load_as_dataframe(
221
+ "sample_data/gerter.hdr",
222
+ filter_stages=[
223
+ 8,
224
+ ],
225
+ filter_scenarios=[
226
+ 10,
227
+ ],
228
+ filter_agents=["Thermal 3", "Thermal 2"],
229
+ )
230
+ print(df.head())
231
+ ```
232
+
233
+ Outputs:
234
+ ```
235
+ Thermal 3 Thermal 2
236
+ stage scenario block
237
+ 8 10 1 8.030441 0.0
238
+ ```
239
+
240
+
241
+
242
+ Usage Samples
243
+ -------------
244
+
245
+ ### [`dataframes_sample.py`](https://github.com/psrenergy/pygraf/blob/main/dataframes_sample.py)
246
+
247
+ Shows how to read data into `pandas.DataFrame`s.
248
+
249
+ Requires `pandas` package installed.
250
+
251
+
252
+ ### [`matplotlib_sample.py`](https://github.com/psrenergy/pygraf/blob/main/matplotlib_sample.py)
253
+
254
+ Shows how to read data and plot data from hdr/bin file pairs.
255
+
256
+ ![](https://raw.githubusercontent.com/psrenergy/pygraf/main/docs/matplotlib_sample_plot.png)
257
+
258
+ Requires `matplotlib` package installed.
259
+
260
+
261
+
262
+ ### [`csv_sample.py`](https://github.com/psrenergy/pygraf/blob/main/csv_sample.py)
263
+
264
+ Shows how to convert from hdr/bin file pairs to csv using `psr.graf` module.
265
+
266
+ This script can also be called from command line:
267
+
268
+ ```bat
269
+ python csv_sample.py input_file.hdr output_file.csv
270
+ ```
271
+
272
+ Where `output_file.csv` is optional.
273
+
274
+
275
+ ### [`parquet_sample.py`](https://github.com/psrenergy/pygraf/blob/main/parquet_sample.py)
276
+
277
+ Shows how to convert from hdr/bin file pairs to Apache Parquet format.
278
+
279
+ Requires `pyarrow` package installed.
280
+
281
+ This script can also be called from command line:
282
+
283
+ ```bat
284
+ python parquet_sample.py input_file.hdr output_file.parquet
285
+ ```
286
+
287
+ Where `output_file.parquet` is optional.
288
+
289
+
290
+
291
+ Development
292
+ -----------
293
+
294
+ The project uses [uv](https://docs.astral.sh/uv/) for development. To set up an environment with the
295
+ development dependencies, run the tests, and lint and format the code:
296
+
297
+ ```
298
+ uv sync
299
+ uv run pytest
300
+ uv run ruff check
301
+ uv run ruff format
302
+ ```
303
+
304
+ To build the source distribution and wheel into `dist/`:
305
+
306
+ ```
307
+ uv build
308
+ ```
309
+
310
+
311
+ ### Releasing
312
+
313
+ Update `__version__` in `psr/graf/graf.py`, then push a tag with the same version:
314
+
315
+ ```
316
+ git tag 2.3.0
317
+ git push origin 2.3.0
318
+ ```
319
+
320
+ PyPI never accepts a version that was uploaded before, even after it is deleted, so versions like 2.2.0
321
+ cannot be used again.
322
+
323
+ The [Publish](.github/workflows/publish.yml) workflow runs the tests, builds the package, and
324
+ uploads it to PyPI using [trusted publishing](https://docs.pypi.org/trusted-publishers/).
325
+
326
+
327
+ Issues and Support
328
+ ------------------
329
+
330
+ Check PyGraf's [GitHub repository](https://github.com/psrenergy/pygraf/) and [issues page](https://github.com/psrenergy/pygraf/issues) for support.
331
+
332
+
333
+ License
334
+ -------
335
+
336
+ PyGraf is licensed under the [MIT License](https://github.com/psrenergy/pygraf/blob/main/LICENSE).
@@ -0,0 +1,310 @@
1
+ PyGraf
2
+ ======
3
+
4
+
5
+ Utility module to read SDDP hdr/bin result file pairs. Some [examples](#usage-samples) to convert it to
6
+ popular formats are available.
7
+
8
+
9
+ Installing
10
+ ----------
11
+
12
+ Install the latest version from [PyPI](https://pypi.org/project/psr-graf/)
13
+
14
+ ```
15
+ pip install psr-graf
16
+ ```
17
+
18
+ To use `load_as_dataframe`, install it with the optional `pandas` dependency:
19
+
20
+ ```
21
+ pip install "psr-graf[pandas]"
22
+ ```
23
+
24
+ Or download this repository contents.
25
+
26
+
27
+ Usage
28
+ -----
29
+
30
+ Start by importing `psr.graf` module. It's possible to read data directly using `open_bin` and `open_csv` functions
31
+ or `load_as_dataframe` function if `pandas` package is available.
32
+
33
+
34
+ The example below shows how to load data directly into a `pandas.DataFrame` and prints the first 5 lines of data.
35
+
36
+ ```python
37
+ import psr.graf
38
+
39
+ df = psr.graf.load_as_dataframe("sample_data/gerter.hdr")
40
+ print(df.head())
41
+ ```
42
+
43
+ The output is:
44
+ ```
45
+ Thermal 1 Thermal 2 Thermal 3
46
+ stage scenario block
47
+ 1 1 1 7.440000 0.744 0.368069
48
+ 2 1 6.437624 0.000 0.000000
49
+ 3 1 7.440000 0.744 0.576140
50
+ 4 1 7.440000 0.744 2.994997
51
+ 5 1 7.440000 0.744 0.916644
52
+ ```
53
+
54
+ Alternatively, `open_bin` and `open_csv` functions can be used for direct data access as shown in the example next.
55
+
56
+ ```python
57
+ import psr.graf
58
+
59
+ with psr.graf.open_bin("sample_data/gerter.hdr") as graf_file:
60
+ print("Stages:", graf_file.stages)
61
+ print("Scenarios:", graf_file.scenarios)
62
+ print("Agents:", graf_file.agents)
63
+ print(f"Initial date: {graf_file.initial_year:04d}/{graf_file.initial_stage:02d}")
64
+ print("Units:", graf_file.units)
65
+ stage = 2
66
+ print(f"Number of blocks at stage {stage}: {graf_file.blocks(stage)}")
67
+ scenario = 10
68
+ block = 1
69
+ print(
70
+ f"Data at stage {stage}, scenario {scenario}, block {block}:",
71
+ graf_file.read(stage, scenario, block),
72
+ )
73
+ ```
74
+
75
+ The output is:
76
+ ```
77
+ Stages: 12
78
+ Scenarios: 50
79
+ Agents: ('Thermal 1', 'Thermal 2', 'Thermal 3')
80
+ Initial date: 2013/01
81
+ Units: GWh
82
+ Number of blocks at stage 2: 1
83
+ Data at stage 2, scenario 10, block 1: (7.440000057220459, 0.7440000176429749, 0.3680693209171295)
84
+ ```
85
+
86
+ Requirements
87
+ ------------
88
+
89
+ * Python 3.10 or newer.
90
+ * (Optional) `pandas` package to use `load_as_dataframe` function, installed with the `pandas` extra.
91
+
92
+
93
+ File Formats
94
+ ------------
95
+
96
+ | File Extension | Description |
97
+ |:--------------:|:---------------------------------|
98
+ | .hdr or .bin | Binary .hdr and .bin pair |
99
+ | .dat | Single-binary file |
100
+ | .csv | CSV file with specific structure |
101
+
102
+ * `load_as_dataframe` supports all of them and will determine which reader will be used based on the file extension.
103
+ * `open_bin` supports only .hdr/bin pairs or single-binary files.
104
+ * `open_csv` supports only CSV.
105
+
106
+
107
+ Both `open_bin`, `open_csv`, and `load_as_dataframe` functions accept `encoding` parameter to specify the encoding of the strings in file. The default is `utf-8`.
108
+
109
+
110
+ DataFrame options
111
+ -----------------
112
+
113
+
114
+ ### MultiIndex or single index
115
+
116
+ `load_as_dataframe` accepts an optional keyword argument `multi_index` (default `True`) to specify if the
117
+ returned `pandas.DataFrame` should use `pandas.MultiIndex` or not. If `False`, the returned `pandas.DataFrame` will have a
118
+ single automatic index and the columns 'stage', 'scenario', 'block' will appear before the agents' data.
119
+
120
+ Example:
121
+ ```python
122
+ import psr.graf
123
+
124
+ df = psr.graf.load_as_dataframe("sample_data/gerter.hdr", multi_index=False)
125
+ print(df.head())
126
+ print("Column names:", df.columns.values)
127
+ ```
128
+
129
+ The output is:
130
+ ```
131
+ stage scenario block Thermal 1 Thermal 2 Thermal 3
132
+ 0 1 1 1 7.440000 0.744 0.368069
133
+ 1 1 2 1 6.437624 0.000 0.000000
134
+ 2 1 3 1 7.440000 0.744 0.576140
135
+ 3 1 4 1 7.440000 0.744 2.994997
136
+ 4 1 5 1 7.440000 0.744 0.916644
137
+ Column names: ['stage' 'scenario' 'block' 'Thermal 1' 'Thermal 2' 'Thermal 3']
138
+ ```
139
+
140
+ On the other hand,
141
+
142
+ ```python
143
+ import psr.graf
144
+
145
+ df = psr.graf.load_as_dataframe("sample_data/gerter.hdr", multi_index=True)
146
+ print(df.head())
147
+ print("Column names:", df.columns.values)
148
+ ```
149
+
150
+ Will produce the following output:
151
+ ```
152
+ Thermal 1 Thermal 2 Thermal 3
153
+ stage scenario block
154
+ 1 1 1 7.440000 0.744 0.368069
155
+ 2 1 6.437624 0.000 0.000000
156
+ 3 1 7.440000 0.744 0.576140
157
+ 4 1 7.440000 0.744 2.994997
158
+ 5 1 7.440000 0.744 0.916644
159
+ Column names: ['Thermal 1' 'Thermal 2' 'Thermal 3']
160
+ ```
161
+
162
+ ### Index formats
163
+
164
+ The `index_format` specifies the format of index columns of the returned `pandas.Dataframe`. It accepts the following
165
+ values:
166
+
167
+ | Index Format | Columns |
168
+ |:------------:|:---------------------------------------------|
169
+ | `default` | stage, scenario, block or hour |
170
+ | `period` | year, month or week, scenario, block or hour |
171
+
172
+ * `default` creates a `pandas.DataFrame` with the columns as they are stored in the original file.
173
+ * `period` converts `stage` into `year`, `month` or `week` depending on the stage type of the file and the
174
+ initial year and stage.
175
+
176
+
177
+ ### Filter by stage, scenario, block and/or agent
178
+
179
+ The `load_as_dataframe` function accepts the following optional keyword arguments to filter data:
180
+
181
+ | Argument | Description |
182
+ |:------------------:|:------------------------------------------------------------------------------|
183
+ | `filter_stages` | List of stages to be included |
184
+ | `filter_blocks` | List of blocks to be included |
185
+ | `filter_scenarios` | List of scenarios to be included |
186
+ | `filter_agents` | List of agents to be included. The name is case insensitive. |
187
+
188
+ An empty list or unspecified means all stages, blocks, scenarios or agents will be included.
189
+
190
+ Example:
191
+ ```python
192
+ import psr.graf
193
+
194
+ df = psr.graf.load_as_dataframe(
195
+ "sample_data/gerter.hdr",
196
+ filter_stages=[
197
+ 8,
198
+ ],
199
+ filter_scenarios=[
200
+ 10,
201
+ ],
202
+ filter_agents=["Thermal 3", "Thermal 2"],
203
+ )
204
+ print(df.head())
205
+ ```
206
+
207
+ Outputs:
208
+ ```
209
+ Thermal 3 Thermal 2
210
+ stage scenario block
211
+ 8 10 1 8.030441 0.0
212
+ ```
213
+
214
+
215
+
216
+ Usage Samples
217
+ -------------
218
+
219
+ ### [`dataframes_sample.py`](https://github.com/psrenergy/pygraf/blob/main/dataframes_sample.py)
220
+
221
+ Shows how to read data into `pandas.DataFrame`s.
222
+
223
+ Requires `pandas` package installed.
224
+
225
+
226
+ ### [`matplotlib_sample.py`](https://github.com/psrenergy/pygraf/blob/main/matplotlib_sample.py)
227
+
228
+ Shows how to read data and plot data from hdr/bin file pairs.
229
+
230
+ ![](https://raw.githubusercontent.com/psrenergy/pygraf/main/docs/matplotlib_sample_plot.png)
231
+
232
+ Requires `matplotlib` package installed.
233
+
234
+
235
+
236
+ ### [`csv_sample.py`](https://github.com/psrenergy/pygraf/blob/main/csv_sample.py)
237
+
238
+ Shows how to convert from hdr/bin file pairs to csv using `psr.graf` module.
239
+
240
+ This script can also be called from command line:
241
+
242
+ ```bat
243
+ python csv_sample.py input_file.hdr output_file.csv
244
+ ```
245
+
246
+ Where `output_file.csv` is optional.
247
+
248
+
249
+ ### [`parquet_sample.py`](https://github.com/psrenergy/pygraf/blob/main/parquet_sample.py)
250
+
251
+ Shows how to convert from hdr/bin file pairs to Apache Parquet format.
252
+
253
+ Requires `pyarrow` package installed.
254
+
255
+ This script can also be called from command line:
256
+
257
+ ```bat
258
+ python parquet_sample.py input_file.hdr output_file.parquet
259
+ ```
260
+
261
+ Where `output_file.parquet` is optional.
262
+
263
+
264
+
265
+ Development
266
+ -----------
267
+
268
+ The project uses [uv](https://docs.astral.sh/uv/) for development. To set up an environment with the
269
+ development dependencies, run the tests, and lint and format the code:
270
+
271
+ ```
272
+ uv sync
273
+ uv run pytest
274
+ uv run ruff check
275
+ uv run ruff format
276
+ ```
277
+
278
+ To build the source distribution and wheel into `dist/`:
279
+
280
+ ```
281
+ uv build
282
+ ```
283
+
284
+
285
+ ### Releasing
286
+
287
+ Update `__version__` in `psr/graf/graf.py`, then push a tag with the same version:
288
+
289
+ ```
290
+ git tag 2.3.0
291
+ git push origin 2.3.0
292
+ ```
293
+
294
+ PyPI never accepts a version that was uploaded before, even after it is deleted, so versions like 2.2.0
295
+ cannot be used again.
296
+
297
+ The [Publish](.github/workflows/publish.yml) workflow runs the tests, builds the package, and
298
+ uploads it to PyPI using [trusted publishing](https://docs.pypi.org/trusted-publishers/).
299
+
300
+
301
+ Issues and Support
302
+ ------------------
303
+
304
+ Check PyGraf's [GitHub repository](https://github.com/psrenergy/pygraf/) and [issues page](https://github.com/psrenergy/pygraf/issues) for support.
305
+
306
+
307
+ License
308
+ -------
309
+
310
+ PyGraf is licensed under the [MIT License](https://github.com/psrenergy/pygraf/blob/main/LICENSE).
@@ -0,0 +1 @@
1
+ from .graf import * # noqa: F403