flixopt 2.1.6__tar.gz → 2.1.8__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.

Potentially problematic release.


This version of flixopt might be problematic. Click here for more details.

Files changed (116) hide show
  1. {flixopt-2.1.6 → flixopt-2.1.8}/.github/CONTRIBUTING.md +2 -2
  2. flixopt-2.1.8/.github/ISSUE_TEMPLATE/bug_report.yml +111 -0
  3. flixopt-2.1.8/.github/ISSUE_TEMPLATE/config.yml +14 -0
  4. flixopt-2.1.8/.github/ISSUE_TEMPLATE/feature_request.yml +127 -0
  5. flixopt-2.1.8/.github/ISSUE_TEMPLATE/general_issue.yml +40 -0
  6. {flixopt-2.1.6 → flixopt-2.1.8}/.github/pull_request_template.md +3 -3
  7. flixopt-2.1.8/.github/workflows/python-app.yaml +385 -0
  8. flixopt-2.1.8/.pre-commit-config.yaml +16 -0
  9. {flixopt-2.1.6 → flixopt-2.1.8}/CHANGELOG.md +70 -3
  10. {flixopt-2.1.6/flixopt.egg-info → flixopt-2.1.8}/PKG-INFO +64 -53
  11. {flixopt-2.1.6 → flixopt-2.1.8}/README.md +15 -10
  12. {flixopt-2.1.6 → flixopt-2.1.8}/docs/SUMMARY.md +3 -3
  13. flixopt-2.1.8/docs/contribute.md +45 -0
  14. {flixopt-2.1.6 → flixopt-2.1.8}/docs/examples/00-Minimal Example.md +1 -1
  15. {flixopt-2.1.6 → flixopt-2.1.8}/docs/examples/01-Basic Example.md +1 -1
  16. {flixopt-2.1.6 → flixopt-2.1.8}/docs/examples/02-Complex Example.md +1 -1
  17. {flixopt-2.1.6 → flixopt-2.1.8}/docs/examples/index.md +1 -1
  18. {flixopt-2.1.6/docs → flixopt-2.1.8/docs/faq}/contribute.md +26 -14
  19. {flixopt-2.1.6 → flixopt-2.1.8}/docs/faq/index.md +1 -1
  20. {flixopt-2.1.6 → flixopt-2.1.8}/docs/getting-started.md +2 -2
  21. {flixopt-2.1.6 → flixopt-2.1.8}/docs/javascripts/mathjax.js +1 -1
  22. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/Bus.md +1 -1
  23. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +21 -21
  24. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/Flow.md +3 -3
  25. flixopt-2.1.8/docs/user-guide/Mathematical Notation/InvestParameters.md +3 -0
  26. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/LinearConverter.md +5 -5
  27. flixopt-2.1.8/docs/user-guide/Mathematical Notation/OnOffParameters.md +3 -0
  28. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
  29. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/Storage.md +2 -2
  30. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/Mathematical Notation/index.md +1 -1
  31. flixopt-2.1.8/docs/user-guide/Mathematical Notation/others.md +3 -0
  32. {flixopt-2.1.6 → flixopt-2.1.8}/docs/user-guide/index.md +2 -2
  33. {flixopt-2.1.6 → flixopt-2.1.8}/examples/01_Simple/simple_example.py +0 -1
  34. {flixopt-2.1.6 → flixopt-2.1.8}/examples/02_Complex/complex_example_results.py +0 -4
  35. {flixopt-2.1.6 → flixopt-2.1.8}/examples/03_Calculation_types/example_calculation_types.py +5 -8
  36. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/__init__.py +4 -0
  37. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/aggregation.py +33 -32
  38. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/calculation.py +161 -65
  39. flixopt-2.1.8/flixopt/components.py +1279 -0
  40. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/config.py +17 -8
  41. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/core.py +69 -60
  42. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/effects.py +146 -64
  43. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/elements.py +297 -110
  44. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/features.py +78 -71
  45. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/flow_system.py +72 -50
  46. flixopt-2.1.8/flixopt/interface.py +1104 -0
  47. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/io.py +15 -10
  48. flixopt-2.1.8/flixopt/linear_converters.py +623 -0
  49. flixopt-2.1.8/flixopt/network_app.py +791 -0
  50. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/plotting.py +215 -87
  51. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/results.py +382 -209
  52. flixopt-2.1.8/flixopt/solvers.py +81 -0
  53. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/structure.py +41 -39
  54. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/utils.py +10 -7
  55. {flixopt-2.1.6 → flixopt-2.1.8/flixopt.egg-info}/PKG-INFO +64 -53
  56. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt.egg-info/SOURCES.txt +5 -0
  57. flixopt-2.1.8/flixopt.egg-info/requires.txt +60 -0
  58. {flixopt-2.1.6 → flixopt-2.1.8}/mkdocs.yml +1 -2
  59. {flixopt-2.1.6 → flixopt-2.1.8}/pyproject.toml +79 -47
  60. flixopt-2.1.8/renovate.json +23 -0
  61. {flixopt-2.1.6 → flixopt-2.1.8}/scripts/extract_release_notes.py +5 -5
  62. {flixopt-2.1.6 → flixopt-2.1.8}/scripts/gen_ref_pages.py +1 -1
  63. {flixopt-2.1.6 → flixopt-2.1.8}/tests/conftest.py +14 -4
  64. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_bus.py +26 -19
  65. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_component.py +55 -37
  66. flixopt-2.1.8/tests/test_effect.py +163 -0
  67. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_examples.py +1 -2
  68. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_flow.py +101 -77
  69. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_integration.py +1 -3
  70. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_io.py +1 -2
  71. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_linear_converter.py +62 -118
  72. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_network_app.py +1 -6
  73. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_on_hours_computation.py +50 -47
  74. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_plots.py +4 -6
  75. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_results_plots.py +2 -0
  76. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_storage.py +49 -58
  77. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_timeseries.py +1 -3
  78. {flixopt-2.1.6 → flixopt-2.1.8}/tests/todos.txt +2 -2
  79. flixopt-2.1.6/.github/ISSUE_TEMPLATE/bug_report.yml +0 -81
  80. flixopt-2.1.6/.github/ISSUE_TEMPLATE/config.yml +0 -8
  81. flixopt-2.1.6/.github/ISSUE_TEMPLATE/feature_request.yml +0 -88
  82. flixopt-2.1.6/.github/workflows/python-app.yaml +0 -223
  83. flixopt-2.1.6/docs/faq/contribute.md +0 -49
  84. flixopt-2.1.6/docs/user-guide/Mathematical Notation/others.md +0 -3
  85. flixopt-2.1.6/flixopt/components.py +0 -746
  86. flixopt-2.1.6/flixopt/interface.py +0 -265
  87. flixopt-2.1.6/flixopt/linear_converters.py +0 -331
  88. flixopt-2.1.6/flixopt/network_app.py +0 -612
  89. flixopt-2.1.6/flixopt/solvers.py +0 -77
  90. flixopt-2.1.6/flixopt.egg-info/requires.txt +0 -55
  91. flixopt-2.1.6/tests/test_effect.py +0 -142
  92. {flixopt-2.1.6 → flixopt-2.1.8}/.gitignore +0 -0
  93. {flixopt-2.1.6 → flixopt-2.1.8}/LICENSE +0 -0
  94. {flixopt-2.1.6 → flixopt-2.1.8}/docs/examples/03-Calculation Modes.md +0 -0
  95. {flixopt-2.1.6 → flixopt-2.1.8}/docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
  96. {flixopt-2.1.6 → flixopt-2.1.8}/docs/images/architecture_flixOpt.png +0 -0
  97. {flixopt-2.1.6 → flixopt-2.1.8}/docs/images/flixopt-icon.svg +0 -0
  98. {flixopt-2.1.6 → flixopt-2.1.8}/docs/index.md +0 -0
  99. {flixopt-2.1.6 → flixopt-2.1.8}/examples/00_Minmal/minimal_example.py +0 -0
  100. {flixopt-2.1.6 → flixopt-2.1.8}/examples/02_Complex/complex_example.py +0 -0
  101. {flixopt-2.1.6 → flixopt-2.1.8}/examples/03_Calculation_types/Zeitreihen2020.csv +0 -0
  102. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/commons.py +0 -0
  103. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt/config.yaml +0 -0
  104. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt.egg-info/dependency_links.txt +0 -0
  105. {flixopt-2.1.6 → flixopt-2.1.8}/flixopt.egg-info/top_level.txt +0 -0
  106. {flixopt-2.1.6 → flixopt-2.1.8}/pics/architecture_flixOpt-pre2.0.0.png +0 -0
  107. {flixopt-2.1.6 → flixopt-2.1.8}/pics/architecture_flixOpt.png +0 -0
  108. {flixopt-2.1.6 → flixopt-2.1.8}/pics/flixOpt_plotting.jpg +0 -0
  109. {flixopt-2.1.6 → flixopt-2.1.8}/pics/flixopt-icon.svg +0 -0
  110. {flixopt-2.1.6 → flixopt-2.1.8}/pics/pics.pptx +0 -0
  111. {flixopt-2.1.6 → flixopt-2.1.8}/setup.cfg +0 -0
  112. {flixopt-2.1.6 → flixopt-2.1.8}/tests/__init__.py +0 -0
  113. {flixopt-2.1.6 → flixopt-2.1.8}/tests/ressources/Zeitreihen2020.csv +0 -0
  114. {flixopt-2.1.6 → flixopt-2.1.8}/tests/run_all_tests.py +0 -0
  115. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_dataconverter.py +0 -0
  116. {flixopt-2.1.6 → flixopt-2.1.8}/tests/test_functional.py +0 -0
