diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5129b6c..aeb7c19 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,13 +24,13 @@ jobs: name: Build documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.5.0 + - uses: actions/checkout@v3.3.0 - - uses: actions/setup-python@v4.3.0 + - uses: actions/setup-python@v4.5.0 with: python-version: "3.10" - - uses: syphar/restore-virtualenv@v1.2 + - uses: syphar/restore-virtualenv@v1.3 id: cache-virtualenv with: requirement_files: requirements.txt @@ -45,7 +45,7 @@ jobs: run: pip install -r requirements.txt - name: Cache docs build directory - uses: actions/cache@v3.0.11 + uses: actions/cache@v3.2.5 if: env.USE_CACHE == 'true' with: path: docs/build/ @@ -53,7 +53,7 @@ jobs: - name: Cache animation nodes source code id: animation-nodes-source-code - uses: actions/cache@v3.0.11 + uses: actions/cache@v3.2.5 if: env.USE_CACHE == 'true' with: path: animation_nodes/ @@ -94,7 +94,7 @@ jobs: cp -r docs/build/html build_docs zip -r build_docs build_docs - - uses: actions/upload-artifact@v3.1.1 + - uses: actions/upload-artifact@v3.1.2 if: env.DEPLOY == 'true' with: name: build_docs @@ -106,9 +106,9 @@ jobs: needs: build if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags') steps: - - uses: actions/checkout@v2.5.0 + - uses: actions/checkout@v3.3.0 - - uses: actions/download-artifact@v3.0.1 + - uses: actions/download-artifact@v3.0.2 with: name: build_docs path: . diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 504574a..6c838bc 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -14,9 +14,9 @@ jobs: stylecheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.5.0 + - uses: actions/checkout@v3.3.0 - - uses: actions/setup-python@v4.3.0 + - uses: actions/setup-python@v4.5.0 with: python-version: "3.10" diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml new file mode 100644 index 0000000..cad34f3 --- /dev/null +++ b/.github/workflows/unit-testing.yml @@ -0,0 +1,46 @@ +name: Unit testing +on: + pull_request: # Run in pull requests + + workflow_dispatch: # Allow to run this workflow manually + + push: + branches: # Run when there is a push to master + - "master" + +jobs: + Unit-testing: + runs-on: ${{ matrix.os }} + strategy: + max-parallel: 4 + fail-fast: false + matrix: + blender-version: ["2.93.11", "3.4.1",] + os: ["ubuntu-latest", "windows-latest",] + env: + BLENDER_CACHE: ${{ github.workspace }}/cache # The place where blender releases are downloaded + BLENDER_VERSION: ${{ matrix.blender-version }} + steps: + - uses: actions/checkout@v3.3.0 + + - uses: actions/setup-python@v4.5.0 + with: + python-version: "3.10" + + - uses: syphar/restore-virtualenv@v1.3 + id: cache-virtualenv + with: + requirement_files: requirements.txt + + - name: Cache Blender release download + uses: actions/cache@v3.2.5 + with: + path: ${{ env.BLENDER_CACHE }} + key: ${{ matrix.os }}-blender-${{ matrix.blender-version }} + + - name: Install dependencies + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + run: pip install -r requirements.txt + + - name: Run test suite (Blender ${{ matrix.blender-version }}, ${{ matrix.os }}) + run: python -m scripts.test -b ${{ matrix.blender-version }} -os ${{ matrix.os }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a8bc5fe..55eaba4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,13 @@ docs/source/developers_manual/code/ animation_nodes/ # Cache +.pytest_cache/ **/__pycache__ +cache/ # Blender *.pyc -*.blend[1-9] \ No newline at end of file +*.blend[1-9] + +# Tests +local_addon/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fd6efc0..2bee47b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: flake8 additional_dependencies: [ - "flake8-quotes==3.3.1", + "flake8-quotes==3.3.2", ] - repo: https://github.com/codespell-project/codespell @@ -16,7 +16,7 @@ repos: ] - repo: https://github.com/pycqa/pydocstyle - rev: 6.1.1 + rev: 6.3.0 hooks: - id: pydocstyle exclude: quantum_nodes/lib/quantumblur.py diff --git a/docs/Makefile b/docs/Makefile index 2faefb6..e67895c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,4 +1,4 @@ -# Minimal makefile for Sphinx documentation +# Makefile for Sphinx documentation # You can set these variables from the command line, and also # from the environment for the first two. diff --git a/docs/make.bat b/docs/make.bat index 9534b01..90ec63a 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -4,28 +4,25 @@ pushd %~dp0 REM Command file for Sphinx documentation -if "%SPHINXBUILD%" == "" ( +if "%2" == "" ( set SPHINXBUILD=sphinx-build +) else ( + set SPHINXBUILD=%2 ) + set SOURCEDIR=source set BUILDDIR=build +set MODULE=quantum_nodes if "%1" == "" goto help -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) +move "../%MODULE%/__init__.py" "../%MODULE%/___init__.py" +powershell -ExecutionPolicy ByPass -command ". replace_matching_string_in_files.ps1 -find '@persistent' -replace '#@persistent';" %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +powershell -ExecutionPolicy ByPass -command ". replace_matching_string_in_files.ps1 -find '#@persistent' -replace '@persistent';" +move "../%MODULE%/___init__.py" "../%MODULE%/__init__.py" goto end :help diff --git a/docs/replace_matching_string_in_files.ps1 b/docs/replace_matching_string_in_files.ps1 new file mode 100644 index 0000000..9aa46c1 --- /dev/null +++ b/docs/replace_matching_string_in_files.ps1 @@ -0,0 +1,23 @@ +# Replace matching strings with another string in the given list of files +param( + [string]$folder = "../quantum_nodes/", + [string]$find = "@persistent", + [string]$replace = "#@persistent" +) + +[array]$files = Get-ChildItem -Path $folder -Include *.py -Recurse -Force | select -expand fullname + +function Find-And-Replace-Strings { + + param( + [array]$files, + [string]$find, + [string]$replace + ) + + ForEach ($file in $files) { + (Get-Content -Path $file -Raw) -replace $find, $replace | Set-Content -Path $file -NoNewLine + } +} + +Find-And-Replace-Strings $files $find $replace \ No newline at end of file diff --git a/docs/source/developers_manual/addon/index.rst b/docs/source/developers_manual/addon/index.rst deleted file mode 100644 index f8ba235..0000000 --- a/docs/source/developers_manual/addon/index.rst +++ /dev/null @@ -1,48 +0,0 @@ -Contribute to Quantum Nodes -=========================== - - -.. _dev-env-contrib-addon: - -Development environment -####################### - - -.. toctree:: - :maxdepth: 1 - :glob: - - linux - windows - mac - tools - - -.. _instructions-contrib-addon: - -Contribute -########## - - -.. note:: - Click `here `_ to learn about the forking workflow on Github. - - -* Fork our `git repository `_ -* Do your modifications -* Open a new `pull request `_ -* Wait for your modifications to be reviewed and accepted - - -.. _git-workflow-contrib-addon: - -Git workflow -############ - -.. image:: https://miro.medium.com/max/560/1*UH5ozOBwkaFhWA1mrJkVIQ.png - :alt: Git workflow - :align: center - :width: 80% - :class: img-rounded - -| \ No newline at end of file diff --git a/docs/source/developers_manual/addon/linux.rst b/docs/source/developers_manual/addon/linux.rst deleted file mode 100644 index f2ad7e4..0000000 --- a/docs/source/developers_manual/addon/linux.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _linux-contrib-addon: - -Linux -===== - - -.. _dev-env-dependencies-contrib-addon: - -Dependencies -############ - - -.. important:: - We recommend you to setup an anaconda environment and link it to Blender. - - -Install using ``pip install -r requirements.txt`` - - -.. _dev-env-ide-contrib-addon: - -IDE -### - -* We recommend you to use `Visual Studio Code `_. -* See :ref:`tools-dev-addon` for more information. \ No newline at end of file diff --git a/docs/source/developers_manual/addon/mac.rst b/docs/source/developers_manual/addon/mac.rst deleted file mode 100644 index b40cacd..0000000 --- a/docs/source/developers_manual/addon/mac.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _mac-contrib-addon: - -Mac -=== - -TODO \ No newline at end of file diff --git a/docs/source/developers_manual/addon/tools.rst b/docs/source/developers_manual/addon/tools.rst deleted file mode 100644 index ae2cc88..0000000 --- a/docs/source/developers_manual/addon/tools.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _tools-dev-addon: - -Tools -===== - -Here is a list of tools which will help you to develop. - -* :ref:`vs-code-extensions-addon` - * :ref:`blender-vscode` - - -.. _vs-code-extensions-addon: - -VSCode extensions -################# - - -.. _blender-vscode: - -Blender Development -******************* - - -.. note:: - Tools to simplify Blender development. Developed by `Jacques Lucke `_. - - -* Install `blender development `_ \ No newline at end of file diff --git a/docs/source/developers_manual/addon/windows.rst b/docs/source/developers_manual/addon/windows.rst deleted file mode 100644 index 4c170fb..0000000 --- a/docs/source/developers_manual/addon/windows.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _windows-contrib-addon: - -Windows -======= - -TODO \ No newline at end of file diff --git a/docs/source/developers_manual/index.rst b/docs/source/developers_manual/index.rst index 90e5f3d..266e991 100644 --- a/docs/source/developers_manual/index.rst +++ b/docs/source/developers_manual/index.rst @@ -1,9 +1,46 @@ -Developers manual -================= +Contribute to Quantum Nodes +=========================== + + +.. _dev-env-contrib-addon: + +Development environment +####################### + .. toctree:: :maxdepth: 1 + :glob: + + instructions + tools + + +.. _contribute-contrib-addon: + +Contribute +########## + + +.. note:: + Click `here `_ to learn about the forking workflow on Github. + + +* Fork our `git repository `_ +* Do your modifications +* Open a new `pull request `_ +* Wait for your modifications to be reviewed and accepted + + +.. _git-workflow-contrib-addon: + +Git workflow +############ + +.. image:: https://miro.medium.com/max/560/1*UH5ozOBwkaFhWA1mrJkVIQ.png + :alt: Git workflow + :align: center + :width: 80% + :class: img-rounded - addon/index - manual/index - code/quantum_nodes \ No newline at end of file +| \ No newline at end of file diff --git a/docs/source/developers_manual/instructions.rst b/docs/source/developers_manual/instructions.rst new file mode 100644 index 0000000..d55aef2 --- /dev/null +++ b/docs/source/developers_manual/instructions.rst @@ -0,0 +1,317 @@ +.. _instructions-contrib-addon: + +Instructions +============ + +.. note:: + + This tutorial will help you to setup a full development environment. + + +#. :ref:`dev-env-downloads-contrib-addon` +#. :ref:`dev-env-installations-contrib-addon` +#. :ref:`dev-env-build-and-test-contrib-addon` +#. :ref:`guidelines-contrib-manual` + +Glossary +######## + +#. | ``pythonblender``: path to python executable which comes with blender. + | Example: ``C:\Users\felix\Documents\blender-3.0.1-windows-x64\3.0\python\bin\python.exe``. +#. | ``sphinx-apidoc``: path to sphinx-apidoc executable. + | Example: ``C:\Users\felix\AppData\Roaming\Python\Python39\Scripts\sphinx-apidoc``. +#. | ``sphinx-build``: path to sphinx-build executable. + | Example: ``C:\Users\felix\AppData\Roaming\Python\Python39\Scripts\sphinx-build``. + +.. _dev-env-downloads-contrib-addon: + +Downloads +######### + + +.. _dev-env-downloads-blender-contrib-addon: + +Blender +******* + +* | First, we need to download a portable version of Blender. + | Download a version from here: https://download.blender.org/release/. + + +.. _dev-env-downloads-animation-nodes-contrib-addon: + +Animation Nodes +*************** + +* | Before downloading Animation Nodes, we need to know which python version is shipped with the + | chosen Blender version. We can get it by looking at the files (from the archive) located + | at: ``blender[...]/[X.Y]/python/bin/``. + | For Blender >= 2.93.0, it will probably be something between python 3.9 and 3.10. + +* | Once we know that, we have to download the add-on from the release page of + | Animation Nodes (take latest): https://github.com/JacquesLucke/animation_nodes/releases/tag/master-cd-build. + + +.. _dev-env-downloads-quantum-nodes-contrib-addon: + +Quantum Nodes +************* + +* Make a fork of the repository: https://github.com/Quantum-Creative-Group/quantum_nodes/fork. +* Then, clone the forked repository on your computer. + + +.. _dev-env-downloads-ide-contrib-addon: + +IDE +*** + +* We recommend to use `Visual Studio Code `_. +* See :ref:`tools-dev-addon` for more information. + + +.. _dev-env-installations-contrib-addon: + +Installations +############# + + +.. note:: + + Since Blender comes with its own python environment, we will use this one as our development environment too. + + +.. _dev-env-installations-blender-contrib-addon: + +Blender +******* + +* | Decompress the downloaded archive which contains the blender version. + | We can place these files where we want. + | ``Linux`` : We recommend you to place them in the ``/opt/`` folder. + | ``Windows`` : We recommend you to place them in the ``Documents/`` folder. + | ``Mac`` : [TODO]. + +* Run Blender at least one time to make sure it works fine. + + +.. _dev-env-installations-python-contrib-addon: + +Python dependencies +******************* + + +* Go to the ``quantum_nodes/`` directory. +* Run ``pythonblender -m pip install -r requirements.txt``. + + +.. _dev-env-installations-animation-nodes-contrib-addon: + +Animation Nodes +*************** + +In this part we will install the add-on for Blender and copy the content of the ``animation_nodes/`` folder in the +blender python distribution so it will be available for our different scripts (documentation build and test suite). + +Installation in Blender: + +* Install the add-on inside Blender (as in the :ref:`installation guide `). +* Make sure it works fine. + +Installation for the IDE and the python scripts: + +* | Extract the ``animation_nodes/`` folder from the archive. + | ``Linux`` : Run the following bash script ``scripts/setup_animation_nodes.sh``. + | ``Windows`` : Run the following bash script ``scripts/setup_animation_nodes.ps1``. + | ``Mac`` : [TODO] +* | You need to provide several information in order to run the script correctly: + | -> The path to the ``site-packages/`` folder in the python distribution shipped with Blender. + | -> The path to the ``animation_nodes/`` folder previously extracted. + | -> The path to the ``quantum_nodes/`` folder. + | Examples: + | ``Linux`` : ``bash scripts/setup_animation_nodes.sh /opt/blender-3.0.1-linux-x64/3.0/python/lib/python3.9/site-packages/ ~/Documents/animation_nodes/ ~/Documents/quantum_nodes/`` + | ``Windows`` : ``scripts/setup_animation_nodes.ps1 -site_packages C:\Users\felix\Documents\blender-3.0.1-windows-x64\3.0\python\lib\site-packages\ -animation_nodes C:\Users\felix\Documents\quantum_nodes\animation_nodes - C:\Users\felix\Documents\quantum_nodes`` + | ``Mac`` : [TODO] + + +.. _dev-env-build-and-test-contrib-addon: + +Build and test +############## + +.. note:: + + Before following the next instructions, please install and configure the recommended Visual Studio Code + extensions cited in :ref:`this section `. + + +.. _dev-env-build-and-test-run-from-vscode-contrib-addon: + +Run Quantum Nodes from Visual Studio Code +***************************************** + +The `Blender Development` extension let us to quickly run Blender with the modifications made to the add-on +on which we are currently working. It runs Blender in a sort of 'debug' mode to test our add-on. + +* In VSCode, hit ``ctrl + shift + p`` and type ``blender start``. Then, hit ``enter``. +* If no blender executable was previously set, follow the instructions given by the extension. +* Wait for Blender to start. +* Once ready, edit code in live and save files to apply changes (it reloads the add-on automatically). + + +.. _dev-env-build-and-test-build-documentation-contrib-addon: + +Build the documentation +*********************** + +Generate automatic code documentation: + + +.. note:: + + This step is not mandatory to build the documentation. You can skip it if you don't need this part + in your local build. + + +* Go in the ``quantum_nodes/docs/`` folder. +* | Run: ``sphinx-apidoc -t "_templates/" --implicit-namespaces -d 1 -f -M -T -o source/developers_manual/code/ ../quantum_nodes "/*animation_nodes/*" "/*lib/*"`` + + +Build the documentation: + +* | Go in the ``quantum_nodes/docs/`` folder. + | Run (``Linux``) : ``make html SPHINXBUILD=sphinx-build`` + | Run (``Windows``) : ``make.bat html SPHINXBUILD=sphinx-build`` + | Run (``Mac``) : [TODO] +* The build is then available in the following folder: ``quantum_nodes/docs/build/``. + + +.. _dev-env-build-and-test-run-test-suite-contrib-addon: + +Run the test suite +****************** + +* | From root of the repository, run: ``pythonblender -m scripts.test -b [blender version] -os [operating system]`` + | Example (``Linux``) : ``pythonblender -m scripts.test -b 3.0.0 -os ubuntu-latest`` + | Example (``Windows``) : ``pythonblender -m scripts.test -b 3.0.0 -os windows-latest`` + | Example (``Mac``) : [TODO] + + +.. _guidelines-contrib-manual: + +Guidelines manual +################# + + +File architecture +***************** + +.. raw:: html + +
+    docs/
+    ├── _static/
+    │   ├── animation_nodes_init_replacement_file.txt
+    │   ├── css/
+    │   └── images/
+    │
+    ├── _templates/
+    │   ├── modules.rst_t
+    │   ├── packages.rst_t
+    │   └── toc.rst_t
+    │
+    ├── build/
+    │
+    ├── source/
+    │   ├── conf.py
+    │   ├── index.rst
+    │   ├── MethodNameFilter.py
+    │   ├── spelling_wordlist.txt
+    │   │
+    │   ├── [chapter]/
+    │   │   ├── index.rst
+    │   │   ├── file.rst
+    │   │   ├── [subchapter]/
+    │   │   ├── ...
+    │   │   └── [subchapter]/
+    │   │
+    │   ├── ...
+    │   └── [chapter]/
+    │       └── ...
+    │
+    └── ...
+    

