diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..b46c31e5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,16 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = crlf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{yml,yaml}]
+indent_style = space
+indent_size = 2
diff --git a/.github/workflows/_build_package.yml b/.github/workflows/_build_package.yml
index e53ff158..7872bc74 100644
--- a/.github/workflows/_build_package.yml
+++ b/.github/workflows/_build_package.yml
@@ -46,7 +46,7 @@ jobs:
# python-version: '3.11'
# cache: 'pip' # cache pip dependencies
# - name: Install cibuildwheel
- # run: python -m pip install cibuildwheel==2.3.1
+ # run: python -m pip install cibuildwheel==2.16
# - name: Build wheels
# run: python -m cibuildwheel --output-dir wheels
# - uses: actions/upload-artifact@v3
diff --git a/.github/workflows/_code_quality.yml b/.github/workflows/_code_quality.yml
index 6eaf416b..f155ab83 100644
--- a/.github/workflows/_code_quality.yml
+++ b/.github/workflows/_code_quality.yml
@@ -13,7 +13,7 @@ jobs:
options: '--check --diff'
src: '.'
jupyter: true
- version: '>=23.9'
+ version: '==23.11'
ruff:
runs-on: ubuntu-latest
@@ -27,7 +27,7 @@ jobs:
- name: Install dependencies
run: pip install -r requirements.txt
- name: Install ruff
- run: pip install ruff>=0.0.290
+ run: pip install ruff==0.1.6
- name: Run ruff
run: ruff .
@@ -45,6 +45,6 @@ jobs:
pip install -r requirements.txt
pip install pytest
- name: Install pyright
- run: pip install pyright>=1.1.325
+ run: pip install pyright==1.1.336
- name: Run pyright
run: pyright .
diff --git a/.gitignore b/.gitignore
index ba4fef34..ba7ab27e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,17 +3,6 @@ __pycache__/
*.py[cod]
*$py.class
-#vi & emacs
-*~
-'#'*
-
-#packaging
-*.whl
-*.tar.gz
-
-#doxygen
-html/
-
# C extensions
*.so
@@ -139,15 +128,8 @@ dmypy.json
# Pyre type checker
.pyre/
-# zip
-*.zip
-*.ZIP
-*.tar.gz
-*.tgz
-*.tar.bz2
-*.tbz
-*.7z
-*.rar
+# PyCharm
+.idea
# modules
modules.txt
@@ -159,3 +141,4 @@ modules.txt
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
+
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 64ba7f8e..a4d80c37 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -10,6 +10,7 @@
"charliermarsh.ruff",
"sourcery.sourcery",
"njpwerner.autodocstring",
+ "editorconfig.editorconfig",
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 0f38dd24..e8c4d618 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,31 +7,35 @@
{
"name": "Debug Unit Test",
"type": "python",
- "request": "test",
+ "request": "launch",
"justMyCode": false,
+ "program": "${file}"
},
{
- "name": "Python: Current File",
+ "name": "Python: Current File, cwd = file dir, envFile",
"type": "python",
"request": "launch",
- "cwd": "${fileDirname}",
+ "cwd": "${fileDirname}", // working dir = dir where current file is
"program": "${file}",
"console": "integratedTerminal",
- "justMyCode": true,
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
- "name": "Python: Current File, cwd = workspace root folder",
+ "name": "Python: Current File, cwd = workspace root folder, envFile",
"type": "python",
"request": "launch",
- "cwd": "${workspaceFolder}",
+ "cwd": "${workspaceFolder}", // working dir = workspace (mvx) dir
"program": "${file}",
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder test_caseDict --inspect",
@@ -46,7 +50,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder test_caseDict_minimal_inspect --inspect",
@@ -61,7 +67,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "importSystemStructure test_import_OspSystemStructure.xml",
@@ -75,7 +83,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder test_caseDict_imported_test_graph",
@@ -90,7 +100,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder test_caseDict --graph",
@@ -105,7 +117,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "watchCosim test_caseDict",
@@ -120,7 +134,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder test_caseDict_minimal_set_int_for_real",
@@ -134,7 +150,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder house",
@@ -148,7 +166,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder variable_group --inspect",
@@ -163,7 +183,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "importSystemStructure variable_group OspSystemStructure.xml",
@@ -177,7 +199,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder variable_group",
@@ -191,7 +215,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "watchCosim variable_group",
@@ -206,7 +232,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "importSystemStructure variable_group OspSystemStructure_original.xml",
@@ -220,7 +248,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder variable_group --inspect multiple_connection_types",
@@ -235,7 +265,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder variable_group multiple_connection_types",
@@ -249,7 +281,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "importSystemStructure gunnerus-dp/control-system OspSystemStructure.xml (cwd=gunnerus-dp)",
@@ -263,7 +297,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "importSystemStructure gunnerus-dp/control-system OspSystemStructure.xml (cwd=control-system)",
@@ -277,7 +313,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder gunnerus-dp",
@@ -291,7 +329,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder gunnerus-dp --inspect",
@@ -306,7 +346,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "watchCosim gunnerus-dp",
@@ -321,7 +363,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "importSystemStructure spring_mass_damper",
@@ -335,7 +379,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder spring_mass_damper --inspect",
@@ -350,7 +396,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "ospCaseBuilder spring_mass_damper --graph",
@@ -365,7 +413,9 @@
"console": "integratedTerminal",
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
{
"name": "watchCosim spring_mass_damper",
@@ -381,7 +431,9 @@
"justMyCode": false,
"autoReload": {
"enable": true
- }
+ },
+ "justMyCode": false,
+ "envFile": "${workspaceFolder}/.env" // specify where .env file is
},
]
}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d434bc14..30f287f8 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,7 @@
{
+ "terminal.integrated.env.windows": {
+ "PYTHONPATH": "${workspaceFolder}/src"
+ },
"python.terminal.activateEnvInCurrentTerminal": true,
"python.languageServer": "Pylance",
"ruff.importStrategy": "fromEnvironment",
@@ -19,9 +22,11 @@
"python.analysis.diagnosticSeverityOverrides": {},
"python.analysis.indexing": true,
"python.analysis.autoImportCompletions": true,
+ "python.analysis.autoImportUserSymbols": true,
"python.analysis.inlayHints.variableTypes": false,
"python.analysis.inlayHints.functionReturnTypes": false,
"python.analysis.inlayHints.pytestParameters": true,
+ "python.terminal.executeInFileDir": true,
"python.analysis.packageIndexDepths": [
{
"name": "pandas",
diff --git a/README.md b/README.md
index a1d10fc2..88248ada 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ ospx supports
* watching the progress of cosim, and saving final simulation results as a pandas dataframe.
## Installation
+
```sh
pip install ospx
```
@@ -19,18 +20,18 @@ However, dictIO gets installed automatically with ospx.
ospx provides both an API for use inside Python as well as a CLI for shell execution of core functions.
Reading a caseDict file and building the case-specific OSP (co-)simulation configuration files:
-~~~py
+```py
from ospx import OspCaseBuilder
OspCaseBuilder.build('caseDict')
-~~~
+```
The above task can also be invoked from the command line, using the 'ospCaseBuilder' command line script installed with ospx:
-~~~sh
+```sh
ospCaseBuilder caseDict
-~~~
+```
-_For more examples and usage, please refer to [ospx's documentation][ospx_docs] on GitHub Pages._
+_For more examples and usage, please refer to [ospx's documentation][ospx_docs]._
## File Format
A caseDict is a file in dictIO dict file format used with farn.
@@ -41,44 +42,72 @@ _For a detailed documentation of the dictIO dict file format used by farn, see [
## Development Setup
-1. Install Python 3.9 or higher, i.e. [Python 3.9](https://www.python.org/downloads/release/python-3912/) or [Python 3.10](https://www.python.org/downloads/release/python-3104/)
+1. Install Python 3.9 or higher, i.e. [Python 3.10](https://www.python.org/downloads/release/python-3104/) or [Python 3.11](https://www.python.org/downloads/release/python-3114/)
2. Update pip and setuptools:
- ~~~sh
- $ python -m pip install --upgrade pip setuptools
- ~~~
+ ```sh
+ python -m pip install --upgrade pip setuptools
+ ```
-3. git clone the farn repository into your local development directory:
+3. git clone the dictIO repository into your local development directory:
- ~~~sh
+ ```sh
git clone https://github.com/dnv-opensource/ospx path/to/your/dev/ospx
- ~~~
+ ```
4. In the ospx root folder:
Create a Python virtual environment:
- ~~~sh
- $ python -m venv .venv
- ~~~
- Activate the virtual environment:
+
+ ```sh
+ python -m venv .venv
+ ```
+
+ Activate the virtual environment:
+
..on Windows:
- ~~~sh
+
+ ```sh
> .venv\Scripts\activate.bat
- ~~~
+ ```
+
..on Linux:
- ~~~sh
- $ source .venv/bin/activate
- ~~~
+
+ ```sh
+ source .venv/bin/activate
+ ```
+
Update pip and setuptools:
- ~~~sh
- $ python -m pip install --upgrade pip setuptools
- ~~~
- Install farn's dependencies:
- ~~~sh
- $ pip install -r requirements.txt
- ~~~
+ ```sh
+ (.venv) $ python -m pip install --upgrade pip setuptools
+ ```
+
+ Install ospx's dependencies:
+ ```sh
+ (.venv) $ pip install -r requirements-dev.txt
+ ```
+
+ This should return without errors.
+
+5. Setup your development environment to locate Python source codes:
+
+ For example, Visual Studio Code on Windows assumes the Python environment is specified in a `.env` file.
+ If you are developing and running the Python code from VSCode, make sure to create a `.env` file in the mypackage root folder with below content.
+ Set the path for `PROJ_DIR` to where your mypackage folder is on your system.
+ _Note_: `.env` is part of `.gitignore`, such that you do not commit your `.env` file to the repository.
+
+ ```ini
+ PROJ_DIR=
+ PYTHONPATH=${PROJ_DIR}/src
+ ```
+
+6. Test that the installation works (in the mypackage root folder):
+
+ ```sh
+ (.venv) $ pytest .
+ ```
## Meta
@@ -98,9 +127,9 @@ Distributed under the MIT license. See [LICENSE](LICENSE.md) for more informatio
1. Fork it ()
2. Create your branch (`git checkout -b myBranchName`)
-3. Commit your changes (`git commit -am 'place your commit message here'`)
-4. Push to the branch (`git push origin myBranchName`)
-5. Create a new Pull Request
+3. Commit your changes (e.g. `git commit -m 'place a descriptive commit message here'`)
+4. Push to the branch (e.g. `git push origin myBranchName`)
+5. Create a new Pull Request in GitHub
For your contribution, please make sure you follow the [STYLEGUIDE](STYLEGUIDE.md) before creating the Pull Request.
diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md
index 469d81e7..bc79f85f 100644
--- a/STYLEGUIDE.md
+++ b/STYLEGUIDE.md
@@ -1,8 +1,12 @@
# Style Guide
-All code shall be [black](https://pypi.org/project/black/) formatted.
+
+All code shall be [black](https://pypi.org/project/black/) formatted.
+
References, details as well as examples of bad/good styles and their respective reasoning can be found below.
+
## References
+
* [PEP-8](https://www.python.org/dev/peps/pep-0008/) (see also [pep8.org](https://pep8.org/))
* [PEP-257](https://www.python.org/dev/peps/pep-0257/)
* Python style guide by [theluminousmen.com](https://luminousmen.com/post/the-ultimate-python-style-guidelines)
@@ -12,54 +16,63 @@ References, details as well as examples of bad/good styles and their respective
* [flake8](https://flake8.pycqa.org/en/latest/)
* [black](https://pypi.org/project/black/)
-
## Code Layout
+
* Use 4 spaces instead of tabs
* Maximum line length is 88 characters (not 79 as proposed in [PEP-8](https://www.python.org/dev/peps/pep-0008/))
* 2 blank lines between classes and functions
* 1 blank line within class, between class methods
* Use blank lines for logic separation of functionality within functions/methods wherever it is justified
* No whitespace adjacent to parentheses, brackets, or braces
-~~~py
+
+```py
# Bad
spam( items[ 1 ], { key1 : arg1, key2 : arg2 }, )
# Good
spam(items[1], {key1: arg1, key2: arg2}, [])
-~~~
+```
+
* Surround operators with single whitespace on either side.
-~~~py
+
+```py
# Bad
x<1
# Good
x == 1
-~~~
+```
+
* Never end your lines with a semicolon, and do not use a semicolon to put two statements on the same line
* When branching, always start a new block on a new line
-~~~py
+
+```py
# Bad
if flag: return None
# Good
if flag:
return None
-~~~
+```
+
* Similarly to branching, do not write methods on one line in any case:
-~~~py
+
+```py
# Bad
def do_something(self): print("Something")
# Good
def do_something(self):
print("Something")
-~~~
-* Place a class's `__init__` function (the constructor) always at the beginning of the class
+```
+* Place a class's `__init__` function (the constructor) always at the beginning of the class
## Line Breaks
+
* If function arguments do not fit into the specified line length, move them to a new line with indentation
-~~~py
+
+```py
# Bad
def long_function_name(var_one, var_two, var_three,
var_four):
@@ -85,9 +98,11 @@ References, details as well as examples of bad/good styles and their respective
var_four,
):
print(var_one)
-~~~
+```
+
* Move concatenated logical conditions to new lines if the line does not fit the maximum line size. This will help you understand the condition by looking from top to bottom. Poor formatting makes it difficult to read and understand complex predicates.
-~~~py
+
+```py
# Good
if (
this_is_one_thing
@@ -97,9 +112,11 @@ References, details as well as examples of bad/good styles and their respective
and one_more_thing
):
do_something()
-~~~
+```
+
* Where binary operations stretch multiple lines, break lines before the binary operators, not thereafter
-~~~py
+
+```py
# Bad
GDP = (
private_consumption +
@@ -117,9 +134,11 @@ References, details as well as examples of bad/good styles and their respective
+ government_spending
+ (exports - imports)
)
-~~~
+```
+
* Chaining methods should be broken up on multiple lines for better readability
-~~~py
+
+```py
(
df.write.format("jdbc")
.option("url", "jdbc:postgresql:dbserver")
@@ -128,9 +147,11 @@ References, details as well as examples of bad/good styles and their respective
.option("password", "password")
.save()
)
-~~~
+```
+
* Add a trailing comma to sequences of items when the closing container token ], ), or } does not appear on the same line as the final element
-~~~py
+
+```py
# Bad
y = [
0,
@@ -157,29 +178,32 @@ References, details as well as examples of bad/good styles and their respective
'a': 1,
'b': 2, <- note the trailing comma
}
-~~~
-
+```
## String Formatting
+
* When quoting string literals, use double-quoted strings. When the string itself contains single or double quote characters, however, use the respective other one to avoid backslashes in the string. It improves readability.
* Use f-strings to format strings:
-~~~py
+
+```py
# Bad
print("Hello, %s. You are %s years old. You are a %s." % (name, age, profession))
# Good
print(f"Hello, {name}. You are {age} years old. You are a {profession}.")
-~~~
+```
+
* Use multiline strings, not \ , since it gets much more readable.
-~~~py
+
+```py
raise AttributeError(
"Here is a multiline error message with a very long first line "
"and a shorter second line."
)
-~~~
-
+```
## Naming Conventions
+
* For module names: `lowercase` .
Long module names can have words separated by underscores (`really_long_module_name.py`), but this is not required. Try to use the convention of nearby files.
* For class names: `CamelCase`
@@ -192,19 +216,22 @@ Long module names can have words separated by underscores (`really_long_module_n
* Names shall be clear about what a variable, class, or function contains or does. If you struggle to come up with a clear name, rethink your architecture: Often, the difficulty in finding a crisp name for something is a hint that separation of responsibilities can be improved. The solution then is less to agree on a name, but to start a round of refactoring: The name you're seeking often comes naturally then with refactoring to an improved architecture with clear responsibilities.
(see [SRP](https://en.wikipedia.org/wiki/Single-responsibility_principle), Single-Responsibilty Principle by Robert C. Martin)
-
## Named Arguments
+
* Use named arguments to improve readability and avoid mistakes introduced with future code maintenance
-~~~py
+
+```py
# Bad
urlget("[http://google.com](http://google.com/)", 20)
# Good
urlget("[http://google.com](http://google.com/)", timeout=20)
-~~~
+```
+
* Never use mutable objects as default arguments in Python. If an attribute in a class or a named parameter in a function is of a mutable data type (e.g. a list or dict), never set its default value in the declaration of an object but always set it to None first, and then only later assign the default value in the class's constructor, or the functions body, respectively. Sounds complicated? If you prefer the shortcut, the examples below are your friend.
If your are interested in the long story including the why‘s, read these discussions on [Reddit](https://old.reddit.com/r/Python/comments/opb7hm/do_not_use_mutable_objects_as_default_arguments/) and [Twitter](https://twitter.com/willmcgugan/status/1419616480971399171).
-~~~py
+
+```py
# Bad
class Foo:
items = []
@@ -235,25 +262,30 @@ If your are interested in the long story including the why‘s, read these discu
def some_function(x, y, items=None):
items = items or []
...
-~~~
-
+```
## Commenting
+
* First of all, if the code needs comments to clarify its work, you should think about refactoring it. The best comment to code is the code itself.
* Describe complex, possibly incomprehensible points and side effects in the comments
* Separate `#` and the comment with one whitespace
-~~~py
+
+```py
#bad comment
# good comment
-~~~
+```
+
* Use inline comments sparsely
* Where used, inline comments shall have 2 whitespaces before the `#` and one whitespace thereafter
-~~~py
+
+```py
x = y + z # inline comment
str1 = str2 + str3 # another inline comment
-~~~
+```
+
* If a piece of code is poorly understood, mark the piece with a `@TODO:` tag and your name to support future refactoring:
-~~~py
+
+```py
def get_ancestors_ids(self):
# @TODO: Do a cache reset while saving the category tree. CLAROS, YYYY-MM-DD
cache_name = f"{self._meta.model_name}_ancestors_{self.pk}"
@@ -265,20 +297,21 @@ If your are interested in the long story including the why‘s, read these discu
cache.set(cache_name, ids, timeout=3600)
return ids
-~~~
-
+```
## Type hints
+
* Use type hints in function signatures and module-scope variables. This is good documentation and can be used with linters for type checking and error checking. Use them whenever possible.
* Use pyi files to type annotate third-party or extension modules.
-
## Docstrings
+
* All Docstrings should be written in [Numpy](https://numpydoc.readthedocs.io/en/latest/format.html) format. For a good tutorial on Docstrings, see [Documenting Python Code: A Complete Guide](https://realpython.com/documenting-python-code)
* In a Docstring, summarize function/method behavior and document its arguments, return value(s), side effects, exceptions raised, and restrictions
* Wrap Docstrings with triple double quotes (""")
* The description of the arguments must be indented
-~~~py
+
+```py
def some_method(name, print=False):
"""This function does something
@@ -301,21 +334,23 @@ If your are interested in the long story including the why‘s, read these discu
"""
...
return 0
-~~~
+```
## Exceptions
+
* Raise specific exceptions and catch specific exceptions, such as KeyError, ValueError, etc.
* Do not raise or catch just Exception, except in rare cases where this is unavoidable, such as a try/except block on the top-level loop of some long-running process. For a good tutorial on why this matters, see [The Most Diabolical Python Antipattern](https://realpython.com/the-most-diabolical-python-antipattern/).
* Minimize the amount of code in a try/except block. The larger the body of the try,
the more likely that an exception will be raised by a line of code that you didn’t expect to raise an exception.
-
## Imports
+
* Avoid creating circular imports by importing modules more specialized than the one you are editing
* Relative imports are forbidden ([PEP-8](https://www.python.org/dev/peps/pep-0008/) only “highly discourages” them). Where absolutely needed, the `from future import absolute_import` syntax should be used (see [PEP-328](https://www.python.org/dev/peps/pep-0328/))
* Never use wildcard imports (`from import *`). Always be explicit about what you're importing. Namespaces make code easier to read, so use them.
* Break long imports using parentheses and indent by 4 spaces. Include the trailing comma after the last import and place the closing bracket on a separate line
-~~~py
+
+```py
from my_pkg.utils import (
some_utility_method_1,
some_utility_method_2,
@@ -323,12 +358,14 @@ If your are interested in the long story including the why‘s, read these discu
some_utility_method_4,
some_utility_method_5,
)
-~~~
+```
+
* Imports should be written in the following order, separated by a blank line:
1. build-in modules
2. third-party modules
3. local application/library specific imports
-~~~py
+
+```py
import logging
import os
import typing as T
@@ -339,22 +376,25 @@ If your are interested in the long story including the why‘s, read these discu
import my_package
import my_package.my_module
from my_package.my_module import my_function, MyClass
-~~~
+```
+
* Even if a Python file is intended to be used as executable / script file only, it shall still be importable as a module, and its import should not have any side effects. Its main functionality shall hence be in a `main()` function, so that the code can be imported as a module for testing or being reused in the future:
-~~~py
+
+```py
def main():
...
if __name__ == "__main__":
main()
-~~~
-
+```
## Unit-tests
+
* Use pytest as the preferred testing framework.
* The name of a test shall clearly express what is being tested.
* Each test should preferably check only one specific aspect.
-~~~py
+
+```py
# Bad
def test_smth():
result = f()
@@ -372,10 +412,10 @@ If your are interested in the long story including the why‘s, read these discu
def test_smth_values():
result = f()
assert set(result) == set(expected), f"Result should be {set(expected)}"
-~~~
+```
+## And finally: It is a bad idea to use
-## And finally: It is a bad idea to use..
* global variables.
* iterators where they can be replaced by vectorized operations.
* lambda where it is not required.
diff --git a/pyproject.toml b/pyproject.toml
index 1f96ce0b..1533511d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[build-system]
-requires = ["lxml", "setuptools", "wheel"]
+requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[tool.black]
@@ -70,6 +70,7 @@ exclude = [
"dist",
"**/__pycache__",
"./docs/source/conf.py",
+ "./venv",
]
extraPaths = ["./src"]
typeCheckingMode = "basic"
@@ -109,4 +110,5 @@ reportUntypedNamedTuple = "warning"
[tool.pytest.ini_options]
testpaths = "tests"
addopts = "--strict-markers"
-xfail_strict = true
\ No newline at end of file
+xfail_strict = true
+pythonpath = ["src"]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 5521e044..d9188985 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,14 +1,14 @@
pytest>=7.4
pytest-cov>=4.1
pytest-randomly>=3.15
-black[jupyter]>=23.9
-ruff>=0.0.290
-pyright==1.1.325
+black[jupyter]==23.11
+ruff==0.1.6
+pyright==1.1.336
Sphinx>=7.2
sphinx-argparse-cli>=1.11
myst-parser>=2.0
furo>=2023.9.10
-sourcery>=1.9.0
+sourcery==1.14
-r requirements.txt
-r requirements-types.txt
diff --git a/requirements-types.txt b/requirements-types.txt
index 73b93104..e4e98ed5 100644
--- a/requirements-types.txt
+++ b/requirements-types.txt
@@ -1,2 +1 @@
-types-lxml>=2023.3.28
-# lxml-stubs>=0.4.0
+types-lxml>=2023.10.21
diff --git a/requirements.txt b/requirements.txt
index 119412db..556bbf09 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
lxml>=4.9
-numpy>=1.24
+numpy>=1.26
pandas>=2.1
matplotlib>=3.8
graphviz>=0.20
diff --git a/setup.cfg b/setup.cfg
index cb0ff287..97da2c9b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -39,7 +39,7 @@ include_package_data = True
python_requires = >=3.9
install_requires =
lxml>=4.9
- numpy>=1.24
+ numpy>=1.26
pandas>=2.1
matplotlib>=3.8
graphviz>=0.20
@@ -83,6 +83,7 @@ envlist = py{39,310,311}-{linux,macos,windows}
# envlist = py{39,310,311}
[testenv]
+system_site_packages = True
deps =
pytest>=7.4
pytest-cov>=4.1
diff --git a/tests/conftest.py b/tests/conftest.py
index 18894619..ee1cad92 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,6 @@
+import logging
import os
+from glob import glob
from pathlib import Path
from shutil import rmtree
@@ -13,10 +15,15 @@ def chdir():
os.chdir(Path(__file__).parent.absolute() / "test_dicts")
-ospx_dirs = [
+@pytest.fixture(scope="package", autouse=True)
+def test_dir():
+ return Path(__file__).parent.absolute()
+
+
+output_dirs = [
"xyz",
]
-ospx_files = [
+output_files = [
"parsed*",
"*.xml",
"*.fmu",
@@ -28,11 +35,21 @@ def chdir():
]
-def _remove_ospx_dirs_and_files():
- for folder in ospx_dirs:
+@pytest.fixture(autouse=True)
+def default_setup_and_teardown(caplog: LogCaptureFixture):
+ _remove_output_dirs_and_files()
+ _create_test_fmu()
+ yield
+ # _remove_test_fmu()
+ _remove_output_dirs_and_files()
+
+
+def _remove_output_dirs_and_files():
+ for folder in output_dirs:
rmtree(folder, ignore_errors=True)
- for pattern in ospx_files:
- for file in Path(".").rglob(pattern):
+ for pattern in output_files:
+ for file in glob(pattern):
+ file = Path(file)
if not file.name.startswith("test_"):
file.unlink(missing_ok=True)
@@ -55,16 +72,12 @@ def _remove_test_fmu():
Path("test_fmu.fmu").unlink()
-@pytest.fixture(autouse=True)
-def default_setup_and_teardown(caplog: LogCaptureFixture):
- _remove_ospx_dirs_and_files()
- _create_test_fmu()
- yield
- # _remove_test_fmu()
- _remove_ospx_dirs_and_files()
-
-
@pytest.fixture(autouse=True)
def setup_logging(caplog: LogCaptureFixture):
caplog.set_level("WARNING")
caplog.clear()
+
+
+@pytest.fixture(autouse=True)
+def logger():
+ return logging.getLogger()