@@ -58,7 +58,7 @@ def create_storage(
58
58
  ) -> Storage:
59
59
  """
60
60
  Create a battery storage component.
61
-
61
+
62
62
  Args:
63
63
  label: Unique identifier
64
64
  capacity_kwh: Storage capacity [kWh]
@@ -82,4 +82,4 @@ def create_storage(
82
82
 
83
83
  ---
84
84
 
85
- **Every contribution helps advance sustainable energy solutions! 🌱⚡**
85
+ **Every contribution helps advance sustainable energy solutions! 🌱⚡**
@@ -0,0 +1,111 @@
1
+ name: 🐛 Bug Report
2
+ description: Report a bug in flixopt
3
+ title: "[BUG] "
4
+ labels: ["type: bug"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for taking the time to fill out this bug report!
10
+
11
+ **Before submitting**: Please search [existing issues](https://github.com/flixOpt/flixopt/issues) to avoid duplicates.
12
+
13
+ - type: checkboxes
14
+ id: checks
15
+ attributes:
16
+ label: Version Confirmation
17
+ description: Please confirm you can reproduce this on a supported version
18
+ options:
19
+ - label: I have confirmed this bug exists on the latest [release](https://github.com/flixOpt/flixopt/releases) of FlixOpt
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: problem
24
+ attributes:
25
+ label: Bug Description
26
+ description: Clearly describe what went wrong
27
+ placeholder: |
28
+ What happened? What did you expect to happen instead?
29
+
30
+ Include any error messages or unexpected outputs.
31
+ validations:
32
+ required: true
33
+
34
+ - type: textarea
35
+ id: example
36
+ attributes:
37
+ label: Minimal Reproducible Example
38
+ description: |
39
+ Provide the smallest possible code example that reproduces the bug.
40
+ See [how to create minimal bug reports](https://matthewrocklin.com/minimal-bug-reports).
41
+ placeholder: |
42
+ import flixopt as fx
43
+ import pandas as pd
44
+
45
+ # Minimal example that reproduces the bug
46
+ timesteps = pd.date_range('2024-01-01', periods=24, freq='h')
47
+ flow_system = fx.FlowSystem(timesteps)
48
+
49
+ # Add components that trigger the bug...
50
+
51
+ # Show the problematic operation
52
+ result = flow_system.solve() # This should fail/behave unexpectedly
53
+ render: python
54
+ validations:
55
+ required: true
56
+
57
+ - type: textarea
58
+ id: error-output
59
+ attributes:
60
+ label: Error Output
61
+ description: If there's an error message, paste the full traceback here
62
+ render: shell
63
+
64
+ - type: dropdown
65
+ id: solver
66
+ attributes:
67
+ label: Solver Used
68
+ description: Which solver were you using?
69
+ options:
70
+ - HiGHS (default)
71
+ - Gurobi
72
+ - CPLEX
73
+ - GLPK
74
+ - CBC
75
+ - Other (specify below)
76
+ validations:
77
+ required: true
78
+
79
+ - type: input
80
+ id: os
81
+ attributes:
82
+ label: Operating System
83
+ placeholder: "e.g., Windows 11, macOS 14.2, Ubuntu 22.04"
84
+ validations:
85
+ required: true
86
+
87
+ - type: input
88
+ id: python-version
89
+ attributes:
90
+ label: Python Version
91
+ placeholder: "e.g., 3.11.5"
92
+ validations:
93
+ required: true
94
+
95
+ - type: textarea
96
+ id: environment
97
+ attributes:
98
+ label: Environment Info
99
+ description: |
100
+ Run one of these commands and paste the output:
101
+ - `pip freeze`
102
+ - `conda env export`
103
+ render: shell
104
+ value: >
105
+ <details>
106
+
107
+ ```
108
+ Replace this with your environment info
109
+ ```
110
+
111
+ </details>
@@ -0,0 +1,14 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: 🤔 Modeling Questions
4
+ url: https://github.com/flixOpt/flixopt/discussions/categories/q-a
5
+ about: "How to model specific energy systems, components, and constraints"
6
+ - name: ⚡ Performance & Optimization
7
+ url: https://github.com/flixOpt/flixopt/discussions/categories/performance
8
+ about: "Solver performance, memory usage, and optimization speed issues"
9
+ - name: 💡 Ideas & Suggestions
10
+ url: https://github.com/flixOpt/flixopt/discussions/categories/ideas
11
+ about: "Share ideas and discuss potential improvements with the community"
12
+ - name: 📖 Documentation
13
+ url: https://flixopt.github.io/flixopt/latest/
14
+ about: "Browse guides, API reference, and examples"
@@ -0,0 +1,127 @@
1
+ name: ✨ Feature Request
2
+ description: Suggest a new feature or enhancement
3
+ title: "[FEATURE] "
4
+ labels: ["type: feature"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for suggesting a new feature!
10
+
11
+ **Before submitting**: Please search [existing issues](https://github.com/flixOpt/flixopt/issues) and check our [roadmap](https://github.com/flixOpt/flixopt/discussions) to avoid duplicates.
12
+
13
+ - type: checkboxes
14
+ id: checks
15
+ attributes:
16
+ label: Prerequisites
17
+ options:
18
+ - label: I have searched existing issues and discussions
19
+ required: true
20
+ - label: I have checked the [documentation](https://flixopt.github.io/flixopt/latest/)
21
+ required: true
22
+
23
+ - type: dropdown
24
+ id: feature-type
25
+ attributes:
26
+ label: Feature Category
27
+ description: What type of feature is this?
28
+ options:
29
+ - New Component (storage, generation, conversion, etc.)
30
+ - Enhancement to Existing Component
31
+ - New Optimization Feature
32
+ - Data Input/Output Improvement
33
+ - Results/Visualization Enhancement
34
+ - Performance/Solver Improvement
35
+ - API/Usability Improvement
36
+ - Documentation/Examples
37
+ - Other
38
+ validations:
39
+ required: true
40
+
41
+ - type: textarea
42
+ id: problem
43
+ attributes:
44
+ label: Problem Statement
45
+ description: What problem would this feature solve?
46
+ placeholder: |
47
+ Current limitation: "FlixOpt doesn't support [specific energy system component/feature]..."
48
+
49
+ Impact: "This prevents users from modeling [specific scenarios]..."
50
+
51
+ - type: textarea
52
+ id: solution
53
+ attributes:
54
+ label: Proposed Solution
55
+ description: Describe your proposed solution in detail
56
+ placeholder: |
57
+ I propose adding a new component/feature that would...
58
+
59
+ Key capabilities:
60
+ - Feature 1
61
+ - Feature 2
62
+ - Feature 3
63
+ validations:
64
+ required: true
65
+
66
+ - type: textarea
67
+ id: use-case
68
+ attributes:
69
+ label: Use Case & Examples
70
+ description: Provide concrete examples of how this would be used
71
+ placeholder: |
72
+ Real-world scenario: "I'm modeling a microgrid with battery storage and need to..."
73
+
74
+ Specific requirements:
75
+ - Must handle [specific constraint]
76
+ - Should support [specific behavior]
77
+ - Would benefit [specific user group]
78
+ validations:
79
+ required: true
80
+
81
+ - type: textarea
82
+ id: code-example
83
+ attributes:
84
+ label: Desired API (Optional)
85
+ description: Show how you'd like to use this feature
86
+ placeholder: |
87
+ # Example of proposed API
88
+ component = fx.NewComponent(
89
+ label='example',
90
+ parameter1=value1,
91
+ parameter2=value2
92
+ )
93
+
94
+ flow_system.add_component(component)
95
+ render: python
96
+
97
+ - type: textarea
98
+ id: alternatives
99
+ attributes:
100
+ label: Alternatives Considered
101
+ description: What workarounds or alternatives have you tried?
102
+ placeholder: |
103
+ Current workaround: "I'm currently using [existing component] but it doesn't support..."
104
+
105
+ Other approaches considered: "I looked into [alternative] but..."
106
+
107
+ - type: dropdown
108
+ id: priority
109
+ attributes:
110
+ label: Priority/Impact
111
+ description: How important is this feature for your work?
112
+ options:
113
+ - Critical - Blocking important work
114
+ - High - Would significantly improve workflow
115
+ - Medium - Nice to have enhancement
116
+ - Low - Minor improvement
117
+
118
+ - type: textarea
119
+ id: additional-context
120
+ attributes:
121
+ label: Additional Context
122
+ description: References, papers, examples from other tools, etc.
123
+ placeholder: |
124
+ References:
125
+ - Research paper: [Title and link]
126
+ - Similar feature in [other tool]: [description]
127
+ - Industry standard: [description]
@@ -0,0 +1,40 @@
1
+ name: 📝 General Issue
2
+ description: For issues that don't fit the specific templates below
3
+ title: ""
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ **For specific issue types, please use the dedicated templates:**
9
+ - 🐛 **Bug Report** - Something is broken or not working as expected
10
+ - ✨ **Feature Request** - Suggest new functionality
11
+
12
+ **For other topics, consider using Discussions instead:**
13
+ - 🤔 [Modeling Questions](https://github.com/flixOpt/flixopt/discussions/categories/q-a) - How to model specific energy systems
14
+ - ⚡ [Performance Help](https://github.com/flixOpt/flixopt/discussions/categories/performance) - Optimization speed and memory issues
15
+
16
+ - type: textarea
17
+ id: issue-description
18
+ attributes:
19
+ label: Issue Description
20
+ description: Describe your issue, question, or concern
21
+ placeholder: |
22
+ Please describe:
23
+ - What you're trying to accomplish
24
+ - What's not working as expected
25
+ - Any relevant context or background
26
+ validations:
27
+ required: true
28
+
29
+ - type: textarea
30
+ id: context
31
+ attributes:
32
+ label: Additional Context
33
+ description: Code examples, environment details, error messages, etc.
34
+ placeholder: |
35
+ Optional: Add any relevant details like:
36
+ - Code snippets
37
+ - Error messages
38
+ - Environment info
39
+ - Screenshots
40
+ render: markdown
@@ -15,6 +15,6 @@ Closes #(issue number)
15
15
  - [ ] Existing tests still pass
16
16
 
17
17
  ## Checklist
18
- - [ ] My code follows the project style
19
- - [ ] I have updated documentation if needed
20
- - [ ] I have added tests for new functionality (if applicable)
18
+ - [x] My code follows the project style
19
+ - [x] I have updated documentation if needed
20
+ - [x] I have added tests for new functionality (if applicable)
@@ -0,0 +1,385 @@
1
+ name: Python Package CI/CD
2
+
3
+ on:
4
+ push:
5
+ branches: [main] # Only main branch
6
+ tags: ['v*.*.*']
7
+ pull_request:
8
+ branches: [main, dev]
9
+ types: [opened, synchronize, reopened]
10
+ paths-ignore:
11
+ - 'docs/**'
12
+ - '*.md'
13
+ - 'README*'
14
+ workflow_dispatch: # Allow manual triggering
15
+
16
+ # Set permissions for security
17
+ permissions:
18
+ contents: read
19
+
20
+ # Cancel previous runs on new push
21
+ concurrency:
22
+ group: ${{ github.workflow }}-${{ github.ref }}
23
+ cancel-in-progress: true
24
+
25
+ env:
26
+ PYTHON_VERSION: "3.11"
27
+
28
+ jobs:
29
+ lint:
30
+ runs-on: ubuntu-24.04
31
+ steps:
32
+ - name: Check out code
33
+ uses: actions/checkout@v5
34
+
35
+ - name: Set up uv
36
+ uses: astral-sh/setup-uv@v6
37
+ with:
38
+ version: "0.8.19"
39
+ enable-cache: true
40
+
41
+ - name: Set up Python
42
+ uses: actions/setup-python@v6
43
+ with:
44
+ python-version: ${{ env.PYTHON_VERSION }}
45
+
46
+ - name: Install Ruff
47
+ run: |
48
+ uvx ruff --version
49
+
50
+ - name: Run Ruff Linting
51
+ run: |
52
+ echo "::group::Ruff Linting"
53
+ uvx ruff check . --output-format=github
54
+ echo "::endgroup::"
55
+
56
+ - name: Run Ruff Formatting Check
57
+ run: |
58
+ echo "::group::Ruff Formatting"
59
+ uvx ruff format --check --diff .
60
+ echo "::endgroup::"
61
+
62
+ test:
63
+ runs-on: ubuntu-24.04
64
+ timeout-minutes: 30
65
+ needs: lint # Run tests only after linting passes
66
+ strategy:
67
+ fail-fast: false # Continue testing other Python versions if one fails
68
+ matrix:
69
+ python-version: ['3.10', '3.11', '3.12', '3.13']
70
+
71
+ steps:
72
+ - name: Check out code
73
+ uses: actions/checkout@v5
74
+
75
+ - name: Set up uv
76
+ uses: astral-sh/setup-uv@v6
77
+ with:
78
+ version: "0.8.19"
79
+ enable-cache: true
80
+
81
+ - name: Set up Python ${{ matrix.python-version }}
82
+ uses: actions/setup-python@v6
83
+ with:
84
+ python-version: ${{ matrix.python-version }}
85
+
86
+ - name: Install dependencies
87
+ run: |
88
+ uv pip install --system .[dev] "pytest-xdist==3.*"
89
+
90
+ - name: Run tests
91
+ run: pytest -v -p no:warnings --numprocesses=auto
92
+
93
+ security:
94
+ name: Security Scan
95
+ runs-on: ubuntu-24.04
96
+ needs: lint
97
+ steps:
98
+ - name: Check out code
99
+ uses: actions/checkout@v5
100
+
101
+ - name: Set up uv
102
+ uses: astral-sh/setup-uv@v6
103
+ with:
104
+ version: "0.8.19"
105
+ enable-cache: true
106
+
107
+ - name: Set up Python
108
+ uses: actions/setup-python@v6
109
+ with:
110
+ python-version: ${{ env.PYTHON_VERSION }}
111
+
112
+ - name: Run Bandit security scan
113
+ run: |
114
+ # Gate on HIGH severity & MEDIUM confidence; produce JSON artifact
115
+ uvx bandit -r flixopt/ -c pyproject.toml -f json -o bandit-report.json -q --severity-level high --confidence-level medium
116
+ # Human-readable output without affecting job status
117
+ uvx bandit -r flixopt/ -c pyproject.toml -q --exit-zero
118
+
119
+ - name: Upload security reports
120
+ uses: actions/upload-artifact@v4
121
+ if: always()
122
+ with:
123
+ name: security-report
124
+ path: bandit-report.json
125
+ retention-days: 30
126
+
127
+ create-release:
128
+ name: Create GitHub Release
129
+ runs-on: ubuntu-24.04
130
+ permissions:
131
+ contents: write
132
+ needs: [lint, test, security]
133
+ if: startsWith(github.ref, 'refs/tags/v')
134
+
135
+ steps:
136
+ - name: Checkout repository
137
+ uses: actions/checkout@v5
138
+ with:
139
+ fetch-depth: 0
140
+
141
+ - name: Set up uv
142
+ uses: astral-sh/setup-uv@v6
143
+ with:
144
+ version: "0.8.19"
145
+ enable-cache: true
146
+
147
+ - name: Set up Python
148
+ uses: actions/setup-python@v6
149
+ with:
150
+ python-version: ${{ env.PYTHON_VERSION }}
151
+
152
+ - name: Extract release notes
153
+ run: |
154
+ VERSION=${GITHUB_REF#refs/tags/v}
155
+ echo "Extracting release notes for version: $VERSION"
156
+ python scripts/extract_release_notes.py $VERSION > current_release_notes.md
157
+
158
+ - name: Create GitHub Release
159
+ uses: softprops/action-gh-release@v2
160
+ with:
161
+ body_path: current_release_notes.md
162
+ draft: false
163
+ prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
164
+ generate_release_notes: true
165
+ env:
166
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
167
+
168
+ publish-testpypi:
169
+ name: Publish to TestPyPI
170
+ runs-on: ubuntu-24.04
171
+ needs: [test, create-release] # Run after tests and release creation
172
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') # Only on tag push
173
+ environment:
174
+ name: testpypi
175
+ url: https://test.pypi.org/p/flixopt
176
+ env:
177
+ SKIP_TESTPYPI_UPLOAD: "false"
178
+
179
+ steps:
180
+ - name: Checkout repository
181
+ uses: actions/checkout@v5
182
+
183
+ - name: Set up uv
184
+ uses: astral-sh/setup-uv@v6
185
+ with:
186
+ version: "0.8.19"
187
+ enable-cache: true
188
+
189
+ - name: Set up Python
190
+ uses: actions/setup-python@v6
191
+ with:
192
+ python-version: ${{ env.PYTHON_VERSION }}
193
+
194
+ - name: Install dependencies
195
+ run: |
196
+ uv pip install --system twine
197
+
198
+ - name: Build the distribution
199
+ run: |
200
+ uv build
201
+
202
+ - name: Upload to TestPyPI
203
+ run: |
204
+ twine upload --repository-url https://test.pypi.org/legacy/ dist/* --verbose --skip-existing
205
+ env:
206
+ TWINE_USERNAME: __token__
207
+ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
208
+ TWINE_NON_INTERACTIVE: "1"
209
+
210
+ - name: Test install from TestPyPI
211
+ if: env.SKIP_TESTPYPI_UPLOAD != 'true'
212
+ run: |
213
+ set -Eeuo pipefail
214
+ # Create a temporary environment to test installation
215
+ uv venv test_env
216
+ source test_env/bin/activate
217
+
218
+ # Get project name from pyproject.toml (PEP 621)
219
+ PACKAGE_NAME=$(python - <<'PY'
220
+ import sys, tomllib, pathlib
221
+ data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
222
+ print(data["project"]["name"])
223
+ PY
224
+ )
225
+
226
+ # Extract version from git tag
227
+ VERSION=${GITHUB_REF#refs/tags/v}
228
+
229
+ # Wait and retry while TestPyPI indexes the package
230
+ INSTALL_SUCCESS=false
231
+ for d in 10 20 40 80 120; do
232
+ sleep "$d"
233
+ echo "Attempting to install $PACKAGE_NAME==$VERSION from TestPyPI (retry after ${d}s)..."
234
+
235
+ # Install specific version and verify it matches
236
+ if uv pip install --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "$PACKAGE_NAME==$VERSION" && \
237
+ python -c "from importlib.metadata import version; installed = version('$PACKAGE_NAME'); print(f'Installed: {installed}'); assert '$VERSION' == installed"; then
238
+ INSTALL_SUCCESS=true
239
+ break
240
+ fi
241
+ done
242
+
243
+ # Check if installation succeeded
244
+ if [ "$INSTALL_SUCCESS" = "false" ]; then
245
+ echo "ERROR: Failed to install $PACKAGE_NAME==$VERSION from TestPyPI after all retries"
246
+ echo "This could indicate:"
247
+ echo " - TestPyPI indexing issues"
248
+ echo " - Package upload problems"
249
+ echo " - Version mismatch between tag and package"
250
+ exit 1
251
+ fi
252
+
253
+ # Final success confirmation
254
+ python -c "import flixopt; print('TestPyPI installation successful!')"
255
+
256
+ publish-pypi:
257
+ name: Publish to PyPI
258
+ runs-on: ubuntu-24.04
259
+ needs: [publish-testpypi] # Only run after TestPyPI publish succeeds
260
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') # Only on tag push
261
+ environment:
262
+ name: pypi
263
+ url: https://pypi.org/p/flixopt
264
+
265
+ steps:
266
+ - name: Checkout repository
267
+ uses: actions/checkout@v5
268
+
269
+ - name: Set up uv
270
+ uses: astral-sh/setup-uv@v6
271
+ with:
272
+ version: "0.8.19"
273
+ enable-cache: true
274
+
275
+ - name: Set up Python
276
+ uses: actions/setup-python@v6
277
+ with:
278
+ python-version: ${{ env.PYTHON_VERSION }}
279
+
280
+ - name: Install dependencies
281
+ run: |
282
+ uv pip install --system twine
283
+
284
+ - name: Build the distribution
285
+ run: |
286
+ uv build
287
+
288
+ - name: Upload to PyPI
289
+ run: |
290
+ twine upload dist/* --verbose --skip-existing
291
+ env:
292
+ TWINE_USERNAME: __token__
293
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
294
+ TWINE_NON_INTERACTIVE: "1"
295
+
296
+ - name: Verify PyPI installation
297
+ run: |
298
+ set -Eeuo pipefail
299
+ # Create a temporary environment to test installation
300
+ uv venv prod_test_env
301
+ source prod_test_env/bin/activate
302
+
303
+ # Get project name from pyproject.toml (PEP 621)
304
+ PACKAGE_NAME=$(python - <<'PY'
305
+ import sys, tomllib, pathlib
306
+ data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
307
+ print(data["project"]["name"])
308
+ PY
309
+ )
310
+
311
+ # Extract version from git tag
312
+ VERSION=${GITHUB_REF#refs/tags/v}
313
+
314
+ # Wait and retry while PyPI indexes the package
315
+ INSTALL_SUCCESS=false
316
+ for d in 10 20 40 80 120; do
317
+ sleep "$d"
318
+ echo "Attempting to install $PACKAGE_NAME==$VERSION from PyPI (retry after ${d}s)..."
319
+
320
+ # Install specific version and verify it matches
321
+ if uv pip install "$PACKAGE_NAME==$VERSION" && \
322
+ python -c "from importlib.metadata import version; installed = version('$PACKAGE_NAME'); print(f'Installed: {installed}'); assert '$VERSION' == installed"; then
323
+ INSTALL_SUCCESS=true
324
+ break
325
+ fi
326
+ done
327
+
328
+ # Check if installation succeeded
329
+ if [ "$INSTALL_SUCCESS" = "false" ]; then
330
+ echo "ERROR: Failed to install $PACKAGE_NAME==$VERSION from PyPI after all retries"
331
+ echo "This could indicate:"
332
+ echo " - PyPI indexing issues"
333
+ echo " - Package upload problems"
334
+ echo " - Version mismatch between tag and package"
335
+ exit 1
336
+ fi
337
+
338
+ # Final success confirmation
339
+ python -c "import flixopt; print('PyPI installation successful!')"
340
+
341
+ deploy-docs:
342
+ name: Deploy Documentation
343
+ runs-on: ubuntu-24.04
344
+ permissions:
345
+ contents: write
346
+ needs: [publish-pypi] # Deploy docs after successful PyPI publishing
347
+ if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, 'rc')
348
+
349
+ steps:
350
+ - name: Checkout repository
351
+ uses: actions/checkout@v5
352
+ with:
353
+ fetch-depth: 0 # Fetch all history for proper versioning
354
+
355
+ - name: Set up uv
356
+ uses: astral-sh/setup-uv@v6
357
+ with:
358
+ version: "0.8.19"
359
+ enable-cache: true
360
+
361
+ - name: Set up Python
362
+ uses: actions/setup-python@v6
363
+ with:
364
+ python-version: ${{ env.PYTHON_VERSION }}
365
+
366
+ - name: Sync changelog to docs
367
+ run: |
368
+ cp CHANGELOG.md docs/changelog.md
369
+ echo "✅ Synced changelog to docs"
370
+
371
+ - name: Install documentation dependencies
372
+ run: |
373
+ uv pip install --system ".[docs]"
374
+
375
+ - name: Configure Git Credentials
376
+ run: |
377
+ git config user.name github-actions[bot]
378
+ git config user.email 41898282+github-actions[bot]@users.noreply.github.com
379
+
380
+ - name: Deploy docs
381
+ run: |
382
+ VERSION=${GITHUB_REF#refs/tags/v}
383
+ echo "Deploying docs after successful PyPI publish: $VERSION"
384
+ mike deploy --push --update-aliases $VERSION latest
385
+ mike set-default --push latest