+ + +Add a new chapter +***************** + +#. Create a new folder + * If your chapter is a new section, create a new folder under ``source/`` + * If your chapter is a subchapter, create a new folder under ``source/parent_chapter/`` + * Your chapter may be a subsubchapter. No problem, keep the same logic as described before + * Give it a short and precise name (snake_case naming style) + +#. Create a new ``index.rst`` file in your chapter + * This file is the "welcome page" of your chapter + * Here you can add links to any subchapters and so on ... + +#. If you need to add custom css to your pages + * Create a new folder under ``docs/_static/css/`` + * Give it the same name as your chapter + * Insert your css files + * | Once this is done, add your path to the ``html_css_files`` variable in ``config.py`` + +In a more visual way, here is the architecture of a section/chapter: + +.. raw:: html + +
+    ├── index.rst
+    ├── my_subchapter/
+    │   ├── index.rst
+    │   ├── my_subsubchapter/
+    │   ├── file.rst
+    │   └── ...
+    ├── file.rst
+    └── ...
+    

+ +So, at the end, here is what the global architecture should look like + +.. raw:: html + +
+    docs/
+    ├── _static/
+    │   ├── animation_nodes_init_replacement_file.txt
+    │   ├── css/
+    │   └── images/
+    │
+    ├── _templates/
+    │   ├── modules.rst_t
+    │   ├── packages.rst_t
+    │   └── toc.rst_t
+    │
+    ├── build/
+    │
+    ├── source/
+    │   ├── conf.py
+    │   ├── index.rst
+    │   ├── MethodNameFilter.py
+    │   ├── spelling_wordlist.txt
+    │   │
+    │   ├── my_chapter/
+    │   │   ├── index.rst
+    │   │   ├── my_subchapter/
+    │   │   │   ├── index.rst
+    │   │   │   ├── my_subsubchapter/
+    │   │   │   ├── file.rst
+    │   │   │   └── ...
+    │   │   ├── file.rst
+    │   │   └── ...
+    │   │
+    │   └── ...
+    │
+    └── ...
+    

