flixopt 2.1.6__tar.gz → 2.1.7__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 (109) hide show
  1. {flixopt-2.1.6 → flixopt-2.1.7}/.github/CONTRIBUTING.md +2 -2
  2. flixopt-2.1.7/.github/ISSUE_TEMPLATE/bug_report.yml +111 -0
  3. flixopt-2.1.7/.github/ISSUE_TEMPLATE/config.yml +14 -0
  4. flixopt-2.1.7/.github/ISSUE_TEMPLATE/feature_request.yml +127 -0
  5. flixopt-2.1.7/.github/ISSUE_TEMPLATE/general_issue.yml +40 -0
  6. {flixopt-2.1.6 → flixopt-2.1.7}/.github/pull_request_template.md +3 -3
  7. flixopt-2.1.7/.github/workflows/python-app.yaml +354 -0
  8. flixopt-2.1.7/.pre-commit-config.yaml +16 -0
  9. {flixopt-2.1.6 → flixopt-2.1.7}/CHANGELOG.md +35 -2
  10. {flixopt-2.1.6/flixopt.egg-info → flixopt-2.1.7}/PKG-INFO +22 -15
  11. {flixopt-2.1.6 → flixopt-2.1.7}/README.md +15 -10
  12. {flixopt-2.1.6 → flixopt-2.1.7}/docs/SUMMARY.md +2 -2
  13. flixopt-2.1.7/docs/contribute.md +45 -0
  14. {flixopt-2.1.6 → flixopt-2.1.7}/docs/examples/00-Minimal Example.md +1 -1
  15. {flixopt-2.1.6 → flixopt-2.1.7}/docs/examples/01-Basic Example.md +1 -1
  16. {flixopt-2.1.6 → flixopt-2.1.7}/docs/examples/02-Complex Example.md +1 -1
  17. {flixopt-2.1.6 → flixopt-2.1.7}/docs/examples/index.md +1 -1
  18. {flixopt-2.1.6/docs → flixopt-2.1.7/docs/faq}/contribute.md +26 -14
  19. {flixopt-2.1.6 → flixopt-2.1.7}/docs/faq/index.md +1 -1
  20. {flixopt-2.1.6 → flixopt-2.1.7}/docs/javascripts/mathjax.js +1 -1
  21. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/Bus.md +1 -1
  22. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +13 -13
  23. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/Flow.md +1 -1
  24. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/LinearConverter.md +2 -2
  25. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/Piecewise.md +1 -1
  26. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/Storage.md +1 -1
  27. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/Mathematical Notation/index.md +1 -1
  28. flixopt-2.1.7/docs/user-guide/Mathematical Notation/others.md +3 -0
  29. {flixopt-2.1.6 → flixopt-2.1.7}/docs/user-guide/index.md +2 -2
  30. {flixopt-2.1.6 → flixopt-2.1.7}/examples/02_Complex/complex_example_results.py +0 -1
  31. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/__init__.py +4 -0
  32. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/calculation.py +3 -7
  33. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/components.py +14 -4
  34. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/core.py +10 -6
  35. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/effects.py +2 -1
  36. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/elements.py +5 -3
  37. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/features.py +17 -13
  38. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/flow_system.py +4 -3
  39. flixopt-2.1.7/flixopt/network_app.py +755 -0
  40. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/structure.py +1 -3
  41. {flixopt-2.1.6 → flixopt-2.1.7/flixopt.egg-info}/PKG-INFO +22 -15
  42. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt.egg-info/SOURCES.txt +3 -0
  43. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt.egg-info/requires.txt +5 -4
  44. {flixopt-2.1.6 → flixopt-2.1.7}/mkdocs.yml +1 -1
  45. {flixopt-2.1.6 → flixopt-2.1.7}/pyproject.toml +13 -6
  46. flixopt-2.1.7/renovate.json +16 -0
  47. {flixopt-2.1.6 → flixopt-2.1.7}/scripts/extract_release_notes.py +5 -5
  48. {flixopt-2.1.6 → flixopt-2.1.7}/tests/conftest.py +14 -4
  49. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_bus.py +26 -14
  50. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_component.py +55 -35
  51. flixopt-2.1.7/tests/test_effect.py +168 -0
  52. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_flow.py +101 -76
  53. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_integration.py +1 -0
  54. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_io.py +1 -0
  55. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_linear_converter.py +62 -116
  56. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_on_hours_computation.py +48 -45
  57. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_results_plots.py +2 -0
  58. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_storage.py +49 -55
  59. {flixopt-2.1.6 → flixopt-2.1.7}/tests/todos.txt +2 -2
  60. flixopt-2.1.6/.github/ISSUE_TEMPLATE/bug_report.yml +0 -81
  61. flixopt-2.1.6/.github/ISSUE_TEMPLATE/config.yml +0 -8
  62. flixopt-2.1.6/.github/ISSUE_TEMPLATE/feature_request.yml +0 -88
  63. flixopt-2.1.6/.github/workflows/python-app.yaml +0 -223
  64. flixopt-2.1.6/docs/faq/contribute.md +0 -49
  65. flixopt-2.1.6/docs/user-guide/Mathematical Notation/others.md +0 -3
  66. flixopt-2.1.6/flixopt/network_app.py +0 -612
  67. flixopt-2.1.6/tests/test_effect.py +0 -142
  68. {flixopt-2.1.6 → flixopt-2.1.7}/.gitignore +0 -0
  69. {flixopt-2.1.6 → flixopt-2.1.7}/LICENSE +0 -0
  70. {flixopt-2.1.6 → flixopt-2.1.7}/docs/examples/03-Calculation Modes.md +0 -0
  71. {flixopt-2.1.6 → flixopt-2.1.7}/docs/getting-started.md +0 -0
  72. {flixopt-2.1.6 → flixopt-2.1.7}/docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
  73. {flixopt-2.1.6 → flixopt-2.1.7}/docs/images/architecture_flixOpt.png +0 -0
  74. {flixopt-2.1.6 → flixopt-2.1.7}/docs/images/flixopt-icon.svg +0 -0
  75. {flixopt-2.1.6 → flixopt-2.1.7}/docs/index.md +0 -0
  76. {flixopt-2.1.6 → flixopt-2.1.7}/examples/00_Minmal/minimal_example.py +0 -0
  77. {flixopt-2.1.6 → flixopt-2.1.7}/examples/01_Simple/simple_example.py +0 -0
  78. {flixopt-2.1.6 → flixopt-2.1.7}/examples/02_Complex/complex_example.py +0 -0
  79. {flixopt-2.1.6 → flixopt-2.1.7}/examples/03_Calculation_types/Zeitreihen2020.csv +0 -0
  80. {flixopt-2.1.6 → flixopt-2.1.7}/examples/03_Calculation_types/example_calculation_types.py +0 -0
  81. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/aggregation.py +0 -0
  82. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/commons.py +0 -0
  83. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/config.py +0 -0
  84. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/config.yaml +0 -0
  85. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/interface.py +0 -0
  86. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/io.py +0 -0
  87. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/linear_converters.py +0 -0
  88. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/plotting.py +0 -0
  89. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/results.py +0 -0
  90. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/solvers.py +0 -0
  91. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt/utils.py +0 -0
  92. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt.egg-info/dependency_links.txt +0 -0
  93. {flixopt-2.1.6 → flixopt-2.1.7}/flixopt.egg-info/top_level.txt +0 -0
  94. {flixopt-2.1.6 → flixopt-2.1.7}/pics/architecture_flixOpt-pre2.0.0.png +0 -0
  95. {flixopt-2.1.6 → flixopt-2.1.7}/pics/architecture_flixOpt.png +0 -0
  96. {flixopt-2.1.6 → flixopt-2.1.7}/pics/flixOpt_plotting.jpg +0 -0
  97. {flixopt-2.1.6 → flixopt-2.1.7}/pics/flixopt-icon.svg +0 -0
  98. {flixopt-2.1.6 → flixopt-2.1.7}/pics/pics.pptx +0 -0
  99. {flixopt-2.1.6 → flixopt-2.1.7}/scripts/gen_ref_pages.py +0 -0
  100. {flixopt-2.1.6 → flixopt-2.1.7}/setup.cfg +0 -0
  101. {flixopt-2.1.6 → flixopt-2.1.7}/tests/__init__.py +0 -0
  102. {flixopt-2.1.6 → flixopt-2.1.7}/tests/ressources/Zeitreihen2020.csv +0 -0
  103. {flixopt-2.1.6 → flixopt-2.1.7}/tests/run_all_tests.py +0 -0
  104. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_dataconverter.py +0 -0
  105. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_examples.py +0 -0
  106. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_functional.py +0 -0
  107. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_network_app.py +1 -1
  108. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_plots.py +0 -0
  109. {flixopt-2.1.6 → flixopt-2.1.7}/tests/test_timeseries.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,354 @@