\ No newline at end of file diff --git a/docs/source/developers_manual/manual/index.rst b/docs/source/developers_manual/manual/index.rst deleted file mode 100644 index ee32df1..0000000 --- a/docs/source/developers_manual/manual/index.rst +++ /dev/null @@ -1,174 +0,0 @@ -Contribute to the manual -======================== - - -.. _dev-env-contrib-manual: - -Development environment -*********************** - -.. toctree:: - :maxdepth: 1 - - linux - windows - mac - tools - - -.. _intructions-linux-contrib-manual: - -Contribute -********** - - -.. note:: - Click `here `_ to learn about the forking workflow on Github. - - -* Fork the repository: https://github.com/Quantum-Creative-Group/quantum_nodes -* Do your modifications -* Once you are ready, open a new `pull request `_ -* Wait for your modifications to be reviewed and accepted - - -.. _guidelines-contrib-addon: - -Guidelines -********** - - -#. :ref:`files-architecture-contrib-manual` -#. :ref:`add-a-new-chapter-contrib-manual` - - -.. _files-architecture-contrib-manual: - -Files architecture ------------------- - -.. raw:: html - -
-    docs/
-    ├── _static/
-    │   ├── animation_nodes_init_replacement_file.txt
-    │   ├── css/
-    │   └── images/
-    │
-    ├── _templates/
-    │   ├── modules.rst_t
-    │   ├── packages.rst_t
-    │   └── toc.rst_t
-    │
-    ├── build/
-    │
-    ├── source/
-    │   ├── conf.py
-    │   ├── index.rst
-    │   ├── MethodNameFilter.py
-    │   ├── spelling_wordlist.txt
-    │   │
-    │   ├── [chapter]/
-    │   │   ├── index.rst
-    │   │   ├── file.rst
-    │   │   ├── [subchapter]/
-    │   │   ├── ...
-    │   │   └── [subchapter]/
-    │   │
-    │   ├── ...
-    │   └── [chapter]/
-    │       └── ...
-    │
-    └── ...
-    

- - -.. _add-a-new-chapter-contrib-manual: - -Add a new chapter ------------------ - -#. Create a new folder - * If your chapter is a new section, create a new folder under ``source/`` - * If your chapter is a subchapter, create a new folder under ``source/parent_chapter/`` - * Your chapter may be a subsubchapter. No problem, keep the same logic as described before - * Give it a short and precise name (snake_case naming style) - -#. Create a new ``index.rst`` file in your chapter - * This file is the "welcome page" of your chapter - * Here you can add links to any subchapters and so on ... - -#. If you need to add custom css to your pages - * Create a new folder under ``docs/_static/css/`` - * Give it the same name as your chapter - * Insert your css files - * | Once this is done, add your path to the ``html_css_files`` variable in ``config.py`` - -In a more visual way, here is the architecture of a section/chapter: - -.. raw:: html - -
-    ├── index.rst
-    ├── my_subchapter/
-    │   ├── index.rst
-    │   ├── my_subsubchapter/
-    │   ├── file.rst
-    │   └── ...
-    ├── file.rst
-    └── ...
-    

- -So, at the end, here is what the global architecture should look like - -.. raw:: html - -
-    docs/
-    ├── _static/
-    │   ├── animation_nodes_init_replacement_file.txt
-    │   ├── css/
-    │   └── images/
-    │
-    ├── _templates/
-    │   ├── modules.rst_t
-    │   ├── packages.rst_t
-    │   └── toc.rst_t
-    │
-    ├── build/
-    │
-    ├── source/
-    │   ├── conf.py
-    │   ├── index.rst
-    │   ├── MethodNameFilter.py
-    │   ├── spelling_wordlist.txt
-    │   │
-    │   ├── my_chapter/
-    │   │   ├── index.rst
-    │   │   ├── my_subchapter/
-    │   │   │   ├── index.rst
-    │   │   │   ├── my_subsubchapter/
-    │   │   │   ├── file.rst
-    │   │   │   └── ...
-    │   │   ├── file.rst
-    │   │   └── ...
-    │   │
-    │   └── ...
-    │
-    └── ...
-    