1
+ name: Python Package CI/CD
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev] # Added develop branch
6
+ tags:
7
+ - 'v*.*.*' # Trigger on semantic version tags
8
+ pull_request:
9
+ branches: [main, dev, 'feature/*', 'hotfix/*', 'fix/*']
10
+ types: [opened, synchronize, reopened]
11
+ workflow_dispatch: # Allow manual triggering
12
+
13
+ # Set permissions for security
14
+ permissions:
15
+ contents: read
16
+
17
+ # Cancel previous runs on new push
18
+ concurrency:
19
+ group: ${{ github.workflow }}-${{ github.ref }}
20
+ cancel-in-progress: true
21
+
22
+ env:
23
+ PYTHON_VERSION: "3.11"
24
+
25
+ jobs:
26
+ lint:
27
+ runs-on: ubuntu-22.04
28
+ steps:
29
+ - name: Check out code
30
+ uses: actions/checkout@v5
31
+
32
+ - name: Set up Python
33
+ uses: actions/setup-python@v6
34
+ with:
35
+ python-version: ${{ env.PYTHON_VERSION }}
36
+ cache: 'pip'
37
+ cache-dependency-path: |
38
+ pyproject.toml
39
+
40
+ - name: Install Ruff
41
+ run: |
42
+ python -m pip install --upgrade pip
43
+ pip install "ruff==0.9.*"
44
+ ruff --version
45
+
46
+ - name: Run Ruff Linting
47
+ run: |
48
+ echo "::group::Ruff Linting"
49
+ ruff check . --output-format=github
50
+ echo "::endgroup::"
51
+
52
+ - name: Run Ruff Formatting Check
53
+ run: |
54
+ echo "::group::Ruff Formatting"
55
+ ruff format --check --diff .
56
+ echo "::endgroup::"
57
+
58
+ test:
59
+ runs-on: ubuntu-22.04
60
+ timeout-minutes: 30
61
+ needs: lint # Run tests only after linting passes
62
+ strategy:
63
+ fail-fast: false # Continue testing other Python versions if one fails
64
+ matrix:
65
+ python-version: ['3.10', '3.11', '3.12', '3.13']
66
+
67
+ steps:
68
+ - name: Check out code
69
+ uses: actions/checkout@v5
70
+
71
+ - name: Set up Python ${{ matrix.python-version }}
72
+ uses: actions/setup-python@v6
73
+ with:
74
+ python-version: ${{ matrix.python-version }}
75
+ cache: 'pip'
76
+ cache-dependency-path: |
77
+ pyproject.toml
78
+
79
+ - name: Install dependencies
80
+ run: |
81
+ python -m pip install --upgrade pip
82
+ pip install .[dev] pytest-xdist
83
+
84
+ - name: Run tests
85
+ run: pytest -v -p no:warnings --numprocesses=auto
86
+
87
+ security:
88
+ name: Security Scan
89
+ runs-on: ubuntu-latest
90
+ needs: lint
91
+ steps:
92
+ - name: Check out code
93
+ uses: actions/checkout@v5
94
+
95
+ - name: Set up Python
96
+ uses: actions/setup-python@v6
97
+ with:
98
+ python-version: ${{ env.PYTHON_VERSION }}
99
+ cache: 'pip'
100
+ cache-dependency-path: |
101
+ pyproject.toml
102
+
103
+ - name: Install security tools
104
+ run: |
105
+ python -m pip install --upgrade pip
106
+ pip install bandit[toml]
107
+
108
+ - name: Run Bandit security scan
109
+ run: |
110
+ # Gate on HIGH severity & MEDIUM confidence; produce JSON artifact
111
+ bandit -r flixopt/ -c pyproject.toml -f json -o bandit-report.json -q -lll -ii
112
+ # Human-readable output without affecting job status
113
+ bandit -r flixopt/ -c pyproject.toml -q --exit-zero
114
+
115
+ - name: Upload security reports
116
+ uses: actions/upload-artifact@v4
117
+ if: always()
118
+ with:
119
+ name: security-report
120
+ path: bandit-report.json
121
+ retention-days: 30
122
+
123
+ create-release:
124
+ name: Create GitHub Release
125
+ runs-on: ubuntu-latest
126
+ permissions:
127
+ contents: write
128
+ needs: [lint, test, security]
129
+ if: startsWith(github.ref, 'refs/tags/v')
130
+
131
+ steps:
132
+ - name: Checkout repository
133
+ uses: actions/checkout@v5
134
+ with:
135
+ fetch-depth: 0
136
+
137
+ - name: Set up Python
138
+ uses: actions/setup-python@v6
139
+ with:
140
+ python-version: ${{ env.PYTHON_VERSION }}
141
+
142
+ - name: Extract release notes
143
+ run: |
144
+ VERSION=${GITHUB_REF#refs/tags/v}
145
+ echo "Extracting release notes for version: $VERSION"
146
+ python scripts/extract_release_notes.py $VERSION > current_release_notes.md
147
+
148
+ - name: Create GitHub Release
149
+ uses: softprops/action-gh-release@v2
150
+ with:
151
+ body_path: current_release_notes.md
152
+ draft: false
153
+ prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
154
+ generate_release_notes: true
155
+ env:
156
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
157
+
158
+ publish-testpypi:
159
+ name: Publish to TestPyPI
160
+ runs-on: ubuntu-22.04
161
+ needs: [test, create-release] # Run after tests and release creation
162
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') # Only on tag push
163
+ environment:
164
+ name: testpypi
165
+ url: https://test.pypi.org/p/flixopt
166
+
167
+ steps:
168
+ - name: Checkout repository
169
+ uses: actions/checkout@v5
170
+
171
+ - name: Set up Python
172
+ uses: actions/setup-python@v6
173
+ with:
174
+ python-version: ${{ env.PYTHON_VERSION }}
175
+ cache: 'pip'
176
+ cache-dependency-path: |
177
+ pyproject.toml
178
+
179
+ - name: Install dependencies
180
+ run: |
181
+ python -m pip install --upgrade pip
182
+ pip install build setuptools wheel twine
183
+
184
+ - name: Build the distribution
185
+ run: |
186
+ python -m build
187
+
188
+ - name: Upload to TestPyPI
189
+ run: |
190
+ twine upload --repository-url https://test.pypi.org/legacy/ dist/* --verbose
191
+ env:
192
+ TWINE_USERNAME: __token__
193
+ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
194
+
195
+ - name: Test install from TestPyPI
196
+ run: |
197
+ # Create a temporary environment to test installation
198
+ python -m venv test_env
199
+ source test_env/bin/activate
200
+ # Get project name from pyproject.toml (PEP 621)
201
+ PACKAGE_NAME=$(python - <<'PY'
202
+ import sys, tomllib, pathlib
203
+ data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
204
+ print(data["project"]["name"])
205
+ PY
206
+ )
207
+ # Extract version from git tag
208
+ VERSION=${GITHUB_REF#refs/tags/v}
209
+ # Wait and retry while TestPyPI indexes the package
210
+ INSTALL_SUCCESS=false
211
+ for d in 15 30 60 120 180 360 720 1080; do
212
+ sleep "$d"
213
+ echo "Attempting to install $PACKAGE_NAME==$VERSION from TestPyPI (retry after ${d}s)..."
214
+ # Install specific version and verify it matches
215
+ if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "$PACKAGE_NAME==$VERSION" && \
216
+ python -c "from importlib.metadata import version; installed = version('$PACKAGE_NAME'); print(f'Installed: {installed}'); assert '$VERSION' == installed"; then
217
+ INSTALL_SUCCESS=true
218
+ break
219
+ fi
220
+ done
221
+
222
+ # Check if installation succeeded
223
+ if [ "$INSTALL_SUCCESS" = "false" ]; then
224
+ echo "ERROR: Failed to install $PACKAGE_NAME==$VERSION from TestPyPI after all retries"
225
+ echo "This could indicate:"
226
+ echo " - TestPyPI indexing issues"
227
+ echo " - Package upload problems"
228
+ echo " - Version mismatch between tag and package"
229
+ exit 1
230
+ fi
231
+
232
+ # Final success confirmation
233
+ python -c "import flixopt; print('TestPyPI installation successful!')"
234
+
235
+ publish-pypi:
236
+ name: Publish to PyPI
237
+ runs-on: ubuntu-22.04
238
+ needs: [publish-testpypi] # Only run after TestPyPI publish succeeds
239
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') # Only on tag push
240
+ environment:
241
+ name: pypi
242
+ url: https://pypi.org/p/flixopt
243
+
244
+ steps:
245
+ - name: Checkout repository
246
+ uses: actions/checkout@v5
247
+
248
+ - name: Set up Python
249
+ uses: actions/setup-python@v6
250
+ with:
251
+ python-version: ${{ env.PYTHON_VERSION }}
252
+ cache: 'pip'
253
+ cache-dependency-path: |
254
+ pyproject.toml
255
+
256
+ - name: Install dependencies
257
+ run: |
258
+ python -m pip install --upgrade pip
259
+ pip install build setuptools wheel twine
260
+
261
+ - name: Build the distribution
262
+ run: |
263
+ python -m build
264
+
265
+ - name: Upload to PyPI
266
+ run: |
267
+ twine upload dist/* --verbose
268
+ env:
269
+ TWINE_USERNAME: __token__
270
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
271
+
272
+ - name: Verify PyPI installation
273
+ run: |
274
+ # Create a temporary environment to test installation
275
+ python -m venv prod_test_env
276
+ source prod_test_env/bin/activate
277
+ # Get project name from pyproject.toml (PEP 621)
278
+ PACKAGE_NAME=$(python - <<'PY'
279
+ import sys, tomllib, pathlib
280
+ data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
281
+ print(data["project"]["name"])
282
+ PY
283
+ )
284
+ # Extract version from git tag
285
+ VERSION=${GITHUB_REF#refs/tags/v}
286
+ # Wait and retry while PyPI indexes the package
287
+ INSTALL_SUCCESS=false
288
+ for d in 5 10 15 30 60 120 180 360 720 1080; do
289
+ sleep "$d"
290
+ echo "Attempting to install $PACKAGE_NAME==$VERSION from PyPI (retry after ${d}s)..."
291
+ # Install specific version and verify it matches
292
+ if pip install "$PACKAGE_NAME==$VERSION" && \
293
+ python -c "from importlib.metadata import version; installed = version('$PACKAGE_NAME'); print(f'Installed: {installed}'); assert '$VERSION' == installed"; then
294
+ INSTALL_SUCCESS=true
295
+ break
296
+ fi
297
+ done
298
+
299
+ # Check if installation succeeded
300
+ if [ "$INSTALL_SUCCESS" = "false" ]; then
301
+ echo "ERROR: Failed to install $PACKAGE_NAME==$VERSION from PyPI after all retries"
302
+ echo "This could indicate:"
303
+ echo " - PyPI indexing issues"
304
+ echo " - Package upload problems"
305
+ echo " - Version mismatch between tag and package"
306
+ exit 1
307
+ fi
308
+
309
+ # Final success confirmation
310
+ python -c "import flixopt; print('PyPI installation successful!')"
311
+
312
+ deploy-docs:
313
+ name: Deploy Documentation
314
+ runs-on: ubuntu-22.04
315
+ permissions:
316
+ contents: write
317
+ needs: [publish-pypi] # Deploy docs after successful PyPI publishing
318
+ if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, 'rc')
319
+
320
+ steps:
321
+ - name: Checkout repository
322
+ uses: actions/checkout@v5
323
+ with:
324
+ fetch-depth: 0 # Fetch all history for proper versioning
325
+
326
+ - name: Set up Python
327
+ uses: actions/setup-python@v6
328
+ with:
329
+ python-version: ${{ env.PYTHON_VERSION }}
330
+ cache: 'pip'
331
+ cache-dependency-path: |
332
+ pyproject.toml
333
+
334
+ - name: Sync changelog to docs
335
+ run: |
336
+ cp CHANGELOG.md docs/changelog.md
337
+ echo "✅ Synced changelog to docs"
338
+
339
+ - name: Install documentation dependencies
340
+ run: |
341
+ python -m pip install --upgrade pip
342
+ pip install -e ".[docs]"
343
+
344
+ - name: Configure Git Credentials
345
+ run: |
346
+ git config user.name github-actions[bot]
347
+ git config user.email 41898282+github-actions[bot]@users.noreply.github.com
348
+
349
+ - name: Deploy docs
350
+ run: |
351
+ VERSION=${GITHUB_REF#refs/tags/v}
352
+ echo "Deploying docs after successful PyPI publish: $VERSION"
353
+ mike deploy --push --update-aliases $VERSION latest
354
+ mike set-default --push latest
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ exclude: ^mkdocs\.yml$ # Skip mkdocs.yml
9
+ - id: check-added-large-files
10
+
11
+ - repo: https://github.com/astral-sh/ruff-pre-commit
12
+ rev: v0.12.4
13
+ hooks:
14
+ - id: ruff-check
15
+ args: [ --fix ]
16
+ - id: ruff-format