- - -.. _git-workflow-contrib-manual: - -Git workflow -************ - -.. image:: https://miro.medium.com/max/560/1*UH5ozOBwkaFhWA1mrJkVIQ.png - :alt: Git workflow - :align: center - :width: 80% - :class: img-rounded - -| \ No newline at end of file diff --git a/docs/source/developers_manual/manual/linux.rst b/docs/source/developers_manual/manual/linux.rst deleted file mode 100644 index d5180cf..0000000 --- a/docs/source/developers_manual/manual/linux.rst +++ /dev/null @@ -1,34 +0,0 @@ -Linux -===== - - -.. important:: - This tutorial is written for Ubuntu - - -.. _dev-env-ide-contrib-manual: - -IDE -### - -* We recommend you to use `Visual Studio Code `_. -* See :ref:`tools-contrib-manual` for more information. - - -.. _dependencies-linux-contrib-manual: - -Dependencies -############ - -| Install using: ``pip install -r requirements.txt`` - - -.. _build-linux-contrib-manual: - -Build the manual -################ - -* Clone the repository next to the `quantum_nodes` repository. - -* | Open a terminal and enter: ``make html spelling`` - | Visualize the html in ``build/html`` \ No newline at end of file diff --git a/docs/source/developers_manual/manual/mac.rst b/docs/source/developers_manual/manual/mac.rst deleted file mode 100644 index 10b210f..0000000 --- a/docs/source/developers_manual/manual/mac.rst +++ /dev/null @@ -1,4 +0,0 @@ -Mac -=== - -TODO \ No newline at end of file diff --git a/docs/source/developers_manual/manual/windows.rst b/docs/source/developers_manual/manual/windows.rst deleted file mode 100644 index 77c292d..0000000 --- a/docs/source/developers_manual/manual/windows.rst +++ /dev/null @@ -1,45 +0,0 @@ -Windows -======= - - -#. :ref:`install-dependencies-windows-contrib-manual` -#. :ref:`intructions-windows-contrib-manual` -#. :ref:`build-windows-contrib-manual` - - -.. _install-dependencies-windows-contrib-manual: - -Install dependencies -#################### - - -.. important:: - Make sure to install the dependencies in the right anaconda environment. - - -| ``pip install -r requirements.txt`` - -.. _intructions-windows-contrib-manual: - -Contribute -########## - - -.. note:: - Click `here `_ to learn about the forking workflow on Github. - - -* Fork the repository: https://github.com/Quantum-Creative-Group/quantum_nodes -* Do your modifications -* Once you are ready, open a new `pull request `_ -* Wait for your modifications to be reviewed and accepted - -.. _build-windows-contrib-manual: - -Build the manual -################ - -* Clone the repository next to the `quantum_nodes` repository. - -* | Open a terminal and enter: ``make html spelling`` - | Visualize the html in ``build/html`` \ No newline at end of file diff --git a/docs/source/developers_manual/manual/tools.rst b/docs/source/developers_manual/tools.rst similarity index 65% rename from docs/source/developers_manual/manual/tools.rst rename to docs/source/developers_manual/tools.rst index 3f06d3e..29439fe 100644 --- a/docs/source/developers_manual/manual/tools.rst +++ b/docs/source/developers_manual/tools.rst @@ -1,22 +1,36 @@ -.. _tools-contrib-manual: +.. _tools-dev-addon: Tools ===== -Here is a list of tools which will help you to write documentation. +Here is a list of tools which will help you to develop Quantum Nodes. -#. :ref:`vs-code-extensions` - #. :ref:`pydocstring-generator-vscode` - #. :ref:`rst-vscode` +* :ref:`vs-code-extensions-addon` + * :ref:`tools-blender-vscode` + * :ref:`tools-pydocstring-generator-vscode` + * :ref:`tools-rst-vscode` -.. _vs-code-extensions: +.. _vs-code-extensions-addon: VSCode extensions ################# -.. _pydocstring-generator-vscode: +.. _tools-blender-vscode: + +Blender Development +******************* + + +.. note:: + Tools to simplify Blender development. Developed by `Jacques Lucke `_. + + +* Install `blender development `_ + + +.. _tools-pydocstring-generator-vscode: Python Docstring Generator ************************** @@ -38,7 +52,7 @@ Python Docstring Generator | -.. _rst-vscode: +.. _tools-rst-vscode: reStructuredText Syntax highlighting ************************************ diff --git a/docs/source/spelling_wordlist.txt b/docs/source/spelling_wordlist.txt index ff8fffd..f99f4c7 100644 --- a/docs/source/spelling_wordlist.txt +++ b/docs/source/spelling_wordlist.txt @@ -27,4 +27,5 @@ addon namespace qasm statevector -uncorrectable \ No newline at end of file +uncorrectable +ubuntu \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..62d5d56 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths=tests/ +norecursedirs=tests/helpers \ No newline at end of file diff --git a/quantum_nodes/__init__.py b/quantum_nodes/__init__.py index 37ec480..7163655 100644 --- a/quantum_nodes/__init__.py +++ b/quantum_nodes/__init__.py @@ -1,29 +1,14 @@ -""" -Copyright (C) 2021-2022 Quantum Creative Group.\ -contact@quantum-nodes.com. - -Created by Quantum-Creative-Group - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" +import os +import sys +import site +from pathlib import Path bl_info = { "name": "Quantum Nodes", "author": "Quantum Creative Group", "version": (0, 1, 2), "blender": (2, 93, 0), - "location": "Animation Nodes Editor", + "location": "Animation Nodes", "description": "Animation Nodes extension which implements quantum computing tools.", "warning": "This version is still in development.", "doc_url": "https://quantum-creative-group.github.io/quantum_nodes/", @@ -31,6 +16,11 @@ "category": "Node", } +# Add user default folders where pip will install some of the dependencies +# This is because some folders may not be writable +sys.path.append(os.path.abspath(site.USER_SITE)) +sys.path.append(os.path.join(os.path.abspath(Path(site.USER_SITE).parent), "Scripts")) + import addon_utils from . import auto_load diff --git a/requirements.txt b/requirements.txt index dab7cc9..4f84f9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,9 @@ sphinx-rtd-theme sphinxcontrib-spelling fake-bpy-module-latest +blender-addon-tester +pytest + scipy numpy pillow diff --git a/scripts/load.py b/scripts/load.py new file mode 100644 index 0000000..00ab453 --- /dev/null +++ b/scripts/load.py @@ -0,0 +1,143 @@ +"""Run the test suite inside blender.""" + +import os +import sys +from pathlib import Path + +# Make utils.py functions available in this file +sys.path.append(os.path.abspath(".")) + +from scripts.utils import TerminalDisplay as TERM +from scripts.utils import PackageAndAddonUtils as PAU +from scripts.utils import FilesUtils + +print(f"{TERM.LIGHT_BLUE}{TERM.centered_str(' LOAD PYTEST ', '=')}{TERM.RESET}") +print("Running file:", __file__, "from Blender.") + +# +---------------------------------------------------------+ +# + GET TEST SUITE CONFIGURATION FROM ENVIRONMENT VARIABLES + +# +---------------------------------------------------------+ + +# Make sure to have BLENDER_ADDON_TO_TEST set as an environment variable first +ADDON = os.environ.get("BLENDER_ADDON_TO_TEST", False) +if not ADDON: + print("ERROR: no add-on to test was found in the 'BLENDER_ADDON_TO_TEST' environment variable.") + sys.exit(1) + +# Set any value to the BLENDER_ADDON_COVERAGE_REPORTING environment variable to enable it +COVERAGE_REPORTING = os.environ.get("BLENDER_ADDON_COVERAGE_REPORTING", False) + +# The Pytest tests/ path can be overridden through the BLENDER_ADDON_TESTS_PATH environment variable +default_tests_dir = Path(ADDON).parent.joinpath("tests") +TESTS_PATH = os.environ.get("BLENDER_ADDON_TESTS_PATH", default_tests_dir.as_posix()) + +# +----------------------+ +# + INSTALL REQUIREMENTS + +# +----------------------+ + +PAU.reload_available_modues() + +try: + import PIL + import scipy + import numpy + import qiskit + import qiskit_finance + import qiskit_machine_learning + + import pytest + import blender_addon_tester + +except Exception as e: + print(f"{TERM.LIGHT_YELLOW}Missing module...{TERM.RESET}", e) + print(f"{TERM.LIGHT_YELLOW}Trying to install missing dependencies...{TERM.RESET}") + try: + PAU.install_py_requirements(os.path.join(os.path.abspath("./scripts"), "requirements.txt")) + PAU.reload_available_modues() + except Exception as e: + print(e) + sys.exit(1) + +# Import unit testing utils functions +import blender_addon_tester.addon_helper as BAT + + +class SetupPlugin: + """Setup class for pytest.""" + + def __init__(self, addon: str, addon_dir: str = os.path.abspath("./local_addon/")): + """ + Init method of the class. + + Args: + addon (str): absolute path to the addon (zip file). + addon_dir (str, optional): absolute path to the local addon path. + Defaults to: os.path.abspath("./local_addon/"). + """ + + self.root = Path(__file__).parent.parent + self.addon = addon + self.addon_dir = addon_dir + self.bpy_module = None + self.zfile = None + + def pytest_configure(self, config: dict): + """ + Configure pytest. + + Args: + config (dict): configuration. + """ + + print("PyTest configure...") + + self.bpy_module, self.zfile = BAT.zip_addon(self.addon, self.addon_dir) + BAT.change_addon_dir(self.bpy_module, self.addon_dir) + BAT.install_addon( + os.environ.get(f"{PAU.ANIMATION_NODES['module']}_module", None), + os.environ.get(f"{PAU.ANIMATION_NODES['module']}_path", None) + ) + BAT.install_addon(self.bpy_module, self.zfile) + config.cache.set("bpy_module", self.bpy_module) + + print("PyTest configure successful!") + + def pytest_unconfigure(self): + """Unconfigure pytest.""" + + print("PyTest unconfigure...") + + # Cleanup zip files + print("Cleaning up - zip files") + exclude = [os.path.abspath("./cache")] + FilesUtils.remove_files_matching_pattern(self.root, exclude_folders=exclude, pattern="*.zip") + + BAT.cleanup(None, self.bpy_module, os.path.join(self.addon_dir, "addons", self.bpy_module)) + + # TODO: find a better fix to "[WinError 5] Access denied: + # '[....]\\local_addon\\addons\\animation_nodes\\algorithms\\hashing\\murmurhash3.cp39-win_amd64.pyd'" + try: + an_module = os.environ.get(f"{PAU.ANIMATION_NODES['module']}_module", None) + an_local_addon_path = os.path.join(self.addon_dir, "addons", an_module) + BAT.cleanup(None, an_module, an_local_addon_path) + except BaseException as exception: + print(f"{TERM.LIGHT_YELLOW}WARNING: failed to clean animation_nodes \ +directory ({an_local_addon_path}).{TERM.RESET}") + print(exception) + + print("PyTest unconfigure successful!") + + +try: + import pytest + + pytest_main_args = ["-x", TESTS_PATH] + if COVERAGE_REPORTING is not False: + pytest_main_args += ["--cov", "--cov-report", "term", "--cov-report", "xml"] + exit_val = pytest.main(pytest_main_args, plugins=[SetupPlugin(ADDON)]) + +except Exception as e: + print(e) + exit_val = 1 + +sys.exit(exit_val) diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..e074c00 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,14 @@ +# Requirements for unit tests +wheel +pyenchant +myst-parser + +blender-addon-tester +pytest + +scipy +numpy +pillow +qiskit +qiskit-finance +qiskit-machine-learning \ No newline at end of file diff --git a/scripts/setup_animation_nodes.ps1 b/scripts/setup_animation_nodes.ps1 new file mode 100644 index 0000000..10802c7 --- /dev/null +++ b/scripts/setup_animation_nodes.ps1 @@ -0,0 +1,29 @@ +param( + [string]$animation_nodes = "./animation_nodes/", + [string]$quantum_nodes = "./", + [string]$site_packages +) + +Write-Output "---------- SETUP ANIMATION NODES: START ---------" + +Write-Output "STEP 1: replace __init__.py file" + +Remove-Item $animation_nodes/__init__.py +Copy-Item $quantum_nodes/docs/_static/animation_nodes_init_replacement_file.txt -Destination $animation_nodes/__init__.py + +Write-Output "STEP 2: edit preferences.py" + +$file = "$animation_nodes/preferences.py" +$find = "return bpy.app.version" +$replace = "return bpy.app.version if bpy.app.version is not None else (2, 93, 0)" +(Get-Content -Path $file -Raw) -replace $find, $replace | Set-Content -Path $file -NoNewLine + +Write-Output "STEP 3: remove '@persistent' decorators" + +"$PSScriptRoot\..\docs\replace_matching_string_in_files.ps1 -folder ./animation_nodes/" + +Write-Output "STEP 4: move animation_nodes to 'site-packages/'" + +Copy-Item $animation_nodes -Destination $site_packages -Recurse + +Write-Output "----------- SETUP ANIMATION NODES: END ----------" diff --git a/scripts/setup_animation_nodes.sh b/scripts/setup_animation_nodes.sh new file mode 100644 index 0000000..3abf2d8 --- /dev/null +++ b/scripts/setup_animation_nodes.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +SITE_PACKAGES=$1 +ANIMATION_NODES=$2 +QUANTUM_NODES=$3 + +echo "---------- SETUP ANIMATION NODES: START ---------" + +echo "STEP 1: replace __init__.py file" + +rm $ANIMATION_NODES/__init__.py +cp $QUANTUM_NODES/docs/_static/animation_nodes_init_replacement_file.txt $ANIMATION_NODES/__init__.py + +echo "STEP 2: edit preferences.py" + +find $ANIMATION_NODES/ -type f -name "*.py" -exec sed -i 's/return bpy.app.version/return bpy.app.version if bpy.app.version is not None else (2, 93, 0)/g' {} + + +echo "STEP 3: remove '@persistent' decorators" + +find $ANIMATION_NODES/ -type f -name "*.py" -exec sed -i 's/@persistent/#@persistent/g' {} + + +echo "STEP 4: move animation_nodes to 'site-packages/'" + +sudo cp -r $ANIMATION_NODES $SITE_PACKAGES + +echo "----------- SETUP ANIMATION NODES: END ----------" diff --git a/scripts/test.py b/scripts/test.py new file mode 100644 index 0000000..2a688c6 --- /dev/null +++ b/scripts/test.py @@ -0,0 +1,91 @@ +"""Script to run the test suite for a given os and blender version.""" +import os +import sys +import zipfile +from pathlib import Path + +from scripts.utils import PackageAndAddonUtils as PAU +from scripts.utils import TerminalDisplay as TERM +from scripts.utils import FilesUtils +from scripts.utils import parser + +print(f"{TERM.LIGHT_BLUE}{TERM.centered_str(' TEST SUITE: START ', '=')}{TERM.RESET}") + +PAU.reload_available_modues() + +# Check that blender-addon-tester is installed +try: + import blender_addon_tester as BAT +except Exception as e: + print(e) + sys.exit(1) + +if __name__ == "__main__": + + args = parser.parse_args() + + if args.b is None: + print(f"{TERM.LIGHT_RED}ERROR: -b option is None.{TERM.RESET}") + parser.parse_args(['-h']) + + blender = args.b + + if args.os is None: + print(f"{TERM.LIGHT_RED}ERROR: -os option is None.{TERM.RESET}") + parser.parse_args(['-h']) + + system = args.os + + if not any(system == supported_os for supported_os in ['macos-latest', 'ubuntu-latest', 'windows-latest']): + print(f"{TERM.LIGHT_RED}ERROR: -os option must be one of: ['macos-latest', 'ubuntu-latest', 'windows-latest'].{TERM.RESET}") # noqa: E501 + parser.parse_args(['-h']) + + module = "quantum_nodes" + here = Path(__file__).parent + addon = os.path.join(os.path.abspath('.'), module) + cache = os.path.abspath(here.joinpath("../cache").as_posix()) + python = PAU.get_python_version(blender) + + try: + # Cleanup '__pychache__' folders in the module folder + FilesUtils.remove_folders_matching_pattern(addon) + + # Download addons on which this add-on depends + PAU.ANIMATION_NODES["path"] = PAU.download_blender_addon(f"{PAU.ANIMATION_NODES[system]}_py{python}.zip", + f"{PAU.ANIMATION_NODES['module']}_py{python}", cache) + os.environ[f"{PAU.ANIMATION_NODES['module']}_module"] = PAU.ANIMATION_NODES['module'] + os.environ[f"{PAU.ANIMATION_NODES['module']}_path"] = PAU.ANIMATION_NODES['path'] + + # Zip addon + print(f"Zipping folder: {addon}") + zipf = zipfile.ZipFile(module + ".zip", 'w', zipfile.ZIP_DEFLATED) + FilesUtils.zipdir("./" + module, zipf) + zipf.close() + addon = os.path.join(os.path.abspath("."), module + ".zip") + + except Exception as e: + print(e) + exit_val = 1 + + # Custom configuration + config = { + "blender_load_tests_script": os.path.abspath(here.joinpath("load.py").as_posix()), + "coverage": False, + "tests": os.path.abspath(here.joinpath("../tests").as_posix()), + "blender_cache": os.path.abspath(here.joinpath("../cache").as_posix()) + } + + try: + # Setup custom blender cache (where the blender versions will be downloaded and extracted) + # The blender_addon_tester module raises an error when passed as a key in the config dict + if config.get("blender_cache", None): + os.environ["BLENDER_CACHE"] = config["blender_cache"] + config.pop("blender_cache") + + exit_val = BAT.test_blender_addon(addon_path=addon, blender_revision=blender, config=config) + except Exception as e: + print(e) + exit_val = 1 + + print(f"{TERM.LIGHT_BLUE}{TERM.centered_str(' TEST SUITE: END ', '=')}{TERM.RESET}") + sys.exit(exit_val) diff --git a/scripts/utils.py b/scripts/utils.py new file mode 100644 index 0000000..6f5d284 --- /dev/null +++ b/scripts/utils.py @@ -0,0 +1,244 @@ +"""Utility functions and classes to setup the unit testing environment.""" + +import os +import sys +import site +import shutil +import fnmatch +import zipfile +import requests +import argparse +import subprocess +from pathlib import Path +from importlib import invalidate_caches + +# Parser for test.py +parser = argparse.ArgumentParser(description="Add-on test suite") +parser.add_argument( + "-b", + metavar="Blender version", + type=str, + nargs='?', + default="3.0.0", + help="Version of Blender to test." +) +parser.add_argument( + "-os", + metavar="Operating system", + type=str, + nargs='?', + default="ubuntu", + help="Operating system on which to run tests: ['macos-latest', 'ubuntu-latest', 'windows-latest']." +) + + +class TerminalDisplay: + """Useful tools to better the messages displayed in the terminal.""" + + # List of colors which can be used to color texts in the terminal. + LIGHT_RED = '\033[91m' + LIGHT_GREEN = '\033[92m' + LIGHT_YELLOW = '\033[93m' + LIGHT_BLUE = '\033[94m' + LIGHT_MAGENTA = '\033[95m' + LIGHT_CYAN = '\033[96m' + + UNDERLINE = '\033[4m' + RESET = '\033[0m' + BOLD = '\033[1m' + + @classmethod + def centered_str(cls, message: str, char: str = "-") -> str: + """ + Generate a line full of 'char' with the given message at the center. + + Args: + message (str): message to display. + char (str, optional): char with which to fill the line. Defaults to "-". + + Returns: + str: generated line + """ + + terminal_size = shutil.get_terminal_size((80, 20)) + return message.center(terminal_size.columns, char) + + +class PackageAndAddonUtils: + """Utility methods to manage python packages and blender add-ons.""" + + ANIMATION_NODES = { + "module": "animation_nodes", + "path": "", + "windows-latest": "https://github.com/JacquesLucke/animation_nodes/releases/download/master-cd-build/animation_nodes_v2_3_windows", # noqa: E501 + "ubuntu-latest": "https://github.com/JacquesLucke/animation_nodes/releases/download/master-cd-build/animation_nodes_v2_3_linux", # noqa: E501 + "macos-latest": "https://github.com/JacquesLucke/animation_nodes/releases/download/master-cd-build/animation_nodes_v2_3_macOS", # noqa: E501 + } + + @classmethod + def reload_available_modues(cls): + # Add user default folders where pip will install some of the dependencies + # This is because some folders may not be writable + USER_SITE = site.getusersitepackages() + sys.path.append(os.path.abspath(USER_SITE)) + sys.path.append(os.path.join(os.path.abspath(Path(USER_SITE).parent), "Scripts")) + # Force to reload list of available modules and packages + invalidate_caches() + + @classmethod + def get_python_version(cls, blender: str) -> str: + """ + Get the python version used by the given blender version. + + Args: + blender (str): blender version (format: major.minor.patch). + + Returns: + str: python version used by Blender (MajorMinor). + """ + + if any(version in blender for version in ["2.9", "3.0"]): + return "39" + + if any(version in blender for version in ["3.1", "3.2", "3.3", "3.4"]): + return "310" + + raise ValueError(f"Unable to determine which python version is used by the given blender version ({blender})") + + @classmethod + def install_py_package(cls, package: str, force: bool = False) -> None: + """ + Install the given python package. + + Args: + package (str): name of the package. + force (bool, optional): force reinstall. Defaults to False. + """ + + args = [sys.executable, "-m", "pip", "install", package, "--user"] + if force: + args.append("--force-reinstall") + subprocess.check_call(args) + + @classmethod + def install_py_requirements(cls, requirements: str, force: bool = False) -> None: + """ + Install python packages from the given requirements file. + + Args: + requirements (str): path to the requirements file. + force (bool, optional): force reinstall. Defaults to False. + """ + + args = [sys.executable, "-m", "pip", "install", "-r", requirements, "--upgrade", "--user"] + if force: + args.append("--force-reinstall") + subprocess.check_call(args) + + @classmethod + def install_local_py_package(cls, path: str, force: bool = False) -> None: + """ + Install a local package. + + Args: + path (str): path to the folder of the local package. + force (bool, optional): force reinstall. Defaults to False. + """ + + args = [sys.executable, "-m", "pip", "install", "-e", path] + if force: + args.append("--force-reinstall") + subprocess.check_call(args) + + @classmethod + def download_blender_addon(cls, url: str, name: str, dest: str) -> str: + """ + Download the given blender add-on and put it in the destination folder. + + Args: + url (str): base url to download the file. + name (str): name of the add-on's folder. + dest (str): destination of the downloaded file. + + Returns: + str: path to the zip file + """ + + filename = f"{name}.zip" + path = os.path.abspath(os.path.join(dest, filename)) + + if not os.path.exists(dest): + print(f"The given path does not exist: {dest}") + os.mkdir(dest) + print(f"Created destination folder: {dest}") + + if os.path.exists(os.path.join(dest, filename)): + print(f"{name} - found: {path}") + return path + + # Else, download it and save it at the given destination + print(f"Downloading: {filename} ({url})") + response = requests.get(url) + open(os.path.join(dest, filename), "wb").write(response.content) + + return path + + +class FilesUtils: + """Methods to manage files and folders when setting up the unit testing environment.""" + + @classmethod + def zipdir(cls, path: str, ziph: zipfile.ZipFile) -> None: + """ + Zip the given folder. + + Args: + path (str): path to the folder. + ziph (zipfile.ZipFile): zip file. + """ + + # Inspired from: https://www.tutorialspoint.com/How-to-zip-a-folder-recursively-using-Python + # ziph is zipfile handle + for root, dirs, files in os.walk(path): + for file in files: + ziph.write(os.path.join(root, file)) + + @classmethod + def remove_files_matching_pattern(cls, root_folder: str, exclude_folders: list[str] = [], + pattern: str = "*.zip") -> None: + """ + Remove files which name match the given pattern. + + Inspired from: + https://thispointer.com/python-how-to-remove-files-by-matching-pattern-wildcards-certain-extensions-only/ + + Args: + root_folder (str): root folder. + exclude_folders (list[str], optional): list of folders to exclude from this function. Defaults to []. + pattern (str, optional): pattern of the files to remove. Defaults to "*.zip". + """ + # Get a list of all files in directory + for rootDir, subdirs, filenames in os.walk(root_folder): + # Find the files that matches the given pattern + for filename in fnmatch.filter(filenames, pattern): + try: + if os.path.dirname(os.path.join(rootDir, filename)) not in exclude_folders: + os.remove(os.path.join(rootDir, filename)) + except OSError: + print("Error while deleting file") + + @classmethod + def remove_folders_matching_pattern(cls, root_folder: str, pattern: str = "__pycache__") -> None: + """ + Remove folders which name match the given pattern. + + Args: + root_folder (str): root folder. + pattern (str, optional): pattern of the folders to remove. Defaults to "__pycache__". + """ + # Get a list of all files in directory + for rootDir, subdirs, filenames in os.walk(root_folder): + # Find the files that matches the given pattern + for subdir in subdirs: + if subdir == pattern: + shutil.rmtree(os.path.join(rootDir, subdir), ignore_errors=True) diff --git a/tests/test_hello_world.py b/tests/test_hello_world.py new file mode 100644 index 0000000..0b558bb --- /dev/null +++ b/tests/test_hello_world.py @@ -0,0 +1,9 @@ +import bpy +import pytest + + +def test_hello_world(): + + variable = "HelloWorld" + + assert "HelloWorld" == variable