From ce9845f9bbc67cc513cf7a3126c14e5e2e24ff9a Mon Sep 17 00:00:00 2001 From: HeinrichAD Date: Mon, 11 Mar 2024 15:56:04 +0100 Subject: [PATCH] publish fl demonstrator server --- .dockerignore | 10 + .editorconfig | 26 + .gitattributes | 1 + .github/workflows/main.yml | 170 + .gitignore | 367 + .markdownlint.json | 47 + .pre-commit-config.yaml | 9 + .vscode/extensions.json | 15 + .vscode/launch.json | 23 + .vscode/settings.json | 50 + LICENSE | 201 + README.md | 135 + dev | 365 + docker-compose.yml | 53 + docker/.env | 9 + docker/celery/Dockerfile | 39 + docker/celery/docker-start.sh | 7 + docker/django/Dockerfile | 40 + docker/django/docker-start.sh | 12 + docs/.overrides/.icons/dlr-ki.svg | 217 + docs/.overrides/.icons/dlr-logo.svg | 19 + docs/Fl Platform.postman_collection.json | 947 ++ docs/ai-ecosystem/FL-demonstrator.drawio.png | Bin 0 -> 143145 bytes docs/ai-ecosystem/ecosystem.drawio.png | Bin 0 -> 100110 bytes docs/ai-ecosystem/ideal-data.drawio.png | Bin 0 -> 16979 bytes docs/ai-ecosystem/ideal-services.drawio.png | Bin 0 -> 17272 bytes docs/arc42/quality-model.drawio.png | Bin 0 -> 23011 bytes docs/gitlab-runner/check.png | Bin 0 -> 41815 bytes docs/gitlab-runner/register.png | Bin 0 -> 88654 bytes docs/index.md | 76 + docs/javascripts/katex.js | 10 + docs/styles/style.css | 3 + docs/web-service/BuildingBlock.drawio.png | Bin 0 -> 30651 bytes docs/web-service/RuL-Interaction.drawio.png | Bin 0 -> 32895 bytes docs/web-service/model-enums.png | Bin 0 -> 12391 bytes docs/web-service/model-enums.puml | 24 + docs/web-service/models.png | Bin 0 -> 57145 bytes docs/web-service/models.puml | 71 + docs/web-service/offline-usage.drawio.png | Bin 0 -> 16834 bytes docs/web-service/online-inference.drawio.png | Bin 0 -> 29318 bytes docs/web-service/training-process.png | Bin 0 -> 62694 bytes docs/web-service/training-process.puml | 84 + docs/web-service/training.drawio.png | Bin 0 -> 42093 bytes fl_server/__init__.py | 3 + fl_server/asgi.py | 16 + fl_server/settings/__init__.py | 0 fl_server/settings/base.py | 221 + fl_server/settings/development.py | 56 + fl_server/settings/production.py | 78 + fl_server/urls.py | 33 + fl_server/wsgi.py | 16 + fl_server_ai/__init__.py | 7 + fl_server_ai/aggregation/__init__.py | 6 + fl_server_ai/aggregation/base.py | 18 + fl_server_ai/aggregation/fed_dc.py | 29 + fl_server_ai/aggregation/fed_prox.py | 17 + fl_server_ai/aggregation/mean.py | 46 + fl_server_ai/aggregation/method.py | 41 + fl_server_ai/apps.py | 6 + fl_server_ai/celery_tasks.py | 31 + fl_server_ai/exceptions.py | 34 + fl_server_ai/notification/__init__.py | 11 + fl_server_ai/notification/notification.py | 101 + .../notification/notification_type.py | 25 + fl_server_ai/notification/serializable.py | 10 + .../notification/training/__init__.py | 16 + .../notification/training/finished.py | 29 + .../notification/training/model_test.py | 6 + .../notification/training/round_start.py | 71 + fl_server_ai/notification/training/start.py | 26 + fl_server_ai/notification/training/swag.py | 6 + .../notification/training/training.py | 15 + fl_server_ai/tests/__init__.py | 0 fl_server_ai/tests/test_aggregation.py | 40 + .../tests/test_aggregation_process.py | 50 + fl_server_ai/tests/test_ai_worker.py | 161 + fl_server_ai/tests/test_feddc.py | 93 + fl_server_ai/tests/test_notification.py | 96 + .../tests/test_uncertainty_ensemble.py | 42 + fl_server_ai/tests/test_uncertainty_mc.py | 46 + fl_server_ai/tests/test_uncertainty_swag.py | 77 + fl_server_ai/trainer/__init__.py | 5 + fl_server_ai/trainer/events/__init__.py | 14 + fl_server_ai/trainer/events/base.py | 22 + .../events/daisy_chain_round_finished.py | 23 + .../trainer/events/model_test_finished.py | 15 + .../trainer/events/swag_round_finished.py | 78 + .../trainer/events/training_round_finished.py | 75 + fl_server_ai/trainer/model_trainer.py | 117 + fl_server_ai/trainer/options.py | 8 + fl_server_ai/trainer/tasks.py | 53 + fl_server_ai/uncertainty/__init__.py | 16 + fl_server_ai/uncertainty/base.py | 114 + fl_server_ai/uncertainty/ensemble.py | 22 + fl_server_ai/uncertainty/mc_dropout.py | 62 + fl_server_ai/uncertainty/method.py | 43 + fl_server_ai/uncertainty/none.py | 15 + fl_server_ai/uncertainty/swag.py | 37 + fl_server_api/__init__.py | 3 + fl_server_api/apps.py | 6 + fl_server_api/openapi.py | 96 + fl_server_api/serializers/__init__.py | 0 fl_server_api/serializers/generic.py | 41 + fl_server_api/serializers/model.py | 172 + fl_server_api/serializers/training.py | 54 + fl_server_api/serializers/user.py | 41 + fl_server_api/tests/__init__.py | 0 fl_server_api/tests/test_dummy.py | 28 + fl_server_api/tests/test_group.py | 105 + fl_server_api/tests/test_inference.py | 140 + fl_server_api/tests/test_model.py | 463 + fl_server_api/tests/test_openapi.py | 44 + fl_server_api/tests/test_training.py | 231 + fl_server_api/tests/test_user.py | 146 + fl_server_api/tests/test_utils.py | 119 + fl_server_api/tests/utils.py | 20 + fl_server_api/urls.py | 48 + fl_server_api/utils.py | 132 + fl_server_api/views/__init__.py | 8 + fl_server_api/views/base.py | 74 + fl_server_api/views/dummy.py | 73 + fl_server_api/views/group.py | 224 + fl_server_api/views/inference.py | 118 + fl_server_api/views/model.py | 423 + fl_server_api/views/training.py | 268 + fl_server_api/views/user.py | 149 + fl_server_core/__init__.py | 3 + fl_server_core/admin.py | 46 + fl_server_core/apps.py | 6 + fl_server_core/exceptions.py | 2 + fl_server_core/migrations/0001_initial.py | 123 + ..._globalmodel_swag_first_moment_and_more.py | 47 + .../0003_alter_training_uncertainty_method.py | 18 + .../0004_training_uncertainty_options.py | 19 + ...er_training_aggregation_method_and_more.py | 23 + ...me_uncertainty_options_training_options.py | 18 + .../0007_globalmodel_input_shape.py | 19 + .../0008_alter_globalmodel_input_shape.py | 18 + fl_server_core/migrations/__init__.py | 0 fl_server_core/models/__init__.py | 16 + fl_server_core/models/metric.py | 50 + fl_server_core/models/model.py | 125 + fl_server_core/models/training.py | 71 + fl_server_core/models/user.py | 25 + fl_server_core/tests/__init__.py | 4 + fl_server_core/tests/dummy.py | 162 + fl_server_core/tests/test_metric.py | 75 + fl_server_core/tests/test_model.py | 30 + fl_server_core/tests/test_training.py | 14 + .../utils/locked_atomic_transaction.py | 31 + fl_server_core/utils/strings.py | 30 + fl_server_core/utils/torch_pickle.py | 46 + licenses/license.txt | 10915 ++++++++++++++++ licenses/license_info.csv | 69 + licenses/license_info.json | 342 + licenses/license_info.md | 70 + licenses/license_info.no_versions.csv | 69 + licenses/license_info.no_versions.json | 274 + licenses/license_info.no_versions.md | 70 + manage.py | 22 + mkdocs.yml | 87 + pyproject.toml | 353 + scripts/utils.sh | 71 + tox.ini | 61 + 164 files changed, 21978 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .markdownlint.json create mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100755 dev create mode 100644 docker-compose.yml create mode 100644 docker/.env create mode 100644 docker/celery/Dockerfile create mode 100755 docker/celery/docker-start.sh create mode 100644 docker/django/Dockerfile create mode 100755 docker/django/docker-start.sh create mode 100644 docs/.overrides/.icons/dlr-ki.svg create mode 100644 docs/.overrides/.icons/dlr-logo.svg create mode 100644 docs/Fl Platform.postman_collection.json create mode 100644 docs/ai-ecosystem/FL-demonstrator.drawio.png create mode 100644 docs/ai-ecosystem/ecosystem.drawio.png create mode 100644 docs/ai-ecosystem/ideal-data.drawio.png create mode 100644 docs/ai-ecosystem/ideal-services.drawio.png create mode 100644 docs/arc42/quality-model.drawio.png create mode 100644 docs/gitlab-runner/check.png create mode 100644 docs/gitlab-runner/register.png create mode 100644 docs/index.md create mode 100644 docs/javascripts/katex.js create mode 100644 docs/styles/style.css create mode 100644 docs/web-service/BuildingBlock.drawio.png create mode 100644 docs/web-service/RuL-Interaction.drawio.png create mode 100644 docs/web-service/model-enums.png create mode 100644 docs/web-service/model-enums.puml create mode 100644 docs/web-service/models.png create mode 100644 docs/web-service/models.puml create mode 100644 docs/web-service/offline-usage.drawio.png create mode 100644 docs/web-service/online-inference.drawio.png create mode 100644 docs/web-service/training-process.png create mode 100644 docs/web-service/training-process.puml create mode 100644 docs/web-service/training.drawio.png create mode 100644 fl_server/__init__.py create mode 100644 fl_server/asgi.py create mode 100644 fl_server/settings/__init__.py create mode 100644 fl_server/settings/base.py create mode 100644 fl_server/settings/development.py create mode 100644 fl_server/settings/production.py create mode 100644 fl_server/urls.py create mode 100644 fl_server/wsgi.py create mode 100644 fl_server_ai/__init__.py create mode 100644 fl_server_ai/aggregation/__init__.py create mode 100644 fl_server_ai/aggregation/base.py create mode 100644 fl_server_ai/aggregation/fed_dc.py create mode 100644 fl_server_ai/aggregation/fed_prox.py create mode 100644 fl_server_ai/aggregation/mean.py create mode 100644 fl_server_ai/aggregation/method.py create mode 100644 fl_server_ai/apps.py create mode 100644 fl_server_ai/celery_tasks.py create mode 100644 fl_server_ai/exceptions.py create mode 100644 fl_server_ai/notification/__init__.py create mode 100644 fl_server_ai/notification/notification.py create mode 100644 fl_server_ai/notification/notification_type.py create mode 100644 fl_server_ai/notification/serializable.py create mode 100644 fl_server_ai/notification/training/__init__.py create mode 100644 fl_server_ai/notification/training/finished.py create mode 100644 fl_server_ai/notification/training/model_test.py create mode 100644 fl_server_ai/notification/training/round_start.py create mode 100644 fl_server_ai/notification/training/start.py create mode 100644 fl_server_ai/notification/training/swag.py create mode 100644 fl_server_ai/notification/training/training.py create mode 100644 fl_server_ai/tests/__init__.py create mode 100644 fl_server_ai/tests/test_aggregation.py create mode 100644 fl_server_ai/tests/test_aggregation_process.py create mode 100644 fl_server_ai/tests/test_ai_worker.py create mode 100644 fl_server_ai/tests/test_feddc.py create mode 100644 fl_server_ai/tests/test_notification.py create mode 100644 fl_server_ai/tests/test_uncertainty_ensemble.py create mode 100644 fl_server_ai/tests/test_uncertainty_mc.py create mode 100644 fl_server_ai/tests/test_uncertainty_swag.py create mode 100644 fl_server_ai/trainer/__init__.py create mode 100644 fl_server_ai/trainer/events/__init__.py create mode 100644 fl_server_ai/trainer/events/base.py create mode 100644 fl_server_ai/trainer/events/daisy_chain_round_finished.py create mode 100644 fl_server_ai/trainer/events/model_test_finished.py create mode 100644 fl_server_ai/trainer/events/swag_round_finished.py create mode 100644 fl_server_ai/trainer/events/training_round_finished.py create mode 100644 fl_server_ai/trainer/model_trainer.py create mode 100644 fl_server_ai/trainer/options.py create mode 100644 fl_server_ai/trainer/tasks.py create mode 100644 fl_server_ai/uncertainty/__init__.py create mode 100644 fl_server_ai/uncertainty/base.py create mode 100644 fl_server_ai/uncertainty/ensemble.py create mode 100644 fl_server_ai/uncertainty/mc_dropout.py create mode 100644 fl_server_ai/uncertainty/method.py create mode 100644 fl_server_ai/uncertainty/none.py create mode 100644 fl_server_ai/uncertainty/swag.py create mode 100644 fl_server_api/__init__.py create mode 100644 fl_server_api/apps.py create mode 100644 fl_server_api/openapi.py create mode 100644 fl_server_api/serializers/__init__.py create mode 100644 fl_server_api/serializers/generic.py create mode 100644 fl_server_api/serializers/model.py create mode 100644 fl_server_api/serializers/training.py create mode 100644 fl_server_api/serializers/user.py create mode 100644 fl_server_api/tests/__init__.py create mode 100644 fl_server_api/tests/test_dummy.py create mode 100644 fl_server_api/tests/test_group.py create mode 100644 fl_server_api/tests/test_inference.py create mode 100644 fl_server_api/tests/test_model.py create mode 100644 fl_server_api/tests/test_openapi.py create mode 100644 fl_server_api/tests/test_training.py create mode 100644 fl_server_api/tests/test_user.py create mode 100644 fl_server_api/tests/test_utils.py create mode 100644 fl_server_api/tests/utils.py create mode 100644 fl_server_api/urls.py create mode 100644 fl_server_api/utils.py create mode 100644 fl_server_api/views/__init__.py create mode 100644 fl_server_api/views/base.py create mode 100644 fl_server_api/views/dummy.py create mode 100644 fl_server_api/views/group.py create mode 100644 fl_server_api/views/inference.py create mode 100644 fl_server_api/views/model.py create mode 100644 fl_server_api/views/training.py create mode 100644 fl_server_api/views/user.py create mode 100644 fl_server_core/__init__.py create mode 100644 fl_server_core/admin.py create mode 100644 fl_server_core/apps.py create mode 100644 fl_server_core/exceptions.py create mode 100644 fl_server_core/migrations/0001_initial.py create mode 100644 fl_server_core/migrations/0002_remove_globalmodel_swag_first_moment_and_more.py create mode 100644 fl_server_core/migrations/0003_alter_training_uncertainty_method.py create mode 100644 fl_server_core/migrations/0004_training_uncertainty_options.py create mode 100644 fl_server_core/migrations/0005_alter_training_aggregation_method_and_more.py create mode 100644 fl_server_core/migrations/0006_rename_uncertainty_options_training_options.py create mode 100644 fl_server_core/migrations/0007_globalmodel_input_shape.py create mode 100644 fl_server_core/migrations/0008_alter_globalmodel_input_shape.py create mode 100644 fl_server_core/migrations/__init__.py create mode 100644 fl_server_core/models/__init__.py create mode 100644 fl_server_core/models/metric.py create mode 100644 fl_server_core/models/model.py create mode 100644 fl_server_core/models/training.py create mode 100644 fl_server_core/models/user.py create mode 100644 fl_server_core/tests/__init__.py create mode 100644 fl_server_core/tests/dummy.py create mode 100644 fl_server_core/tests/test_metric.py create mode 100644 fl_server_core/tests/test_model.py create mode 100644 fl_server_core/tests/test_training.py create mode 100644 fl_server_core/utils/locked_atomic_transaction.py create mode 100644 fl_server_core/utils/strings.py create mode 100644 fl_server_core/utils/torch_pickle.py create mode 100644 licenses/license.txt create mode 100644 licenses/license_info.csv create mode 100644 licenses/license_info.json create mode 100644 licenses/license_info.md create mode 100644 licenses/license_info.no_versions.csv create mode 100644 licenses/license_info.no_versions.json create mode 100644 licenses/license_info.no_versions.md create mode 100755 manage.py create mode 100644 mkdocs.yml create mode 100644 pyproject.toml create mode 100644 scripts/utils.sh create mode 100644 tox.ini diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..023277a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +* +!docker +!fl_server +!fl_server_ai +!fl_server_api +!fl_server_core +!manage.py +!pyproject.toml +!README.md +**/__pycache__ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1fa5de1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 + +[.vscode/*.json] +indent_size = 4 + +[{Dockerfile,Dockerfile.*}] +indent_size = 4 + +[*.md] +indent_size = 4 +max_line_length = off +trim_trailing_whitespace = false + +[*.puml] +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c480d96 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,170 @@ +name: Main + +on: + - push + - pull_request + +jobs: + lint-code: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - name: Install dependencies + run: bash ./dev install -e ".[all]" + - name: Start static code analysis + run: bash ./dev lint-code + + lint-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/hydrogen' + - name: Install dependencies + run: npm install markdownlint-cli2 + - name: Start type checking + run: bash ./dev lint-doc + + lint-scripts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Start shell script analysis tool + run: bash ./dev lint-scripts + + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - name: Install dependencies + run: bash ./dev install -e ".[all]" + - name: Start type checking + run: bash ./dev mypy + + coverage: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:latest + env: + POSTGRES_USER: demo + POSTGRES_PASSWORD: example + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + redis: + image: redis:latest + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - name: Install dependencies + run: bash ./dev install -e ".[all]" + - name: Start coverage + env: + FL_POSTGRES_HOST: localhost + FL_POSTGRES_PORT: 5432 + FL_POSTGRES_DBNAME: demo + FL_POSTGRES_USER: demo + FL_POSTGRES_PASSWD: example + FL_REDIS_HOST: localhost + run: bash ./dev coverage + + build-and-push-docker-image: + needs: [lint-code, lint-doc, lint-scripts, mypy, coverage] + permissions: + contents: read + packages: write + env: + REGISTRY: ghcr.io + runs-on: ubuntu-latest + strategy: + matrix: + docker_target: ['celery', 'django'] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}-${{ matrix.docker_target }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + file: ./docker/${{ matrix.docker_target }}/Dockerfile + context: . + push: ${{ contains(github.ref, 'main') || startsWith(github.ref, 'refs/tags/v') }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-pages: + needs: [lint-code, lint-doc, lint-scripts, mypy, coverage] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - name: Install dependencies + run: bash ./dev install -e ".[all]" + - name: Build pages + run: bash ./dev doc-build + - name: Fix permissions + run: | + chmod -c -R +rX "site/" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v2 + with: + path: site + + deploy-pages: + if: ${{ contains(github.ref, 'main') || startsWith(github.ref, 'refs/tags/v') }} + needs: [build-pages] + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c76895 --- /dev/null +++ b/.gitignore @@ -0,0 +1,367 @@ +/builds/ +/logs/ +/out/ + +# static file generation (admin and rest_framework) +/static/ +admin.tar.gz +rest_framework.tar.gz + +# ignore nodejs requirements for markdown linting +node_modules +package-lock.json +package.json + + +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij,windows,linux,python +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,intellij,windows,linux,python + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij,windows,linux,python + + +!/docker/.env diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..1c93189 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,47 @@ +// Reference: https://github.com/DavidAnson/markdownlint +// Rules: https://github.com/DavidAnson/markdownlint#rules--aliases +// Exception: https://github.com/DavidAnson/markdownlint#configuration +{ + // Enable all markdownlint rules + "default": true, + + // Set unordered list item prefix to dash (use - not * for unordered lists) + "MD004": { "style": "dash" }, + + // Set list indent level to 4 which Python-Markdown requires + "MD007": { "indent": 4 }, + + // Disable line length check + "MD013": false, + //"MD013": { + // "strict": true, + // "line_length": 120, + // "heading_line_length": 80, + // "code_blocks": false + //}, + + // Multiple headings with the same title + "MD024": { "siblings_only": true }, + + // Set Ordered list item prefix to "ordered" (use 1. 2. 3. not 1. 1. 1.) + "MD029": { "style": "ordered" }, + + // Allow inline HTML + "MD033": false, + //"MD033": { "allowed_elements": ["br", "figcaption", "figure"] }, + + // Deny leading and trailing spaces inside code spans like inline code + "MD038": false, + + // Set code block style to fenced (use ``` not indented code blocks) + "MD046": { "style": "fenced" }, + + // Set code block style to backtick (use ``` not ~~~ for code blocks) + "MD048": { "style": "backtick" }, + + // Set emphasis style to underscore (use _text_ not *text* for emphasis) + "MD049": { "style": "underscore" }, + + // Set strong style to asterisk (use **text** not __text__ for strong) + "MD050": { "style": "asterisk" } +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f11f82a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.3.0 + hooks: + - id: mypy diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7aed78 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,15 @@ +{ + "recommendations": [ + "batisteo.vscode-django", + "davidanson.vscode-markdownlint", + "donjayamanne.python-environment-manager", + "editorconfig.editorconfig", + "kevinglasson.cornflakes-linter", + "matangover.mypy", + "ms-python.flake8", + "ms-python.python", + "pachwenko.django-test-runner", + "streetsidesoftware.code-spell-checker", + "streetsidesoftware.code-spell-checker-scientific-terms" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4583a75 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Serve Documentation", + "type": "python", + "request": "launch", + "module": "mkdocs", + "justMyCode": false, + "args": [ + "serve" + ], + "env": { + "DJANGO_SETTINGS_MODULE": "fl_server.settings.production", + "FL_DJANGO_SECRET_KEY": "", + "FL_POSTGRES_PASSWD": "" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c9856ae --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,50 @@ +{ + "cSpell.allowCompoundWords": true, + "cSpell.language": "en,scientific-terms-us", + "cSpell.words": [ + "corsheaders", + "djangorestframework", + "mypy", + "NOSONAR", + "pdoc", + "probs", + "redoc", + "venv" + ], + "shellcheck.customArgs": [ + "--external-sources", + "--shell", "bash" + ], + "files.associations": { + ".markdownlint.json": "jsonc", + "*.env": "ini", + "pyproject.toml": "ini" + }, + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/.mypy_cache/*/**": true, + "**/.venv/*/**": true + }, + "files.exclude": { + ".pytest_cache": true, + ".tox": true, + "*.egg-info": true, + "**/__pycache__": true, + "**/.mypy_cache": true + }, + "mypy.runUsingActiveInterpreter": true, + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "yaml.schemas": { + "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" + }, + "yaml.customTags": [ + "!ENV scalar", + "!ENV sequence", + "!relative scalar", + "tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg", + "tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji", + "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..32c8f89 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# Federated Learning Demonstrator + +This repository contains the Federated Learning Demonstrator, a comprehensive suite of tools designed for machine learning applications in a Federated context. +This server component that handles the orchestration and notification of all training participants and ensuring smooth and efficient operation. + +The demonstrator further provides capabilities for diverse model aggregation methods, merging the model inputs from each participant. +It also offers model inference, enabling the generation of predictions using the trained models. +The repository also includes utilities for quantifying uncertainty, which provide a measure of the reliability of the model's predictions. + +This project is the server component of the Federated Learning (FL) platform, serving as a proof of concept for the [Catena-X](https://catena-x.net/en) project. +The FL platform aims to demonstrate the potential of federated learning in a practical, real-world context. + +For a comprehensive understanding of the FL platform, please refer to the official [FL platform documentation](https://dlr-ki.github.io/fl-documentation). + +A complete list of all repositories relevant to the FL platform can be found [here](https://dlr-ki.github.io/fl-documentation#repositories). + +## Get Started + +This README.md is primarily intended for developers and contributors, providing necessary information for setup, installation, and contribution guidelines. +If you're interested in using or testing this project, we recommend starting with the [GitHub pages](https://dlr-ki.github.io/fl-demonstrator). +They offer a more user-friendly interface and comprehensive guides to get you started. + +## Requirements + +### Python > 3.10 + +```bash +sudo apt install python +#If not available you can add latest versions with +sudo add-apt-repository ppa:deadsnakes/ppa +``` + +### Venv + +```bash +#Install venv +sudo apt install python-venv +``` + +### Virtualenv (alternative to venv) + +```bash +#python 3.10 or later +which python +#virtualenv or venv +pip install -U virtualenv +``` + +## Install + +```bash +# create virtual environment +virtualenv -p $(which python) .venv +# or +python -m venv .venv + +# activate our virtual environment +source .venv/bin/activate + +# update pip (optional) +python -m pip install -U pip + +# install +./dev install -U -e ".[all]" +``` + +## Helpers + +```txt +$ ./dev --help +usage: ./dev [options] + +positional arguments: + {celery,clean,collectstatic,coverage,coverage-report,db-reset,doc,doc-build,docker-build,help,install,licenses,licenses-check,lint,lint-code,lint-doc,lint-scripts,makemigrations,manage,migrate,mypy,safety-check,start,superuser,test,version,versions} + Available sub commands + help Show this help message and exit + start Run the application + celery Run celery worker + migrate Run database migrations + makemigrations Create new database migrations + manage Run django manage.py + superuser Create superuser + collectstatic Collect static files + db-reset Reset database + docker-build Build docker images for local development + test Run all tests + lint Run all linter + lint-code Run code linter + lint-doc Run documentation linter + lint-scripts Run bash script linter + mypy Run type checker + coverage Run unit tests + coverage-report Generate test coverage report + doc Start documentation server + doc-build Build documentation + licenses Generate licenses + licenses-check Check licenses + safety-check Check dependencies for known security vulnerabilities + install Install package + clean Clean up local files + version Show package version + versions Show versions + +options: + --no-http-serve Do not serve the action result via HTTP +``` + +## Contribution + +- Type-Save and linting with mypy+flake8 +- Scripts and examples for linux, wsl (bash) + +### Documentation + +This projects is using the Docstring style from +[Google](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings). +At least public classes, methods, fields, ... should be documented. + +```python +""" +This is the single line short description. + +This is the multiline or long description. +Note, that the whole Docstring support markdown styling. + +The long description can also contains multiple paragraphs. + +Args: + log_filepath (str): Log file path. + ensure_log_dir (bool, optional): Create directory for the log file if not exists. Defaults to True. + +Returns: + Dict[str, Any]: logging configuration dict +""" +``` diff --git a/dev b/dev new file mode 100755 index 0000000..7c5345d --- /dev/null +++ b/dev @@ -0,0 +1,365 @@ +#!/bin/bash +############################################################################### +# Helper commands # +################### +# This script contains a few little helper commands to make development easier. +############################################################################### +set -e +set -o pipefail + +# imports +source "$(dirname "${BASH_SOURCE[0]}")/scripts/utils.sh" + +# global variables +PROJECT_ROOT="." +PROJECT_ROOT_ABSOLUTE="$(realpath ${PROJECT_ROOT})" +BUILD_ROOT="${PROJECT_ROOT}/build" +DOC_ROOT="${PROJECT_ROOT}/docs" + +SOURCE_DJANGO="${PROJECT_ROOT}/fl_server" +SOURCE_AI="${PROJECT_ROOT}/fl_server_ai" +SOURCE_API="${PROJECT_ROOT}/fl_server_api" +SOURCE_CORE="${PROJECT_ROOT}/fl_server_core" +SCRIPTS_ROOT="${PROJECT_ROOT}/scripts" + +# default values of arguments +HTTP_SERVE=true + +# parse arguments +action=${1,,}; shift +[ "$action" == "--help" ] || [ "$action" == "-h" ] && action=help +while [[ $# -gt 0 ]]; do + case $1 in + --no-http-serve) HTTP_SERVE=false; shift ;; + --help|-h) action=help; shift ;; + --version) action=version; shift ;; + --) shift; break ;; + *) + # warn "Unknown argument: '$1'; Skip further parsing."; + break ;; + esac +done + + +no_actions="$(compgen -A function)" +############################################################################### +# action functions + +function help() { + actions="$(actions="$(printf '%s,' "${action_map[@]}")"; echo "{${actions%,}}")" + [ ${#actions} -lt 22 ] && actions=$(printf '%-22s' "$actions") || actions="$actions\n$(printf %24s "")" + info2 "usage: $0 [options]" + info2 "" + info2 "positional arguments:" + info2 -e " ${actions}Available sub commands" + info2 " help Show this help message and exit" + info2 " start Run the application" + info2 " celery Run celery worker" + info2 " migrate Run database migrations" + info2 " makemigrations Create new database migrations" + info2 " manage Run django manage.py" + info2 " superuser Create superuser" + info2 " collectstatic Collect static files" + info2 " db-reset Reset database" + info2 " docker-build Build docker images for local development" + info2 " test Run all tests" + info2 " lint Run all linter" + info2 " lint-code Run code linter" + info2 " lint-doc Run documentation linter" + info2 " lint-scripts Run bash script linter" + info2 " mypy Run type checker" + info2 " coverage Run unit tests" + info2 " coverage-report Generate test coverage report" + info2 " doc Start documentation server" + info2 " doc-build Build documentation" + info2 " licenses Generate licenses" + info2 " licenses-check Check licenses" + info2 " safety-check Check dependencies for known security vulnerabilities" + info2 " install Install package" + info2 " clean Clean up local files" + info2 " version Show package version" + info2 " versions Show versions" + info2 "" + info2 "options:" + info2 " --no-http-serve Do not serve the action result via HTTP" +} + +function version() { + awk -F "=" '/version/ {print $2}' "${PROJECT_ROOT}/pyproject.toml" | awk -F'"' '{print $2}' | awk NF | head -n 1 +} + +function versions() { + info "versions" + info "$(python --version)" + info "$(python -m pip --version)" + if ! command -v docker > /dev/null 2>&1; then + warn "docker not found, skipping docker version" + else + info "$(docker --version)" + info "$(docker compose version)" + fi + info -n "package version: " + version +} + +function install() { + versions + info "install package" + if [ "$#" -eq "0" ]; then + python -m pip install -e . + else + python -m pip install "$@" + + # check if "doc" extra should be installed + install_doc=false + for value in "$@"; do + value="${value//[[:blank:]]/}" + [[ "$value" =~ \[(.*,)?(doc|all)(,.*)?\] ]] && install_doc=true + done + if [[ "$install_doc" == "true" ]]; then + info "install markdown linter" + if command -v npm > /dev/null 2>&1; then + npm install --no-save markdownlint-cli2 + else + warn "npm not found, skipping markdownlint-cli2 installation" + fi + fi + fi + #info "post-install" + #py_pack_dir="$(python -c 'import site; print(site.getsitepackages()[0])')" + #info " + setup user documentation plugins" + #cp "$py_pack_dir/plantuml_markdown.py" "$py_pack_dir/markdown/extensions/" +} + +function clean() { + info "remove __pycache__|.pyc|.pyo" + find "${PROJECT_ROOT}" | grep -E "(__pycache__|\.pyc$$|\.pyo$$)" | xargs rm -rf + info "remove builds" + rm -rf "${BUILD_ROOT}" + rm -rf "${PROJECT_ROOT}/site" + info "remove egg-info" + rm -rf "${PROJECT_ROOT}/*.egg-info" + info "remove tox" + rm -rf "${PROJECT_ROOT}/.tox" + info "remove pytest cache" + rm -rf "${PROJECT_ROOT}/.pytest_cache" + info "remove mypy cache" + rm -rf "${PROJECT_ROOT}/.mypy_cache" +} + +function start() { + docker compose up -d db redis + migrate + python manage.py runserver +} + +function celery() { + docker compose up -d redis + DJANGO_SETTINGS_MODULE="${DJANGO_SETTINGS_MODULE:-"fl_server.settings.development"}" \ + python -m celery --workdir "${PROJECT_ROOT_ABSOLUTE}" --app fl_server_ai worker --loglevel=INFO +} + +function migrate() { + python manage.py migrate + python manage.py check +} + +function makemigrations() { + python manage.py makemigrations fl_server_core +} + +function manage() { + python manage.py "$@" +} + +function superuser() { + DJANGO_SUPERUSER_PASSWORD="${DJANGO_SUPERUSER_PASSWORD:-"password"}" \ + python manage.py createsuperuser --username admin --email "admin@example.com" --no-input +} + +function collectstatic() { + versions + info "$(tar --version | head -n 1)" + info "collect static files" + rm -rf static + rm -rf "${BUILD_ROOT}/static" + FL_DJANGO_SECRET_KEY="${FL_DJANGO_SECRET_KEY:-}" FL_POSTGRES_PASSWD="${FL_POSTGRES_PASSWD:-}" \ + python manage.py collectstatic --settings "fl_server.settings.production" + info "compressing static files" + mkdir -p "${BUILD_ROOT}/static" + info " | create admin.tar.gz" + tar -czf "${BUILD_ROOT}/static/admin.tar.gz" -C static admin + info " | create rest_framework.tar.gz" + tar -czf "${BUILD_ROOT}/static/rest_framework.tar.gz" -C static rest_framework + info "clean up" + rm -rf static +} + +function db-reset() { + if docker ps --format "table {{.Names}}" | grep -q -e "db"; then + info "Destroy db container including its volumes and restart it" + docker compose rm -fsv db + docker compose up -d db + info "Clear redis cache" + docker exec -it redis redis-cli FLUSHALL + sleep 2 + $(MAKE) --no-print-directory migration + else + warn "Remove all unused anonymous volumes" + docker volume prune -f + fi +} + +function docker-build() { + versions + info "build docker images for local development" + project_name="$(awk -F "=" '/name/ {print $2}' "${PROJECT_ROOT}/pyproject.toml" | awk -F'"' '{print $2}' | awk NF | head -n 1)" + celery_image_name="local/${project_name}-celery:latest" + django_image_name="local/${project_name}-django:latest" + info "build docker image: ${django_image_name}" + docker build -f ./docker/celery/Dockerfile -t "${celery_image_name}" . "$@" + info "build docker image: ${django_image_name}" + docker build -f ./docker/django/Dockerfile -t "${django_image_name}" . "$@" +} + +function test() { + versions + info "run all tests" + lint + mypy + coverage + coverage-report + if [ "${HTTP_SERVE}" = "true" ]; then + if [ -d "${BUILD_ROOT}/htmlcov" ]; then + python -m http.server --directory "${BUILD_ROOT}/htmlcov" 8080 + else + error "no coverage report found" + exit 1 + fi + fi +} + +function lint() { + versions + info "linting" + lint-code + lint-doc + lint-scripts +} + +function lint-code() { + info "lint code" + info "flake8 version: $(python -m flake8 --version | xargs)" + python -m flake8 "${SOURCE_CORE}" + python -m flake8 "${SOURCE_API}" + python -m flake8 "${SOURCE_AI}" + python -m flake8 "${SOURCE_DJANGO}" + python -m flake8 "${SCRIPTS_ROOT}" +} + +function lint-doc() { + info "lint documentation" + # use markdownlint from David Anson (based on nodejs) + # https://github.com/DavidAnson/markdownlint + npm exec markdownlint-cli2 "${DOC_ROOT}/**/*.md" "${PROJECT_ROOT}/README.md" +} + +function lint-scripts() { + info "lint bash scripts" + info "shellcheck $(shellcheck --version | head -n 2 | tail -n 1)" + shellcheck --external-sources --shell bash --source-path "${PROJECT_ROOT}" "${PROJECT_ROOT}/dev" + shellcheck --external-sources --shell bash --source-path "${SCRIPTS_ROOT}" "${SCRIPTS_ROOT}/"*.sh +} + +function mypy() { + info "type checking" + python -m mypy "${SOURCE_CORE}" + python -m mypy "${SOURCE_API}" + python -m mypy "${SOURCE_AI}" + python -m mypy "${SOURCE_DJANGO}" + if find "${SCRIPTS_ROOT}" -type f -name "*.py" | grep -q .; then + python -m mypy "${SCRIPTS_ROOT}" + fi +} + +function coverage() { + info "run python tests with coverage" + python -m coverage run manage.py test + python -m coverage html --directory "${BUILD_ROOT}/htmlcov" +} + +function coverage-report() { + info "print test coverage report" + python -m coverage report +} + +function doc() { + versions + info + mkdocs_version="$(python -m mkdocs --version)" + info "${mkdocs_version#"python -m "}" + # check if the user has passed the --dirtyreload flag + dirty_flag=false + for value in "$@"; do + [[ "--dirty" = "$value" ]] && dirty_flag=true + done + if [[ "$dirty_flag" == "false" ]]; then + warn "consider using --dirty to reload only file changes instead of the" + warn "whole project. This can lead to a significant speed up during the" + warn "documentation development." + fi + # create and serve documentation + DJANGO_SETTINGS_MODULE="${DJANGO_SETTINGS_MODULE:-"fl_server.settings.production"}" \ + FL_DJANGO_SECRET_KEY="${FL_DJANGO_SECRET_KEY:-}" FL_POSTGRES_PASSWD="${FL_POSTGRES_PASSWD:-}" \ + python -m mkdocs serve "$@" +} + +function doc-build() { + versions + mkdocs_version="$(python -m mkdocs --version)" + info "${mkdocs_version#"python -m "}" + info "build documentation" + DJANGO_SETTINGS_MODULE="${DJANGO_SETTINGS_MODULE:-"fl_server.settings.production"}" \ + FL_DJANGO_SECRET_KEY="${FL_DJANGO_SECRET_KEY:-}" FL_POSTGRES_PASSWD="${FL_POSTGRES_PASSWD:-}" \ + python -m mkdocs build "$@" +} + +function licenses() { + info "generate licenses" + # create a tox environment and pull in license information + license_dir="${PROJECT_ROOT}/licenses" + mkdir -p "${license_dir}" + python -m tox --recreate -e licenses +} + +function licenses-check() { + info "search for license conflicts" + licensecheck + info "search for license changes" + git --no-pager diff --exit-code "${PROJECT_ROOT}/licenses/license_info.no_versions.csv" +} + +function safety-check() { + info "check dependencies for known security vulnerabilities" + # main only no dev dependencies etc. + python -m tox --recreate -e safety + # alternative + #python -m pip install -U safety + #safety check + ##python -m pip uninstall safety +} + + +# create array with all action functions (above) +readarray -t action_map <<< "$(comm -3 <(compgen -A function) <(echo "$no_actions"))" +############################################################################### +# run action + +if ! printf '%s\n' "${action_map[@]}" | grep -x -q -- "$action"; then + echo "Invalid action : $action" + echo "Allowed actions: ${action_map[*]}" + echo "Use --help for more information" + exit 1 +fi + +$action "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2b51cb8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,53 @@ +version: '3.7' + +services: + web: + image: ghcr.io/dlr-ki/fl-demonstrator:main + container_name: web + restart: always + build: + context: . + dockerfile: ./docker/django/Dockerfile + ports: + - 8000:8000 + depends_on: + - db + - redis + env_file: + - docker/.env + + celery: + image: ghcr.io/dlr-ki/fl-demonstrator-celery:main + container_name: celery + restart: always + build: + context: . + dockerfile: ./docker/celery/Dockerfile + depends_on: + - redis + env_file: + - docker/.env + + redis: + image: redis + container_name: redis + restart: always + ports: + - "6379:6379" # port mapping only for development + + db: + image: postgres + container_name: db + restart: always + ports: + - "5432:5432" # port mapping only for development + environment: + POSTGRES_USER: demo + POSTGRES_PASSWORD: example + + # adminer: + # image: adminer + # container_name: adminer + # restart: always + # ports: + # - 8080:8080 diff --git a/docker/.env b/docker/.env new file mode 100644 index 0000000..a9bc4a8 --- /dev/null +++ b/docker/.env @@ -0,0 +1,9 @@ +FL_POSTGRES_HOST=db +FL_POSTGRES_PORT=5432 +FL_POSTGRES_DBNAME=demo +FL_POSTGRES_USER=demo +FL_POSTGRES_PASSWD=example +FL_REDIS_HOST=redis +# TODO: user docker secret with FL_DJANGO_SECRET_KEY_FILE +FL_DJANGO_SECRET_KEY=django-insecure-*z=iw5n%b--qdim+x5b+j9=^_gq7hq)kk8@7$^(tyn@oc#_8by +FL_DJANGO_ALLOWED_HOSTS=* diff --git a/docker/celery/Dockerfile b/docker/celery/Dockerfile new file mode 100644 index 0000000..2aca594 --- /dev/null +++ b/docker/celery/Dockerfile @@ -0,0 +1,39 @@ +FROM python:3.10-slim + +# system dependencies +RUN apt-get update && \ + apt-get install -y \ + # psycopg2 dependencies + libpq-dev \ + # translations + gettext \ + # git dependencies + git \ + && \ + rm -rf /var/lib/apt/lists/* + +# add user: celery +RUN useradd -ms /bin/bash celery +USER celery +WORKDIR /home/celery/app + +# install dependencies +COPY --chown=celery:celery pyproject.toml README.md /home/celery/app/ +RUN mkdir fl_server fl_server_ai fl_server_api fl_server_core && \ + pip install --no-warn-script-location --extra-index-url https://download.pytorch.org/whl/cpu . && \ + rm -rf fl_server fl_server_ai fl_server_api fl_server_core pyproject.toml README.md + +# copy scripts +COPY --chown=celery:celery ./docker/celery/docker-start.sh /home/celery/app/docker-start.sh + +# copy fl_server +COPY --chown=celery:celery ./manage.py /home/celery/app/manage.py +COPY --chown=celery:celery ./fl_server /home/celery/app/fl_server +COPY --chown=celery:celery ./fl_server_core /home/celery/app/fl_server_core +COPY --chown=celery:celery ./fl_server_api /home/celery/app/fl_server_api +COPY --chown=celery:celery ./fl_server_ai /home/celery/app/fl_server_ai + +ENV DJANGO_SETTINGS_MODULE=fl_server.settings.production +VOLUME [ "./logs" ] +CMD ["./docker-start.sh"] +STOPSIGNAL SIGINT diff --git a/docker/celery/docker-start.sh b/docker/celery/docker-start.sh new file mode 100755 index 0000000..5d8bb66 --- /dev/null +++ b/docker/celery/docker-start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +python -m celery \ + --workdir . \ + --app fl_server_ai \ + worker \ + --loglevel "${CELERY_LOG_LEVEL:-INFO}" diff --git a/docker/django/Dockerfile b/docker/django/Dockerfile new file mode 100644 index 0000000..5beed8e --- /dev/null +++ b/docker/django/Dockerfile @@ -0,0 +1,40 @@ +FROM python:3.10-slim + +# system dependencies +RUN apt-get update && \ + apt-get install -y \ + # psycopg2 dependencies + libpq-dev \ + # translations + gettext \ + # git dependencies + git \ + && \ + rm -rf /var/lib/apt/lists/* + +# add user: django +RUN useradd -ms /bin/bash django +USER django +WORKDIR /home/django/app + +# install dependencies +COPY --chown=django:django pyproject.toml README.md /home/django/app/ +RUN mkdir fl_server fl_server_ai fl_server_api fl_server_core && \ + pip install --no-warn-script-location --extra-index-url https://download.pytorch.org/whl/cpu . && \ + rm -rf fl_server fl_server_ai fl_server_api fl_server_core pyproject.toml README.md + +# copy scripts +COPY --chown=django:django ./docker/django/docker-start.sh /home/django/app/docker-start.sh + +# copy fl_server +COPY --chown=django:django ./manage.py /home/django/app/manage.py +COPY --chown=django:django ./fl_server /home/django/app/fl_server +COPY --chown=django:django ./fl_server_core /home/django/app/fl_server_core +COPY --chown=django:django ./fl_server_api /home/django/app/fl_server_api +COPY --chown=django:django ./fl_server_ai /home/django/app/fl_server_ai + +ENV DJANGO_SETTINGS_MODULE=fl_server.settings.production +EXPOSE 8000 +VOLUME [ "./logs" ] +CMD ["./docker-start.sh"] +STOPSIGNAL SIGINT diff --git a/docker/django/docker-start.sh b/docker/django/docker-start.sh new file mode 100755 index 0000000..02805b3 --- /dev/null +++ b/docker/django/docker-start.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# migration +python manage.py migrate + +# TODO: collect static files +#python manage.py collectstatic --no-input --no-post-process + +# run DLR FL demonstrator server +# NOTE: set insecure otherwise static file will not be served +# TODO: remove insecure flag +python manage.py runserver 0.0.0.0:8000 --insecure diff --git a/docs/.overrides/.icons/dlr-ki.svg b/docs/.overrides/.icons/dlr-ki.svg new file mode 100644 index 0000000..758ea06 --- /dev/null +++ b/docs/.overrides/.icons/dlr-ki.svg @@ -0,0 +1,217 @@ + + + + + + + + + + diff --git a/docs/.overrides/.icons/dlr-logo.svg b/docs/.overrides/.icons/dlr-logo.svg new file mode 100644 index 0000000..b17447d --- /dev/null +++ b/docs/.overrides/.icons/dlr-logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/docs/Fl Platform.postman_collection.json b/docs/Fl Platform.postman_collection.json new file mode 100644 index 0000000..540e1c6 --- /dev/null +++ b/docs/Fl Platform.postman_collection.json @@ -0,0 +1,947 @@ +{ + "info": { + "_postman_id": "a26cce21-d9a3-4b13-abc9-6f06a8ae6d1b", + "name": "Fl Platform", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "20639678" + }, + "item": [ + { + "name": "Create User", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.collectionVariables.set(\"USER_ID\", jsonData[\"id\"]);\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"testuser\",\r\n \"password\": \"password\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Catena\",\r\n \"email\": \"john-doe@example.com\",\r\n \"message_endpoint\": \"https://example.com\",\r\n \"client\": true,\r\n \"actor\": true\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/users/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "users", + "" + ] + } + }, + "response": [] + }, + { + "name": "Create Second User", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.collectionVariables.set(\"USER_ID_2\", jsonData[\"id\"]);\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"testuser2\",\r\n \"password\": \"password\",\r\n \"first_name\": \"Johnanna\",\r\n \"last_name\": \"Catena\",\r\n \"email\": \"jc@example.com\",\r\n \"message_endpoint\": \"https://example.com\",\r\n \"client\": true,\r\n \"actor\": false\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/users/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "users", + "" + ] + } + }, + "response": [] + }, + { + "name": "[MARKED FOR DELETE] List User Groups", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/users/groups/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "users", + "groups", + "" + ] + } + }, + "response": [] + }, + { + "name": "Get User Data", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/users/{{USER_ID}}", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "users", + "{{USER_ID}}" + ] + } + }, + "response": [] + }, + { + "name": "Upload Model", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.collectionVariables.set(\"MODEL_ID\", jsonData[\"model_id\"]);\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "model_file", + "type": "file", + "src": "/C:/Users/fran_bi/Downloads/test_model.pt" + }, + { + "key": "name", + "value": "my_test_model", + "type": "text" + }, + { + "key": "description", + "value": "A test model for postman", + "type": "text" + } + ] + }, + "url": { + "raw": "{{BASE_URL}}/models/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "" + ] + } + }, + "response": [] + }, + { + "name": "Get Models", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/models/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "" + ] + } + }, + "response": [] + }, + { + "name": "Get Model Metadata", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "model.pkl", + "value": "", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "{{BASE_URL}}/models/{{MODEL_ID}}/metadata/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "{{MODEL_ID}}", + "metadata", + "" + ] + } + }, + "response": [] + }, + { + "name": "Download Model", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/models/{{MODEL_ID}}/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "{{MODEL_ID}}", + "" + ] + } + }, + "response": [] + }, + { + "name": "Create Training", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.collectionVariables.set(\"TRAINING_ID\", jsonData[\"training_id\"]);\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"model_id\": \"{{MODEL_ID}}\",\r\n \"target_num_updates\": 1,\r\n \"metric_names\": [\"accuracy\"],\r\n \"aggregation_method\": \"FedAvg\",\r\n \"clients\": [\"{{USER_ID}}\"]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/trainings/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "trainings", + "" + ] + } + }, + "response": [] + }, + { + "name": "Get Trainings", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/trainings/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "trainings", + "" + ] + } + }, + "response": [] + }, + { + "name": "Get Training By ID", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/trainings/{{TRAINING_ID}}", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "trainings", + "{{TRAINING_ID}}" + ] + } + }, + "response": [] + }, + { + "name": "List User Trainings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/users/trainings/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "users", + "trainings", + "" + ] + } + }, + "response": [] + }, + { + "name": "Add Client To Training", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"clients\": [\"{{USER_ID_2}}\"]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/trainings/{{TRAINING_ID}}/clients/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "trainings", + "{{TRAINING_ID}}", + "clients", + "" + ] + } + }, + "response": [] + }, + { + "name": "Remove Client From Training", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"clients\": [\"{{USER_ID_2}}\"]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/trainings/{{TRAINING_ID}}/clients/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "trainings", + "{{TRAINING_ID}}", + "clients", + "" + ] + } + }, + "response": [] + }, + { + "name": "Start Training", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{BASE_URL}}/trainings/{{TRAINING_ID}}/start/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "trainings", + "{{TRAINING_ID}}", + "start", + "" + ] + } + }, + "response": [] + }, + { + "name": "Upload Model Update", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "model_file", + "type": "file", + "src": "/C:/Users/fran_bi/Downloads/test_model.pt" + }, + { + "key": "round", + "value": "0", + "type": "text" + }, + { + "key": "sample_size", + "value": "2000000", + "type": "text" + } + ] + }, + "url": { + "raw": "{{BASE_URL}}/models/{{MODEL_ID}}/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "{{MODEL_ID}}", + "" + ] + } + }, + "response": [] + }, + { + "name": "Upload Model Metrics", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "metric_names", + "value": "accuracy", + "type": "text" + }, + { + "key": "metric_values", + "value": "0.96", + "type": "text" + }, + { + "key": "sample_size", + "value": "2000000", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "{{BASE_URL}}/models/{{MODEL_ID}}/metrics/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "{{MODEL_ID}}", + "metrics", + "" + ] + } + }, + "response": [] + }, + { + "name": "Get Model Metrics", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/models/{{MODEL_ID}}/metrics/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "models", + "{{MODEL_ID}}", + "metrics", + "" + ] + } + }, + "response": [] + }, + { + "name": "Model Inference JSON", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"model_id\": \"{{MODEL_ID}}\",\r\n \"model_input\": [[1.0, 1.0, 1.0, 0.0, 1.0, 5.0, 1.0, 1.0, 7.0, 10.0]]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/inference/", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "inference", + "" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "password", + "type": "string" + }, + { + "key": "username", + "value": "testuser", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "BASE_URL", + "value": "http://127.0.0.1:8000/api", + "type": "string" + }, + { + "key": "USER_ID", + "value": "" + }, + { + "key": "MODEL_ID", + "value": "" + }, + { + "key": "TRAINING_ID", + "value": "" + }, + { + "key": "USER_ID_2", + "value": "" + } + ] +} \ No newline at end of file diff --git a/docs/ai-ecosystem/FL-demonstrator.drawio.png b/docs/ai-ecosystem/FL-demonstrator.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..5634c898b163b3363660f87921ffb18ed2c7db53 GIT binary patch literal 143145 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

s4)w4d3BMdCGl^m`wQr9p+Z!L7!5r@ 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 M(kfDAlFtMG4=lRN#sB~S literal 0 HcmV?d00001 diff --git a/docs/ai-ecosystem/ideal-data.drawio.png b/docs/ai-ecosystem/ideal-data.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4f77354f1f7c9d01fb668c31c446f99cb0ad1c GIT binary patch literal 16979 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 Bb7lYl literal 0 HcmV?d00001 diff --git a/docs/ai-ecosystem/ideal-services.drawio.png b/docs/ai-ecosystem/ideal-services.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..93d2d5c8ef1a8720a81ad0c30645b332aed7bb57 GIT binary patch literal 17272 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 AS^xk5 literal 0 HcmV?d00001 diff --git a/docs/arc42/quality-model.drawio.png b/docs/arc42/quality-model.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..cdde33131d382bb77098cb64d61ef929c9bdf063 GIT binary patch literal 23011 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 literal 0 HcmV?d00001 diff --git a/docs/gitlab-runner/check.png b/docs/gitlab-runner/check.png new file mode 100644 index 0000000000000000000000000000000000000000..768a319e7928df86070054bd748d6d724744e8d2 GIT binary patch literal 41815 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 literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..41490e5 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,76 @@ +--- +title: Home +hide: navigation +--- + + +# Federated Learning Demonstrator + +This repository contains the Federated Learning Demonstrator, a comprehensive suite of tools designed for machine learning applications in a Federated context. +This server component that handles the orchestration and notification of all training participants and ensuring smooth and efficient operation. + +The demonstrator further provides capabilities for diverse model aggregation methods, merging the model inputs from each participant. +It also offers model inference, enabling the generation of predictions using the trained models. +The repository also includes utilities for quantifying uncertainty, which provide a measure of the reliability of the model's predictions. + +This project is the server component of the Federated Learning (FL) platform, serving as a proof of concept for the [Catena-X](https://catena-x.net/en) project. +The FL platform aims to demonstrate the potential of federated learning in a practical, real-world context. + +For a comprehensive understanding of the FL platform, please refer to the official [FL platform documentation](https://dlr-ki.github.io/fl-documentation). + +A complete list of all repositories relevant to the FL platform can be found [here](https://dlr-ki.github.io/fl-documentation#repositories). + +## Quick Start + +Along with the general [FL platform documentation](https://dlr-ki.github.io/fl-documentation), there also exists a [tutorial](https://dlr-ki.github.io/fl-documentation/tutorial) in which all components are explained using a simple example. + +The simplest way to get the demonstrator up running is to use Docker in combination with Docker Compose. + +```yaml title="docker-compose.yml" +version: '3.7' + +services: + web: + image: ghcr.io/dlr-ki/fl-demonstrator:main + container_name: web + restart: always + ports: + - 8000:8000 + depends_on: + - db + - redis + environment: + FL_DJANGO_SECRET_KEY: django-insecure-*z=iw5n%b--qdim+x5b+j9=^_gq7hq)kk8@7$^(tyn@oc#_8by + FL_DJANGO_ALLOWED_HOSTS: '*' + FL_POSTGRES_HOST: db + FL_POSTGRES_PORT: 5432 + FL_POSTGRES_DBNAME: demo + FL_POSTGRES_USER: demo + FL_POSTGRES_PASSWD: example + FL_REDIS_HOST: redis + + celery: + image: ghcr.io/dlr-ki/fl-demonstrator-celery:main + container_name: celery + restart: always + depends_on: + - redis + environment: + FL_REDIS_HOST: redis + + redis: + image: redis + container_name: redis + restart: always + + db: + image: postgres + container_name: db + restart: always + environment: + POSTGRES_USER: demo + POSTGRES_PASSWORD: example +``` + +After successfully starting all necessary server components with `docker compose up -d` you are ready to go. +A fitting start would be the API documentation: . diff --git a/docs/javascripts/katex.js b/docs/javascripts/katex.js new file mode 100644 index 0000000..f7fd704 --- /dev/null +++ b/docs/javascripts/katex.js @@ -0,0 +1,10 @@ +document$.subscribe(({ body }) => { + renderMathInElement(body, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true } + ], + }) +}) diff --git a/docs/styles/style.css b/docs/styles/style.css new file mode 100644 index 0000000..a03ca4f --- /dev/null +++ b/docs/styles/style.css @@ -0,0 +1,3 @@ +.md-content { + text-align: justify; +} diff --git a/docs/web-service/BuildingBlock.drawio.png b/docs/web-service/BuildingBlock.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..328c70d224617f87e383ff9d91c6f7fa1abcecdf GIT binary patch literal 30651 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 JWt~$(695jEz4`zE literal 0 HcmV?d00001 diff --git a/docs/web-service/RuL-Interaction.drawio.png b/docs/web-service/RuL-Interaction.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..5cec13810a1728e4660d5297d26b4892c5e82a3c GIT binary patch literal 32895 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

o~&Zlr^p1*(H^WUP5y0hAcz^no{P zfr!3OoB0;#^!7VbaZ(6q#24#X$)yK7({+jp3bEr!d@j{J;QMI!?UAO0e`Pur;w{b1 zOUuh0g9kquUFM-q23fmLT_W?r}@+eHtgi6UQ#uL0T_867R^1Ar+L$LixCltrlH zc8pU5bK1|y2+_DfrrEcAlZo>6znjcX@>Gj#m4Rvs>V2}%_Pkg|x@>0MCddg+tH<^E zEYMxv#AyNK0g4v&%yrpH%W@d%3)J;uk`(RM6_FPrb-KBg5a&dj35DyeMdUbF2Vv6}Y*&<8RN4wCYyqX1^`~ zDs=`DtTVi1poE)_6_d?Yc|m*iu0JeRjA|1;GDcUmk$wu33-=NGX95CXsZpxm5l$`! z{W1jAsBTD1b z*-E_TK`WtE809SU12VD~Lm!`Mc8yirkT+s10_iDAai?~(u)Vz<0rj5UTK{~;scI4(*8yXrmlP%gpUF27{&{}7NrJwqHy{r(afEo m-*-+J+U5K9&o1AnYgqLo#9_J=VG7Xl50Ma+5h)bX_4;2VYP^yF literal 0 HcmV?d00001 diff --git a/docs/web-service/model-enums.puml b/docs/web-service/model-enums.puml new file mode 100644 index 0000000..ccf47f6 --- /dev/null +++ b/docs/web-service/model-enums.puml @@ -0,0 +1,24 @@ +@startuml Model Enums +!theme plain +hide enum methods + +enum TrainingState { + I + O + C + E + S +} +enum AggregationMethod { + FedAvg + FedDC + FedProx +} +enum UncertaintyMethod { + NONE + ENSEMBLE_LOCAL + ENSEMBLE_GLOBAL + MC_DROPOUT + SWAG +} +@enduml \ No newline at end of file diff --git a/docs/web-service/models.png b/docs/web-service/models.png new file mode 100644 index 0000000000000000000000000000000000000000..d865c495674fb9f14f9894ae0ff5ffb59b0e7e4a GIT binary patch literal 57145 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 "1" User : owner +Model "1" <-left- "*" Metric : model +GlobalModel --|> Model +SWAGModel -up-|> GlobalModel +MeanModel --|> GlobalModel +MeanModel -[hidden]left-> GlobalModel : "\t" +MeanModel "*" --> "1..*" GlobalModel +LocalModel -up-|> Model +LocalModel "1..*" -right-> "1" GlobalModel : base_model +Training "1" --> "1" GlobalModel : model +Training -[hidden]right-> User +Training "1..*" --> "1..*" User : participants +Training "1..*" --> "1" User : actor +Token "1" --> "1" User : user +User -right-|> NotificationReceiver +Metric "1..*" -up-> "1" User : reporter +@enduml \ No newline at end of file diff --git a/docs/web-service/offline-usage.drawio.png b/docs/web-service/offline-usage.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..49edb13a97c6975fe6e2ebfae5e489b34ac78f67 GIT binary patch literal 16834 zcmeHv2Ut_h)@}d+2~9+b6h#q4danY~LAsC-Kp+7kL`one6bn*BQ4|YBq^LA0K?q3i zsDOeLsY>q%2uPRy?+AP!Upe17=iYPvd;WXx^GNodH8X2wuUYfHYu4V6cZ>|QXu)h? z5C}x8qpf}cxJH6NB!dSjfV+FfS|T72>j$g`4C~^33FV9g@kprc-0_HuI-)(WJQC_W z;^GK5H(`4e!U2PD@ep=JVgV6Q?}D~RT|y!4ciM=Hic1KIN(+gKnTksDNT^8206(JQ z!eWv#7CY?`4oKHsh5G0#C}(E`kGQ6kuqdF4&kW^ikM{BaZVgObEX+f~a2 z>){Bs*E=sHF12Gb53J9gb&>Xfv3HAT4Av3tfObVVYy2uz#h`Jn_CL2Sai9I#Mh}g4 z18gY<2rnbCSf5?bLEx}xK;(#Zao(*(dZVx~;9gW%Mhd7Q7R~onsS`bwsG3-zPIS_r z1y?M_XMZnZnb_@@%3u2ttA6RY=M}%2ao4gQXdDLEjX!$&`i=l-cd#CH+R6XA^8og8EReO{taO*(4<&z*ns|nK89=Pnn&rTy?KKt7J zsq5(>Yyr6Xjk_S6Q4T~{wFCA65(Cr$NQ*@Qc%ZUd;exWaC%UgH2I+yig0TJR%0#zt zL!(@=KdmRF$|I!?NdD*yfBvAm`+_(Uat}Cn`tG9I?kIrlFM(rcm3MxLR^GSPyJ|&+ z#l>WI9cUMjcL)E6o#C$+_xDpUA{qr)nceYs2iqN5cIP4YTn>HdlE)rp|9ywsd(5A1 z_sf(1_3R|n{?$&pyKDFPs2S4M1c}+9UQs?3BOSm$iCbtN>ivpoMELS@1i0744e=8l zyZ{yjWc%=Ir?(s84~X&yclqmQ-?P`BxrMmgE=%t&jHJ{q_+69pAHMhm=Va%T7_lWWbFtSHr5w-{q zAm}3EsJN&t$`wI`+MR}fU*YfkM|K><8MM95z`afdK}D*E^HrNoXm z{(ZjOY4?9UUGCcBH-7&+e*z-VpN9iGA7|hcgTzSuee|*23BUAg_rfV#gq@SaPXIH- zVF5KnW+tK-z=c0=iL3wL0a+5_zlAJunca=Q53=^qX+L7g{>LE}5mbo_^;auLw7A
!x)`ORlFg9nEVzWn24A zV~)33Y#utW?6mnm2|N;dm?m*-KNLGguu}S2ViPsfti_Py;sAQ zv7~6_X>~<}HzwLe$=v!I#-rq|7n_XSqt7oZ)S;$ctEaBaa^ z83fME*gazNaAWc{?ewWUk6VoXz>GKQKK9aMjGr_qDvgy7B_ooU>(K3i-`Au;w3CKr zbq%mfm_&E!PGZ+{N-{kZcUBD}Iqe;wMtt_Jt}e=jc$w#{O-zmO@fCN7fk8u4>_D{` zC|z>(gVCmI^xgQ>V~6reTFgW5kGr7;>-E$FVl=jx&%-lpQkwA|b7T~pNW0T^qdnP9 zK(U4fQGNC@y->KrdpXAFfmOS(~qmvRB#@t!LJK z&gKb@$u-0bax2!o8=|DHk&7Qp5ifk_xxhMVzuG#R?}zy@E~gCb?t4=LBl0zu{S4K` z^j_1pj~iEkk3PD5G_$P|Ep2V57@L?~p9Ppcf}ICEi5|I%O2smtOdG;&)HsVEnMO&H zwakqYRB7iOLvwuxjTX7bp4^44-lJ?H0%c6sF)F3`IdQi^X)+m37TAv>aMb5!cfDpX zh%RVkGi$hL5JQs{(KIA+7g5zAv7iIM__m zOne>9YSV*xX(j_8_S3Bu!-LYvLS)26US>fm+ll%RN0ePk>Uc!8v7QhQgeDyT&l-zb zyG!53+iT@ewUGhw1U9lZsB&&D-@V&{hbO5Yp>W^U=qu+^gD6~YIM&pPn76*L`tYni zd~jeD<$i!VIO&%x;H~N{aBEKEvYI$D8;V;KX?*Z_b!f8N)%wGI75joV^J+% zI6l8tN);8U&|DdW*C8W|2Ky5$6a3w;hefKc_9=OdIUG36pA%hj(7+C5qFb9)D@!UY z7{UiE0`B>wretwVG(d1cS>LHJMg=O##4+Asj$*gZKENG0_bJ`A^Xj*D#k7n!O(x|U z@7ux8R%jOLvw|{mrQ7g<=V>%h4Gleq1|rliP|&-suK=VqE7j!bC#Q?>~q82^PYcHs31J!h#<5{#Jk}VJ( zVXp=wJ2&sM*OJc#qlDjOjIz9#7ci*ef=;Zn4B#_Q=C)uQWT6>s(Xz0bsdNny*}_AV z?Af!h$Pyht(LHxXFTIx~J;Rhs-A2dEDZfC;krg47?8&W;xJ+g?50%ZyzzrUh{4v2m z$#*goDD~wW{A6c%{t_aLBSF~BE`4HS1FZuTm1mDRcWt1Gnd1uDblda87n!(=@kjEa zi-@JmfE%eh*D#`U*=xRo{wrS9Qi zXIPFCSVKdFN9sR5CQ$%ZnEfY5im%mjDz{y#+B1`I~x;hZvQFP-YhJ!z?i- z=}e;`y(|Ge2?=M5*2bC9ojJ8V@h}Y}nQHh0LlkrMbVFr1Kwmr2GDNgp2papej2(uv z#E<@8d*>MzRkO8ef(nR|6cn_Hf-MS4R**~+8j&O*IcJ&%B?v7b5)}}EMg(3$lXDId z8jNIAa#BzU5(SA$oYlVHnK?7x%+GJ;%yk|AvD@ydUAwAwRjuc_*Jo7XUBsbLPH=}% z>KCdrqKm12Zq=WdMI%Qj+QM@H52Dtz5^`Bo{Hw4bpRpFJ!`4JVK*hybIQNY^7{@IW zap8xhI^)KVes+npHM8z79^J66ac(9pa1HpDFrT}P58EYs#}O-P6D7751Pkgl8vIx3 zZdOg$eRS^KT*6E(Rpev39p>x-1GE~2z0dQz)`#cym5NT=2`n~dLXAfW>8TKfBbmYP zoNf~ijxAhlDs*DGpvh`aWd1q!%4qM%DN&|IJAuZeM_r(SPHX=ZY0zeH@Nqw;Su5fohewHP$7?3hvt!t;tkX zINmsWq~aK1s_fp)8_Yh>x0%pO?=5~Ypy$?>M`_R!BGRaD`fvSP71U^-R&yw%>WXv^ zy3np&FSi(6p7qTkMZ9Pxqx}}G?{td|wUJC5L&qxV*h#Kz8Ur}39-3_d` z%Z1Nwl^xFn6)W>=i*`Dh(8MdV)qQH#P2z6lCVal2psO#`FJ`-vh}-+>B<|W@M7(xd zueGqz{gU%C_u8Aaj0izCk6qMuYtgkkZ)M5`Zgv0aGw9i%+N}3nh?OXT)qLF9lg=%R zjd!x`SpgjuyZEWtvt9DJxH@urX&&?bQDvu{-HFxRB#y<+u1D9ryHmN!KU7qvspoW& zwk!;D^gHWoSDh!$-7RMCFQl3s6{=2-L8w4*DlIdo$*we~NSI$%r1K8g^UjGV&dBUo zOe~Z(H@PF-25)Fe?HY|pEETXQs8Oaj;F^~GmgDH}f1zyuw&J(-*?@HZY4g>4`+@9E z`b*)ESjTg7B0=l8xbj{{R7#Ub7`>fs%B@)tUN+Xl7^Zg!Px=nZ1ZvQqX&wI_&nC;f;Q5RnPjgfx;i_P0zeVV>AZ%ZO%uwC@` ziGyAB!ynEO%?{6iqu~fmwufO~*q5Xr-^x_IIb8RprzILUX?X?I%~+3HS8C_9C>>xp zR-KF;a z!YSy0sFCJTe6`(1@3gA-Xm)ed@G$nZ6xXBS&@J(qb zJ?F!P&ILuuCUdV5+o&MZ?yH59vF|TUAuOD?(yqr?dG!xc6^-^aSqdX&46TI?a}Gvz zuNu({B=7YgDx#2Oxrq>V_S&7XonEwm^jHcv9eN&HAZ6N*+xsfaPJOup-)Px zPDl9F@iQ9kw#C}LVs8v>`(M~0`kiTcr3wcxCF7ZEuR|1h{Q8GpseJEC`2H6ZebFOq zoXcDm!V3JzBmo6i@jV;aJdd$^nPk;gUdr}88K??4q|N#&A$6e4yaC9gBN&v$;`62P zh)Xl?(OgOIOE;I^efAo03Blg97;K3%>q;@Gb=9?Qv zkFk>hT{GM!S=jz=GCxsExUUP$GOSg6mo--$e7d;zxfSvkCD_-F2nEMRJPV4d(&~^r zJrL<_sBmF{b97z(dOf?o#D)GF!TY!M-Jg1lErb1uNrfpswVPLas+FdGvp2?`Hkl1*T-PtmYuk<{whyW zKP$JQ>bUcaY9+IsQETcCHO+QX6n2$F)r;bAGNc-2X`dNp{?||Wqvi``+ z@%6szFE%iWQq`D4!G0KUQ(^Dk6o{U?vS~zxlM@Tlmz<0ftG2VU$ziR2GkW=oN3yog zj&E#aw`j_q(8kBrq}m(RXAPT2msW>^vII-I>)8oY4X0YZPOzXQTgI9<`*f%0E*xJf zg~d$r#`%^UpW7Gfigp%jpGVl>;qir42%dg#e7}S?Ldvi!Wn+@$QH?@%kO(S+H|BB& ztXwNyRdU6aMrcHiJKCvs-0mv2b`T-hFrBv>9oR*RsB1ok0wOKa1nssl!PacKdrAU|w%G zPBZLtH8eKLnqUy#0Ls_k1!tqP!8$vEipc5@UEdMZylLyw+k(Oy&-^QUQg;RxRiJC= zUs#aD-R-sus0|4+ykP)dm%-$_k_Tz$Nyjy|0=`wbEmb9(>W96qCLa$reAw4m;@tak zPV9``Qpo;RtL(metOmN-y{+H)E8RO8bcl9!69b_?iP4}xSx!qKf24HoQkPHR){=9? zVwd4EgO~m`pmF*Z+*ZXsD{~3G*=0lAvlG48wduXjET@-T=`AW`C3A}?)J|PbIzCb{ zGh{3<^WHg{FEd-$LNGXXwR~cx?;x$UaX|dY=0mTz-`|RBrMIaS@+&Qa8g@^lFZqVc zL#dyOhox5@x7fxkktbo~wbr9r9UdIkiD|6;EO~)l7Yj=gcF;P)fzpF!0fLSH(gHqo z#I5uF$zel^&ks8N$M}}zj4R*2_SaPiph-rR)SrsIsW@Bey>`*~TD~7HjdD+rxK}qq zrZMqL=uEh6yfn7+F3#!AZtRfWN_|5i5!LV$H*6Wk%X$oF8u3hb!C|LtqNojb_f3uE zeau9Ybh)kgU3GiMQAgSCV4d-wqBVPf-0|voRsD|o`DW(ctAQLm?8g^D;Xw{tZy##q zR@ib-thGF8sz|6VZWxKuL+K6%)_4uRevh0xBV$*3)Ngw2yX=8a1>c*ZD90L!AgEr+ zZ&ljtqKahSsU?352d~xTtAio+o7-m^=-4#ZjIxQpZKcMx#wc_#!^XeumqxU!j*Fc@ zqcCo!L~uaQBV^GNanL{6UysLV2}d|DZ7^-hd*-kwY4k+Pd`d<*IG1SB^6peS68ZdQ zQ;8Bm`_fqYBXAtl>FP3t@o`NMScu}rjd@nSsUa@pCGz(yKX#ocWu;_}PpTFf$yzU* z{A?3fbrS+AM1d;_)Y&g76x=yKCIL86jOV+-Rc5r|SbeSJ$H5Y97{+NK@zsy?Oha@k zF~64PBt=4UH$+E@i%wKV)G!3=0LaTMOzW{kP5&h$@LtgqJo)e*^8(Js-Xa_E-su=P zQll^ZVe~o5!UpH{#8@Ply|W@zX}^T>36 zfVaq=e!Lbst=aA#OLr*q2t~%g#WfU8L*H;I|3ik?{2j#K0X+r>*hKp{9K+D?(oYwD zvK?nRT#hl-ir1>3oLwE*6lPPFY$;z(jnLxHWVSp(kV%H@tNm}{kS~A z7E9T4$%#7q$CxM!rxppS`R?Ta0{kTF3?`vZwRK{?SkY2XJ7P}}aJHWHhl7Dj6B+A@ zCFr)#IK(toI~cK8XTo{9M|o2dW5L86u^thc$lotKug&kPVK^qoi5I3>DWkDu$T*h5 zkiO)lP~3@QK$}t&Ei3_@@RYG!R)~&-vE1`F&Kf3t$$oZ-5UaN_C53&_MCsuD05}p@ zOwv&COe+_z4U|K|xv`|Ow(F=X5(7*=^f`V(m2idpJ^@Y>aAfx<(?l>zr5xo=sp;#| z-sUu}HlGn^pyp*4XQmf;aWR36HT%wQJEP0z@E(ufn&lEGn}|D}l_IbDowqUEYcX_bTF#cfSU9 zHp<5SBl47o{iX5IMrG7B@qFNSSdR&?0URgAg}euitgbw)q)oWxF|R@2z1LyT#7(jz zKffS<_m9m|;**2N@3n!GsOc$h1NWD z1F*Z6UZ@~dBWrWDCa`T=1}o$}@^`5zk(WQ6xI`-TW1be9EN*kLY5k#2x7+X;#60D% z+U7lsbt#wMN-A)S+rQm1wYKC~cbbnme{P*Xpf%NVNxVP<0h8+5r=y}+#2lfjC2dq_ z0o}WEbWTxqBCl7|SKvl{WPMnye`I>^anTKSqB%WbAjmgrRwyw8$eQQ39O@P$wZ`q9 zZW9NuIorDvC;VTs1sH`Dz45QUzult8;@yB487agD?2HM6$J(&cE^6>D{{42=m`Nfl zFa45KpREh_idRQ`?YJaws~^LPGMqn<`^9S4&leOP@#x@L{$h0Z^g>sn$W|M2j&j`Q zVy_`WiZZ@hoX2^736A|7!_O02Q#UIoBr7UayM@s+2?42EzCdPWTBJgFXjRM(vbfGf zU1GN8i_1BvnQ#K0&9kE0O)A2kqsv)* zv&%`=r|+)JAdKvZ;|Vp?1D6{{iu&xOO}2qQmi0&goXl{LN;C87CKI6w&K3jhsD|eK zg|@d~rCIwzZ)x;(W}cP~2fqyHE{Bl$C6zrJXGyMH?v5Lq?;1_}qqKC+Z(13}5qgd# zFdC3FWYkDM^>*sljgf|hV%mlZYVYz!;#zN+RH8Qt4VlVOPSkm@>K+*_Hamf4-_~Cd zB&h?!DHJ$?&O1fWIH%wU;YiBb3IB%1e{4Wk0msk|l&p)FL3V2`n{*QnTDdHjd8~8B zA-0J)Iks4w2~(pXPX_A|T=y*rSU}Eq+j#Q~jk3Q~{OxAQ1oNw!g^)27#dVi?2HuY6 zD9|4eOi3O)`d9E?3Px%j(S&c_qlg|Wq50_5pgi0JU)kx(ctJ@xi8`IHWDlQuv_$=5 zQN)e^bqoC3iAd z(Im;k9e`*M`aS{$q*n4eH{O9#h~dlcuUwD+zPx2qGDgwJ#f{VBpu4`wNxp4Ce?&T# zA0E>oCc-Ug4>n1K{B8X(6=8H$;A!fW-4zB@u+GLu$65HgF@l}(|K??!@DPUFE4*!G z{!L&j`#Cv|#aHPnI<|20a@_xP^Qt5P1SNSY2YW`iRsp-@aRlL(U*Q`bh5z@L{WNFl z1A3jgg)_F@zLZnZe5Ar%nT@%X2#@LMKR>2ekfpqpd+7 z$k@0@IRZ%C=RcJ*5m)C*Tc)A3St4`x-l9Ck`YZNhzUTCEmVBwtDdhXkih4k&D5i_H z`Cq%&|Do6YS6;3R>D6Z($^AbyFm(8x^B8#;CZ}=zJbq;=lpONajt%S&|EIp9i~X1A z?t5|KkJ{S)^}D;YF_Os5omoIn#g2PQ=2u1)#I6<(jYGYcxA9?bHVgBZXuBn(y zSZ+7j(%eqr#fLs_`!TN=)XL5U(@l(=%54ppZqolAln2sl>Pk})+!d-yKkgeNLDNKw znqp(TX0EiKv%Lc2n;OP9XIqSZ42m{KiNh32{ack>*)x}P8Z7Y{d2(UnF%Zi7c{1_kuT*>S#xireb0KrNDt@vGt$?k6P@+v zTo~&jtJMZ1+Su1s|I;)KvxQxJ6F+xurFj1Kk&z(M6FTaEOD zG4D??OC?^{UFs)WKkt)tgJsLpRP8Tck{PutCKtY)e#)iR_hWj~l;(SqD57nQT5OK?+y10yPPySM3~BC=EUFEYPFw(jc+zjNhM{lPDRU3CY`8C$x|3fv?)5=CKE8}EVluqS^!c% z#FksPb}*`(oLEH;0TUAwdm>I@g|3)6LqA3=ZB$y^|0@VAeb-ZuPryO zO$Bl6ro(br<%#4x5~vW3>swo$VE?=T)|}cuym^w9arKer{fxzL_sXMze?kl-wCOfMPh5fC9jE{H5DvauDtoyf&8=cSl2(bh0_88DIo(M?Ns7dKw0Ex^Uqi zQY+?#02P~!wPWBh8K#z*zyx?&e)59LaUW!Migw%6b{|~_Jh>W6{(wrQx(bJ16-2W| z?tmb#w!Tq6Km|F#Z=!eVS(wJa+n?Xz7lxSI#TPm8qYs$3vq81x#Ql^-(B$@uJdzzi z7_kKa#fBAW&lkYr3Pbu_xfmj*1>iX_ty6>qq z>Y}h=veEXw;S91Cj6{%GePEczWt3D&eXp1F+Y_XwXB8~1n_=_W+ZaL}jQ#i?t1cJ= z)>D=B=JNXt=yIhByzffcg~Jz>FMFY$Jslb4?Qi&!v^Gsp z4h`%Iz)dG39!nreR5~0CVn(MdPdHlO(v9Fr%PRxH!7NmrR&D`Ah-SVyJYma}LwN?+O8Vj;1jZ8`_w4?A8 z<*!2HsI%pgtY+WA6J+Pwnl8-Ns{a~#CN{yPX+8TgZ>tU~A*)r>)T0O0-YJ!Ku>1XU zUqK?Q27Sm2ZCf2ff_QHDYvwIJ>Eox{m39ERb^04!^jD{AZi9K<8f4lUcev3O%+ZyW7onC{;MaBX=xf2X-2p>>O*aEv$ z*lY3jwQu1(Q=~*3U1_q-d5K15241cCyeE%hAf)MAD$wuMI8@XtHU5*%2aLXK+G7y( zo%U|tL5yV8KEg|n$7#>UkY^6DkU#tE?wd98)(G&=MKRr_2AB{X$wDP@#}Awd(&IdM zUz?Y5$PLx>d^<+Cd2n>p=L{;ezvqw9P(bR9h&g|DA3YqR<&te|Z1rla>HGJxJ)9z{~%5P{+7jEzbZAU#7Y$&crahI3|O+JdK zOgcGYQ3Y-seyzoOOW#9)DHeZxm#B9ATM2r9OiuAy%$Ko$m z-3*-T7N6*1Fe@|9VU`rqik|UxI=j_260wl+p}3+ICyX|GZBFdVI9{?H8$VeN|MzBo zxe-M1bx`RS-9QjbR&n_hD$Uq7dW)LZTp-=58Fj|w-8=qn(-A~&)kEtSN1?U&kp`2L zgj>h$FPlA}>sL=roWV7F3BjP1-_HcM7UvPEg1QzBDQ)fF} zHTars-|~~+B6Nn5FeS?^-Sdi+Mk9C~Q~Q~3vw3gwa_u2%IL93P1Uv8<@~X!ZCnoP* z|LRoAN1eTSFW%jA9kJ+azLPY2uy`rfE$+Q<8l` zr8uf6PeS=HAJZp5jVaSba#s*4bdPIy;}i(*a{=}-Ow5TF3O`7yUh0meJ)Ju01G;F)?z zEBF*vEvAqqjMKoxJp?kEn2@-n;!pSY{S##a{H?6#`|~jq9KFY>c@KK>^r)Pv3}UKP zQ6-M8_6|ebc!da6t!a7WVdf_}A-@~L?`tU4Oq=K>yPKeTL8FH{D5Ow5+zlw}DUfZ) zgWNO&#P3%<*Pzadmv_ua+Nb`)cU~1Jp;7|{qz@*PAf*CtGr|FKf{u}uk6R+Vt(x^= zK7f2dj(4Zb|4P*Ebq1hoDnk#)3-ts+js0TQyhkU_ci1MFKHEJg zEU8ETp||4veC#3n6v-by2guS-0q`s0!A6KAGFILL?E)0sl5WF7^eC|@rQ%l;Xr-hm zTq^yW*6Y}g$b=YADU&gSU08Tbl(Cys0*k20?-p0Qj`=P3tn8s0aL&{{$ik@@cUUHg@IJ4hJLYip&HnSQn8!&mJSso5hp!Vzx$inBqh(XWxrmoj z0wrgjTQl`oYt}*_$x?T;(gPk6V+0!MSvixJjN1znYJfhe#dAWbj+7tbur|}to(qBh zkrx8q*>RDvM|p8SYhU-yRRaEAx?OAT8#bk*^8`8x<@-NiyY(&orq6CKh%(rY14uT` zRcNdS0tAMHn-$vcuI$tuB}|1$nQ>`R1j;Z9l?Dt5XX0y>)liX-Qz3B_@@`b@(d^@A z#Lv+-Ek&mUI`QHccOX{OX+|S-*NTZSb;#O4IfDURGRRt2(Wj?$b{w*nP@m9cSW%H1 zLhfyh(bhXRD69RPCT`>y5$)Zg7oS_^U&rc$&?E1V%m$OBu>?5TZ7KAS`bKO=rqvH@O0{;HVYziAom_1 z{J9v;298EezG6J~LJqpdt9(3k`r;C}$K<50s8B7C8JW7u zBL_&k7kEGxXP1wf4@;0V?O}8?vPZJxVWmT2_i>)1h@9j?y>T~WKtBoevUox_NM2R{ z>7iDEdKU8hp?dC@SI3!~%CO4F-_i7lu7G3ci{uLVqE)9urUc@#D7o06jIOI1E3CL0Ah0{ znE5((IxqOc5C34y+GRj0rEgB&BN%PYqM(riOZ})CfbDVSWJEaZ^A40%Xn@C-d&o)l ziBvXYVtH-A3QO_8(~MI!I8(>aF@f-~1?SJNAS0zMiFM)jRiO?Rf^j^`IX6>AU*=^v zfTObgz#MjQH&U;YkkzSwW;6bFerec@MJ=zBeaco(AfZ1{hlelw|D-h}`g-13hFSJ{ zaIRE>4ftMLKQrXf1R6z8idileKPN;^QuAUp(xqDY?~vFON-KHD-e^YA?OL!p@x8VO zXe%hJca>D7H~Mv>@+EMiKOlY7A0`Rk&A*-x>k>#qE!1tkB+LH?U3Sp#d}%YTk0S6c zWek(VyMsTnPdUeG1^~R8gM9h3b-+5B?zmX$ViTNmBe6n-uPv37X~QmN{h&~TIP=Wd zRW0vi;(J(x+x}6s3P}0I}qKKYgC7j zj+<(^J)~qHubm+ZxPJ!83p_m|ro8E^;}!ZQPodVovDDRz7LuqMi&qqeAWclK{nX|0 zX)ZS{m71rU-xPFaG`4FqNj{y$ZN^yWD#!6mz}^#}>A!OpYj{x*cI>@JW-S5_Wkgy9 zuc~K7)z?Xb zk|==La1ZehnKPR}f^c7j?0^Ie{03z_=6TZ3jUpo`#lZ#W%%|XC3nHy}u0o4_QBVjZ z@do>2K|JuxGV^0G?97Z9zb%u#%&7(5>3nM5l6(Xdx1^vPNGkh~8f>9#ZX<7ztkHjx zw?WwqS|A=6mB+&+?a+8Mu;Ll={*u$A&F*lVEOVkDerhVTi;^YPQk&_EJd^G1{F9`r zkS+*9i6&ZakNo3rZHT+0SoQf2|M?V{u(a^gr%skbu1Zot59I#25K=9_JcQlRVU)|C zPvLI{UZ^eJGF#`*JwPEZpb_rKGoJa!-%6xA_;(fl-3rh*{{QF;zYfT)ZcWl&r7F-Q PgMVtMn@S}L77za$$4)tO literal 0 HcmV?d00001 diff --git a/fl_server/__init__.py b/fl_server/__init__.py new file mode 100644 index 0000000..fddd8ab --- /dev/null +++ b/fl_server/__init__.py @@ -0,0 +1,3 @@ +""" +Django Settings +""" diff --git a/fl_server/asgi.py b/fl_server/asgi.py new file mode 100644 index 0000000..2c56b97 --- /dev/null +++ b/fl_server/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for fl_server project. + +It exposes the ASGI callable as a module-level variable named `application`. + +For more information on this file, see + +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fl_server.settings.development") + +application = get_asgi_application() diff --git a/fl_server/settings/__init__.py b/fl_server/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fl_server/settings/base.py b/fl_server/settings/base.py new file mode 100644 index 0000000..cc34cb7 --- /dev/null +++ b/fl_server/settings/base.py @@ -0,0 +1,221 @@ +""" +General settings for DLR Federated Learning Demonstrator Server project. + +For more information on this file, see + + +For the full list of settings and their values, see + or + +""" + +from dlr.ki.logging import get_default_logging_dict +from pathlib import Path +from os import environ +from sys import argv + +from fl_server_core.utils.strings import str2bool + + +# Build paths inside the project like this: BASE_DIR / "subdir". +BASE_DIR = Path(__file__).resolve().parent.parent.parent + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "polymorphic", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "corsheaders", + "rest_framework", + "rest_framework.authtoken", + "fl_server_core", + "fl_server_ai", + "fl_server_api", + "drf_spectacular", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "fl_server.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "fl_server.wsgi.application" + +REST_FRAMEWORK = { + "DEFAULT_SCHEMA_CLASS": "fl_server_api.openapi.CustomAutoSchema", +} + +SPECTACULAR_SETTINGS = { + "TITLE": "DLR Federated Learning Demonstrator Server API", + "DESCRIPTION": "Catena-X ...", + "VERSION": "0.0.1.dev0", + "SERVE_AUTHENTICATION": [], + "PREPROCESSING_HOOKS": ["fl_server_api.openapi.custom_preprocessing_hook"], +} + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + + +DATABASES = { + "postgresql": { + "ENGINE": "django.db.backends.postgresql", + "HOST": environ.get("FL_POSTGRES_HOST", "db"), + "PORT": environ.get("FL_POSTGRES_PORT", "5432"), + "NAME": environ.get("FL_POSTGRES_DBNAME", "demo"), + "USER": environ.get("FL_POSTGRES_USER", "demo"), + "PASSWORD": environ.get("FL_POSTGRES_PASSWD", "example") + } +} +DATABASES["default"] = DATABASES["postgresql"] + + +# Cache +# https://docs.djangoproject.com/en/4.2/topics/cache + +def get_redis_location(*, fallback_host: str = "redis") -> str: + if (location := environ.get("FL_REDIS_LOCATION")) is not None: + return location + host = environ.get("FL_REDIS_HOST", fallback_host) + post = environ.get("FL_REDIS_PORT", "6379") + return f"redis://{host}:{post}/1" + +CACHES = { # noqa: E305 + "redis": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": get_redis_location(), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} +CACHES["default"] = CACHES["redis"] + + +# Password validation +# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.1/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "Europe/Berlin" + +USE_I18N = True + +USE_TZ = True + + +# Cross-Origin Resource Sharing (CORS) configuration +# https://github.com/adamchainz/django-cors-headers#configuration + +CORS_ORIGIN_WHITELIST = environ.get("CORS_ORIGIN_WHITELIST", "").strip().splitlines() + +CORS_ALLOWED_ORIGIN_REGEXES = environ.get("CORS_ALLOWED_ORIGIN_REGEXES", "").strip().splitlines() + +CORS_ALLOW_ALL_ORIGINS = str2bool(environ.get("CORS_ALLOW_ALL_ORIGINS", "False")) + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = "static/" + + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +# Substituting a custom User model +# https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#substituting-a-custom-user-model + +AUTH_USER_MODEL = "fl_server_core.User" + + +# Celery Configuration Options +# https://docs.celeryq.dev/en/stable/userguide/configuration.html + +CELERY_TASK_ALWAYS_EAGER = False # `True` tells celery to run the task in sync (for debugging) +CELERY_TASK_TRACK_STARTED = True +CELERY_TASK_TIME_LIMIT = 30 * 60 +CELERY_TIMEZONE = TIME_ZONE +CELERY_TASK_SERIALIZER = "pickle" +CELERY_RESULT_SERIALIZER = "pickle" +CELERY_ACCEPT_CONTENT = ["pickle"] +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True + + +# initializing and configuring logging +# https://docs.djangoproject.com/en/4.1/topics/logging/ + +LOGGING = get_default_logging_dict(str(BASE_DIR / "logs" / "server.log")) +# LOGGING["loggers"]["celery"] = LOGGING["loggers"][""] +# LOKI_SETTINGS = { +# "level": "DEBUG", +# "class": "logging_loki.LokiHandler", +# "url": "http://localhost:3100/loki/api/v1/push", +# "tags": { +# "app": "fl.server" +# }, +# "auth": ["admin", "admin"], +# "version": "1", +# } +# LOGGING["handlers"]["loki"] = LOKI_SETTINGS +# LOGGING["loggers"][""]["handlers"].append("loki") +# LOGGING["loggers"]["urllib3"] = {"level": "WARNING"} + + +# Federated Learning Properties + +MAX_RUNNING_CHILD_PROCESSES = int(environ.get("FL_MAX_CHILD_PROCESSES", 8)) +SSL_FORCE = str2bool(environ.get("FL_SSL_FORCE_REQUESTS"), "test" not in argv) diff --git a/fl_server/settings/development.py b/fl_server/settings/development.py new file mode 100644 index 0000000..e76a71e --- /dev/null +++ b/fl_server/settings/development.py @@ -0,0 +1,56 @@ +from os import environ + +# import and define setting defaults +from .base import * # noqa: F403 # NOSONAR + + +# SECRET_KEY and SECRET_KEY_FALLBACK +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#secret-key + +SECRET_KEY = "django-insecure-*z=iw5n%b--qdim+x5b+j9=^_gq7hq)kk8@7$^(tyn@oc#_8by" # cspell:ignore qdim + + +# DEBUG +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#debug + +DEBUG = True # if `True` celery will result in memory leaks + + +# ALLOWED_HOSTS +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#allowed-hosts + +ALLOWED_HOSTS = ["*"] + + +# Cross-Origin Resource Sharing (CORS) configuration +# https://github.com/adamchainz/django-cors-headers#configuration + +CORS_ALLOW_ALL_ORIGINS = True + + +# DATABASES +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#databases + +# overwrite postgresql host default value from "db" to "localhost" +DATABASES["postgresql"]["HOST"] = environ.get("FL_POSTGRES_HOST", "localhost") # noqa: F405 + +# add sqlite3 support +DATABASES["sqlite3"] = { # noqa: F405 + "ENGINE": "django.db.backends.sqlite3", + "NAME": "db.sqlite3", +} +DATABASES["default"] = DATABASES[environ.get("FL_DJANGO_DATABASE", "postgresql")] # noqa: F405 + + +# Cache +# https://docs.djangoproject.com/en/4.2/topics/cache + +# overwrite redis host default value from "redis" to "localhost" +CACHES["redis"]["LOCATION"] = get_redis_location(fallback_host="localhost") # noqa: F405 + + +# HTTPS +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#https + +CSRF_COOKIE_SECURE = False +SESSION_COOKIE_SECURE = True diff --git a/fl_server/settings/production.py b/fl_server/settings/production.py new file mode 100644 index 0000000..1a31455 --- /dev/null +++ b/fl_server/settings/production.py @@ -0,0 +1,78 @@ +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ + +from os import environ + +# import and define setting defaults +from .base import * # noqa: F403 # NOSONAR + + +def get_secret(env_key: str, *, ensure: bool = False) -> str | None: + if (secret_file := environ.get(f"{env_key}_FILE")) is not None: + with open(secret_file) as f: + return f.read().strip() + + if ensure: + # let this throw an exception if neither SECRET_KEY_FILE nor SECRET_KEY is set + return environ[env_key] + + return environ.get(env_key) + + +# SECRET_KEY and SECRET_KEY_FALLBACK +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#secret-key + +SECRET_KEY: str = get_secret("FL_DJANGO_SECRET_KEY", ensure=True) # type:ignore[assignment] +if (_secret_key_fallbacks := get_secret("FL_DJANGO_SECRET_KEY_FALLBACK")) is not None: + SECRET_KEY_FALLBACKS = _secret_key_fallbacks.strip().splitlines() + + +# DEBUG +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#debug + +DEBUG = False + + +# ALLOWED_HOSTS +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#allowed-hosts + +ALLOWED_HOSTS = environ.get("FL_DJANGO_ALLOWED_HOSTS", "").strip().splitlines() + + +# DATABASES +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#databases + +DATABASES["postgresql"]["PASSWORD"] = str(get_secret("FL_POSTGRES_PASSWD", ensure=True)) # noqa: F405 + + +# EMAIL_BACKEND and related settings +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#email-backend-and-related-settings + + +# STATIC_ROOT and STATIC_URL +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#static-root-and-static-url + +STATIC_ROOT = BASE_DIR / "static" # noqa: F405 +STATIC_URL = "static/" +ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/" + + +# MEDIA_ROOT and MEDIA_URL +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#media-root-and-media-url + +# MEDIA_ROOT = "" +# MEDIA_URL = "media/" + + +# HTTPS +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#https + +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True + + +# Performance optimizations +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#performance-optimizations + + +# Error reporting +# https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/#error-reporting diff --git a/fl_server/urls.py b/fl_server/urls.py new file mode 100644 index 0000000..08de532 --- /dev/null +++ b/fl_server/urls.py @@ -0,0 +1,33 @@ +""" +URL configuration for fl_server project. + +The `urlpatterns` list routes URLs to views. For more information please see: + + +Examples: + Function views + 1. Add an import: `from my_app import views` + 2. Add a URL to urlpatterns: `path("", views.home, name="home")` + + Class-based views + 1. Add an import: `from other_app.views import Home` + 2. Add a URL to urlpatterns: `path("", Home.as_view(), name="home")` + + Including another URLconf + 1. Import the include() function: `from django.urls import include, path` + 2. Add a URL to urlpatterns: `path("blog/", include("blog.urls"))` +""" +from django.contrib import admin +from django.urls import path, include + +from fl_server_api import urls as api_urls + + +urlpatterns = [ + path("admin/", admin.site.urls), + path("api-auth/", include("rest_framework.urls")), + path("api/", include(api_urls)), +] + +handler500 = "rest_framework.exceptions.server_error" +handler400 = "rest_framework.exceptions.bad_request" diff --git a/fl_server/wsgi.py b/fl_server/wsgi.py new file mode 100644 index 0000000..0f9bea8 --- /dev/null +++ b/fl_server/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for fl_server project. + +It exposes the WSGI callable as a module-level variable named `application`. + +For more information on this file, see + +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fl_server.settings.development") + +application = get_wsgi_application() diff --git a/fl_server_ai/__init__.py b/fl_server_ai/__init__.py new file mode 100644 index 0000000..e1f6a07 --- /dev/null +++ b/fl_server_ai/__init__.py @@ -0,0 +1,7 @@ +""" +AI Service +""" +from fl_server_ai.celery_tasks import app as celery_app + + +__all__ = ["celery_app"] diff --git a/fl_server_ai/aggregation/__init__.py b/fl_server_ai/aggregation/__init__.py new file mode 100644 index 0000000..1302a47 --- /dev/null +++ b/fl_server_ai/aggregation/__init__.py @@ -0,0 +1,6 @@ +from .base import Aggregation +from .mean import MeanAggregation +from .method import get_aggregation_class + + +__all__ = ["Aggregation", "get_aggregation_class", "MeanAggregation"] diff --git a/fl_server_ai/aggregation/base.py b/fl_server_ai/aggregation/base.py new file mode 100644 index 0000000..5aef5a7 --- /dev/null +++ b/fl_server_ai/aggregation/base.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod +from logging import getLogger +import torch +from typing import Sequence + + +class Aggregation(ABC): + _logger = getLogger("fl.server") + + @abstractmethod + def aggregate( + self, + models: Sequence[torch.nn.Module], + model_sample_sizes: Sequence[int], + *, + deepcopy: bool = True + ) -> torch.nn.Module: + pass diff --git a/fl_server_ai/aggregation/fed_dc.py b/fl_server_ai/aggregation/fed_dc.py new file mode 100644 index 0000000..1fe0444 --- /dev/null +++ b/fl_server_ai/aggregation/fed_dc.py @@ -0,0 +1,29 @@ +import torch +from typing import Sequence + +from .base import Aggregation + + +class FedDC(Aggregation): + """ + FedDC (Federated daisy-chaining) + + To tackle the problem that client models are potentially quite small and thus the models tend to overfit and + therefore result in bad prediction quality on unseen data, one proposed solution is + FedDC (also named Federated daisy-chaining). + FedDC sends before each aggregation step each client model to another randomly selected client, which trains + it on its local data. + From the model perspective, it is as if the model is trained on a larger dataset. + + Paper: [Picking Daisies in Private: Federated Learning from Small Datasets](https://openreview.net/forum?id=GVDwiINkMR) + """ # noqa: E501 + + @torch.no_grad() + def aggregate( + self, + models: Sequence[torch.nn.Module], + model_sample_sizes: Sequence[int], + *, + deepcopy: bool = True + ) -> torch.nn.Module: + raise NotImplementedError("FedDC is not implemented yet!") diff --git a/fl_server_ai/aggregation/fed_prox.py b/fl_server_ai/aggregation/fed_prox.py new file mode 100644 index 0000000..7cacd5e --- /dev/null +++ b/fl_server_ai/aggregation/fed_prox.py @@ -0,0 +1,17 @@ +from .mean import MeanAggregation + + +class FedProx(MeanAggregation): + """ + To tackle the problem that client models drift away from optimum due to data heterogeneity, + different learning speeds, etc. one proposed solution is FedProx. + FedProx limits the client drift by using a modified learning objective but keeping the standard + FedAvg aggregation method. + + Note: + FedProx does not do anything different on the server side than normal FedAvg. + The difference lies in the application of a special loss function on the client side. + + Paper: [Federated Optimization in Heterogeneous Networks](https://arxiv.org/abs/1812.06127) + """ + pass diff --git a/fl_server_ai/aggregation/mean.py b/fl_server_ai/aggregation/mean.py new file mode 100644 index 0000000..6d1e8d7 --- /dev/null +++ b/fl_server_ai/aggregation/mean.py @@ -0,0 +1,46 @@ +import copy +import torch +from typing import Sequence + +from ..exceptions import AggregationException + +from .base import Aggregation + + +class MeanAggregation(Aggregation): + + @torch.no_grad() + def aggregate( + self, + models: Sequence[torch.nn.Module], + model_sample_sizes: Sequence[int], + *, + deepcopy: bool = True + ) -> torch.nn.Module: + assert len(models) == len(model_sample_sizes) + + self._logger.debug(f"Doing mean aggregation for {len(models)} models!") + model_state_dicts = [model.state_dict() for model in models] + + total_dataset_size = model_sample_sizes[0] + result_dict = model_state_dicts[0] + for layer_name in result_dict: + result_dict[layer_name] *= model_sample_sizes[0] + + # sum accumulation + for model_dict, dataset_size in zip(model_state_dicts[1:], model_sample_sizes[1:]): + if set(model_dict.keys()) != set(result_dict.keys()): + raise AggregationException("Models do not have the same architecture!") + + total_dataset_size += dataset_size + for layer_name in result_dict: + result_dict[layer_name] += model_dict[layer_name] * dataset_size + + # factor 1/n + for layer_name in result_dict: + result_dict[layer_name] = result_dict[layer_name] / total_dataset_size + + # return aggregated model + result_model = copy.deepcopy(models[0]) if deepcopy else models[0] + result_model.load_state_dict(result_dict) + return result_model diff --git a/fl_server_ai/aggregation/method.py b/fl_server_ai/aggregation/method.py new file mode 100644 index 0000000..1ddf145 --- /dev/null +++ b/fl_server_ai/aggregation/method.py @@ -0,0 +1,41 @@ +from typing import overload, Type + +from fl_server_core.models import Model, Training +from fl_server_core.models.training import AggregationMethod + +from .base import Aggregation +from .fed_dc import FedDC +from .fed_prox import FedProx +from .mean import MeanAggregation + + +@overload +def get_aggregation_class(value: Model) -> Type[Aggregation]: ... + + +@overload +def get_aggregation_class(value: Training) -> Type[Aggregation]: ... + + +@overload +def get_aggregation_class(value: AggregationMethod) -> Type[Aggregation]: ... + + +def get_aggregation_class(value: AggregationMethod | Model | Training) -> Type[Aggregation]: + if isinstance(value, AggregationMethod): + method = value + elif isinstance(value, Training): + method = value.aggregation_method + elif isinstance(value, Model): + aggregation_method = Training.objects.filter(model=value) \ + .values("aggregation_method") \ + .first()["aggregation_method"] + method = aggregation_method + else: + raise ValueError(f"Unknown type: {type(value)}") + + match method: + case AggregationMethod.FED_AVG: return MeanAggregation + case AggregationMethod.FED_DC: return FedDC + case AggregationMethod.FED_PROX: return FedProx + case _: raise ValueError(f"Unknown aggregation method: {method}") diff --git a/fl_server_ai/apps.py b/fl_server_ai/apps.py new file mode 100644 index 0000000..aca6822 --- /dev/null +++ b/fl_server_ai/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FlServerAiConfig(AppConfig): + name = "fl_server_ai" + verbose_name = "Federated Learning Demonstrator AI" diff --git a/fl_server_ai/celery_tasks.py b/fl_server_ai/celery_tasks.py new file mode 100644 index 0000000..a69385d --- /dev/null +++ b/fl_server_ai/celery_tasks.py @@ -0,0 +1,31 @@ +from celery import Celery +from celery.signals import setup_logging +from django.conf import settings +from logging.config import dictConfig + + +app = Celery("fl_server") + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace="CELERY" means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object("django.conf:settings", namespace="CELERY") +app.conf.update( + broker_url=settings.CACHES["default"]["LOCATION"], + result_backend=settings.CACHES["default"]["LOCATION"], + broker_transport_options={ + "visibility_timeout": 31540000, + "fanout_prefix": True, + "fanout_patterns": True, + } +) + +# Load task modules from all registered Django apps. +app.autodiscover_tasks() + + +# Configure logging +@setup_logging.connect +def init_loggers(*args, **kwargs): + dictConfig(settings.LOGGING) diff --git a/fl_server_ai/exceptions.py b/fl_server_ai/exceptions.py new file mode 100644 index 0000000..cc86f71 --- /dev/null +++ b/fl_server_ai/exceptions.py @@ -0,0 +1,34 @@ +from requests import Response +from typing import Any, Optional + + +class AggregationException(Exception): + pass + + +class NotificationException(Exception): + + def __init__( + self, + endpoint_url: str, + json_data: Any, + max_retries: int, + return_obj: Any = None, + inner_exception: Optional[Exception] = None, + *args, + **kwargs + ): + super().__init__(*args, **kwargs) + self.endpoint_url = endpoint_url + self.json_data = json_data + self.max_retries = max_retries + self.notification_return_object = return_obj + self.inner_exception = inner_exception + + +class ClientNotificationRejectionException(NotificationException): + + def __init__(self, response: Response, *args, **kwargs): + super().__init__(*args, **kwargs) + self.response = response + self.status_code = response.status_code diff --git a/fl_server_ai/notification/__init__.py b/fl_server_ai/notification/__init__.py new file mode 100644 index 0000000..81d3b58 --- /dev/null +++ b/fl_server_ai/notification/__init__.py @@ -0,0 +1,11 @@ +from .notification_type import NotificationType +from .notification import Notification +from .training import TrainingRoundStartNotification, TrainingFinishedNotification + + +__all__ = [ + "NotificationType", + "Notification", + "TrainingRoundStartNotification", + "TrainingFinishedNotification", +] diff --git a/fl_server_ai/notification/notification.py b/fl_server_ai/notification/notification.py new file mode 100644 index 0000000..c5a4b54 --- /dev/null +++ b/fl_server_ai/notification/notification.py @@ -0,0 +1,101 @@ +from abc import ABCMeta +from celery import Signature +from celery.result import AsyncResult +from celery.utils.log import get_task_logger +from dataclasses import dataclass, field +from rest_framework import status +import requests +import time +from typing import Any, Generic, Optional, TypeVar + +from fl_server_core.models.user import NotificationReceiver + +from ..celery_tasks import app +from ..exceptions import ClientNotificationRejectionException, NotificationException + +from .serializable import Serializable +from .notification_type import NotificationType + + +TBody = TypeVar("TBody", bound=Serializable) +TReturn = TypeVar("TReturn", bound=Any) + + +@app.task(bind=False, ignore_result=False) +def send_notifications( + notification: "Notification", + callback_success: Optional[Signature] = None, + callback_error: Optional[Signature] = None +): + logger = get_task_logger("fl.celery") + for receiver in notification.receivers: + logger.info( + f"Sending notification '{notification.type}' " + + f"to client '{receiver.id}' with URL '{receiver.message_endpoint}'" + ) + send_notification.s( + endpoint_url=receiver.message_endpoint, json_data=notification.serialize(), return_obj=receiver + ).apply_async(link=callback_success, link_error=callback_error, retry=False) + + +@app.task(bind=False, ignore_result=False) +def send_notification( + endpoint_url: str, + json_data: Any, + max_retries: int = 5, + *, + return_obj: Optional[TReturn] = None +) -> Optional[TReturn]: + logger = get_task_logger("fl.celery") + retries: int = 0 + while True: + try: + response = requests.post(url=endpoint_url, json=json_data, headers={"content-type": "application/json"}) + if not status.is_success(response.status_code): + raise ClientNotificationRejectionException(response, endpoint_url, json_data, max_retries, return_obj) + return return_obj + except ClientNotificationRejectionException as e: + logger.error(f"Sending notification to URL '{endpoint_url}' return status code: {e.status_code}") + raise e + except Exception as e: + logger.warn(f"Sending notification to URL '{endpoint_url}' produced exception: {e}") + retries += 1 + if retries > max_retries: + raise NotificationException(endpoint_url, json_data, max_retries, return_obj, e) + time.sleep(5*retries) + + +@dataclass +class Notification(Generic[TBody], Serializable, metaclass=ABCMeta): + receivers: list[NotificationReceiver] + body: TBody + type: NotificationType = field(init=False) + + @dataclass + class Body(Serializable): + pass + + @property + def callback_success(self) -> Optional[Signature]: + return None + + @property + def callback_error(self) -> Optional[Signature]: + return None + + def send( + self, + callback_success: Optional[Signature] = None, + callback_error: Optional[Signature] = None + ) -> AsyncResult: + callback_success = callback_success or self.callback_success + callback_error = callback_error or self.callback_error + return send_notifications.s( + notification=self, callback_success=callback_success, callback_error=callback_error + ).apply_async(retry=False) + + def serialize(self) -> dict[str, Any]: + return { + "notification_type": self.type.value, + "body": self.body.serialize() + } diff --git a/fl_server_ai/notification/notification_type.py b/fl_server_ai/notification/notification_type.py new file mode 100644 index 0000000..536b4c8 --- /dev/null +++ b/fl_server_ai/notification/notification_type.py @@ -0,0 +1,25 @@ +from django.utils.translation import gettext_lazy as _ +from enum import Enum +from typing import Optional + + +class NotificationType(Enum): + UPDATE_ROUND_START = "UPDATE_ROUND_START", _("Start update round") + SWAG_ROUND_START = "SWAG_ROUND_START", _("Start swag round") + TRAINING_START = "TRAINING_START", _("Training start") + TRAINING_FINISHED = "TRAINING_FINISHED", _("Training finished") + CLIENT_REMOVED = "CLIENT_REMOVED", _("Client removed") + MODEL_TEST_ROUND = "MODEL_TEST_ROUND", _("Start model round") + + def __new__(cls, *args, **kwargs): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: str, description: Optional[str] = None): + # ignore the first param since it's already set by __new__ + self._description_ = description + + @property + def description(self): + return self._description_ diff --git a/fl_server_ai/notification/serializable.py b/fl_server_ai/notification/serializable.py new file mode 100644 index 0000000..6cc1d91 --- /dev/null +++ b/fl_server_ai/notification/serializable.py @@ -0,0 +1,10 @@ +from dataclasses import asdict, dataclass +from typing import Any +from uuid import UUID + + +@dataclass +class Serializable: + + def serialize(self) -> dict[str, Any]: + return {key: str(value) if isinstance(value, UUID) else value for key, value in asdict(self).items()} diff --git a/fl_server_ai/notification/training/__init__.py b/fl_server_ai/notification/training/__init__.py new file mode 100644 index 0000000..25d50a2 --- /dev/null +++ b/fl_server_ai/notification/training/__init__.py @@ -0,0 +1,16 @@ +from .finished import TrainingFinishedNotification +from .model_test import TrainingModelTestNotification +from .round_start import TrainingRoundStartNotification +from .start import TrainingStartNotification +from .swag import TrainingSWAGRoundStartNotification +from .training import TrainingNotification + + +__all__ = [ + "TrainingModelTestNotification", + "TrainingNotification", + "TrainingFinishedNotification", + "TrainingStartNotification", + "TrainingSWAGRoundStartNotification", + "TrainingRoundStartNotification", +] diff --git a/fl_server_ai/notification/training/finished.py b/fl_server_ai/notification/training/finished.py new file mode 100644 index 0000000..0caba41 --- /dev/null +++ b/fl_server_ai/notification/training/finished.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass +from uuid import UUID + +from fl_server_core.models import Training as TrainingDB + +from ..serializable import Serializable +from ..notification_type import NotificationType +from .training import TrainingNotification + + +class TrainingFinishedNotification(TrainingNotification["TrainingFinishedNotification.Body"]): + type: NotificationType = NotificationType.TRAINING_FINISHED + + @dataclass + class Body(Serializable): + global_model_uuid: UUID + + @classmethod + def from_training(cls, training: TrainingDB): + receivers = list(training.participants.all()) + if not receivers.__contains__(training.actor): + receivers.append(training.actor) + return cls( + receivers=receivers, + body=cls.Body( + global_model_uuid=training.model.id + ), + training_uuid=training.id + ) diff --git a/fl_server_ai/notification/training/model_test.py b/fl_server_ai/notification/training/model_test.py new file mode 100644 index 0000000..ab9868f --- /dev/null +++ b/fl_server_ai/notification/training/model_test.py @@ -0,0 +1,6 @@ +from ..notification_type import NotificationType +from .round_start import TrainingRoundStartNotification + + +class TrainingModelTestNotification(TrainingRoundStartNotification): + type: NotificationType = NotificationType.MODEL_TEST_ROUND diff --git a/fl_server_ai/notification/training/round_start.py b/fl_server_ai/notification/training/round_start.py new file mode 100644 index 0000000..a48b3a9 --- /dev/null +++ b/fl_server_ai/notification/training/round_start.py @@ -0,0 +1,71 @@ +from celery import Signature +from celery.utils.log import get_task_logger +from dataclasses import dataclass +from typing import Optional +from uuid import UUID + +from fl_server_core.models import Training as TrainingDB, User as UserDB +from fl_server_core.models.training import TrainingState +from fl_server_core.models.user import NotificationReceiver + +from ...celery_tasks import app +from ...exceptions import ClientNotificationRejectionException, NotificationException +from ..serializable import Serializable +from ..notification_type import NotificationType +from .training import TrainingNotification + + +@app.task(bind=False, ignore_result=True) +def training_notification_callback_success(receiver: NotificationReceiver, training_uuid: UUID): + logger = get_task_logger("fl.celery") + logger.debug(f"Training {training_uuid}: User {receiver.id} accepted notification") + + +@app.task(bind=False, ignore_result=True) +def training_notification_callback_failure(exception: NotificationException, training_uuid: UUID): + logger = get_task_logger("fl.celery") + if isinstance(exception, ClientNotificationRejectionException): + # client sent error response, remove + receiver: NotificationReceiver = exception.notification_return_object + logger.warn( + f"Training {training_uuid}: User {receiver.id} sent error response: {exception.status_code}." + "User will be removed from training!" + ) + user = receiver if isinstance(receiver, UserDB) else UserDB.objects.get(id=receiver.id) + TrainingDB.objects.get(id=training_uuid).participants.remove(user) + else: + # set training to error state + e = exception.inner_exception if exception.inner_exception else exception + logger.error(f"Training {training_uuid}: Exception occurred during sending: {e}") + logger.error(f"Training {training_uuid} is in error state!") + training = TrainingDB.objects.get(id=training_uuid) + training.state = TrainingState.ERROR + training.save() + + +class TrainingRoundStartNotification(TrainingNotification["TrainingRoundStartNotification.Body"]): + type: NotificationType = NotificationType.UPDATE_ROUND_START + + @property + def callback_success(self) -> Optional[Signature]: + return training_notification_callback_success.s(training_uuid=self.training_uuid) + + @property + def callback_error(self) -> Optional[Signature]: + return training_notification_callback_failure.s(training_uuid=self.training_uuid) + + @dataclass + class Body(Serializable): + round: int + global_model_uuid: UUID + + @classmethod + def from_training(cls, training: TrainingDB): + return cls( + receivers=training.participants.all(), + body=cls.Body( + round=training.model.round, + global_model_uuid=training.model.id + ), + training_uuid=training.id + ) diff --git a/fl_server_ai/notification/training/start.py b/fl_server_ai/notification/training/start.py new file mode 100644 index 0000000..a056704 --- /dev/null +++ b/fl_server_ai/notification/training/start.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from uuid import UUID + +from fl_server_core.models import Training as TrainingDB + +from ..serializable import Serializable +from ..notification_type import NotificationType +from .training import TrainingNotification + + +class TrainingStartNotification(TrainingNotification["TrainingStartNotification.Body"]): + type: NotificationType = NotificationType.TRAINING_START + + @dataclass + class Body(Serializable): + global_model_uuid: UUID + + @classmethod + def from_training(cls, training: TrainingDB): + return cls( + receivers=training.participants.all(), + body=cls.Body( + global_model_uuid=training.model.id + ), + training_uuid=training.id + ) diff --git a/fl_server_ai/notification/training/swag.py b/fl_server_ai/notification/training/swag.py new file mode 100644 index 0000000..c429841 --- /dev/null +++ b/fl_server_ai/notification/training/swag.py @@ -0,0 +1,6 @@ +from ..notification_type import NotificationType +from .round_start import TrainingRoundStartNotification + + +class TrainingSWAGRoundStartNotification(TrainingRoundStartNotification): + type: NotificationType = NotificationType.SWAG_ROUND_START diff --git a/fl_server_ai/notification/training/training.py b/fl_server_ai/notification/training/training.py new file mode 100644 index 0000000..8a42c94 --- /dev/null +++ b/fl_server_ai/notification/training/training.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass +from typing import Any, Generic +from uuid import UUID + +from ..notification import Notification, TBody + + +@dataclass +class TrainingNotification(Generic[TBody], Notification[TBody]): + training_uuid: UUID + + def serialize(self) -> dict[str, Any]: + data = super().serialize() + data["training_uuid"] = str(self.training_uuid) + return data diff --git a/fl_server_ai/tests/__init__.py b/fl_server_ai/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fl_server_ai/tests/test_aggregation.py b/fl_server_ai/tests/test_aggregation.py new file mode 100644 index 0000000..b62d81b --- /dev/null +++ b/fl_server_ai/tests/test_aggregation.py @@ -0,0 +1,40 @@ +from django.test import TestCase +import torch + +from ..aggregation.mean import MeanAggregation + + +def _create_model_and_init(init: float) -> torch.nn.Module: + init = float(init) + model = torch.nn.Sequential( + torch.nn.Linear(1, 5), + torch.nn.Tanh(), + torch.nn.BatchNorm1d(5), + torch.nn.Linear(5, 3) + ) + torch.nn.init.constant_(model[0].weight, init) + torch.nn.init.constant_(model[0].bias, init) + torch.nn.init.constant_(model[3].weight, init) + torch.nn.init.constant_(model[3].bias, init) + return model + + +class AggregationTest(TestCase): + + def test_aggregate(self): + aggr = MeanAggregation() + models = [_create_model_and_init(i) for i in range(10)] + res = aggr.aggregate(models, [1]*10) + self.assertTrue(isinstance(res, torch.nn.Sequential)) + self.assertEqual(len(list(models[0].parameters())), len(list(res.parameters()))) + torch.testing.assert_close(res[0].weight, torch.ones_like(res[0].weight) * 4.5) + torch.testing.assert_close(res[3].weight, torch.ones_like(res[3].weight) * 4.5) + + def test_aggregate_sample_sizes(self): + aggr = MeanAggregation() + models = [_create_model_and_init(i) for i in range(3)] + res = aggr.aggregate(models, [0, 1, 2]) + self.assertTrue(isinstance(res, torch.nn.Sequential)) + self.assertEqual(len(list(models[0].parameters())), len(list(res.parameters()))) + torch.testing.assert_close(res[0].weight, torch.ones_like(res[0].weight) * (5/3)) + torch.testing.assert_close(res[3].weight, torch.ones_like(res[3].weight) * (5/3)) diff --git a/fl_server_ai/tests/test_aggregation_process.py b/fl_server_ai/tests/test_aggregation_process.py new file mode 100644 index 0000000..6c7549e --- /dev/null +++ b/fl_server_ai/tests/test_aggregation_process.py @@ -0,0 +1,50 @@ +from django.test import TransactionTestCase +import json + +from fl_server_core.models.training import Training, TrainingState +from fl_server_core.tests import BASE_URL, Dummy + +from ..trainer import ModelTrainer, TrainerOptions +from ..trainer.events import TrainingRoundFinished + + +class AggregationProcessTest(TransactionTestCase): + + def setUp(self): + self.user = Dummy.create_user_and_authenticate(self.client) + + def test_check_and_run_aggregation_if_applicable_training_step(self): + clients = [Dummy.create_user() for _ in range(10)] + training = Dummy.create_training(state=TrainingState.ONGOING, target_num_updates=10, actor=self.user) + training.participants.set(clients) + updates = [Dummy.create_model_update(base_model=training.model, round=0) for _ in range(10)] + for update in updates: + update.save() + + with self.assertLogs("fl.server", level="INFO"): + ModelTrainer(training, TrainerOptions(skip_model_tests=True)).handle_cls(TrainingRoundFinished) + + response = self.client.get(f"{BASE_URL}/models/{training.model.id}/metadata/") + content = json.loads(response.content) + self.assertEqual(content["round"], 1) + + training = Training.objects.get(id=training.id) + self.assertEqual(TrainingState.ONGOING, training.state) + + def test_check_and_run_aggregation_if_applicable_training_finished(self): + clients = [Dummy.create_user() for _ in range(10)] + model = Dummy.create_model(owner=self.user, round=1) + training = Dummy.create_training(state=TrainingState.ONGOING, target_num_updates=1, actor=self.user, + model=model) + training.participants.set(clients) + updates = [Dummy.create_model_update(base_model=training.model, round=1) for _ in range(10)] + for update in updates: + update.save() + + options = TrainerOptions(skip_model_tests=True) + trainer = ModelTrainer(training, options) + with self.assertLogs("fl.server", level="INFO"): + trainer.handle_cls(TrainingRoundFinished) + + training = Training.objects.get(id=training.id) + self.assertEqual(TrainingState.COMPLETED, training.state) diff --git a/fl_server_ai/tests/test_ai_worker.py b/fl_server_ai/tests/test_ai_worker.py new file mode 100644 index 0000000..5d783fb --- /dev/null +++ b/fl_server_ai/tests/test_ai_worker.py @@ -0,0 +1,161 @@ +from django.test import TestCase +import pickle +import torch +from unittest.mock import MagicMock, patch + +from fl_server_core.models.model import SWAGModel +from fl_server_core.models.training import Training, TrainingState, UncertaintyMethod +from fl_server_core.tests import Dummy + +from ..notification import Notification +from ..trainer import ModelTrainer, process_trainer_task +from ..trainer.events import SWAGRoundFinished, TrainingRoundFinished +from ..trainer.options import TrainerOptions + +from .test_aggregation import _create_model_and_init + + +class AiWorkerTest(TestCase): + + def test_process_training_good(self): + training = Dummy.create_training(state=TrainingState.ONGOING, locked=True) + model1 = _create_model_and_init(0) + model2 = _create_model_and_init(1) + Dummy.create_model_update( + base_model=training.model, + owner=training.participants.all()[0], + round=0, + weights=pickle.dumps(model1), + ) + Dummy.create_model_update( + base_model=training.model, + owner=training.participants.all()[1], + round=0, + weights=pickle.dumps(model2) + ) + with self.assertLogs(level="INFO"): + ModelTrainer(training, TrainerOptions(skip_model_tests=True)).handle_cls(TrainingRoundFinished) + res = training.model.get_torch_model() + torch.testing.assert_close(res[0].weight, torch.ones_like(res[0].weight) * 0.5) + torch.testing.assert_close(res[3].weight, torch.ones_like(res[3].weight) * 0.5) + + @patch("fl_server_ai.aggregation.mean.MeanAggregation.aggregate") + def test_process_training_bad1(self, aggregate_updates: MagicMock): + training = Dummy.create_training(state=TrainingState.ONGOING, locked=True) + model1 = _create_model_and_init(0) + model2 = _create_model_and_init(1) + model3 = _create_model_and_init(500) + Dummy.create_model_update( + base_model=training.model, + owner=training.participants.all()[0], + round=0, + weights=pickle.dumps(model1), + ) + Dummy.create_model_update( + base_model=training.model, + owner=training.participants.all()[1], + round=0, + weights=pickle.dumps(model2) + ) + Dummy.create_model_update( + base_model=training.model, + owner=training.participants.all()[1], + round=0, + weights=pickle.dumps(model3) + ) + with self.assertLogs(level="ERROR"): + with self.assertRaises(RuntimeError): + ModelTrainer(training, TrainerOptions(skip_model_tests=True)).handle_cls(TrainingRoundFinished) + + self.assertFalse(aggregate_updates.called) + + @patch("fl_server_ai.aggregation.mean.MeanAggregation.aggregate") + def test_process_training_bad2(self, aggregate_updates: MagicMock): + training = Dummy.create_training(state=TrainingState.ONGOING, locked=True) + model1 = _create_model_and_init(0) + Dummy.create_model_update( + base_model=training.model, + owner=training.participants.all()[0], + round=0, + weights=pickle.dumps(model1), + ) + with self.assertLogs(level="ERROR"), self.assertRaises(RuntimeError): + ModelTrainer(training, TrainerOptions(skip_model_tests=True)).handle_cls(TrainingRoundFinished) + + self.assertFalse(aggregate_updates.called) + + @patch.object(Notification, "send") + def test_process_training(self, send_notification): + model = Dummy.create_model( + model_cls=SWAGModel, + weights=pickle.dumps(torch.nn.Sequential( # this model has exactly 15 parameters + torch.nn.Linear(1, 5), + torch.nn.ReLU(), + torch.nn.Linear(5, 1) + )), + round=100 + ) + training = Dummy.create_training( + state=TrainingState.SWAG_ROUND, + uncertainty_method=UncertaintyMethod.SWAG, + locked=True, + model=model, + target_num_updates=100 + ) + Dummy.create_metric( + step=100, + key="SWAG First Moment Local", + model=model, + reporter=training.participants.all()[0], + value_binary=pickle.dumps(torch.zeros(15)) + ) + Dummy.create_metric( + step=100, + key="SWAG Second Moment Local", + model=model, + reporter=training.participants.all()[0], + value_binary=pickle.dumps(torch.ones(15)) + ) + Dummy.create_metric( + step=100, + key="SWAG Sample Size Local", + model=model, + reporter=training.participants.all()[0], + value_float=1000 + ) + Dummy.create_metric( + step=100, + key="SWAG First Moment Local", + model=model, + reporter=training.participants.all()[1], + value_binary=pickle.dumps(torch.zeros(15)) + ) + Dummy.create_metric( + step=100, + key="SWAG Second Moment Local", + model=model, + reporter=training.participants.all()[1], + value_binary=pickle.dumps(torch.ones(15)) + ) + Dummy.create_metric( + step=100, + key="SWAG Sample Size Local", + model=model, + reporter=training.participants.all()[1], + value_float=1000 + ) + with self.assertLogs("fl.server", level="INFO") as cm: + process_trainer_task(training.id, SWAGRoundFinished) + self.assertEqual(cm.output, [ + f"INFO:fl.server:Training {training.id}: Doing SWAG aggregation as all 2 updates arrived", + f"INFO:fl.server:SWAG completed for training {training.id}", + ]) + self.assertTrue(send_notification.called) + training = Training.objects.get(id=training.id) + self.assertFalse(training.locked) + model = training.model + self.assertEquals(TrainingState.ONGOING, training.state) # next would be ModelTestFinished + fst = model.first_moment + snd = model.second_moment + torch.testing.assert_close(torch.zeros(15), fst) + torch.testing.assert_close(torch.ones(15), snd) diff --git a/fl_server_ai/tests/test_feddc.py b/fl_server_ai/tests/test_feddc.py new file mode 100644 index 0000000..bc976d9 --- /dev/null +++ b/fl_server_ai/tests/test_feddc.py @@ -0,0 +1,93 @@ +from django.test import TestCase +from unittest.mock import patch + +from fl_server_core.tests.dummy import Dummy + +from ..notification.training import TrainingRoundStartNotification +from ..trainer.events import DaisyChainRoundFinished, ModelTestFinished, TrainingRoundFinished +from ..trainer.model_trainer import get_trainer, get_trainer_class, FedDCModelTrainer + + +class FedDCTest(TestCase): + + def test_trainer_class(self): + training = Dummy.create_training(options=dict(daisy_chain_period=5)) + trainer_cls = get_trainer_class(training) + self.assertTrue(trainer_cls is FedDCModelTrainer) + + def test_trainer_type(self): + training = Dummy.create_training(options=dict(daisy_chain_period=5)) + trainer = get_trainer(training) + self.assertTrue(type(trainer) is FedDCModelTrainer) + + @patch.object(TrainingRoundStartNotification, "send") + def test_start_dc_round(self, send_method): + training = Dummy.create_training(options=dict(daisy_chain_period=5)) + trainer = get_trainer(training) + trainer.start_round() + self.assertTrue(send_method.called) + send_method.assert_called_once() + + @patch.object(TrainingRoundStartNotification, "send") + def test_start_permuted_dc_round(self, send_method): + N = 10 + participants = [Dummy.create_client() for _ in range(N)] + model = Dummy.create_model(round=2) + [Dummy.create_model_update(base_model=model, owner=participant) for participant in participants] + training = Dummy.create_training( + model=model, + participants=participants, + target_num_updates=model.round - 1, + options=dict(daisy_chain_period=5) + ) + trainer = get_trainer(training) + trainer.start_round() + self.assertTrue(send_method.called) + self.assertEqual(N, send_method.call_count) + + @patch.object(DaisyChainRoundFinished, "handle") + @patch.object(DaisyChainRoundFinished, "next") + def test_handle_round_finished(self, next_fn, handle_fn): + training = Dummy.create_training(options=dict(daisy_chain_period=5)) + trainer = get_trainer(training) + event = TrainingRoundFinished(trainer) + trainer.handle(event) + self.assertTrue(next_fn.called) + next_fn.assert_called_once() + self.assertTrue(handle_fn.called) + handle_fn.assert_called_once() + + @patch.object(TrainingRoundFinished, "handle") + @patch.object(DaisyChainRoundFinished, "next") + def test_handle_round_finished_handle_dc_period(self, next_fn, base_handle_fn): + training = Dummy.create_training(target_num_updates=42, options=dict(daisy_chain_period=5)) + trainer = get_trainer(training) + event = TrainingRoundFinished(trainer) + trainer.handle(event) + self.assertTrue(next_fn.called) + next_fn.assert_called_once() + self.assertFalse(base_handle_fn.called) + + @patch.object(TrainingRoundFinished, "handle") + @patch.object(DaisyChainRoundFinished, "next") + def test_handle_round_finished_handle_trainings_epoch(self, next_fn, base_handle_fn): + training = Dummy.create_training(options=dict(daisy_chain_period=5)) + trainer = get_trainer(training) + event = TrainingRoundFinished(trainer) + trainer.handle(event) + self.assertTrue(next_fn.called) + next_fn.assert_called_once() + self.assertTrue(base_handle_fn.called) + base_handle_fn.assert_called_once() + + @patch.object(ModelTestFinished, "handle") + @patch.object(ModelTestFinished, "next") + def test_handle_test_round_finished(self, next_fn, handle_fn): + training = Dummy.create_training(options=dict(daisy_chain_period=5)) + trainer = get_trainer(training) + event = ModelTestFinished(trainer) + trainer.handle(event) + self.assertTrue(next_fn.called) + next_fn.assert_called_once() + self.assertTrue(handle_fn.called) + handle_fn.assert_called_once() diff --git a/fl_server_ai/tests/test_notification.py b/fl_server_ai/tests/test_notification.py new file mode 100644 index 0000000..b1a0115 --- /dev/null +++ b/fl_server_ai/tests/test_notification.py @@ -0,0 +1,96 @@ +from django.test import TestCase +from requests import Response +import responses +from uuid import UUID, uuid4 + +from fl_server_core.models.training import Training, TrainingState +from fl_server_core.models.user import NotificationReceiver +from fl_server_core.tests.dummy import Dummy + +from ..exceptions import ClientNotificationRejectionException, NotificationException +from ..notification.notification import send_notification +from ..notification.training import TrainingRoundStartNotification, TrainingFinishedNotification + + +class NotificationTest(TestCase): + + DUMMY_MESSAGE_ENDPOINT = "http://example.com/" + + def _create_dummy_notification_receiver( + self, + id: UUID = uuid4(), + message_endpoint: str = DUMMY_MESSAGE_ENDPOINT + ) -> NotificationReceiver: + receiver = NotificationReceiver() + receiver.id = id + receiver.message_endpoint = message_endpoint + return receiver + + def test_send_notification(self): + notification = TrainingFinishedNotification( + receivers=[self._create_dummy_notification_receiver() for _ in range(10)], + body=TrainingFinishedNotification.Body(uuid4()), + training_uuid=uuid4() + ) + with responses.RequestsMock() as mock: + mock.add(responses.POST, NotificationTest.DUMMY_MESSAGE_ENDPOINT) + send_notification(NotificationTest.DUMMY_MESSAGE_ENDPOINT, notification.serialize()) + self.assertEqual(len(mock.calls), 1) + + def test_send_notification_bad(self): + notification = TrainingFinishedNotification( + receivers=[self._create_dummy_notification_receiver(message_endpoint="ooo://example.com/")], + body=TrainingFinishedNotification.Body(uuid4()), + training_uuid=uuid4() + ) + with self.assertLogs(level="WARN"), self.assertRaises(NotificationException) as cm: + send_notification("ooo://example.com/", notification.serialize(), max_retries=0) + self.assertTrue(isinstance(cm.exception.inner_exception, ValueError)) + + def test_make_training_notification_callback_success(self): + user = Dummy.create_client() + training = Dummy.create_training(participants=[user]) + notification = TrainingRoundStartNotification( + receivers=[user], + body=TrainingRoundStartNotification.Body(1, uuid4()), + training_uuid=training.id + ) + with self.assertLogs(logger="fl.celery", level="DEBUG"): + notification.callback_success(user) + + def test_make_training_notification_callback_failure_client_error(self): + user_kaputt = Dummy.create_client() + user_good = Dummy.create_client() + training = Dummy.create_training(participants=[user_kaputt, user_good]) + notification = TrainingRoundStartNotification( + receivers=[user_good, user_kaputt], + body=TrainingRoundStartNotification.Body(1, uuid4()), + training_uuid=training.id + ) + response = Response() + response.status_code = 404 + error = ClientNotificationRejectionException( + response, + NotificationTest.DUMMY_MESSAGE_ENDPOINT, + notification.serialize(), 5, user_kaputt + ) + with self.assertLogs(level="WARN"): + notification.callback_error(error) + + def test_training_round_start_notification_callback_failure_server_error(self): + user = Dummy.create_client() + training = Dummy.create_training() + notification = TrainingRoundStartNotification( + receivers=[user], + body=TrainingRoundStartNotification.Body(1, uuid4()), + training_uuid=training.id + ) + error = NotificationException( + NotificationTest.DUMMY_MESSAGE_ENDPOINT, + notification.serialize(), 5, user, + ValueError("Oops, your LAN-Cable decided to die!") + ) + with self.assertLogs(level="ERROR"): + notification.callback_error(error) + training = Training.objects.get(id=training.id) + self.assertEqual(TrainingState.ERROR, training.state) diff --git a/fl_server_ai/tests/test_uncertainty_ensemble.py b/fl_server_ai/tests/test_uncertainty_ensemble.py new file mode 100644 index 0000000..91805c5 --- /dev/null +++ b/fl_server_ai/tests/test_uncertainty_ensemble.py @@ -0,0 +1,42 @@ +from django.test import TestCase +import pickle +import torch + +from fl_server_core.models.model import GlobalModel, MeanModel +from fl_server_core.models.training import UncertaintyMethod +from fl_server_core.tests.dummy import Dummy + +from ..trainer.model_trainer import get_trainer, get_trainer_class, ModelTrainer +from ..uncertainty import Ensemble + + +class EnsembleTest(TestCase): + + def test_trainer_class(self): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.ENSEMBLE) + trainer_cls = get_trainer_class(training) + self.assertTrue(trainer_cls is ModelTrainer) + + def test_trainer_type(self): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.ENSEMBLE) + trainer = get_trainer(training) + self.assertTrue(type(trainer) is ModelTrainer) + + def test_prediction(self): + layer = torch.nn.Linear(1, 1) + layer.weight = torch.nn.Parameter(torch.tensor([[1.]])) + layer.bias = torch.nn.Parameter(torch.tensor([0.])) + models = [pickle.dumps(torch.nn.Sequential(layer, torch.nn.Tanh())) for _ in range(10)] + models_db = [Dummy.create_model(GlobalModel, weights=model) for model in models] + model = Dummy.create_model(MeanModel) + model.models.set(models_db) + Dummy.create_training( + model=model, + uncertainty_method=UncertaintyMethod.ENSEMBLE + ) + X = torch.tensor([[-4.0], [-2.0], [2.0], [4.0]]) + y = torch.tensor([-1.0, -1.0, 1.0, 1.0]) + logits, uncertainty_dict = Ensemble.prediction(X, model) + torch.testing.assert_close(y, torch.sign(torch.squeeze(logits))) + torch.testing.assert_close(torch.tensor([[0.]] * 4), uncertainty_dict["variance"]) + torch.testing.assert_close(torch.tensor([[0.]] * 4), uncertainty_dict["std"]) diff --git a/fl_server_ai/tests/test_uncertainty_mc.py b/fl_server_ai/tests/test_uncertainty_mc.py new file mode 100644 index 0000000..ce05f9d --- /dev/null +++ b/fl_server_ai/tests/test_uncertainty_mc.py @@ -0,0 +1,46 @@ +from django.test import TestCase +import pickle +import torch + +from fl_server_core.models.model import GlobalModel +from fl_server_core.models.training import UncertaintyMethod +from fl_server_core.tests.dummy import Dummy + +from ..trainer.model_trainer import get_trainer, get_trainer_class, ModelTrainer +from ..uncertainty import MCDropout + + +class MCDropoutTest(TestCase): + + def test_trainer_class(self): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.MC_DROPOUT) + trainer_cls = get_trainer_class(training) + self.assertTrue(trainer_cls is ModelTrainer) + + def test_trainer_type(self): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.MC_DROPOUT) + trainer = get_trainer(training) + self.assertTrue(type(trainer) is ModelTrainer) + + def test_prediction(self): + torch.manual_seed(42) + layer = torch.nn.Linear(1, 1) + layer.weight = torch.nn.Parameter(torch.tensor([[1.]])) + layer.bias = torch.nn.Parameter(torch.tensor([0.])) + model = pickle.dumps(torch.nn.Sequential(layer, torch.nn.Dropout(0.5), torch.nn.Tanh())) + model = Dummy.create_model(GlobalModel, weights=model) + Dummy.create_training( + model=model, + uncertainty_method=UncertaintyMethod.MC_DROPOUT + ) + X = torch.tensor([[-4.0], [-2.0], [2.0], [4.0]]) + y = torch.tensor([-1.0, -1.0, 1.0, 1.0]) + logits, uncertainty_dict = MCDropout.prediction(X, model) + torch.testing.assert_close(y, torch.sign(torch.squeeze(logits))) + torch.testing.assert_close(torch.tensor([[0.2667], [0.2330], [0.2663], [0.2333]]), + uncertainty_dict["variance"], atol=1e-4, rtol=0.001) + torch.testing.assert_close(torch.tensor([[0.5164], [0.4827], [0.5161], [0.4830]]), + uncertainty_dict["std"], atol=1e-4, rtol=0.001) + self.assertFalse("predictive_entropy" in uncertainty_dict) + self.assertFalse("expected_entropy" in uncertainty_dict) + self.assertFalse("mutual_info" in uncertainty_dict) diff --git a/fl_server_ai/tests/test_uncertainty_swag.py b/fl_server_ai/tests/test_uncertainty_swag.py new file mode 100644 index 0000000..0f8f2b5 --- /dev/null +++ b/fl_server_ai/tests/test_uncertainty_swag.py @@ -0,0 +1,77 @@ +from django.test import TestCase +import pickle +import torch +from unittest.mock import patch + +from fl_server_core.models.model import SWAGModel +from fl_server_core.models.training import TrainingState, UncertaintyMethod +from fl_server_core.tests.dummy import Dummy + +from ..notification.training import TrainingSWAGRoundStartNotification +from ..trainer.events import SWAGRoundFinished, TrainingRoundFinished +from ..trainer.model_trainer import get_trainer, get_trainer_class, SWAGModelTrainer +from ..uncertainty import SWAG + + +class SwagTest(TestCase): + + def test_trainer_class(self): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.SWAG) + trainer_cls = get_trainer_class(training) + self.assertTrue(trainer_cls is SWAGModelTrainer) + + def test_trainer_type(self): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.SWAG) + trainer = get_trainer(training) + self.assertTrue(type(trainer) is SWAGModelTrainer) + + @patch.object(TrainingSWAGRoundStartNotification, "send") + def test_start_swag_round(self, send_method): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.SWAG) + trainer = get_trainer(training) + assert type(trainer) is SWAGModelTrainer + trainer.start_swag_round() + self.assertEqual(TrainingState.SWAG_ROUND, training.state) + self.assertTrue(send_method.called) + + @patch.object(TrainingRoundFinished, "handle") + @patch.object(TrainingRoundFinished, "next") + @patch.object(TrainingSWAGRoundStartNotification, "send") + def test_start_swag_round_via_handle(self, send_method, next_method, handle_method): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.SWAG) + trainer = get_trainer(training) + assert type(trainer) is SWAGModelTrainer + event = TrainingRoundFinished(trainer) + trainer.handle(event) + self.assertEqual(TrainingState.SWAG_ROUND, training.state) + self.assertTrue(handle_method.called) + self.assertFalse(next_method.called) + self.assertTrue(send_method.called) + + @patch.object(SWAGRoundFinished, "handle") + @patch.object(TrainingRoundFinished, "next") + def test_handle_swag_round_finished(self, base_cls_next_method, handle_method): + training = Dummy.create_training(uncertainty_method=UncertaintyMethod.SWAG) + trainer = get_trainer(training) + assert type(trainer) is SWAGModelTrainer + event = SWAGRoundFinished(trainer) + trainer.handle(event) + self.assertEqual(TrainingState.ONGOING, training.state) + self.assertTrue(handle_method.called) + self.assertTrue(base_cls_next_method.called) + + def test_prediction(self): + model = pickle.dumps(torch.nn.Sequential(torch.nn.Linear(1, 1), torch.nn.Tanh())) + first_moment = pickle.dumps(torch.tensor([1.0, 0.0])) + second_moment = pickle.dumps(torch.tensor([1.0, 0.0])) + model = Dummy.create_model(SWAGModel, weights=model, swag_first_moment=first_moment, + swag_second_moment=second_moment) + Dummy.create_training( + model=model, + uncertainty_method=UncertaintyMethod.SWAG, + options=dict(uncertainty={"N": 10}) + ) + X = torch.tensor([[-4.0], [-2.0], [2.0], [4.0]]) + y = torch.tensor([-1.0, -1.0, 1.0, 1.0]) + logits, _ = SWAG.prediction(X, model) + torch.testing.assert_close(y, torch.sign(torch.squeeze(logits))) diff --git a/fl_server_ai/trainer/__init__.py b/fl_server_ai/trainer/__init__.py new file mode 100644 index 0000000..6af84cc --- /dev/null +++ b/fl_server_ai/trainer/__init__.py @@ -0,0 +1,5 @@ +from .model_trainer import ModelTrainer +from .options import TrainerOptions +from .tasks import process_trainer_task + +__all__ = ["ModelTrainer", "process_trainer_task", "TrainerOptions"] diff --git a/fl_server_ai/trainer/events/__init__.py b/fl_server_ai/trainer/events/__init__.py new file mode 100644 index 0000000..fdca7ca --- /dev/null +++ b/fl_server_ai/trainer/events/__init__.py @@ -0,0 +1,14 @@ +from .base import ModelTrainerEvent +from .daisy_chain_round_finished import DaisyChainRoundFinished +from .model_test_finished import ModelTestFinished +from .swag_round_finished import SWAGRoundFinished +from .training_round_finished import TrainingRoundFinished + + +__all__ = [ + "ModelTrainerEvent", + "DaisyChainRoundFinished", + "ModelTestFinished", + "SWAGRoundFinished", + "TrainingRoundFinished", +] diff --git a/fl_server_ai/trainer/events/base.py b/fl_server_ai/trainer/events/base.py new file mode 100644 index 0000000..fdb7352 --- /dev/null +++ b/fl_server_ai/trainer/events/base.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod +from logging import getLogger + +from .. import model_trainer + + +class ModelTrainerEvent(ABC): + + _logger = getLogger("fl.server") + + def __init__(self, trainer: "model_trainer.ModelTrainer"): + super().__init__() + self.trainer = trainer + self.training = trainer.training + + @abstractmethod + def handle(self): + pass + + @abstractmethod + def next(self): + pass diff --git a/fl_server_ai/trainer/events/daisy_chain_round_finished.py b/fl_server_ai/trainer/events/daisy_chain_round_finished.py new file mode 100644 index 0000000..b164b84 --- /dev/null +++ b/fl_server_ai/trainer/events/daisy_chain_round_finished.py @@ -0,0 +1,23 @@ +from .training_round_finished import TrainingRoundFinished +from .. import model_trainer + + +class DaisyChainRoundFinished(TrainingRoundFinished): + + def __init__(self, trainer: "model_trainer.ModelTrainer"): + super().__init__(trainer) + self.trainer.options.model_test_after_each_round = False + + def handle(self): + # the round increment is not done yet, therefore `model.round + 1` + if (self.training.model.round + 1) >= self.training.target_num_updates: + # local client training is finished, let's do the aggregation + super().handle() # also does the round increment + return + + # local client training is not finished yet, but we reach a daisy chaining period + # => no aggregation, just send the permutated client models back for further training + # also see `FedDCModelTrainer.handle()` + + self.training.model.round += 1 + self.training.model.save() diff --git a/fl_server_ai/trainer/events/model_test_finished.py b/fl_server_ai/trainer/events/model_test_finished.py new file mode 100644 index 0000000..eaf5d27 --- /dev/null +++ b/fl_server_ai/trainer/events/model_test_finished.py @@ -0,0 +1,15 @@ +from .base import ModelTrainerEvent + + +class ModelTestFinished(ModelTrainerEvent): + + def next(self): + if self.training.model.round < self.training.target_num_updates: + self.trainer.start_round() + else: + self.trainer.finish() + + def handle(self): + # currently do nothing + # Potentially, one could aggregate all common metrics here. + pass diff --git a/fl_server_ai/trainer/events/swag_round_finished.py b/fl_server_ai/trainer/events/swag_round_finished.py new file mode 100644 index 0000000..3b1d723 --- /dev/null +++ b/fl_server_ai/trainer/events/swag_round_finished.py @@ -0,0 +1,78 @@ +from django.db.models.query import QuerySet +import torch +from typing import List + +from fl_server_core.models import Metric +from fl_server_core.models.training import TrainingState + +from ...exceptions import AggregationException +from .training_round_finished import TrainingRoundFinished + + +class SWAGRoundFinished(TrainingRoundFinished): + + def next(self): + self.training.state = TrainingState.ONGOING + self.training.save() + super().next() + + def handle(self): + # collect metric value + swag_fst = [m.to_torch() for m in self._get_metric("SWAG First Moment Local")] + swag_snd = [m.to_torch() for m in self._get_metric("SWAG Second Moment Local")] + sample_sizes = [m.value_float for m in self._get_metric("SWAG Sample Size Local")] + n_participants = self.training.participants.count() + + # validate + self._validate_swag(swag_fst, swag_snd, sample_sizes, n_participants) + self._logger.info( + f"Training {self.training.id}: Doing SWAG aggregation as all {n_participants} updates arrived" + ) + + # SWAG aggregation and save + self.training.model.first_moment = self._aggregate_param_vectors(swag_fst, sample_sizes) + self.training.model.second_moment = self._aggregate_param_vectors(swag_snd, sample_sizes) + self.training.model.save() + self._logger.info(f"SWAG completed for training {self.training.id}") + + def _get_metric(self, key: str) -> QuerySet[Metric]: + return Metric.objects.filter( + model=self.training.model, + step=self.training.model.round, + key=key + ) + + def _validate_swag( + self, + swag_fst: List[torch.Tensor], + swag_snd: List[torch.Tensor], + sample_sizes: List[int], + n_participants: int + ): + if len(swag_fst) != len(swag_snd) != len(sample_sizes): + self.training.state = TrainingState.ERROR + self.training.save() + raise ValueError("SWAG stats in inconsistent state!") + + if len(swag_fst) != n_participants: + text = f"Aggregation was started, but training {self.training.id}" \ + f"has {len(swag_fst)} updates," \ + f"but {n_participants} clients!" + self._logger.error(text) + raise RuntimeError(text) + + @torch.no_grad() + def _aggregate_param_vectors( + self, + param_vectors: List[torch.Tensor], + sample_sizes: List[int] + ) -> torch.Tensor: + if not all(map(lambda v: len(v) == len(param_vectors[0]), param_vectors[1:])): + raise AggregationException("Models do not have the same number of parameters!") + if len(param_vectors) != len(sample_sizes): + raise RuntimeError("len(sample_sizes) != len(param_vectors)") + + factors = torch.tensor([s / sum(sample_sizes) for s in sample_sizes]) + result = torch.stack(param_vectors) * factors[:, None] + result = torch.sum(result, dim=0) + return result diff --git a/fl_server_ai/trainer/events/training_round_finished.py b/fl_server_ai/trainer/events/training_round_finished.py new file mode 100644 index 0000000..3171ef6 --- /dev/null +++ b/fl_server_ai/trainer/events/training_round_finished.py @@ -0,0 +1,75 @@ +from typing import List + +from fl_server_core.models import LocalModel + +from ...aggregation import get_aggregation_class + +from .base import ModelTrainerEvent +from .model_test_finished import ModelTestFinished + + +class TrainingRoundFinished(ModelTrainerEvent): + """ + After a trainings round is finised. + + This event should only be triggered when all model updates (local models) + that are to participate in the aggregation have arrived. + """ + + def next(self): + tests_enabled = not self.trainer.options.skip_model_tests + if tests_enabled and self.trainer.options.model_test_after_each_round: + self.trainer.test_round() + elif tests_enabled and self.training.model.round >= self.training.target_num_updates: + # at least test the final trained model + self.trainer.test_round() + else: + ModelTestFinished(self.trainer).next() + + def handle(self): + """ + Does the following: + - aggregates all model updates (local models) into a new global model + - saves the new global model into the database (i.e. updates/overwrites the weights field of the model) + - increase the round field of the global model by 1 + - deletes the model updates (local models) from the database, if the trainer options do not disagree + + Note: If not enough updates have arrived, the method does nothing. + """ + model_updates = LocalModel.objects.filter(base_model=self.training.model, round=self.training.model.round) + models = [m.get_torch_model() for m in model_updates] + model_sample_sizes = [m.sample_size for m in model_updates] + n_participants = self.training.participants.count() + + # validate + self._validate(models, n_participants) + self._logger.info(f"Training {self.training.id}: Doing aggregation as all {n_participants} updates arrived") + + # do aggregation + aggregation_cls = get_aggregation_class(self.training) + final_model = aggregation_cls().aggregate( + models, + model_sample_sizes, + deepcopy=not self.trainer.options.delete_local_models_after_aggregation + ) + + # write the result back to database and update the trainings round + self.training.model.set_torch_model(final_model) + self.training.model.round += 1 + self.training.model.save() + + # clean local updates + if self.trainer.options.delete_local_models_after_aggregation: + model_updates.delete() + + def _validate(self, models: List, n_participants: int): + if not models: + text = f"Aggregation was run for training {self.training.id} but no model updates were in db!" + self._logger.error(text) + raise RuntimeError(text) + + if len(models) != n_participants: + text = f"Aggregation was started, but training {self.training.id} has {len(models)} updates," \ + f"but {n_participants} clients!" + self._logger.error(text) + raise RuntimeError(text) diff --git a/fl_server_ai/trainer/model_trainer.py b/fl_server_ai/trainer/model_trainer.py new file mode 100644 index 0000000..9aadeea --- /dev/null +++ b/fl_server_ai/trainer/model_trainer.py @@ -0,0 +1,117 @@ +from random import shuffle +from typing import Optional, Type + +from fl_server_core.models.model import LocalModel +from fl_server_core.models.training import Training, TrainingState, UncertaintyMethod +from fl_server_ai.notification.training import ( + TrainingFinishedNotification, + TrainingModelTestNotification, + TrainingRoundStartNotification, + TrainingStartNotification, + TrainingSWAGRoundStartNotification, +) + +from .events import ModelTrainerEvent, DaisyChainRoundFinished, TrainingRoundFinished +from .options import TrainerOptions + + +def get_trainer_class(training: Training) -> Type["ModelTrainer"]: + if training.uncertainty_method == UncertaintyMethod.SWAG: + return SWAGModelTrainer + if training.options.get("daisy_chain_period", 0) > 0: + return FedDCModelTrainer + return ModelTrainer + + +def get_trainer(training: Training, options: Optional[TrainerOptions] = None) -> "ModelTrainer": + return get_trainer_class(training)(training, options) + + +class ModelTrainer: + + def __new__(cls, training: Training, options: Optional[TrainerOptions] = None) -> "ModelTrainer": + return super().__new__(cls.get_trainer_class(training)) + + @classmethod + def get_trainer_class(cls, training: Training) -> Type["ModelTrainer"]: + return get_trainer_class(training) + + def __init__(self, training: Training, options: Optional[TrainerOptions] = None): + super().__init__() + self.training = training + self.options = options if options else TrainerOptions() + + def start(self): + self.training.state = TrainingState.ONGOING + self.training.save() + TrainingStartNotification.from_training(self.training).send() + TrainingRoundStartNotification.from_training(self.training).send() + + def finish(self): + self.training.state = TrainingState.COMPLETED + self.training.save() + TrainingFinishedNotification.from_training(self.training).send() + + def start_round(self): + TrainingRoundStartNotification.from_training(self.training).send() + + def test_round(self): + TrainingModelTestNotification.from_training(self.training).send() + + def handle(self, event: ModelTrainerEvent): + event.handle() + event.next() + + def handle_cls(self, event_cls: Type[ModelTrainerEvent]): + self.handle(event_cls(self)) + + +class SWAGModelTrainer(ModelTrainer): + + def start_swag_round(self): + self.training.state = TrainingState.SWAG_ROUND + self.training.save() + TrainingSWAGRoundStartNotification.from_training(self.training).send() + + def handle(self, event: ModelTrainerEvent): + event.handle() + if type(event) is TrainingRoundFinished: + self.start_swag_round() + else: + event.next() + + +class FedDCModelTrainer(ModelTrainer): + + def start_round(self): + dc_period = self.training.options.get("daisy_chain_period", 0) + if dc_period < 1 or self.training.model.round % dc_period == 0: + # start training round, first (local) training round, therefore no local models to permute + TrainingRoundStartNotification.from_training(self.training).send() + return + + # daily chaining period, therefore send the permutated client models back for further training + clients = self.training.participants.all() + model_ids = list(LocalModel.objects.filter( + base_model=self.training.model, + round=self.training.model.round - 1 + ).values_list("pk", flat=True)) + shuffle(model_ids) + for client, model_id in zip(clients, model_ids): + TrainingRoundStartNotification( + receivers=[client], + body=TrainingRoundStartNotification.Body( + round=self.training.model.round, + global_model_uuid=model_id + ), + training_uuid=self.training.id + ).send() + + def handle(self, event: ModelTrainerEvent): + if type(event) is TrainingRoundFinished: + real_event = DaisyChainRoundFinished(self) + real_event.handle() + real_event.next() + else: + event.handle() + event.next() diff --git a/fl_server_ai/trainer/options.py b/fl_server_ai/trainer/options.py new file mode 100644 index 0000000..da6c9b4 --- /dev/null +++ b/fl_server_ai/trainer/options.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + + +@dataclass +class TrainerOptions: + skip_model_tests: bool = False + model_test_after_each_round: bool = True + delete_local_models_after_aggregation: bool = True diff --git a/fl_server_ai/trainer/tasks.py b/fl_server_ai/trainer/tasks.py new file mode 100644 index 0000000..60d290b --- /dev/null +++ b/fl_server_ai/trainer/tasks.py @@ -0,0 +1,53 @@ +from celery.utils.log import get_task_logger +from django.db import transaction, DatabaseError +from logging import getLogger +from traceback import format_exception +from typing import Type +from uuid import UUID + +from fl_server_core.models import Training + +from ..celery_tasks import app + +from .events import ModelTrainerEvent +from .model_trainer import ModelTrainer + + +@app.task(bind=False, ignore_result=False) +def process_trainer_task(training_uuid: UUID, event_cls: Type[ModelTrainerEvent]): + logger = get_task_logger("fl.celery") + try: + training = Training.objects.get(id=training_uuid) + ModelTrainer(training).handle_cls(event_cls) + except Exception as e: + error_msg = f"Exception occurred for training {training_uuid}: {e}" + logger.error(error_msg) + logger.debug(error_msg + "\n" + "".join(format_exception(e))) + raise e + finally: + logger.info(f"Unlocking training {training_uuid}") + if training: + training = Training.objects.get(id=training_uuid) + training.locked = False + training.save() + + +def dispatch_trainer_task(training: Training, event_cls: Type[ModelTrainerEvent], lock_training: bool): + logger = getLogger("fl.server") + if lock_training: + try: + with transaction.atomic(): + training.refresh_from_db() + assert not training.locked + + # set lock and do the aggregation + training.locked = True + training.save() + + logger.debug(f"Locking training {training.id}") + except (DatabaseError, AssertionError): + logger.debug(f"Training {training.id} is locked!") + return + + # start task async + process_trainer_task.s(training_uuid=training.id, event_cls=event_cls).apply_async(retry=False) diff --git a/fl_server_ai/uncertainty/__init__.py b/fl_server_ai/uncertainty/__init__.py new file mode 100644 index 0000000..551c997 --- /dev/null +++ b/fl_server_ai/uncertainty/__init__.py @@ -0,0 +1,16 @@ +from .base import UncertaintyBase +from .ensemble import Ensemble +from .mc_dropout import MCDropout +from .method import get_uncertainty_class +from .none import NoneUncertainty +from .swag import SWAG + + +__all__ = [ + "get_uncertainty_class", + "Ensemble", + "MCDropout", + "NoneUncertainty", + "SWAG", + "UncertaintyBase", +] diff --git a/fl_server_ai/uncertainty/base.py b/fl_server_ai/uncertainty/base.py new file mode 100644 index 0000000..f7745b2 --- /dev/null +++ b/fl_server_ai/uncertainty/base.py @@ -0,0 +1,114 @@ +from abc import ABC, abstractmethod +import json +from logging import getLogger +import numpy as np +import torch +from typing import Any, Dict, Tuple + +from fl_server_core.models import Model, Training + + +class UncertaintyBase(ABC): + _logger = getLogger("fl.server") + + @classmethod + @abstractmethod + def prediction(cls, input: torch.Tensor, model: Model) -> Tuple[torch.Tensor, Dict[str, Any]]: + pass + + @classmethod + def interpret(cls, outputs: torch.Tensor) -> Dict[str, Any]: + """ + Interpret the different network (model) outputs and calculate the uncertainty. + + Args: + outputs (torch.Tensor): multiple network (model) outputs (N, batch_size, n_classes) + + Return: + Tuple[torch.Tensor, Dict[str, Any]]: inference and uncertainty + """ + variance = outputs.var(dim=0) + std = outputs.std(dim=0) + if not (torch.all(outputs <= 1.) and torch.all(outputs >= 0.)): + return dict(variance=variance, std=std) + + predictive_entropy = cls.predictive_entropy(outputs) + expected_entropy = cls.expected_entropy(outputs) + mutual_info = predictive_entropy - expected_entropy # see cls.mutual_information + return dict( + variance=variance, + std=std, + predictive_entropy=predictive_entropy, + expected_entropy=expected_entropy, + mutual_info=mutual_info, + ) + + @classmethod + def expected_entropy(cls, predictions: torch.Tensor) -> torch.Tensor: + """ + Calculate the mean entropy of the predictive distribution across the MC samples. + + Args: + predictions (torch.Tensor): predictions of shape (n_mc x batch_size x n_classes) + + Returns: + torch.Tensor: mean entropy of the predictive distribution + """ + return torch.distributions.Categorical(probs=predictions).entropy().mean(dim=0) + + @classmethod + def predictive_entropy(cls, predictions: torch.Tensor) -> torch.Tensor: + """ + Calculate the entropy of the mean predictive distribution across the MC samples. + + Args: + predictions (torch.Tensor): predictions of shape (n_mc x batch_size x n_classes) + + Returns: + torch.Tensor: entropy of the mean predictive distribution + """ + return torch.distributions.Categorical(probs=predictions.mean(dim=0)).entropy() + + @classmethod + def mutual_information(cls, predictions: torch.Tensor) -> torch.Tensor: + """ + Calculate the BALD (Bayesian Active Learning by Disagreement) of a model; + the difference between the mean of the entropy and the entropy of the mean + of the predicted distribution on the predictions. + This method is also sometimes referred to as the mutual information (MI). + + Args: + predictions (torch.Tensor): predictions of shape (n_mc x batch_size x n_classes) + + Returns: + torch.Tensor: difference between the mean of the entropy and the entropy of the mean + of the predicted distribution + """ + return cls.predictive_entropy(predictions) - cls.expected_entropy(predictions) + + @classmethod + def get_options(cls, obj: Model | Training) -> Dict[str, Any]: + if isinstance(obj, Model): + return Training.objects.filter(model=obj) \ + .values("options") \ + .first()["options"] \ + .get("uncertainty", {}) + if isinstance(obj, Training): + return obj.options.get("uncertainty", {}) + raise TypeError(f"Expected Model or Training, got {type(obj)}") + + @classmethod + def to_json(cls, inference: torch.Tensor, uncertainty: Dict[str, Any] = {}, **json_kwargs) -> str: + def prepare(v): + if isinstance(v, torch.Tensor): + return v.cpu().tolist() + if isinstance(v, np.ndarray): # cspell:ignore ndarray + return v.tolist() + if isinstance(v, dict): + return {k: prepare(_v) for k, _v in v.items()} + return v + + return json.dumps({ + "inference": inference.tolist(), + "uncertainty": prepare(uncertainty) if uncertainty else {}, + }, **json_kwargs) diff --git a/fl_server_ai/uncertainty/ensemble.py b/fl_server_ai/uncertainty/ensemble.py new file mode 100644 index 0000000..15327b5 --- /dev/null +++ b/fl_server_ai/uncertainty/ensemble.py @@ -0,0 +1,22 @@ +import torch +from typing import Any, Dict, Tuple + +from fl_server_core.models import MeanModel + +from .base import UncertaintyBase + + +class Ensemble(UncertaintyBase): + + @classmethod + def prediction(cls, input: torch.Tensor, model: MeanModel) -> Tuple[torch.Tensor, Dict[str, Any]]: + output_list = [] + for m in model.models.all(): + net = m.get_torch_model() + output = net(input).detach() + output_list.append(output) + outputs = torch.stack(output_list, dim=0) # (N, batch_size, n_classes) # N = number of models + + inference = outputs.mean(dim=0) + uncertainty = cls.interpret(outputs) + return inference, uncertainty diff --git a/fl_server_ai/uncertainty/mc_dropout.py b/fl_server_ai/uncertainty/mc_dropout.py new file mode 100644 index 0000000..4dbf4f3 --- /dev/null +++ b/fl_server_ai/uncertainty/mc_dropout.py @@ -0,0 +1,62 @@ +import torch +from torch import Tensor +from torch.nn import Module +from torch.nn.modules.dropout import _DropoutNd +from typing import Any, Dict, Tuple + +from fl_server_core.models import Model + +from .base import UncertaintyBase + + +def set_dropout(model: Module, state: bool = True): + """ + Set the state of the dropout layers to enable or disable them even during inference. + + Args: + model (Module): PyTorch module + state (bool, optional): Enable or disable dropout layers. Defaults to True. + """ + for m in model.modules(): + if isinstance(m, _DropoutNd) or m.__class__.__name__.lower().__contains__("dropout"): + m.train(mode=state) + + +class MCDropout(UncertaintyBase): + """ + Monte Carlo (MC) Dropout Uncertainty Estimation + + Requirements: + + - model with dropout layers + - T, number of samples per input (number of monte-carlo samples/forward passes) + + References: + + - Paper: Understanding Measures of Uncertainty for Adversarial Example Detection + + - Code inspiration: + """ + + @classmethod + def prediction(cls, input: Tensor, model: Model) -> Tuple[torch.Tensor, Dict[str, Any]]: + options = cls.get_options(model) + N = options.get("N", 10) + softmax = options.get("softmax", False) + + net: Module = model.get_torch_model() + net.eval() + set_dropout(net, state=True) + + out_list = [] + for _ in range(N): + output = net(input).detach() + # convert to probabilities if necessary + if softmax: + output = torch.softmax(output, dim=1) + out_list.append(output) + out = torch.stack(out_list, dim=0) # (n_mc, batch_size, n_classes) + + inference = out.mean(dim=0) + uncertainty = cls.interpret(out) + return inference, uncertainty diff --git a/fl_server_ai/uncertainty/method.py b/fl_server_ai/uncertainty/method.py new file mode 100644 index 0000000..789d41a --- /dev/null +++ b/fl_server_ai/uncertainty/method.py @@ -0,0 +1,43 @@ +from typing import overload, Type + +from fl_server_core.models import Model, Training +from fl_server_core.models.training import UncertaintyMethod + +from .base import UncertaintyBase +from .ensemble import Ensemble +from .mc_dropout import MCDropout +from .none import NoneUncertainty +from .swag import SWAG + + +@overload +def get_uncertainty_class(value: Model) -> Type[UncertaintyBase]: ... + + +@overload +def get_uncertainty_class(value: Training) -> Type[UncertaintyBase]: ... + + +@overload +def get_uncertainty_class(value: UncertaintyMethod) -> Type[UncertaintyBase]: ... + + +def get_uncertainty_class(value: Model | Training | UncertaintyMethod) -> Type[UncertaintyBase]: + if isinstance(value, UncertaintyMethod): + method = value + elif isinstance(value, Training): + method = value.uncertainty_method + elif isinstance(value, Model): + uncertainty_method = Training.objects.filter(model=value) \ + .values("uncertainty_method") \ + .first()["uncertainty_method"] + method = uncertainty_method + else: + raise ValueError(f"Unknown type: {type(value)}") + + match method: + case UncertaintyMethod.ENSEMBLE: return Ensemble + case UncertaintyMethod.MC_DROPOUT: return MCDropout + case UncertaintyMethod.NONE: return NoneUncertainty + case UncertaintyMethod.SWAG: return SWAG + case _: raise ValueError(f"Unknown uncertainty method: {method}") diff --git a/fl_server_ai/uncertainty/none.py b/fl_server_ai/uncertainty/none.py new file mode 100644 index 0000000..3f89a08 --- /dev/null +++ b/fl_server_ai/uncertainty/none.py @@ -0,0 +1,15 @@ +import torch +from typing import Any, Dict, Tuple + +from fl_server_core.models import Model + +from .base import UncertaintyBase + + +class NoneUncertainty(UncertaintyBase): + + @classmethod + def prediction(cls, input: torch.Tensor, model: Model) -> Tuple[torch.Tensor, Dict[str, Any]]: + net: torch.nn.Module = model.get_torch_model() + prediction: torch.Tensor = net(input) + return prediction.argmax(dim=1), {} diff --git a/fl_server_ai/uncertainty/swag.py b/fl_server_ai/uncertainty/swag.py new file mode 100644 index 0000000..6063141 --- /dev/null +++ b/fl_server_ai/uncertainty/swag.py @@ -0,0 +1,37 @@ +import torch +from typing import Any, Dict, Tuple + +from fl_server_core.models import SWAGModel + +from .base import UncertaintyBase + + +class SWAG(UncertaintyBase): + """ + Stochastic weight averaging Gaussian + """ + + @classmethod + def prediction(cls, input: torch.Tensor, model: SWAGModel) -> Tuple[torch.Tensor, Dict[str, Any]]: + options = cls.get_options(model) + N = options.get("N", 10) + + net: torch.nn.Module = model.get_torch_model() + + # first and second moment are already ensured to be in + # alphabetical order in the database + fm = model.first_moment + sm = model.second_moment + std = sm - torch.pow(fm, 2) + params = torch.normal(mean=fm[None, :], std=std).expand(N, -1) + + prediction_list = [] + for n in range(N): + torch.nn.utils.vector_to_parameters(params[n], net.parameters()) + prediction = net(input) + prediction_list.append(prediction) + predictions = torch.stack(prediction_list) + + inference = predictions.mean(dim=0) + uncertainty = cls.interpret(predictions) + return inference, uncertainty diff --git a/fl_server_api/__init__.py b/fl_server_api/__init__.py new file mode 100644 index 0000000..297fd45 --- /dev/null +++ b/fl_server_api/__init__.py @@ -0,0 +1,3 @@ +""" +Federated Learning API +""" diff --git a/fl_server_api/apps.py b/fl_server_api/apps.py new file mode 100644 index 0000000..596ddd5 --- /dev/null +++ b/fl_server_api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FlServerApiConfig(AppConfig): + name = "fl_server_api" + verbose_name = "Federated Learning Demonstrator API" diff --git a/fl_server_api/openapi.py b/fl_server_api/openapi.py new file mode 100644 index 0000000..6eb0f19 --- /dev/null +++ b/fl_server_api/openapi.py @@ -0,0 +1,96 @@ +from docstring_parser import Docstring, parse, RenderingStyle +from docstring_parser.google import compose +from drf_spectacular.authentication import BasicScheme +from drf_spectacular.openapi import AutoSchema +from drf_spectacular.utils import OpenApiExample, OpenApiResponse +from inspect import cleandoc +from typing import Callable, List, Optional, Tuple + +from .serializers.generic import ErrorSerializer +from .utils import fullname + + +class BasicAuthAllowingTokenAuthInUrlScheme(BasicScheme): + target_class = "fl_server_api.views.base.BasicAuthAllowingTokenAuthInUrl" + priority = 0 + + +def create_error_response( + response_description: Optional[str], + example_name: str, + example_details: str, + example_description: Optional[str], + **example_kwargs +) -> OpenApiResponse: + return OpenApiResponse( + response=ErrorSerializer, + description=response_description, + examples=[ + OpenApiExample( + example_name, + value={"details": example_details}, + description=example_description, + **example_kwargs, + ) + ] + ) + + +error_response_403 = create_error_response( + "Unauthorized", + "Unauthorized", + "Authentication credentials were not provided.", + "Do not forget to authorize first!" +) + + +def custom_preprocessing_hook(endpoints: List[Tuple[str, str, str, Callable]]): + # your modifications to the list of operations that are exposed in the schema + # for (path, path_regex, method, callback) in endpoints: + # pass + return filter(lambda endpoint: endpoint[0] != "/api/dummy/", endpoints) + + +class CustomAutoSchema(AutoSchema): + + show_examples = True + rendering_style = RenderingStyle.CLEAN + + def _get_docstring(self): + return parse(super().get_description()) + + def _get_param_docstring(self, docstring: Docstring, argument: str) -> Optional[str]: + params = [p for p in docstring.params if p.arg_name == argument] + if not params: + return None + return params[0].description + + def get_description(self): + docstring = self._get_docstring() + tmp_docstring = Docstring(style=docstring.style) + tmp_docstring.short_description = docstring.short_description + tmp_docstring.long_description = docstring.long_description + if self.show_examples: + tmp_docstring.meta.extend(docstring.examples) + desc = compose(tmp_docstring, self.rendering_style, indent="") + if self.show_examples and desc.__contains__("Examples:"): + # customize examples section: + # - examples should be in a new paragraph (not concatenated with the description) + # - the examples header should be a h3 title + desc = desc.replace("\nExamples:\n", "\n\n### Examples:\n\n") + desc = cleandoc(desc) + return desc + + def _resolve_path_parameters(self, variables: List[str]): + parameters = super()._resolve_path_parameters(variables) + docstring = self._get_docstring() + for parameter in parameters: + if "description" not in parameter: + description = self._get_param_docstring(docstring, parameter["name"]) + if description: + parameter["description"] = description + return parameters + + def get_operation_id(self): + action_or_method = getattr(self.view, getattr(self.view, 'action', self.method.lower()), None) + return fullname(action_or_method or self.view.__class__) diff --git a/fl_server_api/serializers/__init__.py b/fl_server_api/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fl_server_api/serializers/generic.py b/fl_server_api/serializers/generic.py new file mode 100644 index 0000000..83ec1e6 --- /dev/null +++ b/fl_server_api/serializers/generic.py @@ -0,0 +1,41 @@ +from django.contrib.auth.models import Group +from django.db import models +from rest_framework import serializers +from typing import List, Optional, Type + +from fl_server_core.models import ( + Metric, + Training, +) + + +class ErrorSerializer(serializers.Serializer): + detail = serializers.CharField() + + +def _create_generic_serializer( + cls: Type[models.Model], + selected_fields: Optional[List[str]] = None +) -> Type[serializers.ModelSerializer]: + """ + Create a generic database model serializer. + + Args: + cls (Type[models.Model]): database model + selected_fields (Optional[List[str]], optional): model fields to serialize. + If `None` serialize all model fields. Defaults to `None`. + + Returns: + Type[serializers.ModelSerializer]: model serializer + """ + class GenericSerializer(serializers.ModelSerializer): + class Meta: + model = cls + fields = selected_fields if selected_fields else serializers.ALL_FIELDS + extra_kwargs = {"id": {"read_only": True}} + return type(f"{cls.__name__}Serializer", (GenericSerializer, ), {}) + + +TrainingSerializer: Type[serializers.ModelSerializer] = _create_generic_serializer(Training) +MetricSerializer: Type[serializers.ModelSerializer] = _create_generic_serializer(Metric) +GroupSerializer: Type[serializers.ModelSerializer] = _create_generic_serializer(Group, ["id", "name"]) diff --git a/fl_server_api/serializers/model.py b/fl_server_api/serializers/model.py new file mode 100644 index 0000000..ea47302 --- /dev/null +++ b/fl_server_api/serializers/model.py @@ -0,0 +1,172 @@ +from django.http import HttpRequest +from logging import getLogger +from rest_framework import serializers +from typing import Any, Dict, Optional, Tuple, Type + +from fl_server_core.models import ( + GlobalModel, + LocalModel, + MeanModel, + Model, + SWAGModel, +) +from fl_server_core.models.model import TModel + +from ..utils import get_file + + +def _create_model_serializer(cls: Type[TModel], *, name: Optional[str] = None) -> Type[serializers.ModelSerializer]: + """ + Create a generic Model serializer. + + Args: + cls (Type[TModel]): database Model model + name (Optional[str], optional): serializer name. Defaults to `None`. + + Returns: + Type[serializers.ModelSerializer]: Model model serializer + """ + class ModelSerializer(serializers.ModelSerializer): + + class Meta: + model = cls + exclude = ["polymorphic_ctype"] + + def to_representation(self, instance): + # only return the whole model (weights) if requested + data = serializers.ModelSerializer.to_representation(self, instance) + if not self.context.get("with-weights", False): + del data["weights"] + return data + + return type(name or f"{cls.__name__}Serializer", (ModelSerializer, ), {}) + + +GlobalModelSerializer = _create_model_serializer(GlobalModel) +LocalModelSerializer = _create_model_serializer(LocalModel) +ModelFallbackSerializer = _create_model_serializer(Model, name="ModelFallbackSerializer") + +MeanModelSerializer = _create_model_serializer(MeanModel) +SWAGModelSerializer = _create_model_serializer(SWAGModel) + + +class ModelSerializer(serializers.ModelSerializer): + + class Meta: + model = Model + exclude = ["polymorphic_ctype"] + + @classmethod + def get_serializer(cls, model: Type[Model]): + if model == GlobalModel: + return GlobalModelSerializer + if model == LocalModel: + return LocalModelSerializer + if model == MeanModel: + return MeanModelSerializer + if model == SWAGModel: + return SWAGModelSerializer + getLogger("fl.server").warning(f"Using fallback model serializer for {model.__name__}") + return ModelFallbackSerializer + + def to_representation(self, instance: Model): + serializer = self.get_serializer(instance.__class__) + return serializer(instance, context=self.context).data + + +class ModelSerializerNoWeights(ModelSerializer): + class Meta: + model = Model + exclude = ["polymorphic_ctype", "weights"] + + +####################################################################################################################### +# POST BODY SERIALIZERS # + + +def load_and_create_model_request(request: HttpRequest) -> Model: + """ + Load and create a model from a Django request. + + Args: + request (HttpRequest): request object + + Returns: + Model: created model + """ + model_cls, data = load_model_request(request) + if model_cls is MeanModel: + sub_models = data.pop("models") + model = model_cls.objects.create(**data) + model.models.set(sub_models) + return model + return model_cls.objects.create(**data) + + +def load_model_request(request: HttpRequest) -> Tuple[Type[Model], Dict[str, Any]]: + """ + Load model data from a Django request. + + Args: + request (HttpRequest): request object + + Returns: + Tuple[Type[Model], Dict[str, Any]]: model class and parsed request data + """ + if request.content_type == "application/json": + data = request.data + else: + # multipart/form-data + data = request.POST.dict() + data["owner"] = request.user + model_type = data.pop("type", "GLOBAL").upper() + + # name and description are required + if "name" not in data: + raise ValueError("Missing required field: name") + if "description" not in data: + raise ValueError("Missing required field: description") + + # round is an optional field (it may not even make sense to be able to set it) + if "round" not in data: + data["round"] = 0 + + if "input_shape" not in data: + data["input_shape"] = "[None]" + + # model_file is required except for MEAN models + if model_type != "MEAN": + data["weights"] = get_file(request, "model_file") + + # MEAN models require a list of model UUIDs + if model_type == "MEAN": + data = _parse_mean_model_models(data) + + # return class type and parsed data + match model_type: + case "SWAG": return SWAGModel, data + case "MEAN": return MeanModel, data + case _: return GlobalModel, data + + +def _parse_mean_model_models(data: Dict[str, Any]) -> Dict[str, Any]: + """ + Parse the models field of a mean model request. + + Args: + data (Dict[str, Any]): request data + + Returns: + Dict[str, Any]: parsed request data + """ + if "models" not in data: + raise ValueError("Missing required field: models") + models = data["models"] + if not isinstance(models, list) or not all([isinstance(m, str) for m in models]): + raise ValueError("Invalid type for field: models") + data["models"] = Model.objects.filter(id__in=models) + if data["models"].count() != len(models): + raise ValueError("Invalid model UUIDs found.") + if not all([m.owner.id == data["owner"].id for m in data["models"]]): + raise ValueError("Invalid model selection. Insufficient model permissions.") + return data diff --git a/fl_server_api/serializers/training.py b/fl_server_api/serializers/training.py new file mode 100644 index 0000000..b060c22 --- /dev/null +++ b/fl_server_api/serializers/training.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass, field +from marshmallow import fields, post_load, Schema +from typing import Any, Dict, List +from uuid import UUID + +from fl_server_core.models.training import AggregationMethod, UncertaintyMethod + + +class CreateTrainingRequestSchema(Schema): + model_id = fields.UUID() + target_num_updates = fields.Integer() + metric_names = fields.List(fields.Str()) + aggregation_method = fields.Enum( + AggregationMethod, + required=True, + dump_default=AggregationMethod.FED_AVG, + by_value=True + ) + uncertainty_method = fields.Enum( + UncertaintyMethod, + required=False, + dump_default=UncertaintyMethod.NONE, + by_value=True + ) + options = fields.Dict(required=False, dump_default={}) + clients = fields.List(fields.UUID(), dump_default=[]) + + @post_load + def _make_create_training_request(self, data: Dict[str, Any], **kwargs): + return CreateTrainingRequest(**data) + + +@dataclass +class CreateTrainingRequest: + model_id: UUID + target_num_updates: int + metric_names: list[str] + aggregation_method: AggregationMethod = field(default=AggregationMethod.FED_AVG) # type: ignore[assignment] + uncertainty_method: UncertaintyMethod = field(default=UncertaintyMethod.NONE) # type: ignore[assignment] + options: Dict[str, Any] = field(default_factory=lambda: {}) + clients: List[UUID] = field(default_factory=lambda: []) + + +class ClientAdministrationBodySchema(Schema): + clients = fields.List(fields.UUID, required=True) + + @post_load + def _make_client_administration_body(self, data: Dict[str, Any], **kwargs): + return ClientAdministrationBody(**data) + + +@dataclass +class ClientAdministrationBody: + clients: list[UUID] diff --git a/fl_server_api/serializers/user.py b/fl_server_api/serializers/user.py new file mode 100644 index 0000000..64c18e1 --- /dev/null +++ b/fl_server_api/serializers/user.py @@ -0,0 +1,41 @@ +from rest_framework import serializers +from rest_framework.authtoken.models import Token + +from fl_server_core.models import User + + +class UserSerializer(serializers.ModelSerializer): + token = serializers.SerializerMethodField() + + class Meta: + model = User + fields = [ + "username", "first_name", "last_name", + "email", "id", "actor", "client", + "message_endpoint", "token", "password", + ] + extra_kwargs = { + "id": {"read_only": True}, + "token": {"read_only": True}, + "password": {"write_only": True}, + } + + def get_token(self, user: User) -> str | None: + # return the user token only if the request user is the same as the requested user + if self.context.get("request_user_id") == user.id: + return Token.objects.get(user=user).key + return "**********" + + def to_representation(self, instance): + # remove the token key from the response if the request user is not the same as + # the requested user since its always empty or "**********" + data = super().to_representation(instance) + if data.get("token") == "**********": + del data["token"] + return data + + def create(self, validated_data): + user = User.objects.create(**validated_data) + user.set_password(validated_data["password"]) + user.save() + return user diff --git a/fl_server_api/tests/__init__.py b/fl_server_api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fl_server_api/tests/test_dummy.py b/fl_server_api/tests/test_dummy.py new file mode 100644 index 0000000..b620efb --- /dev/null +++ b/fl_server_api/tests/test_dummy.py @@ -0,0 +1,28 @@ +from django.test import TestCase +import json + +from ..views.dummy import DummyView + + +class DummyTests(TestCase): + + def test_create_dummy_metrics_and_models(self): + response = DummyView().create_dummy_metrics_and_models(_request=None) + self.assertEqual(200, response.status_code) + response_json = json.loads(response.content) + self.assertEqual("Created Dummy Data in Metrics Database!", response_json["message"]) + + def test_create_dummy_metrics_and_models_twice(self): + response = DummyView().create_dummy_metrics_and_models(_request=None) + self.assertEqual(200, response.status_code) + response_json = json.loads(response.content) + self.assertEqual("Created Dummy Data in Metrics Database!", response_json["message"]) + # second time + with self.assertLogs("fl.server", level="WARNING") as cm: + response = DummyView().create_dummy_metrics_and_models(_request=None) + self.assertEqual(cm.output, [ + "WARNING:fl.server:Dummy User already exists", + ]) + self.assertEqual(200, response.status_code) + response_json = json.loads(response.content) + self.assertEqual("Created Dummy Data in Metrics Database!", response_json["message"]) diff --git a/fl_server_api/tests/test_group.py b/fl_server_api/tests/test_group.py new file mode 100644 index 0000000..22625dd --- /dev/null +++ b/fl_server_api/tests/test_group.py @@ -0,0 +1,105 @@ +from django.test import TestCase + +from fl_server_core.tests import BASE_URL, Dummy + + +class GroupTests(TestCase): + + def setUp(self): + self.user = Dummy.create_user_and_authenticate(self.client) + + def test_get_all_groups(self): + [Dummy.create_group() for _ in range(10)] + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.get(f"{BASE_URL}/groups/") + self.assertEqual(cm.output, [ + "WARNING:django.request:Forbidden: /api/groups/", + ]) + self.assertEqual(403, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual("You are not allowed to access all groups.", response_json["detail"]) + + def test_get_all_groups_as_superuser(self): + self.user.is_superuser = True + self.user.save() + [Dummy.create_group() for _ in range(10)] + response = self.client.get(f"{BASE_URL}/groups/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(10, len(response_json)) + + def test_get_own_group(self): + Dummy.create_group() + group = Dummy.create_group() + Dummy.create_group() + self.user.groups.add(group) + self.user.save() + response = self.client.get(f"{BASE_URL}/groups/{group.id}/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(group.id, response_json["id"]) + + def test_get_other_group(self): + group = Dummy.create_group() + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.get(f"{BASE_URL}/groups/{group.id}/") + self.assertEqual(cm.output, [ + f"WARNING:django.request:Forbidden: /api/groups/{group.id}/", + ]) + self.assertEqual(403, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual("You are not allowed to access this group.", response_json["detail"]) + + def test_create_group(self): + group = dict(name="test-group") + response = self.client.post(f"{BASE_URL}/groups/", group) + self.assertEqual(201, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual("test-group", response_json["name"]) + + def test_create_invalid_group(self): + group = dict(hello="test-group") + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.post(f"{BASE_URL}/groups/", group) + self.assertEqual(cm.output, [ + "WARNING:django.request:Bad Request: /api/groups/", + ]) + self.assertEqual(400, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual("This field is required.", response_json["name"][0]) + + def test_update_group(self): + group = Dummy.create_group() + self.user.groups.add(group) + self.user.save() + group_update = dict(name="group-name") + response = self.client.put(f"{BASE_URL}/groups/{group.id}/", group_update, content_type="application/json") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(group_update["name"], response_json["name"]) + + def test_update_group_partial(self): + group = Dummy.create_group() + self.user.groups.add(group) + self.user.save() + group_update = dict(name="group-name") + response = self.client.patch(f"{BASE_URL}/groups/{group.id}/", group_update, content_type="application/json") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(group_update["name"], response_json["name"]) + + def test_delete_group(self): + group = Dummy.create_group() + self.user.groups.add(group) + self.user.save() + response = self.client.delete(f"{BASE_URL}/groups/{group.id}/") + self.assertEqual(204, response.status_code) + self.assertEqual(0, len(response.content)) diff --git a/fl_server_api/tests/test_inference.py b/fl_server_api/tests/test_inference.py new file mode 100644 index 0000000..add5be7 --- /dev/null +++ b/fl_server_api/tests/test_inference.py @@ -0,0 +1,140 @@ +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase +import json +import pickle +import torch +from uuid import uuid4 + +from fl_server_core.tests import BASE_URL, Dummy + + +class mxb(torch.nn.Module): + def forward(self, x: torch.Tensor) -> torch.Tensor: + return 2*x + 5 + + +class InferenceTests(TestCase): + + def setUp(self): + self.user = Dummy.create_user_and_authenticate(self.client) + + def test_inference_success(self): + inp = pickle.dumps(torch.zeros(3, 3)) + training = Dummy.create_training(actor=self.user) + input_file = SimpleUploadedFile( + "input.pkl", + inp, + content_type="application/octet-stream" + ) + response = self.client.post( + f"{BASE_URL}/inference/", + {"model_id": str(training.model.id), "model_input": input_file} + ) + self.assertEqual(response.status_code, 200) + + results = pickle.loads(response.content) + self.assertEqual({}, results["uncertainty"]) + inference = results["inference"] + self.assertIsNotNone(inference) + results = torch.as_tensor(inference) + self.assertTrue(torch.all(results <= 1)) + self.assertTrue(torch.all(results >= 0)) + + def test_inference_json(self): + inp = torch.zeros(3, 3).tolist() + training = Dummy.create_training(actor=self.user) + response = self.client.post( + f"{BASE_URL}/inference/", + json.dumps({"model_id": str(training.model.id), "model_input": inp}), + content_type="application/json" + ) + self.assertEqual(response.status_code, 200) + response_json = response.json() + self.assertEqual({}, response_json["uncertainty"]) + inference = response_json["inference"] + self.assertIsNotNone(inference) + results = torch.as_tensor(inference) + self.assertTrue(torch.all(results <= 1)) + self.assertTrue(torch.all(results >= 0)) + + def test_inference_with_unknown_content_type(self): + with self.assertLogs("root", level="INFO") as cm: + response = self.client.post( + f"{BASE_URL}/inference/", + {"model_id": "not important", "model_input": "not important"}, + "application/octet-stream" + ) + self.assertEqual(cm.output, [ + "ERROR:fl.server:Unknown Content-Type 'application/octet-stream'", + "WARNING:django.request:Unsupported Media Type: /api/inference/", + ]) + self.assertEqual(response.status_code, 415) + + def test_model_not_exist(self): + inp = pickle.dumps(torch.zeros(3, 3)) + Dummy.create_model() + unused_id = uuid4() + input_file = SimpleUploadedFile( + "input.pkl", + inp, + content_type="application/octet-stream" + ) + with self.assertLogs("root", level="WARNING") as cm: + response = self.client.post( + f"{BASE_URL}/inference/", + {"model_id": unused_id, "model_input": input_file}, + # 'multipart/form-data; boundary=...' is set automatically (default) + ) + self.assertEqual(cm.output, [ + "WARNING:django.request:Bad Request: /api/inference/", + ]) + self.assertEqual(response.status_code, 400) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual(f"Model {unused_id} not found.", response_json["detail"]) + + def test_model_weights_corrupted(self): + inp = pickle.dumps(torch.zeros(3, 3)) + model = Dummy.create_broken_model() + Dummy.create_training(model=model, actor=self.user) + input_file = SimpleUploadedFile( + "input.pkl", + inp, + content_type="application/octet-stream" + ) + with self.assertLogs("root", level="ERROR"): + response = self.client.post( + f"{BASE_URL}/inference/", + {"model_id": model.id, "model_input": input_file}, + ) + self.assertEqual(response.status_code, 500) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Unpickled object is not a torch object.", response_json["detail"]) + + def test_inference_result(self): + torch_model = mxb() + model = Dummy.create_model(owner=self.user, weights=pickle.dumps(torch_model)) + training = Dummy.create_training(actor=self.user, model=model) + inputs = torch.as_tensor([ + [0.9102, 1.0899, 2.0304, -0.8448], + [2.2616, -0.2974, 0.3805, -0.9301], + [0.4804, 0.2510, 0.2702, -0.1529], + ]) + input_file = SimpleUploadedFile( + "input.pkl", + pickle.dumps(inputs), + content_type="application/octet-stream" + ) + response = self.client.post( + f"{BASE_URL}/inference/", + {"model_id": str(training.model.id), "model_input": input_file} + ) + self.assertEqual(response.status_code, 200) + + results = pickle.loads(response.content) + self.assertEqual({}, results["uncertainty"]) + inference = results["inference"] + self.assertIsNotNone(inference) + inference_tensor = torch.as_tensor(inference) + self.assertTrue(torch.all(torch.tensor([2, 0, 0]) == inference_tensor)) diff --git a/fl_server_api/tests/test_model.py b/fl_server_api/tests/test_model.py new file mode 100644 index 0000000..70b322d --- /dev/null +++ b/fl_server_api/tests/test_model.py @@ -0,0 +1,463 @@ +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TransactionTestCase +import pickle +import torch +from unittest.mock import MagicMock, patch + +from fl_server_core.models import GlobalModel, MeanModel, Model, SWAGModel +from fl_server_core.models.training import TrainingState +from fl_server_core.tests import BASE_URL, Dummy +from fl_server_ai.trainer.events import SWAGRoundFinished, TrainingRoundFinished + + +class ModelTests(TransactionTestCase): + + def setUp(self): + self.user = Dummy.create_user_and_authenticate(self.client) + + def test_unauthorized(self): + del self.client.defaults["HTTP_AUTHORIZATION"] + with self.assertLogs("root", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/models/", + {"model_file": b"Hello World!"} + ) + self.assertEqual(401, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Authentication credentials were not provided.", response_json["detail"]) + + def test_get_all_models(self): + # make user actor and client + self.user.actor = True + self.user.client = True + self.user.save() + # create models and trainings - some related to user some not + [Dummy.create_model() for _ in range(2)] + models = [Dummy.create_model(owner=self.user) for _ in range(2)] + [Dummy.create_training() for _ in range(2)] + trainings = [Dummy.create_training(actor=self.user) for _ in range(2)] + trainings += [Dummy.create_training(participants=[self.user]) for _ in range(2)] + models += [t.model for t in trainings] + # get user related models + response = self.client.get(f"{BASE_URL}/models/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(len(models), len(response_json)) + self.assertEqual( + sorted([str(model.id) for model in models]), + sorted([model["id"] for model in response_json]) + ) + + def test_get_model_metadata(self): + model = Dummy.create_model(input_shape="torch.FloatTensor(None, 1, 1)") + response = self.client.get(f"{BASE_URL}/models/{model.id}/metadata/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(str(model.id), response_json["id"]) + self.assertEqual(str(model.name), response_json["name"]) + self.assertEqual(str(model.description), response_json["description"]) + self.assertEqual(str(model.input_shape), response_json["input_shape"]) + + def test_get_model(self): + model = Dummy.create_model(weights=b"Hello World!") + response = self.client.get(f"{BASE_URL}/models/{model.id}/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/octet-stream", response["content-type"]) + self.assertEqual(b"Hello World!", response.getvalue()) + + def test_get_model_and_unpickle(self): + model = Dummy.create_model() + response = self.client.get(f"{BASE_URL}/models/{model.id}/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/octet-stream", response["content-type"]) + torch_model = pickle.loads(response.content) + self.assertIsNotNone(torch_model) + self.assertTrue(isinstance(torch_model, torch.nn.Module)) + + def test_upload(self): + torch_model = torch.nn.Sequential( + torch.nn.Linear(3, 64), + torch.nn.ELU(), + torch.nn.Linear(64, 64), + torch.nn.ELU(), + torch.nn.Linear(64, 1), + ) + model_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch_model), + content_type="application/octet-stream" + ) + response = self.client.post(f"{BASE_URL}/models/", { + "model_file": model_file, + "name": "Test Model", + "description": "Test Model Description - Test Model Description Test", + "input_shape": "torch.FloatTensor(None, 3)" + }) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Upload Accepted", response_json["detail"]) + uuid = response_json["model_id"] + self.assertIsNotNone(uuid) + self.assertIsNot("", uuid) + self.assertEqual(GlobalModel, type(Model.objects.get(id=uuid))) + self.assertEqual("torch.FloatTensor(None, 3)", Model.objects.get(id=uuid).input_shape) + + def test_upload_swag_model(self): + torch_model = torch.nn.Sequential( + torch.nn.Linear(3, 64), + torch.nn.ELU(), + torch.nn.Linear(64, 64), + torch.nn.ELU(), + torch.nn.Linear(64, 1), + ) + model_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch_model), + content_type="application/octet-stream" + ) + response = self.client.post(f"{BASE_URL}/models/", { + "type": "SWAG", + "model_file": model_file, + "name": "Test SWAG Model", + "description": "Test SWAG Model Description - Test SWAG Model Description Test", + }) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Upload Accepted", response_json["detail"]) + uuid = response_json["model_id"] + self.assertIsNotNone(uuid) + self.assertIsNot("", uuid) + self.assertEqual(SWAGModel, type(Model.objects.get(id=uuid))) + + def test_upload_mean_model(self): + models = [Dummy.create_model(owner=self.user) for _ in range(10)] + model_uuids = [str(m.id) for m in models] + response = self.client.post(f"{BASE_URL}/models/", { + "type": "MEAN", + "name": "Test MEAN Model", + "description": "Test MEAN Model Description - Test MEAN Model Description Test", + "models": model_uuids, + }, "application/json") + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Upload Accepted", response_json["detail"]) + uuid = response_json["model_id"] + self.assertIsNotNone(uuid) + self.assertIsNot("", uuid) + self.assertEqual(MeanModel, type(Model.objects.get(id=uuid))) + + @patch("fl_server_ai.trainer.tasks.process_trainer_task.apply_async") + def test_upload_update(self, apply_async: MagicMock): + model = Dummy.create_model(owner=self.user, round=0) + Dummy.create_training(model=model, actor=self.user, state=TrainingState.ONGOING, + participants=[self.user, Dummy.create_user()]) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"model_file": model_update_file, "round": 0, + "sample_size": 100} + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Update Accepted", response_json["detail"]) + self.assertFalse(apply_async.called) + + def test_upload_update_bad_keys(self): + model = Dummy.create_model(owner=self.user, round=0) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + with self.assertLogs("django.request", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"xXx_model_file_xXx": model_update_file, "round": 0, "sample_size": 100} + ) + self.assertEqual(400, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("No uploaded file 'model_file' found.", response_json["detail"]) + + def test_upload_update_no_training(self): + model = Dummy.create_model(owner=self.user, round=0) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + with self.assertLogs("django.request", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"model_file": model_update_file, "round": 0, "sample_size": 100} + ) + self.assertEqual(404, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual(f"Model with ID {model.id} does not have a training process running", response_json["detail"]) + + def test_upload_update_no_participant(self): + self.client.defaults["HTTP_ACCEPT"] = "application/json" + actor = Dummy.create_actor() + model = Dummy.create_model(owner=actor, round=0) + training = Dummy.create_training( + model=model, actor=actor, state=TrainingState.ONGOING, + participants=[actor, Dummy.create_client()] + ) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + with self.assertLogs("root", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"model_file": model_update_file, "round": 0, + "sample_size": 500} + ) + self.assertEqual(403, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual(f"You are not a participant of training {training.id}!", response_json["detail"]) + + @patch("fl_server_ai.trainer.tasks.process_trainer_task.apply_async") + def test_upload_update_and_aggregate(self, apply_async: MagicMock): + model = Dummy.create_model(owner=self.user, round=0) + train = Dummy.create_training(model=model, actor=self.user, state=TrainingState.ONGOING, + participants=[self.user]) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"model_file": model_update_file, "round": 0, + "sample_size": 100} + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Update Accepted", response_json["detail"]) + self.assertTrue(apply_async.called) + apply_async.assert_called_once_with( + (), + {"training_uuid": train.id, "event_cls": TrainingRoundFinished}, + retry=False + ) + + @patch("fl_server_ai.trainer.tasks.process_trainer_task.apply_async") + def test_upload_update_and_not_aggregate_since_training_is_locked(self, apply_async: MagicMock): + model = Dummy.create_model(owner=self.user, round=0) + training = Dummy.create_training( + model=model, actor=self.user, state=TrainingState.ONGOING, participants=[self.user] + ) + training.locked = True + training.save() + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"model_file": model_update_file, "round": 0, + "sample_size": 100} + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Update Accepted", response_json["detail"]) + self.assertFalse(apply_async.called) + + def test_upload_update_with_metrics(self): + model = Dummy.create_model(owner=self.user, round=0) + Dummy.create_training(model=model, actor=self.user, state=TrainingState.ONGOING, + participants=[self.user, Dummy.create_user()]) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + { + "model_file": model_update_file, + "round": 0, + "metric_names": ["loss", "accuracy", "dummy_binary"], + "metric_values": [1999.0, 0.12, b"Hello World!"], + "sample_size": 50 + }, + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Update Accepted", response_json["detail"]) + + def test_upload_update_with_metrics_bad(self): + model = Dummy.create_model(owner=self.user) + Dummy.create_training(model=model, actor=self.user, state=TrainingState.ONGOING, + participants=[self.user, Dummy.create_user()]) + model_update_file = SimpleUploadedFile( + "model.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + content_type="application/octet-stream" + ) + with self.assertLogs("root", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/models/{model.id}/", + {"model_file": model_update_file, "round": 0, "metric_names": 5, + "sample_size": 500} + ) + self.assertEqual(400, response.status_code) + + def test_upload_global_model_metrics(self): + model = Dummy.create_model(owner=self.user, round=0) + metrics = dict( + metric_names=["loss", "accuracy", "dummy_binary"], + metric_values=[1999.0, 0.12, b"Hello World!"], + ) + with self.assertLogs("fl.server", level="WARNING") as cm: + response = self.client.post( + f"{BASE_URL}/models/{model.id}/metrics/", + metrics, + ) + self.assertEqual(cm.output, [ + f"WARNING:fl.server:Global model {model.id} is not connected to any training.", + ]) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Metrics Upload Accepted", response_json["detail"]) + self.assertEqual(str(model.id), response_json["model_id"]) + + def test_upload_local_model_metrics(self): + model = Dummy.create_model_update(owner=self.user) + metrics = dict( + metric_names=["loss", "accuracy", "dummy_binary"], + metric_values=[1999.0, 0.12, b"Hello World!"], + ) + response = self.client.post( + f"{BASE_URL}/models/{model.id}/metrics/", + metrics, + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Model Metrics Upload Accepted", response_json["detail"]) + self.assertEqual(str(model.id), response_json["model_id"]) + + def test_upload_bad_metrics(self): + model = Dummy.create_model(owner=self.user, round=0) + metrics = dict( + metric_names=["loss", "accuracy", "dummy_binary"], + metric_values=[1999.0, b"Hello World!"], + ) + with self.assertLogs("django.request", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/models/{model.id}/metrics/", + metrics, + ) + self.assertEqual(400, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Metric names and values must have the same length", response_json["detail"]) + + @patch("fl_server_ai.trainer.tasks.process_trainer_task.apply_async") + def test_upload_swag_stats(self, apply_async: MagicMock): + model = Dummy.create_model(owner=self.user, round=0) + train = Dummy.create_training( + model=model, + actor=self.user, + state=TrainingState.SWAG_ROUND, + participants=[self.user] + ) + + first_moment_file = SimpleUploadedFile( + "first_moment.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + ).state_dict()), + content_type="application/octet-stream" + ) + second_moment_file = SimpleUploadedFile( + "second_moment.pkl", + pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + ).state_dict()), + content_type="application/octet-stream" + ) + response = self.client.post(f"{BASE_URL}/models/{model.id}/swag/", { + "first_moment_file": first_moment_file, + "second_moment_file": second_moment_file, + "sample_size": 100, + "round": 0 + }) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("SWAG Statistic Accepted", response_json["detail"]) + self.assertTrue(apply_async.called) + apply_async.assert_called_once_with( + (), + {"training_uuid": train.id, "event_cls": SWAGRoundFinished}, + retry=False + ) + + def test_get_global_model_metrics(self): + model = Dummy.create_model(owner=self.user) + metric = Dummy.create_metric(model=model) + response = self.client.get(f"{BASE_URL}/models/{model.id}/metrics/") + self.assertEqual(200, response.status_code) + body = response.json() + self.assertEqual(1, len(body)) + self.assertEqual(metric.value_float, body[0]["value_float"]) + self.assertEqual(metric.key, body[0]["key"]) + + def test_get_local_model_metrics(self): + model = Dummy.create_model_update(owner=self.user) + metric = Dummy.create_metric(model=model) + response = self.client.get(f"{BASE_URL}/models/{model.id}/metrics/") + self.assertEqual(200, response.status_code) + body = response.json() + self.assertEqual(1, len(body)) + self.assertEqual(metric.value_float, body[0]["value_float"]) + self.assertEqual(metric.key, body[0]["key"]) diff --git a/fl_server_api/tests/test_openapi.py b/fl_server_api/tests/test_openapi.py new file mode 100644 index 0000000..ae68861 --- /dev/null +++ b/fl_server_api/tests/test_openapi.py @@ -0,0 +1,44 @@ +from django.test import TestCase +import json +import yaml + +from fl_server_core.tests import BASE_URL + + +class OpenApiTests(TestCase): + + def test_get_openapi(self): + response = self.client.get(f"{BASE_URL}/schema/swagger-ui/") + self.assertEqual(200, response.status_code) + + def test_get_redoc(self): + response = self.client.get(f"{BASE_URL}/schema/redoc/") + self.assertEqual(200, response.status_code) + + def test_get_yaml(self): + response = self.client.get(f"{BASE_URL}/schema/", HTTP_ACCEPT="application/yaml") + self.assertEqual(200, response.status_code) + self.assertEqual("application/yaml", response["content-type"].split(";")[0].strip()) + response_yaml = yaml.load(response.content, Loader=yaml.FullLoader) + self.assertIsNotNone(response_yaml) + + def test_get_openapi_yaml(self): + response = self.client.get(f"{BASE_URL}/schema/", HTTP_ACCEPT="application/vnd.oai.openapi") + self.assertEqual(200, response.status_code) + self.assertEqual("application/vnd.oai.openapi", response["content-type"].split(";")[0].strip()) + response_yaml = yaml.load(response.content, Loader=yaml.FullLoader) + self.assertIsNotNone(response_yaml) + + def test_get_json(self): + response = self.client.get(f"{BASE_URL}/schema/", HTTP_ACCEPT="application/json") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"].split(";")[0].strip()) + response_json = response.json() + self.assertIsNotNone(response_json) + + def test_get_openapi_json(self): + response = self.client.get(f"{BASE_URL}/schema/", HTTP_ACCEPT="application/vnd.oai.openapi+json") + self.assertEqual(200, response.status_code) + self.assertEqual("application/vnd.oai.openapi+json", response["content-type"].split(";")[0].strip()) + response_json = json.loads(response.content) # Django `response.json()` requires Content-Type json + self.assertIsNotNone(response_json) diff --git a/fl_server_api/tests/test_training.py b/fl_server_api/tests/test_training.py new file mode 100644 index 0000000..08570a1 --- /dev/null +++ b/fl_server_api/tests/test_training.py @@ -0,0 +1,231 @@ +import json +from unittest.mock import MagicMock, patch +from uuid import uuid4 +from django.test import TestCase + +from fl_server_core.tests import BASE_URL, Dummy +from fl_server_core.models.training import TrainingState + + +class TrainingTests(TestCase): + + def setUp(self): + self.user = Dummy.create_user_and_authenticate(self.client) + + def test_create_training(self): + model = Dummy.create_model(owner=self.user) + request_body = dict( + model_id=str(model.id), + target_num_updates=100, + metric_names=["accuracy", "f1_score"], + uncertainty_method="NONE", + aggregation_method="FedAvg" + ) + response = self.client.post( + f"{BASE_URL}/trainings/", + data=json.dumps(request_body), + content_type="application/json" + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Training created successfully!", response_json["detail"]) + + def test_create_training_with_clients(self): + model = Dummy.create_model(owner=self.user) + clients = [Dummy.create_client(username=f"client-{n}") for n in range(3)] + request_body = dict( + model_id=str(model.id), + target_num_updates=100, + metric_names=["accuracy", "f1_score"], + uncertainty_method="NONE", + aggregation_method="FedAvg", + clients=list(map(lambda c: str(c.id), clients)) + ) + response = self.client.post( + f"{BASE_URL}/trainings/", + data=json.dumps(request_body), + content_type="application/json" + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Training created successfully!", response_json["detail"]) + + def test_create_training_invalid_aggregation_method(self): + model = Dummy.create_model(owner=self.user) + request_body = dict( + model_id=str(model.id), + target_num_updates=100, + metric_names=["accuracy", "f1_score"], + uncertainty_method="NONE", + aggregation_method="INVALID" + ) + with self.assertLogs("root", level="WARNING"): + response = self.client.post( + f"{BASE_URL}/trainings/", + data=json.dumps(request_body), + content_type="application/json" + ) + self.assertEqual(400, response.status_code) + + def test_create_training_not_model_owner(self): + model = Dummy.create_model() + request_body = dict( + model_id=str(model.id), + target_num_updates=100, + metric_names=["accuracy", "f1_score"], + uncertainty_method="NONE", + aggregation_method="FedAvg" + ) + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.post( + f"{BASE_URL}/trainings/", + data=json.dumps(request_body), + content_type="application/json" + ) + self.assertEqual(cm.output, [ + "WARNING:django.request:Forbidden: /api/trainings/", + ]) + self.assertEqual(403, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("You do not have permission to perform this action.", response_json["detail"]) + + def test_get_trainings(self): + # make user actor + self.user.actor = True + self.user.save() + # create trainings - some related to user some not + [Dummy.create_training() for _ in range(3)] + trainings = [Dummy.create_training(actor=self.user) for _ in range(3)] + # get user related trainings + response = self.client.get(f"{BASE_URL}/trainings/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(len(trainings), len(response_json)) + self.assertEqual( + sorted([str(training.id) for training in trainings]), + sorted([training["id"] for training in response_json]) + ) + + def test_get_training_good(self): + training = Dummy.create_training(actor=self.user) + response = self.client.get(f"{BASE_URL}/trainings/{training.id}/") + self.assertEqual(response.status_code, 200) + body = response.json() + self.assertEqual(str(training.actor.id), body["actor"]) + self.assertEqual(TrainingState.INITIAL, body["state"]) + self.assertEqual(0, body["target_num_updates"]) + + def test_get_training_bad(self): + training = Dummy.create_training() + with self.assertLogs("root", level="WARNING"): + response = self.client.get(f"{BASE_URL}/trainings/{training.id}/") + self.assertEqual(response.status_code, 403) + + def test_register_clients_good(self): + training = Dummy.create_training(actor=self.user) + users = [str(Dummy.create_user(username=f"client{i}").id) for i in range(1, 5)] + request_body = dict(clients=users) + + response = self.client.put( + f"{BASE_URL}/trainings/{training.id}/clients/", + json.dumps(request_body) + ) + self.assertEqual(response.status_code, 202) + body = response.json() + self.assertEqual("Users registered as participants!", body["detail"]) + + def test_register_clients_bad(self): + training = Dummy.create_training(actor=self.user) + users = [str(Dummy.create_user(username=f"client{i}").id) for i in range(1, 5)] + [str(uuid4())] + request_body = dict(clients=users) + with self.assertLogs("root", level="WARNING"): + response = self.client.put( + f"{BASE_URL}/trainings/{training.id}/clients/", + json.dumps(request_body) + ) + self.assertEqual(response.status_code, 400) + self.assertIsNotNone(response.content) + response_body = response.json() + self.assertEqual("Not all provided users were found!", response_body["detail"]) + + def test_remove_clients_good(self): + training = Dummy.create_training(actor=self.user) + users = [str(t.id) for t in training.participants.all()] + assert users + request_body = dict(clients=users) + + response = self.client.delete( + f"{BASE_URL}/trainings/{training.id}/clients/", + json.dumps(request_body) + ) + self.assertEqual(response.status_code, 200) + response = self.client.get(f"{BASE_URL}/trainings/{training.id}/") + self.assertEqual(response.status_code, 200) + body = response.json() + self.assertEqual(0, len(body["participants"])) + + @patch("fl_server_ai.notification.notification.send_notifications.apply_async") + def test_start_training(self, apply_async: MagicMock): + user = Dummy.create_user(message_endpoint="http://example.com") + training = Dummy.create_training(actor=self.user) + training.participants.set([user]) + training.save() + response = self.client.post(f"{BASE_URL}/trainings/{training.id}/start/") + self.assertEqual(response.status_code, 202) + self.assertEqual(2, apply_async.call_count) # TrainingStartNotification, TrainingRoundStartNotification + + def test_start_training_no_participants(self): + training = Dummy.create_training(actor=self.user) + training.participants.set([]) + training.save() + with self.assertLogs("root", level="WARNING"): + response = self.client.post(f"{BASE_URL}/trainings/{training.id}/start/") + self.assertEqual(response.status_code, 400) + self.assertIsNotNone(response.content) + response_body = response.json() + self.assertEqual("At least one participant must be registered!", response_body["detail"]) + + def test_start_training_not_initial_state(self): + user = Dummy.create_user(message_endpoint="http://example.com") + training = Dummy.create_training(actor=self.user, state=TrainingState.ONGOING) + training.participants.set([user]) + training.save() + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.post(f"{BASE_URL}/trainings/{training.id}/start/") + self.assertEqual(cm.output, [ + f"WARNING:django.request:Bad Request: /api/trainings/{training.id}/start/", + ]) + self.assertEqual(response.status_code, 400) + self.assertIsNotNone(response.content) + response_body = response.json() + self.assertEqual(f"Training {training.id} is not in state INITIAL!", response_body["detail"]) + + def test_create_training_with_trained_model(self): + training = Dummy.create_training(actor=self.user) + model = training.model + request_body = dict( + model_id=str(model.id), + target_num_updates=100, + metric_names=["accuracy", "f1_score"], + uncertainty_method="NONE", + aggregation_method="FedAvg" + ) + response = self.client.post( + f"{BASE_URL}/trainings/", + data=json.dumps(request_body), + content_type="application/json" + ) + self.assertEqual(201, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertEqual("Training created successfully!", response_json["detail"]) + + response = self.client.get(f"{BASE_URL}/trainings/{response_json['training_id']}/") + self.assertEqual(200, response.status_code) + response_json = response.json() + self.assertIsNotNone(response_json) + self.assertNotEqual(model.id, response_json['id']) diff --git a/fl_server_api/tests/test_user.py b/fl_server_api/tests/test_user.py new file mode 100644 index 0000000..039928b --- /dev/null +++ b/fl_server_api/tests/test_user.py @@ -0,0 +1,146 @@ +import base64 +from django.test import TestCase +from rest_framework.authtoken.models import Token +from typing import Any, Dict, Optional, Union +from uuid import uuid4 + +from fl_server_core.tests import BASE_URL, Dummy +from fl_server_core.models import User + +from .utils import parse + + +class UserTests(TestCase): + + def assertUserEqual( + self, + expected: Union[User, Dict], + actual: Union[User, Dict], + *, + include_id_check: Optional[bool] = True + ) -> None: + expected_obj: Any = parse(expected) if isinstance(expected, Dict) else expected + actual_obj: Any = parse(actual) if isinstance(actual, Dict) else actual + # user model + if include_id_check: + self.assertEqual(str(expected_obj.id), str(actual_obj.id)) + self.assertEqual(bool(expected_obj.actor), bool(actual_obj.actor)) + self.assertEqual(bool(expected_obj.client), bool(actual_obj.client)) + self.assertEqual(str(expected_obj.message_endpoint), str(actual_obj.message_endpoint)) + # auth user model + self.assertEqual(str(expected_obj.username), str(actual_obj.username)) + self.assertEqual(str(expected_obj.first_name), str(actual_obj.first_name)) + self.assertEqual(str(expected_obj.last_name), str(actual_obj.last_name)) + self.assertEqual(str(expected_obj.email), str(actual_obj.email)) + + def test_get_user_via_auth(self): + user = Dummy.create_user_and_authenticate(self.client) + response = self.client.get(f"{BASE_URL}/users/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(1, len(response_json)) + self.assertUserEqual(user, response_json[0]) + + def test_get_user_via_url_token(self): + user = Dummy.create_user() + token = Token.objects.get(user=user).key + credentials = base64.b64encode(token.encode("utf-8")).decode("utf-8") + self.client.defaults["HTTP_AUTHORIZATION"] = "Basic " + credentials + # Note: Django self.client does not support: http://username:password@localhost:8000/api/... + response = self.client.get(f"{BASE_URL}/users/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(1, len(response_json)) + self.assertUserEqual(user, response_json[0]) + + def test_get_user_via_auth_token(self): + user = Dummy.create_user() + token = Token.objects.get(user=user).key + self.client.defaults["HTTP_AUTHORIZATION"] = "Token " + token + response = self.client.get(f"{BASE_URL}/users/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(1, len(response_json)) + self.assertUserEqual(user, response_json[0]) + + def test_get_user_via_auth_unauthenticate(self): + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.get(f"{BASE_URL}/users/") + self.assertEqual(cm.output, [ + "WARNING:django.request:Unauthorized: /api/users/", + ]) + self.assertEqual(401, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual("Authentication credentials were not provided.", response_json["detail"]) + + def test_get_user_via_uuid(self): + user = Dummy.create_user_and_authenticate(self.client) + response = self.client.get(f"{BASE_URL}/users/{user.id}/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertUserEqual(user, response_json) + + def test_get_user_via_uuid_unauthenticate(self): + user = Dummy.create_user() + with self.assertLogs("django.request", level="WARNING") as cm: + response = self.client.get(f"{BASE_URL}/users/{user.id}/") + self.assertEqual(cm.output, [ + f"WARNING:django.request:Unauthorized: /api/users/{user.id}/", + ]) + self.assertEqual(401, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual("Authentication credentials were not provided.", response_json["detail"]) + + def test_create_user(self): + user = dict( + # user model + actor=False, + client=True, + message_endpoint="https://example.com", + # auth user model + username=str(uuid4()).split("-")[0], + first_name="Jane", + last_name="Doe", + email="jane.doe@example.com", + password="secret", + ) + response = self.client.post(f"{BASE_URL}/users/", user, content_type="application/json") + self.assertEqual(201, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertUserEqual(user, response_json, include_id_check=False) + + def test_get_user_groups(self): + user = Dummy.create_user_and_authenticate(self.client) + [Dummy.create_group() for _ in range(3)] + groups = [Dummy.create_group() for _ in range(3)] + [user.groups.add(group) for group in groups] + response = self.client.get(f"{BASE_URL}/users/groups/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(3, len(response_json)) + self.assertEqual( + sorted([group.id for group in groups]), + sorted([group["id"] for group in response_json]) + ) + + def test_get_user_trainings(self): + user = Dummy.create_user_and_authenticate(self.client) + [Dummy.create_training() for _ in range(3)] + trainings = [Dummy.create_training(actor=user) for _ in range(3)] + response = self.client.get(f"{BASE_URL}/users/trainings/") + self.assertEqual(200, response.status_code) + self.assertEqual("application/json", response["content-type"]) + response_json = response.json() + self.assertEqual(3, len(response_json)) + self.assertEqual( + sorted([str(training.id) for training in trainings]), + sorted([training["id"] for training in response_json]) + ) diff --git a/fl_server_api/tests/test_utils.py b/fl_server_api/tests/test_utils.py new file mode 100644 index 0000000..ebec8d8 --- /dev/null +++ b/fl_server_api/tests/test_utils.py @@ -0,0 +1,119 @@ +from django.core.files.uploadedfile import SimpleUploadedFile +from django.http import HttpRequest +from django.test import TestCase +from rest_framework.exceptions import ParseError +from uuid import uuid4 +import pickle +import torch +from typing import Optional + +from fl_server_core.tests import Dummy +from fl_server_core.models import User +from fl_server_core.utils.strings import str2bool + +from ..utils import get_entity, get_file, is_json + + +class UtilsTest(TestCase): + + def test_views_get_entity(self): + user = Dummy.create_actor() + user2 = get_entity(User, pk=user.id) + self.assertIsNotNone(user2) + self.assertEqual(user.id, user2.id) + + def test_views_get_entity_not_exists(self): + uid = uuid4() + with self.assertRaises(ParseError) as context: + get_entity(User, pk=uid) + self.assertTrue(str(context.exception).__contains__(f"User {uid} not found.")) + + def test_views_get_entity_custom_error_identifier(self): + uid = uuid4() + error_identifier = "'hello world!'" + with self.assertRaises(ParseError) as context: + get_entity(User, error_identifier=error_identifier, pk=uid) + self.assertTrue(str(context.exception).__contains__(f"User {error_identifier} not found.")) + + def test_views_get_entity_custom_error_message(self): + uid = uuid4() + error_message = "hello world!" + with self.assertRaises(ParseError) as context: + get_entity(User, error_message=error_message, pk=uid) + self.assertTrue(str(context.exception).__contains__(error_message)) + + def test_views_get_file(self): + key = "model-key" + inp = pickle.dumps(torch.zeros(3, 3)) + input_file = SimpleUploadedFile("input.pkl", inp, content_type="application/octet-stream") + request = HttpRequest() + request.FILES.appendlist(key, input_file) + file_content = get_file(request, key) + self.assertIsNotNone(file_content) + self.assertEqual(len(inp), len(file_content)) + + def test_views_get_file_file_not_exist(self): + key = "model-key" + request = HttpRequest() + request.FILES.appendlist(key, None) + with self.assertRaises(ParseError) as context: + get_file(request, key) + self.assertTrue(str(context.exception).__contains__(f"No uploaded file '{key}' found.")) + + def test_views_get_file_custom_validator(self): + key = "model-key" + inp = pickle.dumps(torch.zeros(3, 3)) + input_file = SimpleUploadedFile("input.pkl", inp, content_type="application/octet-stream") + request = HttpRequest() + request.FILES.appendlist(key, input_file) + + def validator(req, name, uploaded_file, file_content, **kwargs) -> Optional[str]: + self.assertIsNotNone(req) + self.assertEqual(key, name) + self.assertIsNotNone(uploaded_file) + self.assertEqual(len(inp), len(file_content)) + self.assertEqual(5, kwargs["x"]) + return None + + file_content = get_file(request, key, validator=validator, x=5) + self.assertIsNotNone(file_content) + self.assertEqual(len(inp), len(file_content)) + + def test_views_get_file_custom_validator_error(self): + key = "model-key" + inp = pickle.dumps(torch.zeros(3, 3)) + input_file = SimpleUploadedFile("input.pkl", inp, content_type="application/octet-stream") + request = HttpRequest() + request.FILES.appendlist(key, input_file) + error_message = "ERROR: Hello World!" + with self.assertRaises(ParseError) as context: + get_file(request, key, validator=lambda req, name, uploaded_file, file_content, **kwargs: error_message) + self.assertTrue(str(context.exception).__contains__(error_message)) + + def test_str2bool_true(self): + TRUES = [True, "yes", "true", "t", "y", "1"] + for true in TRUES: + self.assertTrue(str2bool(true)) + + def test_str2bool_false(self): + FALSES = [False, "no", "false", "f", "n", "0"] # cspell:ignore FALSES + for false in FALSES: + self.assertFalse(str2bool(false)) + + def test_str2bool_error(self): + value = "hello world!" + with self.assertRaises(ValueError) as context: + str2bool(value) + self.assertTrue(str(context.exception).__contains__(f"Can not convert '{value}' to boolean.")) + + def test_str2bool_fallback(self): + self.assertTrue(str2bool("hello world!", fallback=True)) + self.assertFalse(str2bool("hello world!", fallback=False)) + self.assertTrue(str2bool(None, fallback=True)) + self.assertFalse(str2bool(None, fallback=False)) + + def test_is_json_true(self): + self.assertEqual({"hello": "world"}, is_json('{"hello": "world"}')) + + def test_is_json_false(self): + self.assertFalse(is_json('hello world')) diff --git a/fl_server_api/tests/utils.py b/fl_server_api/tests/utils.py new file mode 100644 index 0000000..0cdd940 --- /dev/null +++ b/fl_server_api/tests/utils.py @@ -0,0 +1,20 @@ +from types import SimpleNamespace +from typing import Dict + + +def parse(d: Dict) -> SimpleNamespace: + """ + Parse nested dict to namespace to support dot notation/access. + + Args: + d (Dict): dictionary to parse + + Returns: + SimpleNamespace: dict as namespace + """ + x = SimpleNamespace() + [setattr( # type:ignore[func-returns-value] + x, k, + parse(v) if isinstance(v, dict) else [parse(e) for e in v] if isinstance(v, list) else v + ) for k, v in d.items()] + return x diff --git a/fl_server_api/urls.py b/fl_server_api/urls.py new file mode 100644 index 0000000..a2ddf63 --- /dev/null +++ b/fl_server_api/urls.py @@ -0,0 +1,48 @@ +from django.conf import settings +from django.urls import path +from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView + +from .views import Group, Inference, Model, Training, User +from .views.dummy import DummyView + + +urlpatterns = [ + # OpenAPI Specification and UIs + path("schema/", SpectacularAPIView.as_view(), name="openapi"), + path("schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="openapi"), name="swagger-ui"), + path("schema/redoc/", SpectacularRedocView.as_view(url_name="openapi"), name="redoc"), + + # groups + path("groups/", view=Group.as_view({"get": "list", "post": "create"}), name="groups"), + path("groups//", view=Group.as_view({ + "get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy" + }), name="group"), + # users + path("users/", view=User.as_view({"get": "get_users", "post": "create_user"}), name="users"), + path("users/groups/", view=User.as_view({"get": "get_user_groups"}), name="user-groups"), + path("users/trainings/", view=User.as_view({"get": "get_user_trainings"}), name="user-trainings"), + path("users//", view=User.as_view({"get": "get_user"}), name="user"), + # models + path("models/", view=Model.as_view({"get": "get_models", "post": "create_model"}), name="models"), + path("models//", view=Model.as_view({"get": "get_model", "post": "create_local_model"}), name="model"), + path("models//metadata/", view=Model.as_view({"get": "get_metadata"}), name="model-metadata"), + path("models//metrics/", view=Model.as_view( + {"get": "get_model_metrics", "post": "create_model_metrics"} + ), name="model-metrics"), + path("models//swag/", view=Model.as_view({"post": "create_swag_stats"}), name="model-swag"), + # trainings + path("trainings/", view=Training.as_view({"get": "get_trainings", "post": "create_training"}), name="trainings"), + path("trainings//", view=Training.as_view({"get": "get_training"}), name="training"), + path("trainings//clients/", view=Training.as_view( + {"put": "register_clients", "delete": "remove_clients"} + ), name="training-clients"), + path("trainings//start/", view=Training.as_view({"post": "start_training"}), name="training-start"), + # inference + path("inference/", view=Inference.as_view({"post": "inference"}), name="inference"), +] + +if settings.DEBUG: + urlpatterns += [ + # Dummies for Johannes + path("dummy/", view=DummyView.as_view({"get": "create_dummy_metrics_and_models"}, name="dummy")), + ] diff --git a/fl_server_api/utils.py b/fl_server_api/utils.py new file mode 100644 index 0000000..e6fb6e6 --- /dev/null +++ b/fl_server_api/utils.py @@ -0,0 +1,132 @@ +from django.db import models +from django.core.exceptions import ObjectDoesNotExist, ValidationError as ValidationException +from django.core.files.uploadedfile import UploadedFile +from django.http import HttpRequest +from collections.abc import Callable +import json +from rest_framework.exceptions import ParseError +from types import FunctionType, MethodType +from typing import Any, Optional, Type, TypeVar, Union + + +_TModel = TypeVar("_TModel", bound=models.Model) + + +def get_entity( + cls: Type[_TModel], + error_message: Optional[str] = None, + error_identifier: Optional[str] = None, + *args, + **kwargs +) -> _TModel: + """ + Get model instance. + + Try to get a model instance. Similar to `.objects.get()`. + But, catch and evaluate all kinds of exceptions which `.objects.get()` throws, + combine it and build an response including an error message inside a `HttpResponseBadRequest` + and throw `EntityNotFoundException` (one single Exception). + + Args: + cls (Type[_TModel]): model class where the entity should be found + error_message (Optional[str], optional): message added to `HttpResponseBadRequest` if an error occurs. + If `None` a " not found." message will be created. + error_identifier (Optional[str], optional): model identifier or field name for the error message creation + if an error occurs. + If `None` the arguments of `.objects.get()` are searched for "pk" or "id" as fallback. + + Raises: + ValidationError: If `.objects.get()` raise any kind of error. + + Returns: + _TModel: instance + + Examples: + ```python + def get_user(self, request: HttpRequest, id: int) -> HttpResponseBase: + user = get_entity(User, pk=id) # EntityNotFoundException will be handled by middleware + serializer = UserSerializer(user) + return Response(serializer.data) + ``` + """ + try: + return cls.objects.get(*args, **kwargs) + except (ObjectDoesNotExist, ValidationException): + if error_message: + raise ParseError(error_message) + + if error_identifier is None: + error_identifier = kwargs.get("pk", kwargs.get("id", None)) + msg = f"{cls.__name__} {error_identifier} not found." + raise ParseError(msg) + + +def get_file( + request: HttpRequest, + name: str, + validator: Optional[Callable[[HttpRequest, str, UploadedFile, bytes], Optional[str]]] = None, + **kwargs +) -> bytes: + """ + Try to get a single uploaded file and optional check it. + + Args: + request (HttpRequest): http request where the file should be found + name (str): name or key of the uploaded file + validator (Optional[Callable[[HttpRequest, str, UploadedFile, bytes], Optional[str]]]], optional): + file validation method, returns error message if file is not valid; otherwise `None` + kwargs: additional validator arguments + + Raises: + ParseError: If no file was found or validator return error messages. + + Returns: + bytes: file content + """ + uploaded_file = request.FILES.get(name) + if not uploaded_file or not uploaded_file.file: + raise ParseError(f"No uploaded file '{name}' found.") + file_content = uploaded_file.file.read() + if validator: + error_message = validator(request, name, uploaded_file, file_content, **kwargs) + if error_message: + raise ParseError(error_message) + return file_content + + +def is_json(s: Union[str, bytes, bytearray]) -> Any: + """ + Check if the given argument s is a valid json. + + Args: + s (Union[str, bytes, bytearray]): validation objective + + Returns: + Any: Deserialize object of objective `s` is a valid json; otherwise `False` + """ + try: + return json.loads(s) + except ValueError: + return False + + +def fullname(cls_or_obj_or_fn: Union[type, object, Callable]) -> str: + """ + Get full qualified name of class, object or function. + + Args: + cls_or_obj_or_fn (Union[type, object, Callable]): Inspection target. + + Returns: + str: qualified name + """ + if isinstance(cls_or_obj_or_fn, (type, FunctionType, MethodType)): + cls = cls_or_obj_or_fn # class or function + else: + cls = cls_or_obj_or_fn.__class__ # object + + module = cls.__module__ + # if module == "builtins": # or module == "__main__": # NOSONAR: S125 + # # avoid outputs like "builtins.str" + # return cls.__qualname__ + return module + "." + cls.__qualname__ diff --git a/fl_server_api/views/__init__.py b/fl_server_api/views/__init__.py new file mode 100644 index 0000000..45961e3 --- /dev/null +++ b/fl_server_api/views/__init__.py @@ -0,0 +1,8 @@ +from .group import Group +from .inference import Inference +from .model import Model +from .training import Training +from .user import User + + +__all__ = ["Group", "Inference", "Model", "Training", "User"] diff --git a/fl_server_api/views/base.py b/fl_server_api/views/base.py new file mode 100644 index 0000000..dee81ea --- /dev/null +++ b/fl_server_api/views/base.py @@ -0,0 +1,74 @@ +from logging import getLogger +from rest_framework.authentication import BasicAuthentication, SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.viewsets import ViewSet as DjangoViewSet + + +class BasicAuthAllowingTokenAuthInUrl(BasicAuthentication): + + def authenticate_credentials(self, userid_or_token, password, request=None): + """ + Authenticate credentials against username/password or token. + + Basic Authentication: + Authenticate the userid and password against username and password + with optional request for context. + + Token Authentication over URL: + Authenticate the given token against the token in the database. + """ + # check if special token authentication is used + if (len(userid_or_token) == 40 and password == ""): + # tokens are always 40 characters long + # see: rest_framework.authtoken.models.Token (class method: generate_key) + # which uses `binascii.hexlify(os.urandom(20)).decode()` + return TokenAuthentication().authenticate_credentials(userid_or_token) + + # default Basic Authentication + return super().authenticate_credentials(userid_or_token, password, request) + + +class ViewSet(DjangoViewSet): + """ + DLR Federated Learning base ViewSet including default authentication and permission classes. + + Authentication as well as permission classes can not only be overwritten by the child class, but also by the + request method. This allows to have different authentication and permission classes for each request method. + To overwrite the authentication and permission classes for a specific request method, simply add the designated + decorators: `@decorators.authentication_classes` and `@decorators.permission_classes` from + `rest_framework.decorators`. + """ + _logger = getLogger("fl.server") + + # Note: BasicAuthentication is sensles here since it will and can't never be called due to + # BasicAuthAllowingTokenAuthInUrl but is required for OpenAPI to work. + # Also note that the order of BasicAuthAllowingTokenAuthInUrl and BasicAuthentication is important + # since if BasicAuthentication is first, Django won't ever call BasicAuthAllowingTokenAuthInUrl! + authentication_classes = [ + TokenAuthentication, + BasicAuthAllowingTokenAuthInUrl, + BasicAuthentication, + SessionAuthentication, + ] + permission_classes = [IsAuthenticated] + + def get_authenticators(self): + if method := self._get_view_method(): + if hasattr(method, "authentication_classes"): + return method.authentication_classes + return super().get_authenticators() + + def get_permissions(self): + if method := self._get_view_method(): + if hasattr(method, "permission_classes"): + return method.permission_classes + return super().get_permissions() + + def _get_view_method(self): + if hasattr(self, "action") and self.action is not None: + return self.__getattribute__(self.action) + if hasattr(self.request, "method") and self.request.method is not None: + http_method = self.request.method.lower() + if hasattr(self, http_method): + return self.__getattribute__(http_method) + return None diff --git a/fl_server_api/views/dummy.py b/fl_server_api/views/dummy.py new file mode 100644 index 0000000..c6cbc05 --- /dev/null +++ b/fl_server_api/views/dummy.py @@ -0,0 +1,73 @@ +from django.db import IntegrityError, transaction +from django.http import HttpRequest, JsonResponse +from logging import getLogger +import random +from rest_framework.authtoken.models import Token +from rest_framework.viewsets import ViewSet as DjangoViewSet +from uuid import uuid4 + +from fl_server_core.models import Training, User +from fl_server_core.models.training import TrainingState +from fl_server_core.tests.dummy import Dummy + + +class DummyView(DjangoViewSet): + _logger = getLogger("fl.server") + authentication_classes: list = [] + permission_classes: list = [] + + def create_dummy_metrics_and_models(self, _request: HttpRequest): + epochs = 80 + try: + # `transaction.atomic` is important + # otherwise Django's internal atomic transaction won't be closed if an IntegrityError is raised + # and `User.objects.get` will raise an TransactionManagementError + with transaction.atomic(): + user = Dummy.create_user(username="dummy-user", password="secret") + except IntegrityError: + self._logger.warning("Dummy User already exists") + user = User.objects.get(username="dummy-user") + + model = Dummy.create_model(owner=user) + participants = [Dummy.create_client() for _ in range(5)] + training = Dummy.create_training( + state=TrainingState.COMPLETED, + target_num_updates=epochs, + actor=user, + model=model, + participants=participants, + ) + self._fill_metrics(training, epochs) + self.created = True + + return JsonResponse({ + "message": "Created Dummy Data in Metrics Database!", + "training_uuid": training.id, + "user_uuid": user.id, + "user_credentials": { + "username": "dummy-user", + "password": "secret", + "token": Token.objects.get(user=user).key, + }, + "participants": [str(p.id) for p in training.participants.all()], + }) + + def _fill_metrics(self, training: Training, epochs: int): + for epoch in range(epochs): + for client in training.participants.all(): + Dummy.create_metric( + model=training.model, + identifier=str(uuid4()).split("-")[0], + key="NLLoss", + value_float=27.354/(epoch+1) + random.randint(0, 100) / 100, + step=epoch, + reporter=client + ) + Dummy.create_metric( + model=training.model, + identifier=str(uuid4()).split("-")[0], + key="Accuracy", + value_float=epoch/epochs + random.randint(0, 100) / 1000, + step=epoch, + reporter=client + ) diff --git a/fl_server_api/views/group.py b/fl_server_api/views/group.py new file mode 100644 index 0000000..4b919e4 --- /dev/null +++ b/fl_server_api/views/group.py @@ -0,0 +1,224 @@ +from django.contrib.auth.models import Group as GroupModel +from django.http import HttpRequest, HttpResponse +from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter +from rest_framework import status +from rest_framework.exceptions import PermissionDenied +from rest_framework.response import Response + +from fl_server_core.models import User as UserModel + +from .base import ViewSet +from ..utils import get_entity +from ..serializers.generic import ErrorSerializer, GroupSerializer +from ..openapi import error_response_403 + + +_default_group_example = OpenApiExample( + name="Get group by id", + description="\n\n".join([ + "Retrieve group data by group ID.", + "_Please not that the user Jane Doe has to be created and authorized first._", + ]), + value=1, + parameter_only=("id", OpenApiParameter.PATH) +) + + +class Group(ViewSet): + + serializer_class = GroupSerializer + + def _get_group(self, user: UserModel, group_id: int) -> GroupModel: + """ + Get group by id if user is member of the group. + Otherwise raise PermissionDenied. + + Args: + user (UserModel): user who makes the request + group_id (int): group id + + Raises: + PermissionDenied: If user is not member of the group. + + Returns: + GroupModel: group instance + """ + group = get_entity(GroupModel, pk=group_id) + if not user.groups.contains(group): + raise PermissionDenied("You are not allowed to access this group.") + return group + + @extend_schema( + responses={ + status.HTTP_200_OK: GroupSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def list(self, request: HttpRequest) -> HttpResponse: + """ + Get all groups. + + Args: + request (HttpRequest): request object + + Raises: + PermissionDenied: If user is not a superuser. + + Returns: + HttpResponse: list of groups as json response + """ + if not request.user.is_superuser: + raise PermissionDenied("You are not allowed to access all groups.") + groups = GroupModel.objects.all() + serializer = GroupSerializer(groups, many=True) + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_200_OK: GroupSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[_default_group_example] + ) + def retrieve(self, request: HttpRequest, id: int) -> HttpResponse: + """ + Get group information by id. + + Args: + request (HttpRequest): request object + id (int): group id + + Returns: + HttpResponse: group as json response + """ + group = self._get_group(request.user, id) + serializer = GroupSerializer(group) + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_201_CREATED: GroupSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[OpenApiExample( + name="Create group", + description="Create a new group.", + value={"name": "My new amazing group"}, + )] + ) + def create(self, request: HttpRequest) -> HttpResponse: + """ + Create a new group. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: new created group as json response + """ + serializer = GroupSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def _update(self, request: HttpRequest, id: int, *, partial: bool) -> HttpResponse: + """ + Update group information. + + Args: + request (HttpRequest): request object + id (int): group id + partial (bool): allow partial update + + Returns: + HttpResponse: updated group as json response + """ + group = self._get_group(request.user, id) + serializer = GroupSerializer(group, data=request.data, partial=partial) + serializer.is_valid(raise_exception=True) + serializer.save() + if getattr(group, '_prefetched_objects_cache', None): + # If 'prefetch_related' has been applied to a queryset, we need to + # forcibly invalidate the prefetch cache on the instance. + group._prefetched_objects_cache = {} + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_200_OK: GroupSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[ + _default_group_example, + OpenApiExample( + name="Update group", + description="Update group fields.", + value={"name": "My new amazing group is the best!"}, + ) + ] + ) + def update(self, request: HttpRequest, id: int) -> HttpResponse: + """ + Update group information. + + Args: + request (HttpRequest): request object + id (int): group id + + Returns: + HttpResponse: updated group as json response + """ + return self._update(request, id, partial=False) + + @extend_schema( + responses={ + status.HTTP_200_OK: GroupSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[ + _default_group_example, + OpenApiExample( + name="Update group partially", + description="Update only some group fields.", + value={"name": "My new amazing group is the best!"}, + ) + ] + ) + def partial_update(self, request: HttpRequest, id: int) -> HttpResponse: + """ + Update group information partially. + + Args: + request (HttpRequest): request object + id (int): group id + + Returns: + HttpResponse: updated group as json response + """ + return self._update(request, id, partial=True) + + @extend_schema( + responses={ + status.HTTP_204_NO_CONTENT: None, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[_default_group_example] + ) + def destroy(self, request: HttpRequest, id: int) -> HttpResponse: + """ + Remove group by id. + + Args: + request (HttpRequest): request object + id (int): group id + + Returns: + HttpResponse: 204 NO CONTENT + """ + group = self._get_group(request.user, id) + group.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/fl_server_api/views/inference.py b/fl_server_api/views/inference.py new file mode 100644 index 0000000..f0c7595 --- /dev/null +++ b/fl_server_api/views/inference.py @@ -0,0 +1,118 @@ +from django.http import HttpRequest, HttpResponse +from drf_spectacular.utils import inline_serializer, extend_schema, OpenApiExample +import json +import pickle +from rest_framework import status +from rest_framework.exceptions import APIException, UnsupportedMediaType, ValidationError +from rest_framework.fields import ListField, DictField, FloatField, CharField +import torch +from typing import Any, Dict, Tuple, Type + +from fl_server_core.exceptions import TorchDeserializationException +from fl_server_core.models import Model +from fl_server_ai.uncertainty import get_uncertainty_class, UncertaintyBase +from ..serializers.generic import ErrorSerializer + +from .base import ViewSet +from ..utils import get_entity + + +class Inference(ViewSet): + + serializer_class = inline_serializer("InferenceSerializer", fields={ + "inference": ListField(child=ListField(child=FloatField())), + "uncertainty": DictField(child=FloatField()) + }) + + @extend_schema( + request=inline_serializer( + "InferenceJsonSerializer", + fields={ + "model_id": CharField(), + "model_input": ListField(child=ListField(child=FloatField())) + } + ), + responses={ + status.HTTP_200_OK: serializer_class, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + }, + examples=[ + OpenApiExample("JSON Example", value={ + "model_id": "mymodel", + "model_input": [ + [1.0, 2.3, -0.4, 3], + [0.01, 9.7, 5.6, 7] + ] + }, request_only=True), + ] + ) + def inference(self, request: HttpRequest) -> HttpResponse: + """ + Processes a request to do inference on a existing model. + + Note that this endpoint can process both JSON data as well as formdata with an + attached pickled input tensor. + Further note that for the provided example to run, the model "mymodel" must have been created + via the model-endpoint first, and the user must be authorized to access it. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: (pickled) results of the inference + """ + match request.content_type.lower(): + case s if s.startswith("multipart/form-data"): + return self._process_post(request) + case s if s.startswith("application/json"): + return self._process_post_json(request) + case _: + # If the content type is specified, but not supported, return 415 + self._logger.error(f"Unknown Content-Type '{request.content_type}'") + raise UnsupportedMediaType( + "Only Content-Type 'application/json' and 'multipart/form-data' is supported." + ) + + def _process_post(self, request: HttpRequest) -> HttpResponse: + try: + model_id = request.POST["model_id"] + uploaded_file = request.FILES.get("model_input") + if not uploaded_file or not uploaded_file.file: + raise ValidationError("No uploaded file 'model_input' not found.") + feature_vectors = uploaded_file.file.read() + except Exception as e: + self._logger.error(e) + raise ValidationError("Inference Request could not be interpreted!") + + model = get_entity(Model, pk=model_id) + input_tensor = pickle.loads(feature_vectors) + _, inference, uncertainty = self.do_inference(model, input_tensor) + response_bytes = pickle.dumps(dict(inference=inference, uncertainty=uncertainty)) + return HttpResponse(response_bytes, content_type="application/octet-stream") + + def _process_post_json(self, request: HttpRequest, body: Any = None) -> HttpResponse: + try: + body = body or json.loads(request.body) + model_id = body["model_id"] + model_input = body["model_input"] + except Exception as e: + self._logger.error(e) + raise ValidationError("Inference Request could not be interpreted!") + + model = get_entity(Model, pk=model_id) + input_tensor = torch.as_tensor(model_input) + uncertainty_cls, inference, uncertainty = self.do_inference(model, input_tensor) + return HttpResponse(uncertainty_cls.to_json(inference, uncertainty), content_type="application/json") + + def do_inference( + self, model: Model, input_tensor: torch.Tensor + ) -> Tuple[Type[UncertaintyBase], torch.Tensor, Dict[str, Any]]: + try: + uncertainty_cls = get_uncertainty_class(model) + inference, uncertainty = uncertainty_cls.prediction(input_tensor, model) + return uncertainty_cls, inference, uncertainty + except TorchDeserializationException as e: + raise APIException(e) + except Exception as e: + self._logger.error(e) + raise APIException("Internal Server Error occurred during inference!") diff --git a/fl_server_api/views/model.py b/fl_server_api/views/model.py new file mode 100644 index 0000000..3e34c93 --- /dev/null +++ b/fl_server_api/views/model.py @@ -0,0 +1,423 @@ +from typing import Any + +from django.db import transaction +from django.db.models import Q +from django.http import HttpRequest, HttpResponse, JsonResponse +from django.http.response import HttpResponseBase +from django.utils.datastructures import MultiValueDictKeyError +from drf_spectacular.utils import extend_schema, OpenApiExample, inline_serializer, PolymorphicProxySerializer, \ + OpenApiResponse +from rest_framework import status +from rest_framework.exceptions import APIException, NotFound, ParseError, PermissionDenied, ValidationError +from rest_framework.response import Response +from rest_framework.fields import UUIDField, CharField + +from fl_server_core.models import ( + GlobalModel as GlobalModelDB, + LocalModel as LocalModelDB, + Metric as MetricDB, + Model as ModelDB, + SWAGModel as SWAGModelDB, + User as UserDB, +) +from fl_server_core.models.training import Training, TrainingState +from fl_server_core.utils.locked_atomic_transaction import locked_atomic_transaction +from fl_server_ai.trainer.events import ModelTestFinished, SWAGRoundFinished, TrainingRoundFinished +from fl_server_ai.trainer.tasks import dispatch_trainer_task + +from .base import ViewSet +from ..utils import get_entity, get_file +from ..serializers.generic import ErrorSerializer, MetricSerializer +from ..serializers.model import ModelSerializer, load_and_create_model_request, GlobalModelSerializer, \ + MeanModelSerializer, SWAGModelSerializer, ModelSerializerNoWeights +from ..openapi import error_response_403 + + +class Model(ViewSet): + + serializer_class = PolymorphicProxySerializer( + "PolymorphicModelSerializer", + [ + GlobalModelSerializer(many=True), + MeanModelSerializer(many=True), + SWAGModelSerializer(many=True) + ], + None, + many=False + ) + + def get_models(self, request: HttpRequest) -> HttpResponse: + """ + Get a list of all models which are related to the requested user. + A model is related to an user if it is owned by the user or if the user + is the actor or a participant of the models training. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: model list as json response + """ + user_ids = Training.objects.filter( + Q(actor=request.user) | Q(participants=request.user) + ).distinct().values_list("model__id", flat=True) + models = ModelDB.objects.filter(Q(owner=request.user) | Q(id__in=user_ids)).distinct() + serializer = ModelSerializer(models, many=True) + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_200_OK: ModelSerializerNoWeights(), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + ) + def get_metadata(self, _request: HttpRequest, id: str) -> HttpResponse: + """ + Get model meta data. + + Args: + request (HttpRequest): request object + id (str): model uuid + + Returns: + HttpResponse: model meta data as json response + """ + model = get_entity(ModelDB, pk=id) + serializer = ModelSerializer(model) + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_200_OK: OpenApiResponse(response=bytes, description="Model is returned as bytes"), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + ) + def get_model(self, _request: HttpRequest, id: str) -> HttpResponseBase: + """ + Download the whole model as pickle file. + + Args: + request (HttpRequest): request object + id (str): model uuid + + Returns: + HttpResponseBase: model as file response or 404 if model not found + """ + model = get_entity(ModelDB, pk=id) + if isinstance(model, SWAGModelDB) and model.swag_first_moment is not None: + if model.swag_second_moment is None: + raise APIException(f"Model {model.id} is in inconsistent state!") + raise NotImplementedError( + "SWAG models need to be returned in 3 parts: model architecture, first moment, second moment" + ) + # NOTE: FileResponse does strange stuff with bytes + # and in case of sqlite the weights will be bytes and not a memoryview + response = HttpResponse(model.weights, content_type="application/octet-stream") + response["Content-Disposition"] = f'filename="model-{id}.pkl"' + return response + + @extend_schema( + request={ + "multipart/form-data": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "description": {"type": "string"}, + "model_file": {"type": "string", "format": "binary"}}, + }, + }, + responses={ + status.HTTP_200_OK: inline_serializer("ModelUploadSerializer", fields={ + "detail": CharField(default="Model Upload Accepted"), + "model_id": UUIDField(), + }), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + ) + def create_model(self, request: HttpRequest) -> HttpResponse: + """ + Upload a global model file. + + The file should be a pickled torch.nn.Module. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: upload success message as json response + """ + model = load_and_create_model_request(request) + return JsonResponse({ + "detail": "Model Upload Accepted", + "model_id": str(model.id), + }, status=status.HTTP_201_CREATED) + + @extend_schema( + responses={ + status.HTTP_200_OK: MetricSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + ) + def get_model_metrics(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Reports all metrics for the selected model. + + Args: + request (HttpRequest): request object + id (str): model UUID + + Returns: + HttpResponse: Metrics as JSON Array + """ + model = get_entity(ModelDB, pk=id) + metrics = MetricDB.objects.filter(model=model).all() + return Response(MetricSerializer(metrics, many=True).data) + + @extend_schema( + request={ + "multipart/form-data": { + "type": "object", + "properties": { + "metric_names": {"type": "list"}, + "metric_values": {"type": "list"}, + }, + }, + }, + responses={ + status.HTTP_200_OK: inline_serializer("MetricUploadResponseSerializer", fields={ + "detail": CharField(default="Model Metrics Upload Accepted"), + "model_id": UUIDField(), + }), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[ + OpenApiExample("Example", value={ + "metric_names": ["accuracy", "training loss"], + "metric_values": [0.6, 0.04] + }, media_type="multipart/form-data") + ] + ) + def create_model_metrics(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Upload model metrics. + + Args: + request (HttpRequest): request object + id (str): model uuid + + Returns: + HttpResponse: upload success message as json response + """ + model = get_entity(ModelDB, pk=id) + formdata = dict(request.POST) + + with locked_atomic_transaction(MetricDB): + self._metric_upload(formdata, model, request.user) + + if isinstance(model, GlobalModelDB): + n_metrics = MetricDB.objects.filter(model=model, step=model.round).distinct("reporter").count() + training = model.get_training() + if training: + if n_metrics == training.participants.count(): + dispatch_trainer_task(training, ModelTestFinished, False) + else: + self._logger.warning(f"Global model {id} is not connected to any training.") + + return JsonResponse({ + "detail": "Model Metrics Upload Accepted", + "model_id": str(model.id), + }, status=status.HTTP_201_CREATED) + + @extend_schema( + request={ + "multipart/form-data": { + "type": "object", + "properties": { + "owner": {"type": "string"}, + "round": {"type": "int"}, + "sample_size": {"type": "int"}, + "metric_names": {"type": "list[string]"}, + "metric_values": {"type": "list[float]"}, + "model_file": {"type": "string", "format": "binary"}, + }, + }, + }, + responses={ + status.HTTP_200_OK: ModelSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + ) + def create_local_model(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Upload a partial trained model file from client. + + Args: + request (HttpRequest): request object + id (str): model uuid of the model, which was used for training + + Returns: + HttpResponse: upload success message as json response + """ + try: + formdata = dict(request.POST) + (round_num,) = formdata["round"] + (sample_size,) = formdata["sample_size"] + round_num, sample_size = int(round_num), int(sample_size) + client = request.user + model_file = get_file(request, "model_file") + global_model = get_entity(GlobalModelDB, pk=id) + + # ensure that a training process coresponding to the model exists, else the process will error out + training = Training.objects.get(model=global_model) + self._verify_valid_update(client, training, round_num, TrainingState.ONGOING) + + local_model = LocalModelDB.objects.create( + base_model=global_model, weights=model_file, + round=round_num, owner=client, sample_size=sample_size + ) + self._metric_upload(formdata, local_model, client, metrics_required=False) + + updates = LocalModelDB.objects.filter(base_model=global_model, round=round_num) + if updates.count() == training.participants.count(): + dispatch_trainer_task(training, TrainingRoundFinished, True) + + return JsonResponse({"detail": "Model Update Accepted"}, status=status.HTTP_201_CREATED) + except Training.DoesNotExist: + raise NotFound(f"Model with ID {id} does not have a training process running") + except (MultiValueDictKeyError, KeyError) as e: + raise ParseError(e) + + @extend_schema( + request={ + "multipart/form-data": { + "type": "object", + "properties": { + "round": {"type": "int"}, + "sample_size": {"type": "int"}, + "first_moment_file": {"type": "string", "format": "binary"}, + "second_moment_file": {"type": "string", "format": "binary"} + }, + }, + }, + responses={ + status.HTTP_200_OK: inline_serializer("MetricUploadSerializer", fields={ + "detail": CharField(default="SWAg Statistics Accepted"), + }), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + ) + def create_swag_stats(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Upload SWAG statistics. + + Args: + request (HttpRequest): request object + id (str): global model uuid + + Raises: + APIException: internal server error + NotFound: model not found + ParseError: request data not valid + + Returns: + HttpResponse: upload success message as json response + """ + try: + client = request.user + formdata = dict(request.POST) + (round_num,) = formdata["round"] + (sample_size,) = formdata["sample_size"] + round_num, sample_size = int(round_num), int(sample_size) + fst_moment = get_file(request, "first_moment_file") + snd_moment = get_file(request, "second_moment_file") + model = get_entity(GlobalModelDB, pk=id) + + # ensure that a training process coresponding to the model exists, else the process will error out + training = Training.objects.get(model=model) + self._verify_valid_update(client, training, round_num, TrainingState.SWAG_ROUND) + + self._save_swag_stats(fst_moment, snd_moment, model, client, sample_size) + + swag_stats_first = MetricDB.objects.filter(model=model, step=model.round, key="SWAG First Moment Local") + swag_stats_second = MetricDB.objects.filter(model=model, step=model.round, key="SWAG Second Moment Local") + + if swag_stats_first.count() != swag_stats_second.count(): + training.state = TrainingState.ERROR + raise APIException("SWAG stats in inconsistent state!") + if swag_stats_first.count() == training.participants.count(): + dispatch_trainer_task(training, SWAGRoundFinished, True) + + return JsonResponse({"detail": "SWAG Statistic Accepted"}, status=status.HTTP_201_CREATED) + except Training.DoesNotExist: + raise NotFound(f"Model with ID {id} does not have a training process running") + except (MultiValueDictKeyError, KeyError) as e: + raise ParseError(e) + except Exception as e: + raise APIException(e) + + @staticmethod + def _save_swag_stats(fst_moment: bytes, snd_moment: bytes, + model: GlobalModelDB, client: UserDB, sample_size: int): + MetricDB.objects.create( + model=model, + key="SWAG First Moment Local", + value_binary=fst_moment, + step=model.round, + reporter=client + ).save() + MetricDB.objects.create( + model=model, + key="SWAG Second Moment Local", + value_binary=snd_moment, + step=model.round, + reporter=client + ).save() + MetricDB.objects.create( + model=model, + key="SWAG Sample Size Local", + value_float=sample_size, + step=model.round, + reporter=client + ).save() + + @transaction.atomic() + def _metric_upload(self, formdata: dict, model: ModelDB, client: UserDB, metrics_required: bool = True): + if "metric_names" not in formdata or "metric_values" not in formdata: + if metrics_required or ("metric_names" in formdata) != ("metric_values" in formdata): + raise ParseError("Metric names or values are missing") + return + if len(formdata["metric_names"]) != len(formdata["metric_values"]): + if metrics_required: + raise ParseError("Metric names and values must have the same length") + return + + for key, value in zip(formdata["metric_names"], formdata["metric_values"]): + try: + metric_float = float(value) + metric_binary = None + except Exception: + metric_float = None + metric_binary = bytes(value, encoding="utf-8") + MetricDB.objects.create( + model=model, + key=key, + value_float=metric_float, + value_binary=metric_binary, + step=model.round, + reporter=client + ).save() + + def _verify_valid_update(self, client: UserDB, train: Training, round_num: int, expected_state: tuple[str, Any]): + if client.id not in [p.id for p in train.participants.all()]: + raise PermissionDenied(f"You are not a participant of training {train.id}!") + if train.state != expected_state: + raise ValidationError(f"Training with ID {train.id} is in state {train.state}") + if int(round_num) != train.model.round: + raise ValidationError(f"Training with ID {train.id} is not currently in round {round_num}") diff --git a/fl_server_api/views/training.py b/fl_server_api/views/training.py new file mode 100644 index 0000000..1741966 --- /dev/null +++ b/fl_server_api/views/training.py @@ -0,0 +1,268 @@ +from django.http import HttpRequest, HttpResponse, JsonResponse +import json +from marshmallow import Schema +from marshmallow.exceptions import ValidationError as MarshmallowValidationError +from rest_framework import status +from rest_framework.exceptions import ParseError, PermissionDenied +from rest_framework.response import Response +from uuid import UUID + +from fl_server_core.models import ( + Model as ModelDB, + Training as TrainingDB, + User as UserDB, +) +from fl_server_core.models.model import clone_model +from fl_server_core.models.training import TrainingState +from fl_server_ai.trainer import ModelTrainer + +from .base import ViewSet +from ..utils import get_entity +from ..serializers.generic import TrainingSerializer +from ..serializers.training import ( + CreateTrainingRequest, CreateTrainingRequestSchema, + ClientAdministrationBody, ClientAdministrationBodySchema +) +from drf_spectacular.utils import extend_schema, inline_serializer +from rest_framework.fields import UUIDField, CharField, IntegerField, ListField +from ..openapi import error_response_403 +from ..serializers.generic import ErrorSerializer + + +class Training(ViewSet): + """ + Federated Learning Platform's API trainings endpoint `/api/trainings`. + This endpoint is used to create and manage trainings. + """ + + serializer_class = TrainingSerializer + + def _check_user_permission_for_training(self, user: UserDB, training_id: UUID | str) -> TrainingDB: + if isinstance(training_id, str): + training_id = UUID(training_id) + training = get_entity(TrainingDB, pk=training_id) + if training.actor != user and user not in training.participants.all(): + raise PermissionDenied() + return training + + def _get_clients_from_body(self, body_raw: bytes) -> list[UserDB]: + body: ClientAdministrationBody = self._load_marshmallow_request(ClientAdministrationBodySchema(), body_raw) + return self._get_clients_from_uuid_list(body.clients) + + def _get_clients_from_uuid_list(self, uuids: list[UUID]) -> list[UserDB]: + if uuids is None or len(uuids) == 0: + return [] + # Note: filter "in" does not raise UserDB.DoesNotExist exceptions + clients = UserDB.objects.filter(id__in=uuids) + if len(clients) != len(uuids): + raise ParseError("Not all provided users were found!") + return clients + + def _load_marshmallow_request(self, schema: Schema, json_data: str | bytes | bytearray): + try: + return schema.load(json.loads(json_data)) # should `schema.loads` be used instead? + except MarshmallowValidationError as e: + raise ParseError(e.messages) + + @extend_schema(responses={ + status.HTTP_200_OK: TrainingSerializer(many=True), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }) + def get_trainings(self, request: HttpRequest) -> HttpResponse: + """ + Get information about all owned trainings. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: list of training data as json response + """ + trainings = TrainingDB.objects.filter(actor=request.user) + serializer = TrainingSerializer(trainings, many=True) + return Response(serializer.data) + + def get_training(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Get information about the selected training. + + Args: + request (HttpRequest): request object + id (str): training uuid + + Returns: + HttpResponse: training data as json response + """ + train = self._check_user_permission_for_training(request.user, id) + serializer = TrainingSerializer(train) + return Response(serializer.data) + + @extend_schema( + request=inline_serializer("EmptyBodySerializer", fields={}), + responses={ + status.HTTP_200_OK: inline_serializer( + "SuccessSerializer", + fields={ + "detail": CharField(default="Training started!") + } + ), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def start_training(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Start training. Will check if at least one participant is registered. + Should be called by an POST request with an empty body. + + Args: + request (HttpRequest): request object + id (str): training uuid + + Returns: + HttpResponse: training data as json response + """ + training = self._check_user_permission_for_training(request.user, id) + if training.participants.count() == 0: + raise ParseError("At least one participant must be registered!") + if training.state != TrainingState.INITIAL: + raise ParseError(f"Training {training.id} is not in state INITIAL!") + ModelTrainer(training).start() + return JsonResponse({"detail": "Training started!"}, status=status.HTTP_202_ACCEPTED) + + @extend_schema( + request=inline_serializer( + "RegisterClientsSerializer", + fields={ + "clients": ListField(child=UUIDField()) + } + ), + responses={ + status.HTTP_200_OK: inline_serializer( + "RegisteredClientsSuccessSerializer", + fields={ + "detail": CharField(default="Users registered as participants!") + } + ), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def register_clients(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Register one or more clients for the training. + Should be called by POST with a json body of the form. + Should be called once before the training is started. + + ```json + {"clients": []} + ``` + + Will check if all the selected clients are registered. + + Args: + request (HttpRequest): request object + id (str): training uuid + + Returns: + HttpResponse: 202 Response if clients were registered, else corresponding error code + """ + train = self._check_user_permission_for_training(request.user, id) + clients = self._get_clients_from_body(request.body) + train.participants.add(*clients) + return JsonResponse({"detail": "Users registered as participants!"}, status=status.HTTP_202_ACCEPTED) + + @extend_schema( + request=inline_serializer( + "RemoveClientsSerializer", + fields={ + "clients": ListField(child=UUIDField()) + } + ), + responses={ + status.HTTP_200_OK: inline_serializer( + "RemovedClientsSuccessSerializer", + fields={ + "detail": CharField(default="Users removed from training participants!") + } + ), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def remove_clients(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Remove clients from the list of registered clients for a training. + This is meant for modification of an already existing training process. + + Args: + request (HttpRequest): request object + id (str): training uuid + + Returns: + HttpResponse: 200 Response if clients were removed, else corresponding error code + """ + train = self._check_user_permission_for_training(request.user, id) + clients = self._get_clients_from_body(request.body) + train.participants.remove(*clients) + return JsonResponse({"detail": "Users removed from training participants!"}) + + @extend_schema( + request=inline_serializer( + name="TrainingCreationSerializer", + fields={ + "model_id": CharField(), + "target_num_updates": IntegerField(), + "metric_names": ListField(child=CharField()), + "aggregation_method": CharField(), + "clients": ListField(child=UUIDField()) + } + ), + responses={ + status.HTTP_200_OK: inline_serializer("TrainingCreatedSerializer", fields={ + "detail": CharField(default="Training created successfully!"), + "model_id": UUIDField(), + }), + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def create_training(self, request: HttpRequest) -> HttpResponse: + """ + Creates a training process. + Should be called by POST according to `CreateTrainingRequestSchema` and + should have a model file (the initial model) as attached FILE. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: 201 if training could be registered + """ + parsed_request: CreateTrainingRequest = self._load_marshmallow_request( + CreateTrainingRequestSchema(), + request.body.decode("utf-8") + ) + model = get_entity(ModelDB, pk=parsed_request.model_id) + if model.owner != request.user: + raise PermissionDenied() + if TrainingDB.objects.filter(model=model).exists(): + # the selected model is already referenced by another training, so we need to copy it + model = clone_model(model) + + clients = self._get_clients_from_uuid_list(parsed_request.clients) + train = TrainingDB.objects.create( + model=model, + actor=request.user, + target_num_updates=parsed_request.target_num_updates, + state=TrainingState.INITIAL, + uncertainty_method=parsed_request.uncertainty_method.value, + aggregation_method=parsed_request.aggregation_method.value, + options=parsed_request.options + ) + train.participants.add(*clients) + return JsonResponse({ + "detail": "Training created successfully!", + "training_id": train.id + }, status=status.HTTP_201_CREATED) diff --git a/fl_server_api/views/user.py b/fl_server_api/views/user.py new file mode 100644 index 0000000..bf3efcf --- /dev/null +++ b/fl_server_api/views/user.py @@ -0,0 +1,149 @@ +from django.db.models import Q +from django.http import HttpRequest, HttpResponse +from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter +from rest_framework import decorators, status +from rest_framework.response import Response + +from fl_server_core.models import User as UserModel, Training as TrainingModel + +from .base import ViewSet +from ..utils import get_entity +from ..openapi import error_response_403 +from ..serializers.generic import ErrorSerializer, GroupSerializer, TrainingSerializer +from ..serializers.user import UserSerializer + + +class User(ViewSet): + + serializer_class = UserSerializer + + @extend_schema( + responses={ + status.HTTP_200_OK: UserSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def get_users(self, request: HttpRequest) -> HttpResponse: + """ + Get current user information as list. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: user list as json response + """ + serializer = UserSerializer([request.user], many=True) + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_200_OK: UserSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[ + OpenApiExample( + name="Get user by id", + description="Retrieve user data by user ID.", + value="88a9f11a-846b-43b5-bd15-367fc332ba59", + parameter_only=("id", OpenApiParameter.PATH) + ) + ] + ) + def get_user(self, request: HttpRequest, id: str) -> HttpResponse: + """ + Get user information. + + Args: + request (HttpRequest): request object + id (str): user uuid + + Returns: + HttpResponse: user as json response + """ + serializer = UserSerializer(get_entity(UserModel, pk=id), context={"request_user_id": request.user.id}) + return Response(serializer.data) + + @decorators.authentication_classes([]) + @decorators.permission_classes([]) + @extend_schema( + responses={ + status.HTTP_200_OK: UserSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + }, + examples=[ + OpenApiExample("Jane Doe", value={ + "message_endpoint": "http://example.com/", + "actor": True, + "client": True, + "username": "jane", + "first_name": "Jane", + "last_name": "Doe", + "email": "jane.doe@example.com", + "password": "my-super-secret-password" + }) + ] + ) + def create_user(self, request: HttpRequest) -> HttpResponse: + """ + Create a new user. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: new created user as json response + """ + user = UserSerializer().create(request.data) + serializer = UserSerializer(user, context={"request_user_id": user.id}) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + @extend_schema( + responses={ + status.HTTP_200_OK: GroupSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + }, + examples=[ + OpenApiExample( + name="User uuid", + value="88a9f11a-846b-43b5-bd15-367fc332ba59", + parameter_only=("id", OpenApiParameter.PATH) + ) + ] + ) + def get_user_groups(self, request: HttpRequest) -> HttpResponse: + """ + Get user groups. + + Args: + request (HttpRequest): request object + id (str): user uuid + + Returns: + HttpResponse: user groups as json response + """ + serializer = GroupSerializer(request.user.groups, many=True) + return Response(serializer.data) + + @extend_schema( + responses={ + status.HTTP_200_OK: TrainingSerializer, + status.HTTP_400_BAD_REQUEST: ErrorSerializer, + status.HTTP_403_FORBIDDEN: error_response_403, + } + ) + def get_user_trainings(self, request: HttpRequest) -> HttpResponse: + """ + Get user trainings. + + Args: + request (HttpRequest): request object + + Returns: + HttpResponse: user trainings as json response + """ + trainings = TrainingModel.objects.filter(Q(actor=request.user) | Q(participants=request.user)).distinct() + serializer = TrainingSerializer(trainings, many=True) + return Response(serializer.data) diff --git a/fl_server_core/__init__.py b/fl_server_core/__init__.py new file mode 100644 index 0000000..71a9214 --- /dev/null +++ b/fl_server_core/__init__.py @@ -0,0 +1,3 @@ +""" +Core Features +""" diff --git a/fl_server_core/admin.py b/fl_server_core/admin.py new file mode 100644 index 0000000..45d7b0b --- /dev/null +++ b/fl_server_core/admin.py @@ -0,0 +1,46 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter + +from .models import ( + GlobalModel, + LocalModel, + Metric, + Model, + Training, + User, +) + + +admin.site.register(User, UserAdmin) +admin.site.register(Training) +admin.site.register(Metric) + + +# Flexible and customizable registration + +# @admin.register(ModelUpdate) +# class ModelUpdateAdmin(admin.ModelAdmin): +# pass + + +class ModelChildAdmin(PolymorphicChildModelAdmin): + base_model = Model + + +@admin.register(GlobalModel) +class GlobalModelAdmin(ModelChildAdmin): + base_model = GlobalModel + + +@admin.register(LocalModel) +class LocalModelAdmin(ModelChildAdmin): + base_model = LocalModel + + +@admin.register(Model) +class ModelParentAdmin(PolymorphicParentModelAdmin): + """The parent model admin""" + base_model = Model + child_models = (GlobalModel, LocalModel) + list_filter = (PolymorphicChildModelFilter,) diff --git a/fl_server_core/apps.py b/fl_server_core/apps.py new file mode 100644 index 0000000..34d549f --- /dev/null +++ b/fl_server_core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FlServerCoreConfig(AppConfig): + name = "fl_server_core" + verbose_name = "Federated Learning Demonstrator" diff --git a/fl_server_core/exceptions.py b/fl_server_core/exceptions.py new file mode 100644 index 0000000..73e36e5 --- /dev/null +++ b/fl_server_core/exceptions.py @@ -0,0 +1,2 @@ +class TorchDeserializationException(Exception): + pass diff --git a/fl_server_core/migrations/0001_initial.py b/fl_server_core/migrations/0001_initial.py new file mode 100644 index 0000000..736f5be --- /dev/null +++ b/fl_server_core/migrations/0001_initial.py @@ -0,0 +1,123 @@ +# Generated by Django 4.0.10 on 2023-07-13 12:46 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import fl_server_core.models.user +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('actor', models.BooleanField(default=False)), + ('client', models.BooleanField(default=False)), + ('message_endpoint', models.URLField()), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + bases=(models.Model, fl_server_core.models.user.NotificationReceiver), + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='Model', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('round', models.IntegerField()), + ('weights', models.BinaryField()), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + ), + migrations.CreateModel( + name='GlobalModel', + fields=[ + ('model_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fl_server_core.model')), + ('name', models.CharField(max_length=256)), + ('description', models.TextField()), + ('swag_first_moment', models.BinaryField(blank=True, null=True)), + ('swag_second_moment', models.BinaryField(blank=True, null=True)), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('fl_server_core.model',), + ), + migrations.CreateModel( + name='Training', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('state', models.CharField(choices=[('I', 'Initial'), ('O', 'Ongoing'), ('C', 'Completed'), ('E', 'Error'), ('S', 'SwagRound')], max_length=1)), + ('target_num_updates', models.IntegerField()), + ('last_update', models.DateTimeField(auto_now=True)), + ('uncertainty_method', models.CharField(choices=[('NONE', 'None'), ('SWAG', 'SWAG'), ('ENSEMBLE_LOCAL', 'Ensemble Local'), ('ENSEMBLE_GLOBAL', 'Ensemble Global'), ('MC_DROPOUT', 'MC Dropout')], default='NONE', max_length=32)), + ('aggregation_method', models.CharField(choices=[('AVG', 'Average')], max_length=3)), + ('locked', models.BooleanField(default=False)), + ('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='actors', to=settings.AUTH_USER_MODEL)), + ('model', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='fl_server_core.model')), + ('participants', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Metric', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('identifier', models.CharField(blank=True, max_length=64, null=True)), + ('key', models.CharField(max_length=32)), + ('value_float', models.FloatField(blank=True, null=True)), + ('value_binary', models.BinaryField(blank=True, null=True)), + ('step', models.IntegerField(blank=True, null=True)), + ('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fl_server_core.model')), + ('reporter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='LocalModel', + fields=[ + ('model_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fl_server_core.model')), + ('sample_size', models.IntegerField()), + ('base_model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fl_server_core.globalmodel')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('fl_server_core.model',), + ), + ] diff --git a/fl_server_core/migrations/0002_remove_globalmodel_swag_first_moment_and_more.py b/fl_server_core/migrations/0002_remove_globalmodel_swag_first_moment_and_more.py new file mode 100644 index 0000000..7f754a0 --- /dev/null +++ b/fl_server_core/migrations/0002_remove_globalmodel_swag_first_moment_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.0.10 on 2023-07-18 09:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='globalmodel', + name='swag_first_moment', + ), + migrations.RemoveField( + model_name='globalmodel', + name='swag_second_moment', + ), + migrations.CreateModel( + name='SWAGModel', + fields=[ + ('globalmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fl_server_core.globalmodel')), + ('swag_first_moment', models.BinaryField()), + ('swag_second_moment', models.BinaryField()), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('fl_server_core.globalmodel',), + ), + migrations.CreateModel( + name='MeanModel', + fields=[ + ('globalmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fl_server_core.globalmodel')), + ('models', models.ManyToManyField(related_name='mean_models', to='fl_server_core.globalmodel')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('fl_server_core.globalmodel',), + ), + ] diff --git a/fl_server_core/migrations/0003_alter_training_uncertainty_method.py b/fl_server_core/migrations/0003_alter_training_uncertainty_method.py new file mode 100644 index 0000000..63e1a43 --- /dev/null +++ b/fl_server_core/migrations/0003_alter_training_uncertainty_method.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.10 on 2023-07-19 14:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0002_remove_globalmodel_swag_first_moment_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='training', + name='uncertainty_method', + field=models.CharField(choices=[('NONE', 'None'), ('SWAG', 'SWAG'), ('ENSEMBLE', 'Ensemble'), ('MC_DROPOUT', 'MC Dropout')], default='NONE', max_length=32), + ), + ] diff --git a/fl_server_core/migrations/0004_training_uncertainty_options.py b/fl_server_core/migrations/0004_training_uncertainty_options.py new file mode 100644 index 0000000..4d9d6ab --- /dev/null +++ b/fl_server_core/migrations/0004_training_uncertainty_options.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.10 on 2023-07-24 13:43 + +import django.core.serializers.json +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0003_alter_training_uncertainty_method'), + ] + + operations = [ + migrations.AddField( + model_name='training', + name='uncertainty_options', + field=models.JSONField(default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + ] diff --git a/fl_server_core/migrations/0005_alter_training_aggregation_method_and_more.py b/fl_server_core/migrations/0005_alter_training_aggregation_method_and_more.py new file mode 100644 index 0000000..5f951f0 --- /dev/null +++ b/fl_server_core/migrations/0005_alter_training_aggregation_method_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.10 on 2023-08-07 08:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0004_training_uncertainty_options'), + ] + + operations = [ + migrations.AlterField( + model_name='training', + name='aggregation_method', + field=models.CharField(choices=[('FedAvg', 'FedAvg'), ('FedDC', 'FedDC'), ('FedProx', 'FedProx')], default='FedAvg', max_length=32), + ), + migrations.AlterField( + model_name='training', + name='uncertainty_method', + field=models.CharField(choices=[('NONE', 'None'), ('ENSEMBLE', 'Ensemble'), ('MC_DROPOUT', 'MC Dropout'), ('SWAG', 'SWAG')], default='NONE', max_length=32), + ), + ] diff --git a/fl_server_core/migrations/0006_rename_uncertainty_options_training_options.py b/fl_server_core/migrations/0006_rename_uncertainty_options_training_options.py new file mode 100644 index 0000000..0bc88ef --- /dev/null +++ b/fl_server_core/migrations/0006_rename_uncertainty_options_training_options.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.10 on 2023-09-27 06:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0005_alter_training_aggregation_method_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='training', + old_name='uncertainty_options', + new_name='options', + ), + ] diff --git a/fl_server_core/migrations/0007_globalmodel_input_shape.py b/fl_server_core/migrations/0007_globalmodel_input_shape.py new file mode 100644 index 0000000..cb53dcc --- /dev/null +++ b/fl_server_core/migrations/0007_globalmodel_input_shape.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.10 on 2024-02-21 08:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0006_rename_uncertainty_options_training_options'), + ] + + operations = [ + migrations.AddField( + model_name='globalmodel', + name='input_shape', + field=models.CharField(default=None, max_length=100), + preserve_default=False, + ), + ] diff --git a/fl_server_core/migrations/0008_alter_globalmodel_input_shape.py b/fl_server_core/migrations/0008_alter_globalmodel_input_shape.py new file mode 100644 index 0000000..8549121 --- /dev/null +++ b/fl_server_core/migrations/0008_alter_globalmodel_input_shape.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.10 on 2024-02-21 08:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fl_server_core', '0007_globalmodel_input_shape'), + ] + + operations = [ + migrations.AlterField( + model_name='globalmodel', + name='input_shape', + field=models.CharField(max_length=100, null=True), + ), + ] diff --git a/fl_server_core/migrations/__init__.py b/fl_server_core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fl_server_core/models/__init__.py b/fl_server_core/models/__init__.py new file mode 100644 index 0000000..ee0e01a --- /dev/null +++ b/fl_server_core/models/__init__.py @@ -0,0 +1,16 @@ +from .metric import Metric +from .model import GlobalModel, LocalModel, MeanModel, Model, SWAGModel +from .training import Training +from .user import User + + +__all__ = [ + "GlobalModel", + "LocalModel", + "Metric", + "MeanModel", + "Model", + "SWAGModel", + "Training", + "User" +] diff --git a/fl_server_core/models/metric.py b/fl_server_core/models/metric.py new file mode 100644 index 0000000..ae9ae7f --- /dev/null +++ b/fl_server_core/models/metric.py @@ -0,0 +1,50 @@ +from django.db import models +from django.db.models import BinaryField, CASCADE, CharField, FloatField, ForeignKey, IntegerField +from torch import Tensor +from torch.nn import Module + +from ..utils.torch_pickle import from_torch_module_or_tensor, to_torch_module_or_tensor + +from .model import Model +from .user import User + + +class Metric(models.Model): + model: ForeignKey = ForeignKey(Model, on_delete=CASCADE) + identifier: CharField = CharField(max_length=64, null=True, blank=True) + key: CharField = CharField(max_length=32) + value_float: FloatField = FloatField(null=True, blank=True) + value_binary: BinaryField = BinaryField(null=True, blank=True) + step: IntegerField = IntegerField(null=True, blank=True) + reporter: ForeignKey = ForeignKey(User, null=True, blank=True, on_delete=CASCADE) + + @property + def value(self) -> float | bytes: + if self.is_float(): + return self.value_float + return self.value_binary + + @value.setter + def value(self, value: float | int | bytes | Module | Tensor): + if isinstance(value, float): + self.value_float = value + elif isinstance(value, int): + self.value_float = float(value) + elif isinstance(value, (Module, Tensor)): + self.value_binary = from_torch_module_or_tensor(value) + else: + self.value_binary = value + + @value.deleter + def value(self): + self.value_float = None + self.value_binary = None + + def is_float(self) -> bool: + return self.value_float is not None + + def is_binary(self) -> bool: + return self.value_binary is not None + + def to_torch(self) -> Module | Tensor: + return to_torch_module_or_tensor(self.value_binary) diff --git a/fl_server_core/models/model.py b/fl_server_core/models/model.py new file mode 100644 index 0000000..c839bdd --- /dev/null +++ b/fl_server_core/models/model.py @@ -0,0 +1,125 @@ +from django.db.models import ( + BinaryField, CASCADE, CharField, ForeignKey, IntegerField, ManyToManyField, TextField, UUIDField +) +from polymorphic.models import PolymorphicModel +import torch +from torch import Tensor +from torch.nn import Module +from typing import Optional, Sequence, TypeVar +from uuid import uuid4 +from copy import copy + +from ..utils.torch_pickle import ( + from_torch_module, to_torch_module, + from_torch_tensor, to_torch_tensor +) + +from .. import models as models +from .user import User + + +class Model(PolymorphicModel): + id: UUIDField = UUIDField(primary_key=True, editable=False, default=uuid4) + owner: ForeignKey = ForeignKey(User, on_delete=CASCADE) + round: IntegerField = IntegerField() + weights: BinaryField = BinaryField() + + def is_global_model(self): + return isinstance(self, GlobalModel) + + def is_local_model(self): + return isinstance(self, LocalModel) + + def get_torch_model(self) -> Module: + return to_torch_module(self.weights) + + def set_torch_model(self, value: Module): + self.weights = from_torch_module(value) + + def get_training(self) -> Optional["models.Training"]: + return models.Training.objects.filter(model=self).first() + + +class GlobalModel(Model): + name: CharField = CharField(max_length=256) + description: TextField = TextField() + input_shape: CharField = CharField(max_length=100, null=True) + + +class SWAGModel(GlobalModel): + swag_first_moment: BinaryField = BinaryField() + swag_second_moment: BinaryField = BinaryField() + + @property + def first_moment(self) -> Tensor: + return to_torch_tensor(self.swag_first_moment) + + @first_moment.setter + def first_moment(self, value: Tensor): + self.swag_first_moment = from_torch_tensor(value) + + @property + def second_moment(self) -> Tensor: + return to_torch_tensor(self.swag_second_moment) + + @second_moment.setter + def second_moment(self, value: Tensor): + self.swag_second_moment = from_torch_tensor(value) + + +class MeanModel(GlobalModel): + models: ManyToManyField = ManyToManyField(GlobalModel, related_name="mean_models") + + def get_torch_model(self) -> Module: + return MeanModule([model.get_torch_model() for model in self.models.all()]) + + def set_torch_model(self, value: Module): + raise NotImplementedError() + + +class LocalModel(Model): + base_model: ForeignKey = ForeignKey(GlobalModel, on_delete=CASCADE) + sample_size: IntegerField = IntegerField() + + def get_training(self) -> Optional["models.Training"]: + return models.Training.objects.filter(model=self.base_model).first() + + +TModel = TypeVar("TModel", bound=Model) + + +class MeanModule(Module): + + def __init__(self, models: Sequence[Model]): + super().__init__() + self.models = models + + def forward(self, input: Tensor) -> Tensor: + return torch.stack([model(input) for model in self.models], dim=0).mean(dim=0) + + +def clone_model(model: Model) -> Model: + """ + Copies a model instance in the database + See https://docs.djangoproject.com/en/5.0/topics/db/queries/#copying-model-instances + and stackoverflow.com/questions/4733609/how-do-i-clone-a-django-model-instance-object-and-save-it-to-the-database + + Args: + model (Model): the model to be copied + + Returns: + Model: New Model instance that is a copy of the old one + """ + model.save() + new_model = copy(model) + new_model.pk = None + new_model.id = None + new_model._state.adding = True + try: + delattr(new_model, '_prefetched_objects_cache') + except AttributeError: + pass + new_model.save() + new_model.owner = model.owner + new_model.save() + return new_model diff --git a/fl_server_core/models/training.py b/fl_server_core/models/training.py new file mode 100644 index 0000000..dca48c7 --- /dev/null +++ b/fl_server_core/models/training.py @@ -0,0 +1,71 @@ +from django.core.serializers.json import DjangoJSONEncoder +from django.db import models +from django.db.models import ( + CASCADE, CharField, ForeignKey, IntegerField, JSONField, ManyToManyField, + OneToOneField, UUIDField, BooleanField +) +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils.translation import gettext_lazy as _ +from uuid import uuid4 + +from .model import Model +from .user import User + + +class TrainingState(models.TextChoices): + INITIAL = "I", _("Initial") + ONGOING = "O", _("Ongoing") + COMPLETED = "C", _("Completed") + ERROR = "E", _("Error") + SWAG_ROUND = "S", _("SwagRound") + + +class AggregationMethod(models.TextChoices): + FED_AVG = "FedAvg", _("FedAvg") + FED_DC = "FedDC", _("FedDC") + FED_PROX = "FedProx", _("FedProx") + + +class UncertaintyMethod(models.TextChoices): + NONE = "NONE", _("None") + ENSEMBLE = "ENSEMBLE", _("Ensemble") + MC_DROPOUT = "MC_DROPOUT", _("MC Dropout") + SWAG = "SWAG", _("SWAG") + + +class Training(models.Model): + id: UUIDField = UUIDField(primary_key=True, editable=False, default=uuid4) + model: OneToOneField = OneToOneField(Model, on_delete=CASCADE) + actor: ForeignKey = ForeignKey(User, on_delete=CASCADE, related_name="actors") + participants: ManyToManyField = ManyToManyField(User) + state: CharField = CharField(max_length=1, choices=TrainingState.choices) + target_num_updates: IntegerField = IntegerField() + last_update = models.DateTimeField(auto_now=True) + uncertainty_method: CharField = CharField( + max_length=32, choices=UncertaintyMethod.choices, default=UncertaintyMethod.NONE + ) + aggregation_method: CharField = CharField( + max_length=32, choices=AggregationMethod.choices, default=AggregationMethod.FED_AVG + ) + # HINT: https://docs.djangoproject.com/en/4.2/topics/db/queries/#querying-jsonfield + options: JSONField = JSONField(default=dict, encoder=DjangoJSONEncoder) + locked: BooleanField = BooleanField(default=False) + + +@receiver(post_save, sender=Training) +def post_save_training(sender, instance, created, *args, **kwargs): + """ + Ensure the correct `target_num_updates` is set. + + This method is called after saving a training instance. + It is used to set the `target_num_updates` to the correct value if this training instance is newly created + and daisy chaining is enabled. + """ + if not created: + return + daisy_chain_period = instance.options.get("daisy_chain_period", 0) + if daisy_chain_period <= 0: + return + instance.target_num_updates = instance.target_num_updates * daisy_chain_period + instance.save() diff --git a/fl_server_core/models/user.py b/fl_server_core/models/user.py new file mode 100644 index 0000000..760f6f5 --- /dev/null +++ b/fl_server_core/models/user.py @@ -0,0 +1,25 @@ +from django.conf import settings +from django.contrib.auth.models import AbstractUser +from django.db.models import BooleanField, URLField, UUIDField +from django.db.models.signals import post_save +from django.dispatch import receiver +from rest_framework.authtoken.models import Token +from uuid import UUID, uuid4 + + +class NotificationReceiver(): + id: UUID + message_endpoint: str + + +class User(AbstractUser, NotificationReceiver): + id: UUIDField = UUIDField(primary_key=True, editable=False, default=uuid4) + actor: BooleanField = BooleanField(default=False) + client: BooleanField = BooleanField(default=False) + message_endpoint: URLField = URLField() + + +@receiver(post_save, sender=settings.AUTH_USER_MODEL) +def create_auth_token(sender, instance=None, created=False, **kwargs): + if created: + Token.objects.create(user=instance) diff --git a/fl_server_core/tests/__init__.py b/fl_server_core/tests/__init__.py new file mode 100644 index 0000000..b4c88c3 --- /dev/null +++ b/fl_server_core/tests/__init__.py @@ -0,0 +1,4 @@ +from .dummy import BASE_URL, Dummy + + +__all__ = ["BASE_URL", "Dummy"] diff --git a/fl_server_core/tests/dummy.py b/fl_server_core/tests/dummy.py new file mode 100644 index 0000000..584473e --- /dev/null +++ b/fl_server_core/tests/dummy.py @@ -0,0 +1,162 @@ +import base64 +from django.contrib.auth.models import Group +from django.test import Client +from faker import Faker +from logging import getLogger +import pickle +import random +import torch +from typing import Type +from uuid import uuid4 + +from fl_server_core.models import GlobalModel, LocalModel, Metric, Model, Training, User +from fl_server_core.models.training import AggregationMethod, TrainingState, UncertaintyMethod + + +BASE_URL: str = "http://127.0.0.1:8000/api" + + +class Dummy: + + fake = Faker("en_US") + _logger = getLogger("fl.server.core") + + @classmethod + def create_model(cls, model_cls: Type[Model] = GlobalModel, **kwargs) -> GlobalModel: + args = dict( + name=f"{cls.fake.company()} Model", + description=f"Model created for {cls.fake.catch_phrase()}.", + round=0, + weights=pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + ) + args.update(kwargs) + if "owner" not in args: + args["owner"] = cls.create_user() + model = model_cls.objects.create(**args) + cls._logger.debug(f"Creating Dummy Model with id {model.id}") + return model + + @classmethod + def create_model_update(cls, **kwargs) -> LocalModel: + args = dict( + round=1, + weights=pickle.dumps(torch.nn.Sequential( + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() + )), + sample_size=10, + ) + args.update(kwargs) + args["base_model"] = args["base_model"] if args.__contains__("base_model") else cls.create_model() + args["owner"] = args["owner"] if args.__contains__("owner") else cls.create_client() + model = LocalModel.objects.create(**args) + cls._logger.debug(f"Creating Dummy Model Update with id {model.id}") + return model + + @classmethod + def create_broken_model(cls, **kwargs): + args = dict(weights=pickle.dumps("I am not a torch.nn.Module!")) + args.update(kwargs) + return cls.create_model(**args) + + @classmethod + def create_training(cls, **kwargs): + args = dict( + state=TrainingState.INITIAL, + target_num_updates=0, + uncertainty_method=UncertaintyMethod.NONE, + aggregation_method=AggregationMethod.FED_AVG, + ) + args.update(kwargs) + if args.__contains__("actor"): + model_kwargs = {"owner": args["actor"]} + else: + args["actor"] = cls.create_actor() + model_kwargs = {} + + args["model"] = args["model"] if args.__contains__("model") else cls.create_model(**model_kwargs) + participants = args.pop("participants", [cls.create_client(), cls.create_client()]) + training = Training.objects.create(**args) + cls._logger.debug(f"Creating Dummy Training with id {training.id}") + for participant in participants: + training.participants.add(participant) + training.save() + return training + + @classmethod + def _create_user(cls, **kwargs) -> User: + user = User.objects.create(**kwargs) + user.set_password(kwargs["password"]) + user.save() + cls._logger.debug(f"Creating Dummy User with id {user.id}") + return user + + @classmethod + def create_user(cls, **kwargs): + args = dict( + message_endpoint="https://" + cls.fake.safe_email().replace("@", "."), + actor=False, + client=False, + username=cls.fake.user_name(), + first_name=cls.fake.first_name(), + last_name=cls.fake.last_name(), + email=cls.fake.safe_email(), + password="secret", + ) + args.update(kwargs) + return cls._create_user(**args) + + @classmethod + def create_actor(cls, **kwargs): + kwargs["actor"] = True + kwargs["client"] = False + return cls.create_user(**kwargs) + + @classmethod + def create_client(cls, **kwargs): + kwargs["actor"] = False + kwargs["client"] = True + return cls.create_user(**kwargs) + + @classmethod + def create_user_and_authenticate(cls, client: Client, **kwargs): + args = dict( + username=cls.fake.user_name(), + password="secret", + ) + args.update(kwargs) + credentials = base64.b64encode( + f'{args["username"]}:{args["password"]}'.encode("utf-8") + ).decode("utf-8") + client.defaults["HTTP_AUTHORIZATION"] = "Basic " + credentials + return cls._create_user(**args) + + @classmethod + def create_group(cls, **kwargs): + args = dict( + name=cls.fake.catch_phrase(), + ) + args.update(kwargs) + group = Group.objects.create(**args) + cls._logger.debug(f"Creating Dummy Group with id {group.id}") + return group + + @classmethod + def create_metric(cls, **kwargs): + epoch = random.randint(0, 100) + args = dict( + identifier=str(uuid4()).split("-")[0], + key="NLLoss", + step=epoch, + ) + args.update(kwargs) + if not args.__contains__("value_float") and not args.__contains__("value_binary"): + args["value_float"] = 27.354/(epoch+1) + random.randint(0, 100) / 100 + args["model"] = args["model"] if args.__contains__("model") else cls.create_model() + args["reporter"] = args["reporter"] if args.__contains__("reporter") else cls.create_client() + metric = Metric.objects.create(**args) + cls._logger.debug(f"Creating Dummy Metric with id {metric.id}") + return metric diff --git a/fl_server_core/tests/test_metric.py b/fl_server_core/tests/test_metric.py new file mode 100644 index 0000000..6792e3e --- /dev/null +++ b/fl_server_core/tests/test_metric.py @@ -0,0 +1,75 @@ +from django.test import TestCase + +from .dummy import Dummy + + +class MetricTest(TestCase): + + def test_value_property_for_float(self): + value = 0.42 + metric = Dummy.create_metric(value_float=value) + self.assertTrue(metric.is_float()) + self.assertFalse(metric.is_binary()) + self.assertEqual(value, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(None, metric.value_binary) + value = 0.84 + metric.value_float = value + self.assertEqual(value, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(None, metric.value_binary) + value = 0.98 + metric.value = value + self.assertEqual(value, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(None, metric.value_binary) + del metric.value + self.assertEqual(None, metric.value_float) + self.assertEqual(None, metric.value) + self.assertEqual(None, metric.value_binary) + + def test_value_property_for_integer(self): + value = 42 + metric = Dummy.create_metric(value_float=value) + self.assertTrue(metric.is_float()) + self.assertFalse(metric.is_binary()) + self.assertEqual(value, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(None, metric.value_binary) + value = 98 + metric.value_float = value + self.assertEqual(value, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(None, metric.value_binary) + value = 4711 + metric.value = value + self.assertEqual(value, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(None, metric.value_binary) + del metric.value + self.assertEqual(None, metric.value_float) + self.assertEqual(None, metric.value) + self.assertEqual(None, metric.value_binary) + + def test_value_property_for_binary(self): + value = b"Hello World!" + metric = Dummy.create_metric(value_binary=value) + self.assertFalse(metric.is_float()) + self.assertTrue(metric.is_binary()) + self.assertEqual(None, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(value, metric.value_binary) + value = b"You are looking especially amazing today!" + metric.value_binary = value + self.assertEqual(None, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(value, metric.value_binary) + value = b"Chuck Norris can take a screenshot of his blue screen." + metric.value = value + self.assertEqual(None, metric.value_float) + self.assertEqual(value, metric.value) + self.assertEqual(value, metric.value_binary) + del metric.value + self.assertEqual(None, metric.value_float) + self.assertEqual(None, metric.value) + self.assertEqual(None, metric.value_binary) diff --git a/fl_server_core/tests/test_model.py b/fl_server_core/tests/test_model.py new file mode 100644 index 0000000..eb3c0ee --- /dev/null +++ b/fl_server_core/tests/test_model.py @@ -0,0 +1,30 @@ +from django.test import TestCase + +from ..models.model import clone_model +from .dummy import Dummy + + +class ModelTest(TestCase): + + def test_is_global_model(self): + model = Dummy.create_model() + self.assertTrue(model.is_global_model()) + self.assertFalse(model.is_local_model()) + + def test_is_local_model(self): + model = Dummy.create_model_update() + self.assertFalse(model.is_global_model()) + self.assertTrue(model.is_local_model()) + + def test_clone_model(self): + model1 = Dummy.create_model() + model2 = clone_model(model1) + model2.description = "This Model was duplicated" + model1.description = "This is the original" + + self.assertEqual(model1.name, model2.name) + self.assertEqual(model2.description, "This Model was duplicated") + self.assertEqual(model1.description, "This is the original") + self.assertEqual(model1.owner, model2.owner) + self.assertEqual(model1.round, model2.round) + self.assertEqual(model1.weights, model2.weights) diff --git a/fl_server_core/tests/test_training.py b/fl_server_core/tests/test_training.py new file mode 100644 index 0000000..c4eed4b --- /dev/null +++ b/fl_server_core/tests/test_training.py @@ -0,0 +1,14 @@ +from django.test import TestCase + +from .dummy import Dummy + + +class TrainingTest(TestCase): + + def test_target_num_updates_without_daisy_chain_period(self): + training = Dummy.create_training(target_num_updates=42) + self.assertEqual(42, training.target_num_updates) + + def test_target_num_updates_with_daisy_chain_period(self): + training = Dummy.create_training(target_num_updates=42, options=dict(daisy_chain_period=5)) + self.assertEqual(42 * 5, training.target_num_updates) diff --git a/fl_server_core/utils/locked_atomic_transaction.py b/fl_server_core/utils/locked_atomic_transaction.py new file mode 100644 index 0000000..462fd38 --- /dev/null +++ b/fl_server_core/utils/locked_atomic_transaction.py @@ -0,0 +1,31 @@ +from contextlib import contextmanager +from django.db import transaction +from django.db.models import Model +from django.db.transaction import get_connection + + +@contextmanager +def locked_atomic_transaction(*models: Model): + """ + Create a locked atomic transaction for a model. + + Opens an atomic transaction and locks a specific table until the context is closed. + Original code taken from: + + Attention: "LOCK TABLE" is not available in SQLite, so this function will not work with SQLite. + A possible fix would be to skip the "LOCK TABLE" part for SQLite since transactions in SQLite + lock the whole database anyway. At least if "BEGIN IMMEDIATE TRANSACTION" or + "BEGIN EXCLUSIVE TRANSACTION" is used. The default "BEGIN TRANSACTION" or "BEGIN DEFERRED TRANSACTION" + does not lock the database until the first database access. + + Args: + model (Model): database model + """ + with transaction.atomic(): + tables = ", ".join(map(lambda model: model._meta.db_table, models)) + cursor = get_connection().cursor() + cursor.execute(f"LOCK TABLE {tables}") + try: + yield + finally: + cursor.close() diff --git a/fl_server_core/utils/strings.py b/fl_server_core/utils/strings.py new file mode 100644 index 0000000..9f8033b --- /dev/null +++ b/fl_server_core/utils/strings.py @@ -0,0 +1,30 @@ +from typing import Optional + + +def str2bool(value: Optional[str], fallback: Optional[bool] = None) -> bool: + """ + Convert string to boolean. + + Sources: + + Args: + value (str): value to convert + fallback (Optional[bool], optional): Fallback if value can not be converted. + If None raise ValueError. Defaults to None. + + Raises: + ValueError: value can not be converted and no fallback is specified + + Returns: + bool: boolean interpretation of value + """ + if value is not None: + if isinstance(value, bool): + return value + if value.lower() in ("yes", "true", "t", "y", "1"): + return True + elif value.lower() in ("no", "false", "f", "n", "0"): + return False + if fallback is None: + raise ValueError(f"Can not convert '{value}' to boolean.") + return fallback diff --git a/fl_server_core/utils/torch_pickle.py b/fl_server_core/utils/torch_pickle.py new file mode 100644 index 0000000..9213c2a --- /dev/null +++ b/fl_server_core/utils/torch_pickle.py @@ -0,0 +1,46 @@ +from logging import getLogger +import pickle +from torch import Tensor +from torch.nn import Module +from typing import Any, Tuple, Type, TypeVar + +from ..exceptions import TorchDeserializationException + + +T = TypeVar("T") + + +def to_torch(obj: Any, supported_types: Type[T] | Tuple[Type[T], ...]): + t_obj = pickle.loads(obj) + if isinstance(t_obj, supported_types): + return t_obj + getLogger("fl.server").error("Unpickled object is not a torch object.") + raise TorchDeserializationException("Unpickled object is not a torch object.") + + +def from_torch(obj: Any, *args, **kwargs) -> bytes: + return pickle.dumps(obj, *args, **kwargs) + + +def to_torch_module_or_tensor(obj: Any) -> Module | Tensor: + return to_torch(obj, (Module, Tensor)) + + +def from_torch_module_or_tensor(obj: Module | Tensor, *args, **kwargs) -> bytes: + return from_torch(obj, *args, **kwargs) + + +def to_torch_module(obj: Any) -> Module: + return to_torch(obj, Module) + + +def from_torch_module(obj: Module, *args, **kwargs) -> bytes: + return from_torch(obj, *args, **kwargs) + + +def to_torch_tensor(obj: Any) -> Tensor: + return to_torch(obj, Tensor) + + +def from_torch_tensor(obj: Tensor, *args, **kwargs) -> bytes: + return from_torch(obj, *args, **kwargs) diff --git a/licenses/license.txt b/licenses/license.txt new file mode 100644 index 0000000..4044b4e --- /dev/null +++ b/licenses/license.txt @@ -0,0 +1,10915 @@ +Django +4.0.10 +BSD License +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Faker +20.1.0 +MIT License +Copyright (c) 2012 Daniele Faraglia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Jinja2 +3.1.3 +BSD License +Copyright 2007 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +MarkupSafe +2.1.4 +BSD License +Copyright 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +PyYAML +6.0.1 +MIT License +Copyright (c) 2017-2021 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +amqp +5.2.0 +BSD License +Copyright (c) 2015-2016 Ask Solem & contributors. All rights reserved. +Copyright (c) 2012-2014 GoPivotal, Inc. All rights reserved. +Copyright (c) 2009, 2010, 2011, 2012 Ask Solem, and individual contributors. All rights reserved. +Copyright (C) 2007-2008 Barry Pederson . All rights reserved. + +py-amqp is licensed under The BSD License (3 Clause, also known as +the new BSD license). The license is an OSI approved Open Source +license and is GPL-compatible(1). + +The license text can also be found here: +http://www.opensource.org/licenses/BSD-3-Clause + +License +======= + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ask Solem, nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ask Solem OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Footnotes +========= +(1) A GPL-compatible license makes it possible to + combine Celery with other software that is released + under the GPL, it does not mean that we're distributing + Celery under the GPL license. The BSD license, unlike the GPL, + let you distribute a modified version without making your + changes open source. + + +ansi +0.3.7 +MIT License +Copyright (c) 2015 Wijnand Modderman-Lenstra + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +asgiref +3.7.2 +BSD License +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +async-timeout +4.0.3 +Apache Software License +Copyright 2016-2020 aio-libs collaboration. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +attrs +23.2.0 +MIT License +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack and the attrs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +billiard +4.2.0 +BSD License +Copyright (c) 2006-2008, R Oudkerk and Contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of author nor the names of any contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + + + +celery +5.3.6 +BSD License +Copyright (c) 2017-2026 Asif Saif Uddin, core team & contributors. All rights reserved. +Copyright (c) 2015-2016 Ask Solem & contributors. All rights reserved. +Copyright (c) 2012-2014 GoPivotal, Inc. All rights reserved. +Copyright (c) 2009, 2010, 2011, 2012 Ask Solem, and individual contributors. All rights reserved. + +Celery is licensed under The BSD License (3 Clause, also known as +the new BSD license). The license is an OSI approved Open Source +license and is GPL-compatible(1). + +The license text can also be found here: +http://www.opensource.org/licenses/BSD-3-Clause + +License +======= + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ask Solem, nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ask Solem OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +Documentation License +===================== + +The documentation portion of Celery (the rendered contents of the +"docs" directory of a software distribution or checkout) is supplied +under the "Creative Commons Attribution-ShareAlike 4.0 +International" (CC BY-SA 4.0) License as described by +https://creativecommons.org/licenses/by-sa/4.0/ + +Footnotes +========= +(1) A GPL-compatible license makes it possible to + combine Celery with other software that is released + under the GPL, it does not mean that we're distributing + Celery under the GPL license. The BSD license, unlike the GPL, + let you distribute a modified version without making your + changes open source. + + +certifi +2023.11.17 +Mozilla Public License 2.0 (MPL 2.0) +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ + + +charset-normalizer +3.3.2 +MIT License +MIT License + +Copyright (c) 2019 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +click +8.1.7 +BSD License +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +click-didyoumean +0.3.0 +MIT License +Copyright (c) 2016 Timo Furrer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +click-plugins +1.1.1 +BSD License +New BSD License + +Copyright (c) 2015-2019, Kevin D. Wurster, Sean C. Gillies +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither click-plugins nor the names of its contributors may not be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +click-repl +0.3.0 +MIT +Copyright (c) 2014-2015 Markus Unterwaditzer & contributors. +Copyright (c) 2016-2026 Asif Saif Uddin & contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +cmake +3.28.1 +Apache Software License; BSD License +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +django-cors-headers +4.1.0 +MIT License +MIT License + +Copyright (c) 2017 Otto Yiu (https://ottoyiu.com) and other contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +django-polymorphic +3.1.0 +BSD License +Copyright (c) 2009 or later by the individual contributors. +Please see the AUTHORS file. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +django-redis +5.2.0 +BSD License +Copyright (c) 2011-2016 Andrey Antukh +Copyright (c) 2011 Sean Bleier + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS`` AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +djangorestframework +3.14.0 +BSD License +# License + +Copyright © 2011-present, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +dlr-logging +0.0.3.dev0 +Apache Software License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +docstring-parser +0.15 +MIT License +The MIT License (MIT) + +Copyright (c) 2018 Marcin Kurczewski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +drf-spectacular +0.26.5 +BSD License +Copyright © 2011-present, Encode OSS Ltd. +Copyright © 2019-2021, T. Franzel , Cashlink Technologies GmbH. +Copyright © 2021-present, T. Franzel . + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +filelock +3.13.1 +The Unlicense (Unlicense) +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + + +fl-demonstrator +0.0.1.dev2 +Apache Software License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +idna +3.6 +BSD License +BSD 3-Clause License + +Copyright (c) 2013-2023, Kim Davies and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +inflection +0.5.1 +MIT License +Copyright (C) 2012-2020 Janne Vanhala + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +jsonschema +4.21.1 +MIT License +Copyright (c) 2013 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +jsonschema-specifications +2023.12.1 +MIT License +Copyright (c) 2022 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +kombu +5.3.5 +BSD License +Copyright (c) 2015-2016 Ask Solem & contributors. All rights reserved. +Copyright (c) 2012-2014 GoPivotal Inc & contributors. All rights reserved. +Copyright (c) 2009-2012, Ask Solem & contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ask Solem nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ask Solem OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +lit +17.0.6 +Apache Software License +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + + + +marshmallow +3.19.0 +MIT License +Copyright 2021 Steven Loria and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +mpmath +1.3.0 +BSD License +Copyright (c) 2005-2021 Fredrik Johansson and mpmath contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + + +networkx +3.2.1 +BSD License +NetworkX is distributed with the 3-clause BSD license. + +:: + + Copyright (C) 2004-2023, NetworkX Developers + Aric Hagberg + Dan Schult + Pieter Swart + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NetworkX Developers nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +numpy +1.24.4 +BSD License +Copyright (c) 2005-2022, NumPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NumPy Developers nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +This binary distribution of NumPy also bundles the following software: + + +Name: OpenBLAS +Files: .libs/libopenb*.so +Description: bundled as a dynamically linked library +Availability: https://github.com/xianyi/OpenBLAS/ +License: 3-clause BSD + Copyright (c) 2011-2014, The OpenBLAS Project + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. Neither the name of the OpenBLAS project nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Name: LAPACK +Files: .libs/libopenb*.so +Description: bundled in OpenBLAS +Availability: https://github.com/xianyi/OpenBLAS/ +License 3-clause BSD + Copyright (c) 1992-2013 The University of Tennessee and The University + of Tennessee Research Foundation. All rights + reserved. + Copyright (c) 2000-2013 The University of California Berkeley. All + rights reserved. + Copyright (c) 2006-2013 The University of Colorado Denver. All rights + reserved. + + $COPYRIGHT$ + + Additional copyrights may follow + + $HEADER$ + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + + - Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + The copyright holders provide no reassurances that the source code + provided does not infringe any patent, copyright, or any other + intellectual property rights of third parties. The copyright holders + disclaim any liability to any recipient for claims brought against + recipient by any third party for infringement of that parties + intellectual property rights. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Name: GCC runtime library +Files: .libs/libgfortran*.so +Description: dynamically linked to files compiled with gcc +Availability: https://gcc.gnu.org/viewcvs/gcc/ +License: GPLv3 + runtime exception + Copyright (C) 2002-2017 Free Software Foundation, Inc. + + Libgfortran 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, or (at your option) + any later version. + + Libgfortran 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. + + Under Section 7 of GPL version 3, you are granted additional + permissions described in the GCC Runtime Library Exception, version + 3.1, as published by the Free Software Foundation. + + You should have received a copy of the GNU General Public License and + a copy of the GCC Runtime Library Exception along with this program; + see the files COPYING3 and COPYING.RUNTIME respectively. If not, see + . + +---- + +Full text of license texts referred to above follows (that they are +listed below does not necessarily imply the conditions apply to the +present binary release): + +---- + +GCC RUNTIME LIBRARY EXCEPTION + +Version 3.1, 31 March 2009 + +Copyright (C) 2009 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This GCC Runtime Library Exception ("Exception") is an additional +permission under section 7 of the GNU General Public License, version +3 ("GPLv3"). It applies to a given file (the "Runtime Library") that +bears a notice placed by the copyright holder of the file stating that +the file is governed by GPLv3 along with this Exception. + +When you use GCC to compile a program, GCC may combine portions of +certain GCC header files and runtime libraries with the compiled +program. The purpose of this Exception is to allow compilation of +non-GPL (including proprietary) programs to use, in this way, the +header files and runtime libraries covered by this Exception. + +0. Definitions. + +A file is an "Independent Module" if it either requires the Runtime +Library for execution after a Compilation Process, or makes use of an +interface provided by the Runtime Library, but is not otherwise based +on the Runtime Library. + +"GCC" means a version of the GNU Compiler Collection, with or without +modifications, governed by version 3 (or a specified later version) of +the GNU General Public License (GPL) with the option of using any +subsequent versions published by the FSF. + +"GPL-compatible Software" is software whose conditions of propagation, +modification and use would permit combination with GCC in accord with +the license of GCC. + +"Target Code" refers to output from any compiler for a real or virtual +target processor architecture, in executable form or suitable for +input to an assembler, loader, linker and/or execution +phase. Notwithstanding that, Target Code does not include data in any +format that is used as a compiler intermediate representation, or used +for producing a compiler intermediate representation. + +The "Compilation Process" transforms code entirely represented in +non-intermediate languages designed for human-written code, and/or in +Java Virtual Machine byte code, into Target Code. Thus, for example, +use of source code generators and preprocessors need not be considered +part of the Compilation Process, since the Compilation Process can be +understood as starting with the output of the generators or +preprocessors. + +A Compilation Process is "Eligible" if it is done using GCC, alone or +with other GPL-compatible software, or if it is done without using any +work based on GCC. For example, using non-GPL-compatible Software to +optimize any GCC intermediate representations would not qualify as an +Eligible Compilation Process. + +1. Grant of Additional Permission. + +You have permission to propagate a work of Target Code formed by +combining the Runtime Library with Independent Modules, even if such +propagation would otherwise violate the terms of GPLv3, provided that +all Target Code was generated by Eligible Compilation Processes. You +may then convey such a combination under terms of your choice, +consistent with the licensing of the Independent Modules. + +2. No Weakening of GCC Copyleft. + +The availability of this Exception does not imply any general +presumption that third-party software is unaffected by the copyleft +requirements of the license of GCC. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + + +nvidia-cublas-cu11 +11.10.3.66 +Other/Proprietary License +UNKNOWN + +nvidia-cuda-cupti-cu11 +11.7.101 +Other/Proprietary License +UNKNOWN + +nvidia-cuda-nvrtc-cu11 +11.7.99 +Other/Proprietary License +UNKNOWN + +nvidia-cuda-runtime-cu11 +11.7.99 +Other/Proprietary License +UNKNOWN + +nvidia-cudnn-cu11 +8.5.0.96 +Other/Proprietary License +UNKNOWN + +nvidia-cufft-cu11 +10.9.0.58 +Other/Proprietary License +UNKNOWN + +nvidia-curand-cu11 +10.2.10.91 +Other/Proprietary License +UNKNOWN + +nvidia-cusolver-cu11 +11.4.0.1 +Other/Proprietary License +UNKNOWN + +nvidia-cusparse-cu11 +11.7.4.91 +Other/Proprietary License +UNKNOWN + +nvidia-nccl-cu11 +2.14.3 +Other/Proprietary License +UNKNOWN + +nvidia-nvtx-cu11 +11.7.91 +Other/Proprietary License +UNKNOWN + +packaging +23.2 +Apache Software License; BSD License +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. + + +prompt-toolkit +3.0.43 +BSD License +Copyright (c) 2014, Jonathan Slenders +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +psycopg2-binary +2.9.9 +GNU Library or Lesser General Public License (LGPL) +psycopg2 and the LGPL +--------------------- + +psycopg2 is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +psycopg2 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 Lesser General Public +License for more details. + +In addition, as a special exception, the copyright holders give +permission to link this program with the OpenSSL library (or with +modified versions of OpenSSL that use the same license as OpenSSL), +and distribute linked combinations including the two. + +You must obey the GNU Lesser General Public License in all respects for +all of the code used other than OpenSSL. If you modify file(s) with this +exception, you may extend this exception to your version of the file(s), +but you are not obligated to do so. If you do not wish to do so, delete +this exception statement from your version. If you delete this exception +statement from all source files in the program, then also delete it here. + +You should have received a copy of the GNU Lesser General Public License +along with psycopg2 (see the doc/ directory.) +If not, see . + + +Alternative licenses +-------------------- + +The following BSD-like license applies (at your option) to the files following +the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``: + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product documentation + would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + + +python-dateutil +2.8.2 +Apache Software License; BSD License +Copyright 2017- Paul Ganssle +Copyright 2017- dateutil contributors (see AUTHORS file) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +The above license applies to all contributions after 2017-12-01, as well as +all contributions that have been re-licensed (see AUTHORS file for the list of +contributors who have re-licensed their code). +-------------------------------------------------------------------------------- +dateutil - Extensions to the standard Python datetime module. + +Copyright (c) 2003-2011 - Gustavo Niemeyer +Copyright (c) 2012-2014 - Tomi Pieviläinen +Copyright (c) 2014-2016 - Yaron de Leeuw +Copyright (c) 2015- - Paul Ganssle +Copyright (c) 2015- - dateutil contributors (see AUTHORS file) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The above BSD License Applies to all code, even that also covered by Apache 2.0. + +pytz +2023.3.post1 +MIT License +Copyright (c) 2003-2019 Stuart Bishop + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +redis +5.0.1 +MIT License +MIT License + +Copyright (c) 2022-2023, Redis, inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +referencing +0.32.1 +MIT License +Copyright (c) 2022 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +requests +2.31.0 +Apache Software License + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + +rpds-py +0.17.1 +MIT License +Copyright (c) 2023 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +six +1.16.0 +MIT License +Copyright (c) 2010-2020 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +sqlparse +0.4.4 +BSD License +Copyright (c) 2016, Andi Albrecht +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the authors nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +sympy +1.12 +BSD License +Copyright (c) 2006-2023 SymPy Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of SymPy nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +-------------------------------------------------------------------------------- + +Patches that were taken from the Diofant project (https://github.com/diofant/diofant) +are licensed as: + +Copyright (c) 2006-2018 SymPy Development Team, + 2013-2023 Sergey B Kirpichev + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of Diofant or SymPy nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +-------------------------------------------------------------------------------- + +Submodules taken from the multipledispatch project (https://github.com/mrocklin/multipledispatch) +are licensed as: + +Copyright (c) 2014 Matthew Rocklin + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of multipledispatch nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +-------------------------------------------------------------------------------- + +The files under the directory sympy/parsing/autolev/tests/pydy-example-repo +are directly copied from PyDy project and are licensed as: + +Copyright (c) 2009-2023, PyDy Authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of this project nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL PYDY AUTHORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +The files under the directory sympy/parsing/latex +are directly copied from latex2sympy project and are licensed as: + +Copyright 2016, latex2sympy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +torch +2.0.1 +BSD License +From PyTorch: + +Copyright (c) 2016- Facebook, Inc (Adam Paszke) +Copyright (c) 2014- Facebook, Inc (Soumith Chintala) +Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) +Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) +Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) +Copyright (c) 2011-2013 NYU (Clement Farabet) +Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston) +Copyright (c) 2006 Idiap Research Institute (Samy Bengio) +Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz) + +From Caffe2: + +Copyright (c) 2016-present, Facebook Inc. All rights reserved. + +All contributions by Facebook: +Copyright (c) 2016 Facebook Inc. + +All contributions by Google: +Copyright (c) 2015 Google Inc. +All rights reserved. + +All contributions by Yangqing Jia: +Copyright (c) 2015 Yangqing Jia +All rights reserved. + +All contributions by Kakao Brain: +Copyright 2019-2020 Kakao Brain + +All contributions by Cruise LLC: +Copyright (c) 2022 Cruise LLC. +All rights reserved. + +All contributions from Caffe: +Copyright(c) 2013, 2014, 2015, the respective contributors +All rights reserved. + +All other contributions: +Copyright(c) 2015, 2016 the respective contributors +All rights reserved. + +Caffe2 uses a copyright model similar to Caffe: each contributor holds +copyright over their contributions to Caffe2. The project versioning records +all such contribution and copyright details. If a contributor wants to further +mark their specific copyright on a particular contribution, they should +indicate their copyright solely in the commit message of the change when it is +committed. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories America + and IDIAP Research Institute nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +The Pytorch repository and source distributions bundle several libraries that are +compatibly licensed. We list these here. + +Name: DCGM +License: Apache-2.0 +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/DCGM + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/DCGM/LICENSE + +Name: FP16 +License: MIT +Files: third_party/FP16 + For details, see the files concatenated below: third_party/FP16/LICENSE + +Name: FXdiv +License: MIT +Files: third_party/FXdiv + For details, see the files concatenated below: third_party/FXdiv/LICENSE + +Name: NNPACK +License: BSD-2-Clause +Files: third_party/NNPACK + For details, see the files concatenated below: third_party/NNPACK/LICENSE + +Name: QNNPACK +License: BSD-3-Clause +Files: third_party/QNNPACK + For details, see the files concatenated below: third_party/QNNPACK/LICENSE + +Name: VulkanMemoryAllocator +License: MIT +Files: third_party/VulkanMemoryAllocator + For details, see the files concatenated below: third_party/VulkanMemoryAllocator/LICENSE.txt + +Name: XNNPACK +License: BSD-3-Clause +Files: third_party/XNNPACK + For details, see the files concatenated below: third_party/XNNPACK/LICENSE + +Name: benchmark +License: Apache-2.0 +Files: third_party/benchmark, + third_party/onnx/third_party/benchmark, + third_party/onnx-tensorrt/third_party/onnx/third_party/benchmark, + third_party/protobuf/third_party/benchmark + For details, see the files concatenated below: third_party/benchmark/LICENSE, + third_party/onnx/third_party/benchmark/LICENSE, + third_party/onnx-tensorrt/third_party/onnx/third_party/benchmark/LICENSE, + third_party/protobuf/third_party/benchmark/LICENSE + +Name: clog +License: BSD-2-Clause +Files: third_party/QNNPACK/deps/clog, + third_party/cpuinfo/deps/clog, + third_party/fbgemm/third_party/cpuinfo/deps/clog + For details, see the files concatenated below: third_party/QNNPACK/deps/clog/LICENSE, + third_party/cpuinfo/deps/clog/LICENSE, + third_party/fbgemm/third_party/cpuinfo/deps/clog/LICENSE + +Name: colorama +License: BSD-3-Clause +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/DCGM/testing/python3/libs_3rdparty/colorama + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/DCGM/testing/python3/libs_3rdparty/colorama/LICENSE.txt + +Name: cpplint +License: BSD-3-Clause +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/json/third_party/cpplint, + third_party/nlohmann/tools/cpplint + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/json/third_party/cpplint/LICENSE, + third_party/nlohmann/tools/cpplint/LICENSE + +Name: cpr +License: MIT +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/cpr + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/cpr/LICENSE + +Name: cpuinfo +License: BSD-2-Clause +Files: third_party/cpuinfo, + third_party/fbgemm/third_party/cpuinfo + For details, see the files concatenated below: third_party/cpuinfo/LICENSE, + third_party/fbgemm/third_party/cpuinfo/LICENSE + +Name: cudnn_frontend +License: MIT +Files: third_party/cudnn_frontend + For details, see the files concatenated below: third_party/cudnn_frontend/LICENSE.txt + +Name: cutlass +License: BSD-3-Clause +Files: third_party/cutlass, + third_party/fbgemm/third_party/cutlass + For details, see the files concatenated below: third_party/cutlass/LICENSE.txt, + third_party/fbgemm/third_party/cutlass/LICENSE.txt + +Name: dart +License: Apache-2.0 +Files: third_party/flatbuffers/dart + For details, see the files concatenated below: third_party/flatbuffers/dart/LICENSE + +Name: doctest +License: MIT +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/json/test/thirdparty/doctest, + third_party/nlohmann/tests/thirdparty/doctest + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/json/test/thirdparty/doctest/LICENSE.txt, + third_party/nlohmann/tests/thirdparty/doctest/LICENSE.txt + +Name: dynolog +License: MIT +Files: third_party/kineto/libkineto/third_party/dynolog + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/LICENSE + +Name: eigen +License: BSD-3-Clause +Files: third_party/eigen + For details, see the files concatenated below: third_party/eigen/COPYING.BSD + +Name: enum +License: BSD-3-Clause +Files: third_party/python-enum/enum + For details, see the files concatenated below: third_party/python-enum/enum/LICENSE + +Name: fbgemm +License: BSD-3-Clause +Files: third_party/fbgemm + For details, see the files concatenated below: third_party/fbgemm/LICENSE + +Name: flatbuffers +License: Apache-2.0 +Files: third_party/flatbuffers + For details, see the files concatenated below: third_party/flatbuffers/LICENSE.txt + +Name: fmt +License: MIT with exception +Files: third_party/fmt, + third_party/kineto/libkineto/third_party/dynolog/third_party/fmt, + third_party/kineto/libkineto/third_party/fmt + For details, see the files concatenated below: third_party/fmt/LICENSE.rst, + third_party/kineto/libkineto/third_party/dynolog/third_party/fmt/LICENSE.rst, + third_party/kineto/libkineto/third_party/fmt/LICENSE.rst + +Name: foxi +License: MIT +Files: third_party/foxi + For details, see the files concatenated below: third_party/foxi/LICENSE + +Name: gemmlowp +License: Apache-2.0 +Files: third_party/gemmlowp/gemmlowp + For details, see the files concatenated below: third_party/gemmlowp/gemmlowp/LICENSE + +Name: generator +License: Apache-2.0 +Files: third_party/fbgemm/third_party/googletest/googlemock/scripts/generator, + third_party/googletest/googlemock/scripts/generator, + third_party/kineto/libkineto/third_party/googletest/googlemock/scripts/generator, + third_party/protobuf/third_party/googletest/googlemock/scripts/generator, + third_party/tensorpipe/third_party/googletest/googlemock/scripts/generator + For details, see the files concatenated below: third_party/fbgemm/third_party/googletest/googlemock/scripts/generator/LICENSE, + third_party/googletest/googlemock/scripts/generator/LICENSE, + third_party/kineto/libkineto/third_party/googletest/googlemock/scripts/generator/LICENSE, + third_party/protobuf/third_party/googletest/googlemock/scripts/generator/LICENSE, + third_party/tensorpipe/third_party/googletest/googlemock/scripts/generator/LICENSE + +Name: gloo +License: BSD-3-Clause +Files: third_party/gloo + For details, see the files concatenated below: third_party/gloo/LICENSE + +Name: googlemock +License: BSD-3-Clause +Files: third_party/fbgemm/third_party/googletest/googlemock, + third_party/kineto/libkineto/third_party/googletest/googlemock, + third_party/protobuf/third_party/googletest/googlemock, + third_party/tensorpipe/third_party/googletest/googlemock + For details, see the files concatenated below: third_party/fbgemm/third_party/googletest/googlemock/LICENSE, + third_party/kineto/libkineto/third_party/googletest/googlemock/LICENSE, + third_party/protobuf/third_party/googletest/googlemock/LICENSE, + third_party/tensorpipe/third_party/googletest/googlemock/LICENSE + +Name: googletest +License: BSD-3-Clause +Files: third_party/fbgemm/third_party/googletest, + third_party/fbgemm/third_party/googletest/googletest, + third_party/googletest, + third_party/kineto/libkineto/third_party/dynolog/third_party/googletest, + third_party/kineto/libkineto/third_party/googletest, + third_party/kineto/libkineto/third_party/googletest/googletest, + third_party/protobuf/third_party/googletest, + third_party/protobuf/third_party/googletest/googletest, + third_party/tensorpipe/third_party/googletest, + third_party/tensorpipe/third_party/googletest/googletest + For details, see the files concatenated below: third_party/fbgemm/third_party/googletest/LICENSE, + third_party/fbgemm/third_party/googletest/googletest/LICENSE, + third_party/googletest/LICENSE, + third_party/kineto/libkineto/third_party/dynolog/third_party/googletest/LICENSE, + third_party/kineto/libkineto/third_party/googletest/LICENSE, + third_party/kineto/libkineto/third_party/googletest/googletest/LICENSE, + third_party/protobuf/third_party/googletest/LICENSE, + third_party/protobuf/third_party/googletest/googletest/LICENSE, + third_party/tensorpipe/third_party/googletest/LICENSE, + third_party/tensorpipe/third_party/googletest/googletest/LICENSE + +Name: gtest +License: BSD-3-Clause +Files: third_party/ideep/mkl-dnn/tests/gtest, + third_party/ideep/mkl-dnn/third_party/oneDNN/tests/gtests/gtest + For details, see the files concatenated below: third_party/ideep/mkl-dnn/tests/gtest/LICENSE, + third_party/ideep/mkl-dnn/third_party/oneDNN/tests/gtests/gtest/LICENSE + +Name: hipify_torch +License: MIT +Files: third_party/fbgemm/third_party/hipify_torch + For details, see the files concatenated below: third_party/fbgemm/third_party/hipify_torch/LICENSE.txt + +Name: ideep +License: MIT +Files: third_party/ideep + For details, see the files concatenated below: third_party/ideep/LICENSE + +Name: ios-cmake +License: BSD-3-Clause +Files: third_party/ios-cmake + For details, see the files concatenated below: third_party/ios-cmake/LICENSE + +Name: json +License: MIT +Files: third_party/cudnn_frontend/include/contrib/nlohmann/json + For details, see the files concatenated below: third_party/cudnn_frontend/include/contrib/nlohmann/json/LICENSE.txt + +Name: kineto +License: BSD-3-Clause +Files: third_party/kineto + For details, see the files concatenated below: third_party/kineto/LICENSE + +Name: libnop +License: Apache-2.0 +Files: third_party/tensorpipe/third_party/libnop + For details, see the files concatenated below: third_party/tensorpipe/third_party/libnop/LICENSE + +Name: libuv +License: MIT +Files: third_party/tensorpipe/third_party/libuv + For details, see the files concatenated below: third_party/tensorpipe/third_party/libuv/LICENSE + +Name: miniz-2.1.0 +License: MIT +Files: third_party/miniz-2.1.0 + For details, see the files concatenated below: third_party/miniz-2.1.0/LICENSE + +Name: mkl-dnn +License: Apache-2.0 +Files: third_party/ideep/mkl-dnn + For details, see the files concatenated below: third_party/ideep/mkl-dnn/LICENSE + +Name: nccl +License: BSD-3-Clause +Files: third_party/nccl/nccl + For details, see the files concatenated below: third_party/nccl/nccl/LICENSE.txt + +Name: neon2sse +License: BSD-Source-Code +Files: third_party/neon2sse + For details, see the files concatenated below: third_party/neon2sse/LICENSE + +Name: oneDNN +License: Apache-2.0 +Files: third_party/ideep/mkl-dnn/third_party/oneDNN + For details, see the files concatenated below: third_party/ideep/mkl-dnn/third_party/oneDNN/LICENSE + +Name: onnx +License: Apache-2.0 +Files: third_party/onnx + For details, see the files concatenated below: third_party/onnx/LICENSE + +Name: onnx +License: MIT +Files: third_party/onnx-tensorrt/third_party/onnx + For details, see the files concatenated below: third_party/onnx-tensorrt/third_party/onnx/LICENSE + +Name: onnx-tensorrt +License: MIT +Files: third_party/onnx-tensorrt + For details, see the files concatenated below: third_party/onnx-tensorrt/LICENSE + +Name: pfs +License: Apache-2.0 +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/pfs + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/pfs/LICENSE + +Name: protobuf +License: BSD-3-Clause +Files: third_party/protobuf + For details, see the files concatenated below: third_party/protobuf/LICENSE + +Name: psimd +License: MIT +Files: third_party/psimd + For details, see the files concatenated below: third_party/psimd/LICENSE + +Name: pthreadpool +License: BSD-2-Clause +Files: third_party/pthreadpool + For details, see the files concatenated below: third_party/pthreadpool/LICENSE + +Name: pybind11 +License: BSD-3-Clause +Files: third_party/onnx/third_party/pybind11, + third_party/onnx-tensorrt/third_party/onnx/third_party/pybind11, + third_party/pybind11, + third_party/tensorpipe/third_party/pybind11 + For details, see the files concatenated below: third_party/onnx/third_party/pybind11/LICENSE, + third_party/onnx-tensorrt/third_party/onnx/third_party/pybind11/LICENSE, + third_party/pybind11/LICENSE, + third_party/tensorpipe/third_party/pybind11/LICENSE + +Name: python-peachpy +License: BSD-2-Clause +Files: third_party/python-peachpy + For details, see the files concatenated below: third_party/python-peachpy/LICENSE.rst + +Name: python-six +License: MIT +Files: third_party/python-six + For details, see the files concatenated below: third_party/python-six/LICENSE + +Name: sleef +License: BSL-1.0 +Files: third_party/sleef + For details, see the files concatenated below: third_party/sleef/LICENSE.txt + +Name: swift +License: Apache-2.0 +Files: third_party/flatbuffers/swift + For details, see the files concatenated below: third_party/flatbuffers/swift/LICENSE + +Name: tb_plugin +License: BSD-3-Clause +Files: third_party/kineto/tb_plugin + For details, see the files concatenated below: third_party/kineto/tb_plugin/LICENSE + +Name: tbb +License: Apache-2.0 +Files: third_party/tbb + For details, see the files concatenated below: third_party/tbb/LICENSE + +Name: tensorpipe +License: BSD-3-Clause +Files: third_party/tensorpipe + For details, see the files concatenated below: third_party/tensorpipe/LICENSE.txt + +Name: test +License: MIT with exception +Files: third_party/kineto/libkineto/third_party/dynolog/third_party/cpr/test + For details, see the files concatenated below: third_party/kineto/libkineto/third_party/dynolog/third_party/cpr/test/LICENSE + +Name: zstd +License: BSD-3-Clause +Files: third_party/zstd + For details, see the files concatenated below: third_party/zstd/LICENSE + +third_party/kineto/libkineto/third_party/dynolog/third_party/DCGM/LICENSE +------------------------------------------------------------------------- +Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +third_party/FP16/LICENSE +------------------------ +The MIT License (MIT) + +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology +Copyright 2019 Google LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +third_party/FXdiv/LICENSE +------------------------- +The MIT License (MIT) + +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2016-2017 Marat Dukhan + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +third_party/NNPACK/LICENSE +-------------------------- +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2015-2017, Georgia Institute of Technology +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/QNNPACK/LICENSE +--------------------------- +BSD License + +For QNNPACK software + +Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/VulkanMemoryAllocator/LICENSE.txt +--------------------------------------------- +Copyright (c) 2017-2022 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +third_party/XNNPACK/LICENSE +--------------------------- +BSD License + +For XNNPACK software + +Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. +Copyright 2019 Google LLC + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/benchmark/LICENSE +----------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/onnx/third_party/benchmark/LICENSE +---------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/onnx-tensorrt/third_party/onnx/third_party/benchmark/LICENSE +------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/protobuf/third_party/benchmark/LICENSE +-------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/QNNPACK/deps/clog/LICENSE +------------------------------------- +Copyright (C) 2018 Marat Dukhan +Copyright (c) 2017-2018 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/cpuinfo/deps/clog/LICENSE +------------------------------------- +Copyright (C) 2018 Marat Dukhan +Copyright (c) 2017-2018 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/cpuinfo/deps/clog/LICENSE +-------------------------------------------------------- +Copyright (C) 2018 Marat Dukhan +Copyright (c) 2017-2018 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/DCGM/testing/python3/libs_3rdparty/colorama/LICENSE.txt +-------------------------------------------------------------------------------------------------------------------- +Copyright (c) 2010 Jonathan Hartley + +Released under the New BSD license (reproduced below), or alternatively you may +use this software under any OSI approved open source license such as those at +http://opensource.org/licenses/alphabetical + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name(s) of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +third_party/kineto/libkineto/third_party/dynolog/third_party/json/third_party/cpplint/LICENSE +--------------------------------------------------------------------------------------------- +cpplint.py and its corresponding unit tests are Copyright (C) 2009 Google Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/nlohmann/tools/cpplint/LICENSE +------------------------------------------ +cpplint.py and its corresponding unit tests are Copyright (C) 2009 Google Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/cpr/LICENSE +------------------------------------------------------------------------ +This license applies to everything except the contents of the "test" +directory and its subdirectories. + +MIT License + +Copyright (c) 2017-2021 Huu Nguyen +Copyright (c) 2022 libcpr and many other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +third_party/cpuinfo/LICENSE +--------------------------- +Copyright (c) 2019 Google LLC +Copyright (c) 2017-2018 Facebook Inc. +Copyright (C) 2012-2017 Georgia Institute of Technology +Copyright (C) 2010-2012 Marat Dukhan + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/cpuinfo/LICENSE +---------------------------------------------- +Copyright (c) 2019 Google LLC +Copyright (c) 2017-2018 Facebook Inc. +Copyright (C) 2012-2017 Georgia Institute of Technology +Copyright (C) 2010-2012 Marat Dukhan + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/cudnn_frontend/LICENSE.txt +-------------------------------------- +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +third_party/cutlass/LICENSE.txt +------------------------------- +Copyright (c) 2017 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: BSD-3-Clause + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/cutlass/LICENSE.txt +-------------------------------------------------- +Copyright (c) 2017 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: BSD-3-Clause + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/flatbuffers/dart/LICENSE +------------------------------------ +The code in lib/flat_buffers.dart is based on code that was releases under the +following license: + +Copyright 2012, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +To the extent permissible, the changes to that code and the other assets in +this package are licensed under the Apache2 license: + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/json/test/thirdparty/doctest/LICENSE.txt +----------------------------------------------------------------------------------------------------- +The MIT License (MIT) + +Copyright (c) 2016-2021 Viktor Kirilov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/nlohmann/tests/thirdparty/doctest/LICENSE.txt +--------------------------------------------------------- +The MIT License (MIT) + +Copyright (c) 2016-2021 Viktor Kirilov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/kineto/libkineto/third_party/dynolog/LICENSE +-------------------------------------------------------- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/eigen/COPYING.BSD +----------------------------- +/* + Copyright (c) 2011, Intel Corporation. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Intel Corporation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +third_party/python-enum/enum/LICENSE +------------------------------------ +Copyright (c) 2013, Ethan Furman. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + Neither the name Ethan Furman nor the names of any + contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/LICENSE +-------------------------- +BSD License + +For FBGEMM software + +Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/flatbuffers/LICENSE.txt +----------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/fmt/LICENSE.rst +--------------------------- +Copyright (c) 2012 - present, Victor Zverovich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/fmt/LICENSE.rst +---------------------------------------------------------------------------- +Copyright (c) 2012 - present, Victor Zverovich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. + + +third_party/kineto/libkineto/third_party/fmt/LICENSE.rst +-------------------------------------------------------- +Copyright (c) 2012 - present, Victor Zverovich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. + + +third_party/foxi/LICENSE +------------------------ +MIT License + +Copyright (c) 2019 Lu Fang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/gemmlowp/gemmlowp/LICENSE +------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/fbgemm/third_party/googletest/googlemock/scripts/generator/LICENSE +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2007] Neal Norwitz + Portions Copyright [2007] Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/googletest/googlemock/scripts/generator/LICENSE +----------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2007] Neal Norwitz + Portions Copyright [2007] Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/kineto/libkineto/third_party/googletest/googlemock/scripts/generator/LICENSE +---------------------------------------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2007] Neal Norwitz + Portions Copyright [2007] Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/protobuf/third_party/googletest/googlemock/scripts/generator/LICENSE +-------------------------------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2007] Neal Norwitz + Portions Copyright [2007] Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/tensorpipe/third_party/googletest/googlemock/scripts/generator/LICENSE +---------------------------------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2007] Neal Norwitz + Portions Copyright [2007] Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/gloo/LICENSE +------------------------ +BSD License + +For Gloo software + +Copyright (c) 2017-present, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/googletest/googlemock/LICENSE +------------------------------------------------------------ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/googletest/googlemock/LICENSE +---------------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/protobuf/third_party/googletest/googlemock/LICENSE +-------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/tensorpipe/third_party/googletest/googlemock/LICENSE +---------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/googletest/LICENSE +------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/googletest/googletest/LICENSE +------------------------------------------------------------ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/googletest/LICENSE +------------------------------ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/googletest/LICENSE +------------------------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/googletest/LICENSE +----------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/googletest/googletest/LICENSE +---------------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/protobuf/third_party/googletest/LICENSE +--------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/protobuf/third_party/googletest/googletest/LICENSE +-------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/tensorpipe/third_party/googletest/LICENSE +----------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/tensorpipe/third_party/googletest/googletest/LICENSE +---------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/ideep/mkl-dnn/tests/gtest/LICENSE +--------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/ideep/mkl-dnn/third_party/oneDNN/tests/gtests/gtest/LICENSE +----------------------------------------------------------------------- +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/fbgemm/third_party/hipify_torch/LICENSE.txt +------------------------------------------------------- +MIT License + +Copyright (c) 2017 AMD Compute Libraries + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/ideep/LICENSE +------------------------- +Copyright (c) 2018 Intel Corporation. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +third_party/ios-cmake/LICENSE +----------------------------- +Copyright (c) 2011-2014, Andrew Fischer +Copyright (c) 2016, Bogdan Cristea +Copyright (c) 2017, Yangqing Jia + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/cudnn_frontend/include/contrib/nlohmann/json/LICENSE.txt +-------------------------------------------------------------------- +MIT License + +Copyright (c) 2013-2021 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/kineto/LICENSE +-------------------------- +BSD License + +For Kineto software + +Copyright (c) Meta Platforms, Inc. and affiliates. + +All contributions by Microsoft: +Copyright (c) Microsoft Corporation. (The Azure AI Platform team) + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Meta nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/tensorpipe/third_party/libnop/LICENSE +------------------------------------------------- +Copyright 2017 The Native Object Protocols Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +third_party/tensorpipe/third_party/libuv/LICENSE +------------------------------------------------ +libuv is licensed for use as follows: + +==== +Copyright (c) 2015-present libuv project contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +==== + +This license applies to parts of libuv originating from the +https://github.com/joyent/libuv repository: + +==== + +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +==== + +This license applies to all parts of libuv that are not externally +maintained libraries. + +The externally maintained libraries used by libuv are: + + - tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license. + + - inet_pton and inet_ntop implementations, contained in src/inet.c, are + copyright the Internet Systems Consortium, Inc., and licensed under the ISC + license. + + - stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three + clause BSD license. + + - pthread-fixes.c, copyright Google Inc. and Sony Mobile Communications AB. + Three clause BSD license. + + - android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design + Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement + n° 289016). Three clause BSD license. + + +third_party/miniz-2.1.0/LICENSE +------------------------------- +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +third_party/ideep/mkl-dnn/LICENSE +--------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Intel Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +third_party/nccl/nccl/LICENSE.txt +--------------------------------- + + Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of NVIDIA CORPORATION, Lawrence Berkeley National + Laboratory, the U.S. Department of Energy, nor the names of their + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The U.S. Department of Energy funded the development of this software + under subcontract 7078610 with Lawrence Berkeley National Laboratory. + + +This code also includes files from the NVIDIA Tools Extension SDK project. + +See: + + https://github.com/NVIDIA/NVTX + +for more information and license details. + + +third_party/neon2sse/LICENSE +---------------------------- +created by Victoria Zhislina, the Senior Application Engineer, Intel Corporation, victoria.zhislina@intel.com + +*** Copyright (C) 2012-2016 Intel Corporation. All rights reserved. + +IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. + +By downloading, copying, installing or using the software you agree to this license. +If you do not agree to this license, do not download, install, copy or use the software. + + License Agreement +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * The name of the copyright holders may not be used to endorse or promote products + derived from this software without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are disclaimed. +In no event shall the Intel Corporation or contributors be liable for any direct, +indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused +and on any theory of liability, whether in contract, strict liability, +or tort (including negligence or otherwise) arising in any way out of +the use of this software, even if advised of the possibility of such damage. + + +third_party/ideep/mkl-dnn/third_party/oneDNN/LICENSE +---------------------------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + ============================================================================ + + Copyright 2016-2021 Intel Corporation + Copyright 2018 YANDEX LLC + Copyright 2019-2021 FUJITSU LIMITED + Copyright 2020 Arm Limited and affiliates + Copyright 2020 Codeplay Software Limited + Copyright 2021 Alanna Tempest + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This distribution includes third party software ("third party programs"). + This third party software, even if included with the distribution of + the Intel software, may be governed by separate license terms, including + without limitation, third party license terms, other Intel software license + terms, and open source software license terms. These separate license terms + govern your use of the third party programs as set forth in the + "THIRD-PARTY-PROGRAMS" file. + + +third_party/onnx/LICENSE +------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/onnx-tensorrt/third_party/onnx/LICENSE +-------------------------------------------------- +Open Neural Network Exchange + +Copyright (c) Facebook, Inc. and Microsoft Corporation. +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +third_party/onnx-tensorrt/LICENSE +--------------------------------- +MIT License + +Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +Copyright (c) 2018 Open Neural Network Exchange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/pfs/LICENSE +------------------------------------------------------------------------ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2020-present Daniel Trugman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/protobuf/LICENSE +---------------------------- +Copyright 2008 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + + +third_party/psimd/LICENSE +------------------------- +The MIT License (MIT) + +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2014-2017 Georgia Institute of Technology +Copyright 2019 Google LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +third_party/pthreadpool/LICENSE +------------------------------- +Copyright 2019 Google LLC +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2015-2017 Georgia Institute of Technology +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +third_party/onnx/third_party/pybind11/LICENSE +--------------------------------------------- +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. + + +third_party/onnx-tensorrt/third_party/onnx/third_party/pybind11/LICENSE +----------------------------------------------------------------------- +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You are under no obligation whatsoever to provide any bug fixes, patches, or +upgrades to the features, functionality or performance of the source code +("Enhancements") to anyone; however, if you choose to make your Enhancements +available either publicly, or directly to the author of this software, without +imposing a separate written license agreement for such Enhancements, then you +hereby grant the following license: a non-exclusive, royalty-free perpetual +license to install, use, modify, prepare derivative works, incorporate into +other computer software, distribute, and sublicense such enhancements or +derivative works thereof, in binary and source code form. + + +third_party/pybind11/LICENSE +---------------------------- +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. + + +third_party/tensorpipe/third_party/pybind11/LICENSE +--------------------------------------------------- +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. + + +third_party/python-peachpy/LICENSE.rst +-------------------------------------- +============================== +PeachPy license (2-clause BSD) +============================== + +Copyright (c) 2017, Facebook Inc. +Copyright (c) 2013-2017, Georgia Institute of Technology +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/python-six/LICENSE +------------------------------ +Copyright (c) 2010-2017 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +third_party/sleef/LICENSE.txt +----------------------------- +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +third_party/flatbuffers/swift/LICENSE +------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/kineto/tb_plugin/LICENSE +------------------------------------ +BSD License + +For Kineto software + +Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. + +All contributions by Microsoft: +Copyright (c) Microsoft Corporation. (The Azure AI Platform team) + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/tbb/LICENSE +----------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +third_party/tensorpipe/LICENSE.txt +---------------------------------- +BSD License + +For TensorPipe software + +Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Meta nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +third_party/kineto/libkineto/third_party/dynolog/third_party/cpr/test/LICENSE +----------------------------------------------------------------------------- +This license applies to everything inside this directory and all +subdirectories. + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +third_party/zstd/LICENSE +------------------------ +BSD License + +For Zstandard software + +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +triton +2.0.0 +MIT License +UNKNOWN + +typing_extensions +4.9.0 +Python Software Foundation License +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + + +tzdata +2023.4 +Apache Software License +Apache Software License 2.0 + +Copyright (c) 2020, Paul Ganssle (Google) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +uritemplate +4.1.1 +Apache Software License; BSD License +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to uritemplate are +made under the terms of *both* these licenses. + + +urllib3 +2.1.0 +MIT License +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +vine +5.1.0 +BSD License +Copyright (c) 2015-2016 Ask Solem & contributors. All rights reserved. + +Vine is licensed under The BSD License (3 Clause, also known as +the new BSD license). The license is an OSI approved Open Source +license and is GPL-compatible(1). + +The license text can also be found here: +http://www.opensource.org/licenses/BSD-3-Clause + +License +======= + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ask Solem, nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ask Solem OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +Documentation License +===================== + +The documentation portion of Vine (the rendered contents of the +"docs" directory of a software distribution or checkout) is supplied +under the "Creative Commons Attribution-ShareAlike 4.0 +International" (CC BY-SA 4.0) License as described by +http://creativecommons.org/licenses/by-sa/4.0/ + +Footnotes +========= +(1) A GPL-compatible license makes it possible to + combine Vine with other software that is released + under the GPL, it does not mean that we're distributing + Vine under the GPL license. The BSD license, unlike the GPL, + let you distribute a modified version without making your + changes open source. + + diff --git a/licenses/license_info.csv b/licenses/license_info.csv new file mode 100644 index 0000000..18dd45b --- /dev/null +++ b/licenses/license_info.csv @@ -0,0 +1,69 @@ +"Name","Version","License" +"Django","4.0.10","BSD License" +"Faker","20.1.0","MIT License" +"Jinja2","3.1.3","BSD License" +"MarkupSafe","2.1.4","BSD License" +"PyYAML","6.0.1","MIT License" +"amqp","5.2.0","BSD License" +"ansi","0.3.7","MIT License" +"asgiref","3.7.2","BSD License" +"async-timeout","4.0.3","Apache Software License" +"attrs","23.2.0","MIT License" +"billiard","4.2.0","BSD License" +"celery","5.3.6","BSD License" +"certifi","2023.11.17","Mozilla Public License 2.0 (MPL 2.0)" +"charset-normalizer","3.3.2","MIT License" +"click","8.1.7","BSD License" +"click-didyoumean","0.3.0","MIT License" +"click-plugins","1.1.1","BSD License" +"click-repl","0.3.0","MIT" +"cmake","3.28.1","Apache Software License; BSD License" +"django-cors-headers","4.1.0","MIT License" +"django-polymorphic","3.1.0","BSD License" +"django-redis","5.2.0","BSD License" +"djangorestframework","3.14.0","BSD License" +"dlr-logging","0.0.3.dev0","Apache Software License" +"docstring-parser","0.15","MIT License" +"drf-spectacular","0.26.5","BSD License" +"filelock","3.13.1","The Unlicense (Unlicense)" +"fl-demonstrator","0.0.1.dev2","Apache Software License" +"idna","3.6","BSD License" +"inflection","0.5.1","MIT License" +"jsonschema","4.21.1","MIT License" +"jsonschema-specifications","2023.12.1","MIT License" +"kombu","5.3.5","BSD License" +"lit","17.0.6","Apache Software License" +"marshmallow","3.19.0","MIT License" +"mpmath","1.3.0","BSD License" +"networkx","3.2.1","BSD License" +"numpy","1.24.4","BSD License" +"nvidia-cublas-cu11","11.10.3.66","Other/Proprietary License" +"nvidia-cuda-cupti-cu11","11.7.101","Other/Proprietary License" +"nvidia-cuda-nvrtc-cu11","11.7.99","Other/Proprietary License" +"nvidia-cuda-runtime-cu11","11.7.99","Other/Proprietary License" +"nvidia-cudnn-cu11","8.5.0.96","Other/Proprietary License" +"nvidia-cufft-cu11","10.9.0.58","Other/Proprietary License" +"nvidia-curand-cu11","10.2.10.91","Other/Proprietary License" +"nvidia-cusolver-cu11","11.4.0.1","Other/Proprietary License" +"nvidia-cusparse-cu11","11.7.4.91","Other/Proprietary License" +"nvidia-nccl-cu11","2.14.3","Other/Proprietary License" +"nvidia-nvtx-cu11","11.7.91","Other/Proprietary License" +"packaging","23.2","Apache Software License; BSD License" +"prompt-toolkit","3.0.43","BSD License" +"psycopg2-binary","2.9.9","GNU Library or Lesser General Public License (LGPL)" +"python-dateutil","2.8.2","Apache Software License; BSD License" +"pytz","2023.3.post1","MIT License" +"redis","5.0.1","MIT License" +"referencing","0.32.1","MIT License" +"requests","2.31.0","Apache Software License" +"rpds-py","0.17.1","MIT License" +"six","1.16.0","MIT License" +"sqlparse","0.4.4","BSD License" +"sympy","1.12","BSD License" +"torch","2.0.1","BSD License" +"triton","2.0.0","MIT License" +"typing_extensions","4.9.0","Python Software Foundation License" +"tzdata","2023.4","Apache Software License" +"uritemplate","4.1.1","Apache Software License; BSD License" +"urllib3","2.1.0","MIT License" +"vine","5.1.0","BSD License" diff --git a/licenses/license_info.json b/licenses/license_info.json new file mode 100644 index 0000000..7ecdec1 --- /dev/null +++ b/licenses/license_info.json @@ -0,0 +1,342 @@ +[ + { + "License": "BSD License", + "Name": "Django", + "Version": "4.0.10" + }, + { + "License": "MIT License", + "Name": "Faker", + "Version": "20.1.0" + }, + { + "License": "BSD License", + "Name": "Jinja2", + "Version": "3.1.3" + }, + { + "License": "BSD License", + "Name": "MarkupSafe", + "Version": "2.1.4" + }, + { + "License": "MIT License", + "Name": "PyYAML", + "Version": "6.0.1" + }, + { + "License": "BSD License", + "Name": "amqp", + "Version": "5.2.0" + }, + { + "License": "MIT License", + "Name": "ansi", + "Version": "0.3.7" + }, + { + "License": "BSD License", + "Name": "asgiref", + "Version": "3.7.2" + }, + { + "License": "Apache Software License", + "Name": "async-timeout", + "Version": "4.0.3" + }, + { + "License": "MIT License", + "Name": "attrs", + "Version": "23.2.0" + }, + { + "License": "BSD License", + "Name": "billiard", + "Version": "4.2.0" + }, + { + "License": "BSD License", + "Name": "celery", + "Version": "5.3.6" + }, + { + "License": "Mozilla Public License 2.0 (MPL 2.0)", + "Name": "certifi", + "Version": "2023.11.17" + }, + { + "License": "MIT License", + "Name": "charset-normalizer", + "Version": "3.3.2" + }, + { + "License": "BSD License", + "Name": "click", + "Version": "8.1.7" + }, + { + "License": "MIT License", + "Name": "click-didyoumean", + "Version": "0.3.0" + }, + { + "License": "BSD License", + "Name": "click-plugins", + "Version": "1.1.1" + }, + { + "License": "MIT", + "Name": "click-repl", + "Version": "0.3.0" + }, + { + "License": "Apache Software License; BSD License", + "Name": "cmake", + "Version": "3.28.1" + }, + { + "License": "MIT License", + "Name": "django-cors-headers", + "Version": "4.1.0" + }, + { + "License": "BSD License", + "Name": "django-polymorphic", + "Version": "3.1.0" + }, + { + "License": "BSD License", + "Name": "django-redis", + "Version": "5.2.0" + }, + { + "License": "BSD License", + "Name": "djangorestframework", + "Version": "3.14.0" + }, + { + "License": "Apache Software License", + "Name": "dlr-logging", + "Version": "0.0.3.dev0" + }, + { + "License": "MIT License", + "Name": "docstring-parser", + "Version": "0.15" + }, + { + "License": "BSD License", + "Name": "drf-spectacular", + "Version": "0.26.5" + }, + { + "License": "The Unlicense (Unlicense)", + "Name": "filelock", + "Version": "3.13.1" + }, + { + "License": "Apache Software License", + "Name": "fl-demonstrator", + "Version": "0.0.1.dev2" + }, + { + "License": "BSD License", + "Name": "idna", + "Version": "3.6" + }, + { + "License": "MIT License", + "Name": "inflection", + "Version": "0.5.1" + }, + { + "License": "MIT License", + "Name": "jsonschema", + "Version": "4.21.1" + }, + { + "License": "MIT License", + "Name": "jsonschema-specifications", + "Version": "2023.12.1" + }, + { + "License": "BSD License", + "Name": "kombu", + "Version": "5.3.5" + }, + { + "License": "Apache Software License", + "Name": "lit", + "Version": "17.0.6" + }, + { + "License": "MIT License", + "Name": "marshmallow", + "Version": "3.19.0" + }, + { + "License": "BSD License", + "Name": "mpmath", + "Version": "1.3.0" + }, + { + "License": "BSD License", + "Name": "networkx", + "Version": "3.2.1" + }, + { + "License": "BSD License", + "Name": "numpy", + "Version": "1.24.4" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cublas-cu11", + "Version": "11.10.3.66" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cuda-cupti-cu11", + "Version": "11.7.101" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cuda-nvrtc-cu11", + "Version": "11.7.99" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cuda-runtime-cu11", + "Version": "11.7.99" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cudnn-cu11", + "Version": "8.5.0.96" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cufft-cu11", + "Version": "10.9.0.58" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-curand-cu11", + "Version": "10.2.10.91" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cusolver-cu11", + "Version": "11.4.0.1" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cusparse-cu11", + "Version": "11.7.4.91" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-nccl-cu11", + "Version": "2.14.3" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-nvtx-cu11", + "Version": "11.7.91" + }, + { + "License": "Apache Software License; BSD License", + "Name": "packaging", + "Version": "23.2" + }, + { + "License": "BSD License", + "Name": "prompt-toolkit", + "Version": "3.0.43" + }, + { + "License": "GNU Library or Lesser General Public License (LGPL)", + "Name": "psycopg2-binary", + "Version": "2.9.9" + }, + { + "License": "Apache Software License; BSD License", + "Name": "python-dateutil", + "Version": "2.8.2" + }, + { + "License": "MIT License", + "Name": "pytz", + "Version": "2023.3.post1" + }, + { + "License": "MIT License", + "Name": "redis", + "Version": "5.0.1" + }, + { + "License": "MIT License", + "Name": "referencing", + "Version": "0.32.1" + }, + { + "License": "Apache Software License", + "Name": "requests", + "Version": "2.31.0" + }, + { + "License": "MIT License", + "Name": "rpds-py", + "Version": "0.17.1" + }, + { + "License": "MIT License", + "Name": "six", + "Version": "1.16.0" + }, + { + "License": "BSD License", + "Name": "sqlparse", + "Version": "0.4.4" + }, + { + "License": "BSD License", + "Name": "sympy", + "Version": "1.12" + }, + { + "License": "BSD License", + "Name": "torch", + "Version": "2.0.1" + }, + { + "License": "MIT License", + "Name": "triton", + "Version": "2.0.0" + }, + { + "License": "Python Software Foundation License", + "Name": "typing_extensions", + "Version": "4.9.0" + }, + { + "License": "Apache Software License", + "Name": "tzdata", + "Version": "2023.4" + }, + { + "License": "Apache Software License; BSD License", + "Name": "uritemplate", + "Version": "4.1.1" + }, + { + "License": "MIT License", + "Name": "urllib3", + "Version": "2.1.0" + }, + { + "License": "BSD License", + "Name": "vine", + "Version": "5.1.0" + } +] diff --git a/licenses/license_info.md b/licenses/license_info.md new file mode 100644 index 0000000..ea113cb --- /dev/null +++ b/licenses/license_info.md @@ -0,0 +1,70 @@ +| Name | Version | License | +|---------------------------|--------------|-----------------------------------------------------| +| Django | 4.0.10 | BSD License | +| Faker | 20.1.0 | MIT License | +| Jinja2 | 3.1.3 | BSD License | +| MarkupSafe | 2.1.4 | BSD License | +| PyYAML | 6.0.1 | MIT License | +| amqp | 5.2.0 | BSD License | +| ansi | 0.3.7 | MIT License | +| asgiref | 3.7.2 | BSD License | +| async-timeout | 4.0.3 | Apache Software License | +| attrs | 23.2.0 | MIT License | +| billiard | 4.2.0 | BSD License | +| celery | 5.3.6 | BSD License | +| certifi | 2023.11.17 | Mozilla Public License 2.0 (MPL 2.0) | +| charset-normalizer | 3.3.2 | MIT License | +| click | 8.1.7 | BSD License | +| click-didyoumean | 0.3.0 | MIT License | +| click-plugins | 1.1.1 | BSD License | +| click-repl | 0.3.0 | MIT | +| cmake | 3.28.1 | Apache Software License; BSD License | +| django-cors-headers | 4.1.0 | MIT License | +| django-polymorphic | 3.1.0 | BSD License | +| django-redis | 5.2.0 | BSD License | +| djangorestframework | 3.14.0 | BSD License | +| dlr-logging | 0.0.3.dev0 | Apache Software License | +| docstring-parser | 0.15 | MIT License | +| drf-spectacular | 0.26.5 | BSD License | +| filelock | 3.13.1 | The Unlicense (Unlicense) | +| fl-demonstrator | 0.0.1.dev2 | Apache Software License | +| idna | 3.6 | BSD License | +| inflection | 0.5.1 | MIT License | +| jsonschema | 4.21.1 | MIT License | +| jsonschema-specifications | 2023.12.1 | MIT License | +| kombu | 5.3.5 | BSD License | +| lit | 17.0.6 | Apache Software License | +| marshmallow | 3.19.0 | MIT License | +| mpmath | 1.3.0 | BSD License | +| networkx | 3.2.1 | BSD License | +| numpy | 1.24.4 | BSD License | +| nvidia-cublas-cu11 | 11.10.3.66 | Other/Proprietary License | +| nvidia-cuda-cupti-cu11 | 11.7.101 | Other/Proprietary License | +| nvidia-cuda-nvrtc-cu11 | 11.7.99 | Other/Proprietary License | +| nvidia-cuda-runtime-cu11 | 11.7.99 | Other/Proprietary License | +| nvidia-cudnn-cu11 | 8.5.0.96 | Other/Proprietary License | +| nvidia-cufft-cu11 | 10.9.0.58 | Other/Proprietary License | +| nvidia-curand-cu11 | 10.2.10.91 | Other/Proprietary License | +| nvidia-cusolver-cu11 | 11.4.0.1 | Other/Proprietary License | +| nvidia-cusparse-cu11 | 11.7.4.91 | Other/Proprietary License | +| nvidia-nccl-cu11 | 2.14.3 | Other/Proprietary License | +| nvidia-nvtx-cu11 | 11.7.91 | Other/Proprietary License | +| packaging | 23.2 | Apache Software License; BSD License | +| prompt-toolkit | 3.0.43 | BSD License | +| psycopg2-binary | 2.9.9 | GNU Library or Lesser General Public License (LGPL) | +| python-dateutil | 2.8.2 | Apache Software License; BSD License | +| pytz | 2023.3.post1 | MIT License | +| redis | 5.0.1 | MIT License | +| referencing | 0.32.1 | MIT License | +| requests | 2.31.0 | Apache Software License | +| rpds-py | 0.17.1 | MIT License | +| six | 1.16.0 | MIT License | +| sqlparse | 0.4.4 | BSD License | +| sympy | 1.12 | BSD License | +| torch | 2.0.1 | BSD License | +| triton | 2.0.0 | MIT License | +| typing_extensions | 4.9.0 | Python Software Foundation License | +| tzdata | 2023.4 | Apache Software License | +| uritemplate | 4.1.1 | Apache Software License; BSD License | +| urllib3 | 2.1.0 | MIT License | +| vine | 5.1.0 | BSD License | diff --git a/licenses/license_info.no_versions.csv b/licenses/license_info.no_versions.csv new file mode 100644 index 0000000..6801383 --- /dev/null +++ b/licenses/license_info.no_versions.csv @@ -0,0 +1,69 @@ +"Name","License" +"Django","BSD License" +"Faker","MIT License" +"Jinja2","BSD License" +"MarkupSafe","BSD License" +"PyYAML","MIT License" +"amqp","BSD License" +"ansi","MIT License" +"asgiref","BSD License" +"async-timeout","Apache Software License" +"attrs","MIT License" +"billiard","BSD License" +"celery","BSD License" +"certifi","Mozilla Public License 2.0 (MPL 2.0)" +"charset-normalizer","MIT License" +"click","BSD License" +"click-didyoumean","MIT License" +"click-plugins","BSD License" +"click-repl","MIT" +"cmake","Apache Software License; BSD License" +"django-cors-headers","MIT License" +"django-polymorphic","BSD License" +"django-redis","BSD License" +"djangorestframework","BSD License" +"dlr-logging","Apache Software License" +"docstring-parser","MIT License" +"drf-spectacular","BSD License" +"filelock","The Unlicense (Unlicense)" +"fl-demonstrator","Apache Software License" +"idna","BSD License" +"inflection","MIT License" +"jsonschema","MIT License" +"jsonschema-specifications","MIT License" +"kombu","BSD License" +"lit","Apache Software License" +"marshmallow","MIT License" +"mpmath","BSD License" +"networkx","BSD License" +"numpy","BSD License" +"nvidia-cublas-cu11","Other/Proprietary License" +"nvidia-cuda-cupti-cu11","Other/Proprietary License" +"nvidia-cuda-nvrtc-cu11","Other/Proprietary License" +"nvidia-cuda-runtime-cu11","Other/Proprietary License" +"nvidia-cudnn-cu11","Other/Proprietary License" +"nvidia-cufft-cu11","Other/Proprietary License" +"nvidia-curand-cu11","Other/Proprietary License" +"nvidia-cusolver-cu11","Other/Proprietary License" +"nvidia-cusparse-cu11","Other/Proprietary License" +"nvidia-nccl-cu11","Other/Proprietary License" +"nvidia-nvtx-cu11","Other/Proprietary License" +"packaging","Apache Software License; BSD License" +"prompt-toolkit","BSD License" +"psycopg2-binary","GNU Library or Lesser General Public License (LGPL)" +"python-dateutil","Apache Software License; BSD License" +"pytz","MIT License" +"redis","MIT License" +"referencing","MIT License" +"requests","Apache Software License" +"rpds-py","MIT License" +"six","MIT License" +"sqlparse","BSD License" +"sympy","BSD License" +"torch","BSD License" +"triton","MIT License" +"typing_extensions","Python Software Foundation License" +"tzdata","Apache Software License" +"uritemplate","Apache Software License; BSD License" +"urllib3","MIT License" +"vine","BSD License" diff --git a/licenses/license_info.no_versions.json b/licenses/license_info.no_versions.json new file mode 100644 index 0000000..776077c --- /dev/null +++ b/licenses/license_info.no_versions.json @@ -0,0 +1,274 @@ +[ + { + "License": "BSD License", + "Name": "Django" + }, + { + "License": "MIT License", + "Name": "Faker" + }, + { + "License": "BSD License", + "Name": "Jinja2" + }, + { + "License": "BSD License", + "Name": "MarkupSafe" + }, + { + "License": "MIT License", + "Name": "PyYAML" + }, + { + "License": "BSD License", + "Name": "amqp" + }, + { + "License": "MIT License", + "Name": "ansi" + }, + { + "License": "BSD License", + "Name": "asgiref" + }, + { + "License": "Apache Software License", + "Name": "async-timeout" + }, + { + "License": "MIT License", + "Name": "attrs" + }, + { + "License": "BSD License", + "Name": "billiard" + }, + { + "License": "BSD License", + "Name": "celery" + }, + { + "License": "Mozilla Public License 2.0 (MPL 2.0)", + "Name": "certifi" + }, + { + "License": "MIT License", + "Name": "charset-normalizer" + }, + { + "License": "BSD License", + "Name": "click" + }, + { + "License": "MIT License", + "Name": "click-didyoumean" + }, + { + "License": "BSD License", + "Name": "click-plugins" + }, + { + "License": "MIT", + "Name": "click-repl" + }, + { + "License": "Apache Software License; BSD License", + "Name": "cmake" + }, + { + "License": "MIT License", + "Name": "django-cors-headers" + }, + { + "License": "BSD License", + "Name": "django-polymorphic" + }, + { + "License": "BSD License", + "Name": "django-redis" + }, + { + "License": "BSD License", + "Name": "djangorestframework" + }, + { + "License": "Apache Software License", + "Name": "dlr-logging" + }, + { + "License": "MIT License", + "Name": "docstring-parser" + }, + { + "License": "BSD License", + "Name": "drf-spectacular" + }, + { + "License": "The Unlicense (Unlicense)", + "Name": "filelock" + }, + { + "License": "Apache Software License", + "Name": "fl-demonstrator" + }, + { + "License": "BSD License", + "Name": "idna" + }, + { + "License": "MIT License", + "Name": "inflection" + }, + { + "License": "MIT License", + "Name": "jsonschema" + }, + { + "License": "MIT License", + "Name": "jsonschema-specifications" + }, + { + "License": "BSD License", + "Name": "kombu" + }, + { + "License": "Apache Software License", + "Name": "lit" + }, + { + "License": "MIT License", + "Name": "marshmallow" + }, + { + "License": "BSD License", + "Name": "mpmath" + }, + { + "License": "BSD License", + "Name": "networkx" + }, + { + "License": "BSD License", + "Name": "numpy" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cublas-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cuda-cupti-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cuda-nvrtc-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cuda-runtime-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cudnn-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cufft-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-curand-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cusolver-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-cusparse-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-nccl-cu11" + }, + { + "License": "Other/Proprietary License", + "Name": "nvidia-nvtx-cu11" + }, + { + "License": "Apache Software License; BSD License", + "Name": "packaging" + }, + { + "License": "BSD License", + "Name": "prompt-toolkit" + }, + { + "License": "GNU Library or Lesser General Public License (LGPL)", + "Name": "psycopg2-binary" + }, + { + "License": "Apache Software License; BSD License", + "Name": "python-dateutil" + }, + { + "License": "MIT License", + "Name": "pytz" + }, + { + "License": "MIT License", + "Name": "redis" + }, + { + "License": "MIT License", + "Name": "referencing" + }, + { + "License": "Apache Software License", + "Name": "requests" + }, + { + "License": "MIT License", + "Name": "rpds-py" + }, + { + "License": "MIT License", + "Name": "six" + }, + { + "License": "BSD License", + "Name": "sqlparse" + }, + { + "License": "BSD License", + "Name": "sympy" + }, + { + "License": "BSD License", + "Name": "torch" + }, + { + "License": "MIT License", + "Name": "triton" + }, + { + "License": "Python Software Foundation License", + "Name": "typing_extensions" + }, + { + "License": "Apache Software License", + "Name": "tzdata" + }, + { + "License": "Apache Software License; BSD License", + "Name": "uritemplate" + }, + { + "License": "MIT License", + "Name": "urllib3" + }, + { + "License": "BSD License", + "Name": "vine" + } +] diff --git a/licenses/license_info.no_versions.md b/licenses/license_info.no_versions.md new file mode 100644 index 0000000..2dc9085 --- /dev/null +++ b/licenses/license_info.no_versions.md @@ -0,0 +1,70 @@ +| Name | License | +|---------------------------|-----------------------------------------------------| +| Django | BSD License | +| Faker | MIT License | +| Jinja2 | BSD License | +| MarkupSafe | BSD License | +| PyYAML | MIT License | +| amqp | BSD License | +| ansi | MIT License | +| asgiref | BSD License | +| async-timeout | Apache Software License | +| attrs | MIT License | +| billiard | BSD License | +| celery | BSD License | +| certifi | Mozilla Public License 2.0 (MPL 2.0) | +| charset-normalizer | MIT License | +| click | BSD License | +| click-didyoumean | MIT License | +| click-plugins | BSD License | +| click-repl | MIT | +| cmake | Apache Software License; BSD License | +| django-cors-headers | MIT License | +| django-polymorphic | BSD License | +| django-redis | BSD License | +| djangorestframework | BSD License | +| dlr-logging | Apache Software License | +| docstring-parser | MIT License | +| drf-spectacular | BSD License | +| filelock | The Unlicense (Unlicense) | +| fl-demonstrator | Apache Software License | +| idna | BSD License | +| inflection | MIT License | +| jsonschema | MIT License | +| jsonschema-specifications | MIT License | +| kombu | BSD License | +| lit | Apache Software License | +| marshmallow | MIT License | +| mpmath | BSD License | +| networkx | BSD License | +| numpy | BSD License | +| nvidia-cublas-cu11 | Other/Proprietary License | +| nvidia-cuda-cupti-cu11 | Other/Proprietary License | +| nvidia-cuda-nvrtc-cu11 | Other/Proprietary License | +| nvidia-cuda-runtime-cu11 | Other/Proprietary License | +| nvidia-cudnn-cu11 | Other/Proprietary License | +| nvidia-cufft-cu11 | Other/Proprietary License | +| nvidia-curand-cu11 | Other/Proprietary License | +| nvidia-cusolver-cu11 | Other/Proprietary License | +| nvidia-cusparse-cu11 | Other/Proprietary License | +| nvidia-nccl-cu11 | Other/Proprietary License | +| nvidia-nvtx-cu11 | Other/Proprietary License | +| packaging | Apache Software License; BSD License | +| prompt-toolkit | BSD License | +| psycopg2-binary | GNU Library or Lesser General Public License (LGPL) | +| python-dateutil | Apache Software License; BSD License | +| pytz | MIT License | +| redis | MIT License | +| referencing | MIT License | +| requests | Apache Software License | +| rpds-py | MIT License | +| six | MIT License | +| sqlparse | BSD License | +| sympy | BSD License | +| torch | BSD License | +| triton | MIT License | +| typing_extensions | Python Software Foundation License | +| tzdata | Apache Software License | +| uritemplate | Apache Software License; BSD License | +| urllib3 | MIT License | +| vine | BSD License | diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..df4f129 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fl_server.settings.development') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..05a867a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,87 @@ +site_name: Federated Learning Demonstrator + +repo_name: fl-demonstrator +repo_url: https://github.com/DLR-KI/fl-demonstrator +#edit_uri: blob/main/docs/ + +theme: + name: material + custom_dir: docs/.overrides + features: + - content.code.annotate + - content.code.copy + - content.tabs.link + - navigation.tabs + - navigation.sections + #- navigation.expand + - navigation.path + - navigation.indexes + - navigation.top + - search.highlight + - search.suggest + language: en + palette: + - scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + primary: teal + accent: deep purple + - scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + primary: teal + accent: blue + +markdown_extensions: + - admonition + - attr_list + - footnotes + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + #- pymdownx.blocks.tab: + # alternate_style: true + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.mark + - pymdownx.snippets + - pymdownx.superfences + +plugins: + - search + - portray + +extra: + social: + - icon: dlr-logo + link: https://www.dlr.de/en + - icon: dlr-ki + link: https://www.dlr.de/ki/en + - icon: fontawesome/brands/square-gitlab + link: https://github.com/DLR-KI/fl-demonstrator + +extra_javascript: + - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js + - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js + - javascripts/katex.js + +extra_css: + - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css + - styles/style.css + +copyright: | + © 2024 + + DLR KI ALG + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..29cd47c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,353 @@ +[project] +# This is the name of your project. The first time you publish this +# package, this name will be registered for you. It will determine how +# users can install this project, e.g.: +# +# $ pip install fl-demonstrator +# +# And where it will live on PyPI: https://pypi.org/project/fl-demonstrator/ +# +# There are some restrictions on what makes a valid project name +# specification here: +# https://packaging.python.org/specifications/core-metadata/#name +name = "fl-demonstrator" # Required + +# Versions should comply with PEP 440: +# https://www.python.org/dev/peps/pep-0440/ +# +# For a discussion on single-sourcing the version, see +# https://packaging.python.org/guides/single-sourcing-package-version/ +version = "0.0.1.dev2" # Required + +# This is a one-line description or tagline of what your project does. This +# corresponds to the "Summary" metadata field: +# https://packaging.python.org/specifications/core-metadata/#summary +description = "Federated Learning Demonstrator" # Optional + +# This is an optional longer description of your project that represents +# the body of text which users will see when they visit PyPI. +# +# Often, this is the same as your README, so you can just read it in from +# that file directly (as we have already done above) +# +# This field corresponds to the "Description" metadata field: +# https://packaging.python.org/specifications/core-metadata/#description-optional +readme = "README.md" # Optional + +# Specify which Python versions you support. In contrast to the +# 'Programming Language' classifiers above, 'pip install' will check this +# and refuse to install the project if the version does not match. See +# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires +requires-python = ">=3.10" + +# This is either text indicating the license for the distribution, or a file +# that contains the license +# https://packaging.python.org/en/latest/specifications/core-metadata/#license +license = {file = "LICENSE"} + +# This field adds keywords for your project which will appear on the +# project page. What does your project relate to? +# +# Note that this is a list of additional keywords, separated +# by commas, to be used to assist searching for the distribution in a +# larger catalog. +keywords = ["ai", "federated learning", "catena-x", "dlr", "demonstrator", "server"] # Optional + +# This should be your name or the name of the organization who originally +# authored the project, and a valid email address corresponding to the name +# listed. +authors = [ # Optional + {name="Benedikt Franke", email="benedikt.franke@dlr.de"}, + {name="Florian Heinrich", email="florian.heinrich@dlr.de"}, +] + +# This should be your name or the names of the organization who currently +# maintains the project, and a valid email address corresponding to the name +# listed. +maintainers = [ # Optional + # {name="Jane Doe", email="jane.doe@example.com"} +] + +# Classifiers help users find your project by categorizing it. +# +# For a list of valid classifiers, see https://pypi.org/classifiers/ +classifiers = [ # Optional + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + "Development Status :: 3 - Alpha", + + # Indicate who your project is intended for + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Operating System :: OS Independent", + + # Pick your license as you wish + "License :: OSI Approved :: Apache Software License", + + # Specify the Python versions you support here. In particular, ensure + # that you indicate you support Python 3. These classifiers are *not* + # checked by "pip install". See instead "python_requires" below. + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", +] + +# This field lists other packages that your project depends on to run. +# Any package you put here will be installed by pip when your project is +# installed, so they must be valid existing projects. +# +# For an analysis of this field vs pip's requirements files see: +# https://packaging.python.org/discussions/install-requires-vs-requirements/ +dependencies = [ # Optional + "celery~=5.3.0", + "django~=4.0.0", + "django-cors-headers~=4.1.0", + "django-polymorphic~=3.1.0", + "django-redis~=5.2.0", + "djangorestframework~=3.14.0", + "docstring-parser~=0.15", + "drf-spectacular~=0.26.3", + "dlr-logging @ git+https://github.com/DLR-KI/fl-logging-base.git@main", # noqa: E501 + "Faker~=20.1.0", # dummy data generation + "marshmallow~=3.19.0", + "numpy~=1.24.3", + "psycopg2-binary~=2.9.6", # cspell:ignore psycopg + "requests~=2.31.0", + "torch~=2.0.1", +] + +# List additional groups of dependencies here (e.g. development +# dependencies). Users will be able to install these using the "extras" +# syntax, for example: +# +# $ pip install -e ".[all]" +# +# Similar to `dependencies` above, these must be valid existing +# projects. +[project.optional-dependencies] # Optional +dev = [ + "pre-commit~=3.3.2", + "tox~=4.5.1", +] +test = [ + "autopep8~=2.0.2", + "coverage~=7.2.7", + "flake8~=6.0.0", + "flake8-pyproject~=1.2.3", + "licensecheck", # no version, install latest version + "mypy~=1.3.0", + "pytest-django~=4.5.2", + "responses~=0.23.1", + "shellcheck-py~=0.9.0.6", +] +stubs = [ + "celery-types~=0.17.0", + "types-requests~=2.31.0", + "types-tabulate~=0.9.0.3", +] +doc = [ + "portray @ git+https://github.com/HeinrichAD/portray.git@v1.8.0-dev", + #"plantuml-markdown~=3.9.2", # post-install: cp plantuml_markdown.py into markdown/extensions +] +all = ["fl-demonstrator[dev,test,stubs,doc]"] + +# List URLs that are relevant to your project +# +# This field corresponds to the "Project-URL" and "Home-Page" metadata fields: +# https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use +# https://packaging.python.org/specifications/core-metadata/#home-page-optional +# +# Examples listed include a pattern for specifying where the package tracks +# issues, where the source is hosted, where to say thanks to the package +# maintainers, and where to support the project financially. The key is +# what's used to render the link text on PyPI. +[project.urls] # Optional +"Homepage" = "https://github.com/DLR-KI/fl-demonstrator" +"Bug Tracker" = "https://github.com/DLR-KI/fl-demonstrator/issues" +#"Changelog" = ""https://github.com/DLR-KI/fl-demonstrator/blob/main/CHANGELOG.md" +#"Funding" = "https://donate.pypi.org" +#"Say Thanks!" = "http://saythanks.io/to/example" +"Source" = "https://github.com/DLR-KI/fl-demonstrator" + +# The following would provide a command line executable called `fl-demonstrator` +# which executes the function `main` inside `__main__.py` from this package when invoked. +[project.scripts] # Optional +#fl-demonstrator = "???.__main__:main" + +# This is configuration specific to the `setuptools` build backend. +# If you are using a different build backend, you will need to change this. +[tool.setuptools] +# If there are data files included in your packages that need to be +# installed, specify them here. +packages = ["fl_server", "fl_server_ai", "fl_server_api", "fl_server_core"] +#package-data = { +# "fl_server" = ["py.typed"], +# "fl_server_ai" = ["py.typed"], +# "fl_server_api" = ["py.typed"], +# "fl_server_core" = ["py.typed"], +#} + +[build-system] +# These are the assumed default build requirements from pip: +# https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support +requires = ["setuptools>=43.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +####################################################################################################################### + +# licensecheck +# https://github.com/FHPythonUtils/LicenseCheck/blob/master/README.md + +[tool.licensecheck] +using = "PEP631" +#using = "PEP631:dev;test;stubs;doc" +zero = true +ignore_packages = [ + # https://docs.nvidia.com/cuda/eula/index.html + "nvidia-cublas-cu11", + "nvidia-cuda-cupti-cu11", + "nvidia-cuda-nvrtc-cu11", + "nvidia-cuda-runtime-cu11", + "nvidia-cudnn-cu11", + "nvidia-cufft-cu11", + "nvidia-curand-cu11", + "nvidia-cusolver-cu11", + "nvidia-cusparse-cu11", + "nvidia-nccl-cu11", + "nvidia-nvtx-cu11", +] + +####################################################################################################################### + +# flake8 +# https://flake8.pycqa.org/en/latest/user/configuration.html + +[tool.flake8] +max-line-length = 120 +per-file-ignores = [ + "__init__.py: F401", + "fl_server/settings/*.py: E305,F405", + "fl_server_core/migrations/*.py: E501", +] +exclude = [ + ".build", + ".git", + ".github", + ".*_cache", + ".tox", + ".venv", + ".vscode", + "*.egg", + "*.egg-info", + "__pycache__", + "Interactive-1.interactive", + "fl_server_core/migrations", +] +#select = "E,W,F" + +####################################################################################################################### + +# mypy +# https://mypy.readthedocs.io/en/stable/config_file.html + +[tool.mypy] +# Global options +exclude = ".build" # https://mypy.readthedocs.io/en/stable/config_file.html#confval-exclude +no_implicit_optional = true +show_error_codes = true +#warn_return_any = true +#warn_unused_configs = true +#disallow_untyped_defs = true + +#plugins = +# mypy_django_plugin.main +#[mypy.plugins.django-stubs] +#django_settings_module = "fl_server.settings" + +# Per-module options + +# Ignore missing imports (missing stubs or py.typed) +[[tool.mypy.overrides]] +module = [ + "django.*", + "polymorphic.*", + "rest_framework.*", + "setuptools.*", +] +ignore_missing_imports = true + +####################################################################################################################### + +# coverage +# https://coverage.readthedocs.io/en/latest/config.html + +[tool.coverage.run] +branch = true +source = [ + "fl_server", + "fl_server_core", + "fl_server_api", + "fl_server_ai", +] +omit = [ + "manage.py", + "fl_server/**/*", + "fl_server_core/migrations/**/*", + "fl_server_api/views/johannes.py", + "**/tests/*", +] + +[tool.coverage.report] +omit = [ + "manage.py", + "fl_server/**/*", + "fl_server_core/migrations/**/*", + "fl_server_api/views/johannes.py", +] + +# Regexes for lines to exclude from consideration +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + "@overload", + + # Don't complain about missing debug-only code: + "def __repr__", + "if self\\.debug", + + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", + "raise NotImplementedError", + + # Don't complain if non-runnable code isn't run: + "if 0:", + "if Flase:", + "if __name__ == .__main__.:", + + # Don't complain about abstract methods, they aren't run: + "@(abc\\.)?abstractmethod", +] + +ignore_errors = true +skip_empty = true + +[tool.coverage.html] +title = "Coverage report for fl_server" + +####################################################################################################################### + +# pytest +# https://docs.pytest.org/en/latest + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "fl_server.settings.development" +testpaths = [ + "fl_server/tests", + "fl_server_core/tests", + "fl_server_api/tests", + "fl_server_ai/tests", +] +python_files = "test_*.py" diff --git a/scripts/utils.sh b/scripts/utils.sh new file mode 100644 index 0000000..e4a1f9a --- /dev/null +++ b/scripts/utils.sh @@ -0,0 +1,71 @@ +#!/bin/bash +############################################################################### +# Utility functions # +##################### +# This script contains utility functions and global variables. +############################################################################### +# Usage: +# Asuming this script is in the same directory: +# source "$(dirname "${BASH_SOURCE[0]}")/utils.sh" +# info "Hello World" +############################################################################### + + +# logging +function info { + echo -en "[\e[0;32mINFO\e[0m] " + info2 "$@" +} +function info2 { + echo "$@" +} +function warn { + echo -en "[\e[0;33mWARN\e[0m] " + warn2 "$@" +} +function warn2 { + echo "$@" +} +function error { + echo -en "[\e[0;31mERROR\e[0m] " >&2 + error2 "$@" +} +function error2 { + echo "$@" >&2 +} +function fatal { + echo -en "[\e[0;41mFATAL\e[0m] " >&2 + echo "$@" >&2 + exit 2 +} + + +# appends a command to a trap +# +# - 1st arg: code to add +# - remaining args: names of traps to modify +# +# Source: https://stackoverflow.com/a/7287873 +trap_add() { + trap_add_cmd=$1; shift || fatal "${FUNCNAME[0]} usage error" + for trap_add_name in "$@"; do + trap -- "$( + # helper fn to get existing trap command from output of trap -p + # shellcheck disable=SC2317 + extract_trap_cmd() { printf '%s\n' "$3"; } + # print existing trap command with newline + eval "extract_trap_cmd $(trap -p "${trap_add_name}")" + # print the new trap command + printf '%s\n' "${trap_add_cmd}" + )" "${trap_add_name}" \ + || fatal "unable to add to trap ${trap_add_name}" + done +} +# set the trace attribute for the above function. this is +# required to modify DEBUG or RETURN traps because functions don't +# inherit them unless the trace attribute is set +declare -f -t trap_add + +# change working diretory to project root but return later +trap_add "cd $(pwd)" EXIT SIGINT SIGQUIT SIGABRT SIGTERM +cd "$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}")")")" || exit 99 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ee6ddaa --- /dev/null +++ b/tox.ini @@ -0,0 +1,61 @@ +[tox] +envlist = py{310,311} + +# Define the minimal tox version required to run; +# if the host tox is less than this the tool with create an environment and +# provision it with a tox that satisfies it under provision_tox_env. +# At least this version is needed for PEP 517/518 support. +minversion = 3.3.0 + +# Activate isolated build environment. tox will use a virtual environment +# to build a source distribution from the source tree. For build tools and +# arguments use the pyproject.toml file as specified in PEP-517 and PEP-518. +isolated_build = true + +[testenv:safety] +basepython = python +deps = + safety +;extras = all +commands = + safety check + +[testenv:licenses] +basepython = python +deps = + pip-licenses +;extras = all +commands = + pip-licenses \ + --from=mixed \ + --format=csv \ + --output-file=./licenses/license_info.csv + pip-licenses \ + --from=mixed \ + --format=json \ + --output-file=./licenses/license_info.json + pip-licenses \ + --from=mixed \ + --format=markdown \ + --output-file=./licenses/license_info.md + pip-licenses \ + --from=mixed \ + --format=csv \ + --no-version \ + --output-file=./licenses/license_info.no_versions.csv + pip-licenses \ + --from=mixed \ + --format=json \ + --no-version \ + --output-file=./licenses/license_info.no_versions.json + pip-licenses \ + --from=mixed \ + --format=markdown \ + --no-version \ + --output-file=./licenses/license_info.no_versions.md + pip-licenses \ + --from=mixed \ + --format=plain-vertical \ + --with-license-file \ + --no-license-path \ + --output-file=./licenses/license.txt

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 O&lZ8Hv`mw1%4q~np z$0&jX0n^{(Gop-)CnVB!Oqw-Hy(7q17?;A*;-7Y$0g&*K9e#nDAbI}mOA`?DP3Z(s zrT9sw&2z7qE!4R23nA%7iSO*>$>A!F++dI(cTNKT0T`%0m=yTSaNAK1BHVl5<;O|w zkx%hNwwOIYD&6ZqC778OPVRJoASp=-V@uGfz2G2i`rJCA6+)q(kWfxn`{8@^*adE% zXA3oTENX2}R7Sjt-o;FR`~;kOM|xiC;TcPcY(9UFS^lueS|Fco**t|*(OHh^3mZQ+ z{FHID{=|l?wDPkC#sPB0T9M%cFqO&MKYE=aQAcCh1-JTuo2=osQ$A&U_2WOvGNxoo zUekoV9hQTvu3ld)2c?9luq$0FB!gy#H6f$r*g}&@SOZ6;>T@&bdLbeGf~}XU3ddVk zt{6v#O)lkRuDyBnd_jk>Jl#F6SJJqbP5WS;k_hyLqx>RJktjL?0|sF{ONIGMWZUGu zmdWtJ%Poa2l5PX%Cb{!u0uUbx)02R0N8~$Q&?a8Aphfe))7m?SjT3Q>Mv}9JB<gpN0m-f2O_Cmxl`RLTU+XzA)4@>z z)hXn8aRKt$QVIEENBr8|LcoSH?@Xn4*)+%w^&9raQoB*LEcU$CA@1Wj6VIR?^B=TCw0*Z`e z3yRnK+nLtCP~GS z5w7F}HeOgu-b@nxgU=oLj~^%SJ4d-?J8{NuI(K(L5tTz*OwJ#mT)Bf^5BQ-P8EGKB zJ?8QklhP6xLBK0De=sQH+#T=~NEV8S@NzC9hh^(n!5By~-tfco!#+TaF3sv6LpL}!oAe;&6QyXdN#}v~R#Kcg^pD`aQQ~(l?PNNn$ z=?8(0a$35QK%FnJK-EEbLY+c>4{65dB;kx;(7$LvaabQUi|~;c8MNne#nw{RP)@xt z^Yc z06}jejUbRw*+Xh378Y!*7!8devf~ODjg5^5=A=16gp-1=fbU2`fY>Lb_SjXyDI;{n zrl+x$mC!Tqp*tB(*@Nf&n2gWFDP6Y!-aP7YDEnT_z1+?}Wn>sDd%~i?RmdPto+nxo z^?1pLc9YnYs%)WAp)u~5Oru9{p?d|qigfPn@h#C#6ZUU&Ac2GggeYrzbqLEi#}JVF z0g?+ojpI@yj3B6bG9yKX`*4k%)nP_nyXqgmjhkZX z174#Im9NjVzQ0^(a_M8#30c<|X<4XVGI?zaV=)(pu3_}7JdL|qR7n7c`o5QBpGSr3 zo9F)NMuuv#%z)Ztuj!i#z$N84PyqUrc)p1PF0J%HA;50JN@1`OxJ*z2g{thu%s4j( z;IbG96b4{dIt>b60~dfp2Vic3WlpDi%-sYPH;Dz=$i6(r8Q}8F7bq|TQ}6nu95U{` z-Ja#k^X$f!k3;5)>gVqtuhJx613ush2@xrWUHe`|X$ySqBK9t7n-3H@C_a#O-+S2n zQG3&^#D=TiLG^G3L5p1N@0rC%N;+Q^^i6)#=>tW^w~Tr+sl78wo93^WrRCv9SCI;; zwK!mSMNUhE-zz1jCeUa<*SF-4v7bygXRRUI};aiXrEgYro zSgdF-f9<~D_%(24ow@iRjc|;wYE=^VWqY}C6A(1?arGC9yAqik$b)A&;N<5Qdr4N^ z9?ABIQBvh79f2c-BHYHjE5@RoY_GebLj#_>L(+OEl46Yf{-Q`NdAeE zCd%bYj>h6trwHy6@L^ZAKN)3g#V8ucmN6<`S#%2>ZsOLw*Bq=9JX-%GQP??bAx?J%!>oXtJat}Im$-Q>LRZ&|JjP!Z zg+H-txBAdn1i%dQ_4Q@_Dp|*7u58y{`SAiVyBb;d_rnmgk5-dEn*8iWU-(G7hNtnH z#>WC5rbH=SFE*+E;K7Evk-7+|)>pE(#3VQ|3)Rbhb0SEKQNlhaXbqMTu zLV&wqWARPhx`(>Tkyp%Qfz)HwLnKD9+$ntAO|3tY)+;DKJRCW302av$D_McN~ p5P;&=H7b=*h>)R0NN6nC zP0BLXu?)j7yytJZuI}r;?)!e;XSv_|dEV#qX_-0ybAIRif9H3;zx7O{iIMKk?FY8g z(b4VH*V8^jM@LTsfBUv>0WA*gPR?|6g5P{~&iT3rI=Z+a=!9jo);u=|T@K`5khzxddbF&kcJ|QO|2}m8baPe?J2Ka!FMrXl4Qj(xqMiYDj z59DRnJ_c@l@b-sDOG-;f9sxac5%#V~KnWTJ{z!qQ6E5BeM`R#qTF;; zF9w=AYs*^6o9la@IDOH;^SFcIS|5Lew~q_bWBrY!BxEGy*BAMnM)&V9YFDZ<4UAr4jqvCCzhQ3@x$J!Gu{9kqkqka~J2wJa_< z0up~oZiA?Mpsu%_r?Vl_0pSKnI0UY(K}uFec8#b*&{~VaF)#%*i<9@pJnPd!4W=%^ z>noC4oeFgGb8$fUtW#SX>5D|V`MP-iWuraP!vkTzzH93g?YzCAty-0g^(IH8+xpwB zH+X{0{)-+^gQ>lp+h4a@xH$Mauk}49t+2KhsJ9-%#mRYNW+{2awI}X&8=cqR#K+kV zNY2{$zrn{1Zh9k;fZi{=w)WuHenaj*|^SGAEci*IE|~a_@~@i z@8@gh?F4Y^@AO+gKM?EIx~-1<`_i%@Sm2l&$>wd{k8mwAKPSOOqoK(3W%pZ1MpAJJ|UG zUGTRi{Ub4mbAJ%_Q+5}@rqR5=2j=iwEEXkQF&iVL5Hk^4H?`=d4{niO@`l4X9olyN;vlvw*f$82I}; z3iSc&0f_XAbgh$>l#rI%fc$k8us->}S^WMjlmEU16No8bJ?rzYPqsewv9*`n5OSoW zqtAws@Q+1&V~$lp|Lslx{_143R(r1^^*`$QSAhPfdnW^=>Q4mgpM;_i-floB*xC(5 z{T&Ve97Ul~zd`8wuvOfIcIkIa-GJY)(y;m4oYt5+W*eLxNboGBIy>0i>3(gH7`i=@NpA;Amx7$ zu;0x~2-yOhfg5D%X}4;q1ptQzwEbdA{!Li=E1~%NP`{x_{!Cv=$*(G0X~}hsB`df7 zJ>YlmLkc1Qy@>NKp2xlPxDh(LXlL*0v}z$4`}qPgP!t9d7Z8P5{RoNC8sPnjtM;F1 z6Dh7A^mWLRR#^Z3i%qm){`_hZ9b51AuVWHH+5y_Azd)=%>>R{#&wo2xr6vEfLpFaj zLFG5h&0pC4rzWVB%)cDNrTz^J|J?_G46*-fAK*`;G{0^1A0ur44$t7fA=}sEY`=)P zmYWNdS@@%Qv;k9o?rz8JeL<}C_weVc-*1BSaeA-))UQk39YoZ2ZmI`7d#jHjw6jjg*aa-+Jcv7ZBJmw|{m2r6B9v6tNn5 zT#F1!uH{@1&_Ix&T6N$zM*cet_0{ozEC2J4>HR@FLlXb55s{SMumb<6V*c|j0uYc~ zgU@vXY(3QWUmTmj= z-4oF2_)@-ka~=1aJ_q~TTJucXpIDll5s#K6?ke>tN!{MeChm2c(+a!w?q_v&`p^J% z-SmJt(#yuhbPBo=)u6T*()&7QIgo&!!^{nJ423i<`^@>A|CrUrc;Y_8r{tfh54FP6 z)t$dS@L}OoQ~9`rns?1Bmv7+~;ZyQ;p3Kg}DG@*JF;+R|`MokJR(*LsY0HlNp5L$S zF$$~=S?MZAQPdooR=V&%y=GRNQ0}q|T67GoVH$yqG0b@pee?3|*{c+Qq$JC1|?ex&5UbEk;1=hW21_P3oR;LPf>Y%1}nA zTJX|Tb*8v=(U?N@JHpbS!gSnMg^>A4xH_fVhamU5yp3BOR{m{29sO1fI)>go8sxG7 zyhdWxohMA8`fxZQluPbXOCP0Tg{HMU*0}8U^<(6oOg5ntzHm)JCZRce#Zp~`Hl-JZ zBwyuQx0WL!ZurmIe~PVV#}57-{&)Leb3fo=TMw6aMRne!`NGE2*Nby~B! zsw|pHHhsht`pweGFWo-eX`=m=Q0Y)Q3Jbj`3PWwq*rgIU`Q2T&Y6ZWmpkEeRCF|BW zIxGQaN>KIgui=6R4csZV@u`=qhNEX&Mbgl79pghynKJ2tKO&t6KHtGNJvLzBR`l+t zmof9adgSC2x_O93m_|KVPX|}!uwyUnkjzcB@h2%B2RvKNEYebGE#7A^KlWrqEDvWa zyR0z7-cB0YzZ?28rKxz=M3j*qFbmMYHx6F6bZl5YBt+K5ksrf$@~H%R78R)I%{Cdp zbY_McWA6gK6K)zU-I5yiXZiA;adq*BFN4K1lv)ah7Fga4c$^GVpT$N?h`!TV*c-G$ zqv*;wHFQ(j?PFC*g!fcn7IYa(2s z%FJ*5=vFszK(7t5;c04CwZ%Zm)w?yrtu~ZThjef;R5_?St*MtGj$V#)13|-2vldyj4(Nk&~?SBG4KwHRIS$J4BQ97z*I?)y-kunYO zTH`?=c=;;}wIUgBo8E@2|LkG!_-Tf|imntoTHs*xqqv81ksAjk;uXrF~ z;$mb)hZ9pS$erdHBv;J6&eJJyYCJ*idUHWj*;A+z|;QDr<)>%}?Q-)E7Gr_fyEEw=)eyX861&dGZ5`5cN*Q&1XOy z=q|gh63l~Fs3p8C1jrJ&_eV^KZVY#1gW^ysF&j?1B#ZdGS6jX7wWjFrq$~3Bj0Kx? zDi(W4SQ3U(c3KZbs@wOLkNPzn+ia|^YK034^6mEW+VgtNgLr^W8B+kUBjJ zr?tX$Dwb#S?E6@RKc{b9=39Elr@b=CvfHlhsVbI06&2VFElp!9Kh;nhj*(XWrsG(U zu4hiRSddc-QZ+Uofrm_0O`XSKSZNx$4$tWUXZSbKYgb$+)-~4M6^|Zl%-}eS(wZ>cS@DiX)%eLh=H%e*~Sk&!S=Me<+aB9O7YL;*jLo9%|=Tw}& z+&g_Ov{cl-^|{%r`a@wRZUtX{il|3mEFu&+p?!J`jnRQ8_Mk}Rc#ewss(G_wvAnm^ zY5?lpkg3c4iN+@g^Pd!cXxXYRk+al!$^om4UbEmSpZxYSKsz}EBv&3#P@k?5+Udo9 z8VwE}hhe&kqSEM#g5Zfl2YO@G*|UY4k71Aph|7n#PJRV%8bAY)mtA0M4RMzEDA?AyZ*>wFvJ?FyR?BG0iSITbtB)Nj^nm2_PFK1N6%TB#Kc^iol?A=mG&9_;p{3r zIs&o{*o-e8ZMinDOtPKTr#|kXX`@$`X}3a0uU3A2sI{od`gWln>7gQ9v*}|^ypW9Y z8#7Jq3U&cZI9M0!F0irHSQ5(gI2lc0ojVSw*2L0bV~Nc4oyRe4dljLU$-8t4C~ZDG zWp4VTBqXN`yEVvcXl-?ZYg9LM_yfV|)CDa_nDLIQfGh+|e4u5f{7KDDu%ltc;2``j zf&URa2q#uhN3!wTL{h)!G*3&90U5(bilj#nhc@31SrrB&0PM$zh0R7?MHm`&#T&1~ z5j0UcQNP&Hh}mbDEImNT2wh%y)iZQr*Qy%(u1v43-uE!kSS@J4P@zW_QXR8B0XAKf z*Li61(q-ys#cUU0**~}~Ii7x1cLW+k+CYadbY+pS@))Qh*9c1QXj3Ii$oEZ5xD^^{ z1(sN!W8+vgygGpiJwpWUgZ_tbmeA=K<%#{Mg@GiC%Ij&#fkSBlCx8yXd?rtaRupg_ zV_pT*=O^g!0jRO2)0roF7FtY=cPcK=|I8k2zB|XO=zS1^kX>WlC8p_@COm=|wykPE z{;l97MsYE*@GiwB9x@Cj+}O!w#3^|}gSFxA$+vTzu6RU}&0JGs=<w>wB5v&B39c>~N?(jGBjHd)uHMNTcO}urlkb^f+DW#-!^`enMQ9S}kkVIH z-p&LtZtWCWPs+|Lh9YlJ^K47J2VRO+d=kkUs!vs%LC7GyMtjRqD@p`MZI3lJYzAwT zht(?$KT5`D=m%{bTvNKby8XAPmB-hnY_a;X1+sGUhTrZ(Ru}) zp03OvHTzach-%Z}N!^x1$) zGy#}I4^VW8x=dR{d`Qz(`jXJRwnrM^{V7H~hkvXrR;(;G*GJ6<#PF(?B7wP~MVLva zXzvj*x_RQ}emr_xB)31Qm&d*Lts-?QHZ){XSDlJ~n3aJE4ol8kdA6`zc4%1Pi`OxV zW-aU58i7PM60Yxl<}{tsOMb#qG5;glDJ5VjR_4PBQ*|t#`n)?5)fI`F8;f+%z5w|F zy9#Xil{~*|6e0;YOgQx&$XIotif=)@bdwU-jOm z6f9*Qk6GPK)&l^#DZD%`QTv@@eEKExR%{#wJHu067F}KSa>im5&Mm72PCkhAMq!tR z*bA7y*EvQkhq29~4~_HAbY(ie!9**rc=@==Rp<7m7;gKGDuFL{j?;89Tz$E6Hza58 z*fboLypCW=v9Onn;B7VH0Gfw(M-dl)G9QJ6nDrpAL-~pYsmt>W1`w+m_37}G>YNCi zI&O``aiB10USj`C0&jADO8Ha;Bz`+lm?+afXD0ZyBPmB`Fji{*0#zkNb zCB<)rj(`i}mECBz@t~Q(svi;;i|wmp=8+2#$DZGd`tIIY_)MVaWQ8*xUhH8*{-l6= zB~UXRH$7I+c*1A!%Ww%XOLd2D^KLep2hmFcvc-G%Y&}WVksWGC>&ny3**Z`hxkp6) z8m7M{ernU@o=oH+YVvkyb8G^n2@q?}D-+S*P91`E`JLFq!zNWUiB*Er1QVKy1;p zT4-uc3K@&tS{Q*7pYTuijuAtGXLgH1qnrB^&B%Tp>Nz<3Pd8mWU-BLB-Mbl2qHQ)< z1;0`O*0)3hmX6mZ^|q5)bY1MUjsmCZaxa^kSwDJXh>iE8gi5DAaoO*AR1FIa5T)jP z?D069n~vFsPwGQk?u{S@_;jposP|SzA!=?Pt*{R>t{9;FR^5NBJMR!n>(d=TM@i>S6HK%Tp?r1%z!5?+yo<(Uhspj}E(}MCVBYvgpcyz+?o2;}33C#uwXH529{;o~vRZ!y``^3)e)cwYb#?bYq zDMH3!NVTV1m(k_}p=kRXp`nqzSC_`ON7<8FL|i&+o7F5vQwmK32KL~Sg7re^a*DSw zT>A8Bm;Q^n(M^|mzHi0DM)DtXLkcyGZL1OaakXeh;RS`-@5l7%(#p5!qAP%Bi8-*lOpo z)3N#J_m~K*YA1g){WLz1EQq~teBV>#>inB&-?WrM8O665W5Z5%nOK;MpLQj0XA+6+ z4=mm%Y!mim=9|kqFYOS&(-evfHSEShX=sP+#m3mPe*#>Evj z+ty^ArjL#CRjGgp?a7sv^YxUL_3{qZs?#M$m%9BT8x1C&=H?93VJ{tO6XxySE%Q?? zwKgt;+hrm8PFVn^wIy)-}`S zbBy%vR|LPpfILoa+0fMEunM8{RL0dFh9C?!pukdA4AY{Kr|T%&_5ti*-I_p$h2yNp zd)3O9rfHWqLkIPWa2Rp1&uVx)^fNbHp2=qcGtVftYKZFsL;SCAu{ql0 zHuF^~F&eGfp<~ao(y2+Cw(OYy{_ZN1E+5E%w0u|Dt#|##4$kQx4~}2;S6Nw_$u_(* zAzNLf@%)4wzlj1vcYxTBsiTx0(a2>v;Ag!Z_$-zevY-XRlaoG;stQUoUu497M16Cd z=qiRCkUyog)Vi|j0E4tcrY;{^)~W)7E_Cb2*9vFhOSiw|Z$0a(yOZBUjNv#TKx^9J z+oA`%_R39=zIY*3-33K5Gt@w|EpF*7IO4!8yAK@5%OW6gAcCSyq}hpzr|g~rPRNpQ zCAtkxaL7M8jvFl}0vBj$V(Mr03MFHyUu2U^>uv4c-GOaoy?(%|<6EvcF%O&lRpe-k_vxXu zw$MBGHr?SK(IszkzNTmA*lYfS0*XDRfFS61mx-8Qo=VuSr)Hnuk) zYQS>83axNYw04>C4)rOqF;!8f2(>UhfEP|WzuUe0jnp#b{dMNdg_$8;7FDvz5-ryz zzs`H55%35*np>7jKnm%`+`N0#ayMl3it$SScpE%Bk~`zlw)I)_Ers6w$-^m_tnDHt zeFrO$3M+!m#TTFYb><_Y8!3%O@m_@S+o?lC!|1lI6Rv}|c}r&+lnI!rDh^c4?wL+u zeukhbb_yPo>B|fpkh9Wv}y^}1qyt}@nT5WO@!x!uB>OUA1@ztlFP!>Rn`O0nBf7tp)1T1 z&bRIJTw2nX3mVn9xdF$Z&0a1~StIa>Co;=vhbof=za&dn)%Y{o$Y9 z7gXlIUwy@Lp1YqSdqZNE0EJvanp_mJ9zoX zKKKG^!{}D0bH?f8E$Geq5>=`iUfEaAaV38}vgJr!I_4|sb+-eLaQWSwidseBZbTL# z3A`+%Xkyt|((n!obRwdI<)l@{BbKvYAEz56N!;SII(VyH+Ha)Ad2Xyjs`wz&D?*jz zt*bYC_0kx4dWUdbnTxg^9V6(rp%e+%C!alpRgbO^yNuL|)t$y~T_8WMZ>=_#(%o#P z)0MthtW(jNiknjz;AZn;N3$!T_n&)x$!sZQ?wgJ4ChZ+&EeCVjA%p!*+lwD6h|jk_ z9bhaA$d-ruwVG8&JlT0z@v{J;M)&h89d(zATsysQ7CP|FR?|(V_jF9^XPtH98Vl~s z+@~Ze>G);8NH~wO|1r*}H>1ws^~aZUpAze+Ps_z6Tqnv@-;(a6z3x^B!3{UffA$bi zJ*s{dZ@f#nSd1#_FPJa-IlnT`0-LAx14v}_1cn3quT1Vej?0VRRq<^%N)MdIg|AYpC_hoo3g7>^G%t5x2 zmp331w~I8YSldT)Fb2Mf1U{)yN$`88(6c*4;0FXqoh_;zrpqza38%oN_ahq4_NJC} z2rNfnDk%tU^$SB(+t;GexUCB}Ib{woFN|Gx?$okamOhvk;-1&4sivsQ@j+iL|APB; ze=Tj0FwQ=hCed%oRfP$Xd^uh?`iOZYpG@|XdYEnx>n|X*10ODO$cDIgNre+v!gA`VaoQk zpV7k|BJdt9vb7@aeFdQ7Z`*1J{IsbD<(F*5i8?0SN2+_zRhwBSiP!pCP9F((c*~a9 zxS0CpWk^%V5z=s(ZEG{|Vk~z)5A$ZDG4PvSO98svsIEq^u*d4#SgV>c$-Ky+KFb=V ztXgpe+ZYp)rlcG<1M+a(mQ|NPVjd+<3RB6s@`H$DK!*I};<}7Je|odoN5oG7Wnzp? zbD`?y^M3gRLelhk7p1IrS~B}y+R-~tvNEop(P`$VviqIi>L>)LNC7IfJAD(5qd%TO zWvNf4iCu_|!gP8B&-KpNe%GkKGSSX9op3o~ER9K7q_YF0v7EY(Wvf6I(wHk;8cVR* zQzOG~oNY4$#&=!V`9!E}cd37e!5*|KO#GP1-3Q9GvJseo@ft6ARa$pEHBkTx{9SU| zh9xA|`K2p2hN&Q;F`%2K!5q!_8yBARf=5n~8Q;kBOq~-ltg-%g!P}N}@3Kp=&`3Xu zg{e{I#&xpgGW@xx+nrMwb4ZpQ(Kq09vZ=A$i~_=d)dffYXfsg}Ed z3#ZQc?JP3tz=DCKh<$X~S0_uk)DUZtyc{u`_|8&y-db_{*E>?8Z}$ans%3Z6@u*bj z4YWTPovlOzb0?rwb3@YUgYu(r+CLJ#$8$W#uLm`P^rO(3jCqSDk*hl&i}(Aq>NE7W zFFblYoazwZBuMRJ*fho?~;y1<$lhW;X5Fef*Bk*#1H30GH@Icl&%PkvhGL0aZR60!dY8 z&lpKpJR**2C5W{=Yi}LCx(iF7Z9a2$R|GCa7E9>bM=Ui)u%UxqpPg9rj!h~O%S@O% zeB0l%C3rDCYoQJqyi!bX)W39X$y%*`q9aHC*@$@6atTvgbJB>5R=2GzvSSO)F z6G3S=^VRv-G9}nBw^%cx!Xt5VSFBiXVMt>0&bg;obKPv;26|O&d8FzN;7wP$>9d~v zgGZ8Gsw@4QE{{Gd>To?*%kvodQ5eM9lqdC@6ww&(orgVO z*%oENM|Xd~6&8{ZzPt7(aHpf)TaNXW2I_yl8%sNRd&&AhdHK^?%-64{H_;>%rv(UI zAEx~mrc+#v;1&jD5=9<2=7B0foPTYsmTLh?%GZrpbWZpyP;;CAn z6UswzAO}NKq=#v%kEbOwyBzt#)CtPvW_OE zy)yr>c)wzFnc&7_*;pQ27{;#yzuuDGh9^A=5a)^_o{uf>i`d!o`haQ1w}U+(R2yqq zl3QVbR1=4@yP{%rQ~6mAh^AZWsOQ{tP<6PiWnofwtqZe#T$?=m?s?A%>)CW1N5Cil zhlw~z26kR5tlUMR%vhLXZ`ZEZgfC|V2p}0Bl4rCBsi;m$$J|Bv*zJ@$Z4!rt`Rxs_ zd%tthPr&Pi%GUP0fruy{K+$Xh(7 zKnG5E>g_(*a>)oj(I-uPPVq|{d`$vuaTS2y&%Ip|e>+&{96mtqe&OW9ITe5J>$vh@ z^^)hRiYsYu*K$K_RKWr;`uJjt#HgQ5=AmdWWfOdG@&fklg#q*YZLouOjL!-Zx~mg}HI1 zs2)36S{P1J>3r0a63?~p#4tsWr`NC!ZYg0?T8QZMche?!J=E#M0W$_)wn0!_QBYsaeb3h8WEj7VFolB|Q=KkY1+h1hCP*UyLVK&*ZiHma1Fl!F%Axg|k zf`UT&k1@$_f36meXRwt5N8r+~IUK`dkTp$;5< zK*sTu*10O)p2?H8a(7~lyoetnx8|xGYv+5GGJ5&&!enB1+L_V#Pmhv!o?$8D$eXJ< zd?m#9^u4HMP(716dkXgRC$--&% z`KI{dww**SW$=?|PmU9Z7X$mgbsy5M7~J{b%SbUR&F)iCg?x4N;QYyC|0j3E?H>Rm z+%RP>{ngn1C-lh&-OrH6#Zwbe>IcK$j_;q#`OzU#e8XS7s96!+QT<++T7QFrNLS|^ zvb|A0fEs_CP0JT@JoBb`?wy{;$AnMX=Sto5xO$VAI3wS>RLrFL1*$7r3C_0U6wY4O zda{cZ&6j88rb%|$^q5VL4b8UL$jiRe_GN`B$?-z;6&>>9@Mp>@owYVtSb1Z=D=PPT zNJ17_LpWzC=QQv5=}mT7(NpKA#u68!hXkn7;(b=%56v1`*9_&W=VZZ}xzC7LYCrY! z%gHsi|B?UBR{cVk2h8E@-QACL_)awKn=aXBc+!~AD{US=&)G|v{hU*56M-qG!7q*P z!RB3r8GK<$wtt|w@4mg)vlH@1&TA89Kc|oGWwU7$L*c&VIG3G2{yeHkfHciYdmEVm z=LPtoayUU(moX*^_oc(2(YDI=BV0}T#mbEsX$j&%n;+emSgtvy%A0 zg{eP`#K0K(B4VvKZ}BcyyXPBtHzdUMU<3}y`;HeBbv5rgDHPYIN~@6iRC`4YhFS#u zdv^-BN~V}&GvqSfj+5lowJWTzq!O5c$qRd$;B<&VTpK94B>YU_F~NNu+Q=>+3) zv6rW;Oi~L~=?=2&+;Na=xjo}3Y1Sav(!`OeDn4Y4LY6apvceHmR~HXL>ROeUXYsGmtRDzJ0x3M zDSZY-vu!XKOzVCoQF1)4sem$Xyrnaq;9IT9L$5dZ^JKH=H4jer7oV|>=;E9Enir*K zaiimg0h8a~RiJ)EiWn(_m>kOZ>hA5rljLS&-Jw*UhGQEJGQtS#I18~a@O^@*U#m&$goJ8q$HwitZn9wQq64!LA%*u zt0Y8!{6Yd5R$m6HiiUbIi{n!g5+UyeJOOWbWwpveDLV2oPZf3D>% zCsB`5?ZHdWx6>$u&?_H54DQ)NzwOerNa(akI17)GY(tB)wVCTcCXu<$|7z9Yl^58I z9Z^Cr75ZdEi;8&p z=+xHRryj&Dder*uz|Oyb;>NgRjxQ7?6rzKcy+NfSQkE2yao}1p!;6ogmW+}LO2Unk zZ_KL}oLu2EWEU%QhG|etgtr!Ig)Mq07oI$oGJvy|!;|EUhJUEr$0#IgT`X_w6T0l~ z%;YyI>j$bWFBylWpjQsPY+sr&9gJHcm?VC?Ol|j-|)B)Rj#D{*&(QAN_%D2b{Sdk^^u#YULcPi=Nxl< zaQ}rEnJ;Y9Co$R?iy(hOclkc3$j&6rd}_;(swVJ`Pi1elby`OM#FI2_Yh#IR{TH+D zLn|ARpzbcBgxqoM5d8*&o@H}4E+ZaSkfgnzdi3tTa(IG ziFbmkn^-?|kM>KC*_`nzqbewe!B34)130l-J_=d)Vq2x>uT_s@^5Gdvdb?Ph|~5iQrC z($Zgf1XYQaus|7!r`y(QMt|qF8tCi82j$#M_5h#<-+trAY9;0G8vxao=G9gi$^A!O zkTn{Uu@J_A1Sm-1jglzoPjChWx(zBEpkiA5+{*`3UF%`{ zfN}b+8--u;p9HN}sW{}j)ZdrGvs;{J%W@xjdn&m&UT-NMl$j7g+QQ@Ki?+n=9Iw#B zO^AT&PKBzt{Ro2u#k*_P>V%ruV`r?vP z{&H?`bWi4qTbEQWbYLg)4MI6qYHF_DwOf2*mL_79+D;rU8;Kue)+Ve(?`MYC?jZb? z70t$)bfHWe&gckY=hZuUx)_qUd04<$NM)fGrpb?yS&GI27`NFpeMf{d{=qqGjHprqyEURE-cV1US^cv`a5_PB(I(QXb<^)9Kr*pKbS< zsg(t`Fs3F>z!s?NV#oN6WD;R%-T@c}ueD z$@3Yi4el~!$&WeCd-9PRc$p&ABh-Y9Z^uta<+mt~PGXuk$mC7s)6e#S^KQVf6Q3k7 z$SLC}yKnl)t-|XexovX^(Y1xu&p*_ZzG<+nNh*@eNx2>}hgCgdz3-KN9()8Fy}7xm zJ%_*K60SoNS7*8x9106UH41!<1eR#`qbL5A%MKGHwo}|NP z9a+tyhSPOT-uPLU-ol6{Z8Mj?4q?@H;EnAaYdx|TpBIz&axYX#AO>JWOjB-!2cH+T z=>ZkAm@r=1Lo=d0~W9-@vj2I-e-5{@U4#shk6-nqF+`O+kU+6k_7=?)s{g+A8f zd=HezYn92nzw633>fadhd&OGTH0@#5Xe)0MOf7$8&-C}EQbV%Fwtedg(lq?<|I zGq$M8E4Az&t9#3HE#vrlPF44Af@^K6^9acGDcHi$)Wak<7FY*5=)o2{gTV2#ZZEvYyA`jhwkaAzvQGpKsZLLZazW9}1-Z{11 zpZtLb(!j4KTIjzNs2p2Z;)#;gq&zd8!&>XiC3^b?!W-%h$Tr7O_nKitS00{)VBG8w zcwB zlr>Jf(O?e^ia*n7y1a{O;tihmxCzRt4xPk6y!7GIWSD5z8#^{N$66tF z?^e$yBW8bl*rR4Tl{4u)aU$d0FPImawBZaZg=KMhW~!);5LTLPlQ0rmU)Aozr^wMc z`yLPIfXd?NgA0(n9nOGq|12*bFfj(68>T;-4-~T~s65Vl%ls@X8q`acf%*A8k6vy| z`Y`iq8#y_2@8_zcJ8nytE!PBZS*R(cNAX+>5n<*S5E|VCilzNdS-O6E;o>IvNWGIw zz|TFkw$c`fpSW8+em9m_Y;cnd#EfU_UQrKrdMi++q;L=l|M{}e_rZQhke2H}HaDI$ zzB5v(pM9^?8tZX7wVT26^A}fNdVJo8@n|J* zAjW}3vxyDODPa+BMxRu^VxuXlwFK(zqrjyk!c%ks2>7{23Jam49U>Lt4pIG$;d9zw z$HT`}#>x+Ly0_@gJai9Wi17i2*Mn`I_B%AlkDW47vy(`LB6*98*)?50D^}>XRGgdZ z(PlbOgVkJzEXBu#IFT~GKF>l%<5I=C_(W!ibI&ug2;#>ZzzrHr5b$6UrTV}2C4{C` z8}C6|#0l_p2Doo_OR_`8zqouGWY@-zY*A)G?ZqZCgG|}A14CdZ9|9v{6DuwAL&vQ? zU(`xy8X<|bLdhW=5lEQG8QpcI)bAA!=hxRdApP(t=@~ueNYb>Qk-Fkr(&tTh7TPy0 z8h8U~V7y(9SWo=D_o&Fv`arfavkXZJac_JbXFPp7e2ae9CF>vklW)syULEaD+S!e88(yzOLsyE16vOu(||n@l&ut8rtd$pKYzK8ln)e!Ywhx@WIm~pireB)bz1v zlOG?*EeS3Q&fOduBrC`_qd`WKx61=-&J#SFt5}7dviu2 zi9QJEw(+sMbgF2UU@9|*#8|=R^F?oYRD*qm-lvJR-`Z6cz2JYXtS}-l!Du@9nAgRl zZ{~E1hFb}U=<_r^&gs*5i&sYJWl)83_D(MOk1HTy;~tQxiq6TT@n^mZ`*f4DnIv$6 z^U;7fi&Xu{Wui|RWITisom@6ur;wG|&}D-DzNO<`#nnGNqKhAyJ!Hot=TOrsC?mKw z!7q2K?>;=d%-`M3!WK(-!5e|O`^4w~IQ1Tk#9_YCsrNb+&3$NyGcmg|4ce`4MP#vT z0*QB@We?p?i8GIw)GoC=bq3YRx6+rXv!iK4my)H=6WjtTl8NAYa@pw^_W-M2>BnDR zq(@+SJP<8F_0<43jiN!mb#Csl_(j*E5jAn8G%>;SG-IoeDqS}~;gGE0R0rFXl}=aU zvm1^8VuPtry?CchO&dhzZe4~f5c(bnwt1sj?rZji!8 zyBnbgG))V*$Q(;#>KJ3A@%Nm33{IZJYNsT_n#N`}khs2L1_qWuH{2?^_qAs>vC()h z3tIygG=X!qJrZO4E+D~d!WX`5WQEBtpAl~K-?a2*OoOaEwQBM#iIiH1)%KyA3 z`j$qK)rjfnR7eO@ovYr}WKT+UV)_~XK$-gB0g7nATYrVD=a~js(8?nv4C8MY25WFj z7h^l03enw<k9|sZ7QGc;a9ALygh`NqCC&GAB%faG;*V#!$cUhcG>F8KY zq2C42#YN%9?mFqVC6deh$45AxTg)W#t0syV#|X^~Yh7_A;#zS_bGcXMK3t8$-A+1n zmFQGbs1KFQeLn{|frS0N9Qd|3lziQaiSdl0yBfO8N^IzHeBFnuZtt=)w*$yOFOq0jHlnH&sG6I6dqFvFyMFchtBA5OQRS+N?zOr^D z6ZYd|<_^eS;9Yax30IlfR(-;~0JtGHTrgMmGh`Her(bigRo}2m(L?4V8xYRm$YoE{ z9p$5AY)(VT=i}rP=qEE3X`nnsGnN+ zai3op;!l{yY0!}xxh!cq0Pf#ESKO8SQ`TopzWRbsszXe3{j0pETl7{7iup~rfk0!h zFgaVdn2y0XCOWp|JuzyG-EPY;Rg4;BJ@K>hgD&Y<*hLuEr~%1VovEJB>E2aaOc;lf z8C0Fk4=pMR*U~vLUT30S9x}07yUzeFWw%+7SF0i`VZP6y8**XkxU!us!{s0>vr!k> zy~-O#qp)-g8bv+hGdxa`sy;G!K|*~pI&EateX|6W+4UlB3~I#{FKbPe!4BMI1eHW{ z$;RSoXTndW1X9izW^}=Mx;1zNK>UVna^Xodk*Rw>8!ar&pcSY;BtVDW(nD?^SZOb5 z2IHVb9kgJm!hzYWwC^^jHUsmO1+?%gJ>GRlfa*6X4F#=2z&*Le!T9^_S2sdclTko* z-BBK~-+_hm!A(tpobZ8rjOw_jcZ4G_Io*Z`K*$(Ev13>dM}Df`%&}SEfP?_(Ui^Oj zKqp|x*Wt6^LiJ&w1L392BQD@S;|fz(@Ji=_z?CeyS}S4U)@`OsV_(HK=fR;thy0k7dRSI zce9~8!A|`?AedSR`U9gDmimM0o9d-)-jjSY55YJhfYV6pbg7QH}R@LvL{qY_4!& zq%LpJL265u8he^=lRo}&lI?-$rG=r4)jLZVx;MbZ&a|)%5~fZkRdA;T^acr^YhM&( zqxH%>S-po7fvI)>o?A8%r8fVL<;1ft0kNs8?FC8#pu39(W%nRuX!)gDlc#HYP4ZQ1A9W;#}?c}TDrh5v`Op64wq!y$!)AO(I94P zu%jkmM>hvg-*?T8+_Lk)Ui54>y#D-%h}u*n5mW@@-HUtfE~JVc)Fx+t7j^?PK({$L z`tjfv*iPjnJ~b;&59P<){HA4lI&de zFc>Es7@ z5MfUZeU;zBxE9{dq>+x+boO#wB~sF*gdy3|ARAoTbhLkzg%=NdCUE8`kSbZA3dq?NuFYMOxLv8~n0Gs4e?S{w zQ{HC39XWiAo;rikWX8k|7dG;9LYW3a%x)^-UJ;798gWFQxQ7? z!js02Ww5?OqDNa21kVy{Jlf%8uu6gpmwSc;!O>>` z1ArNP3OI@Ql8lw5p-e81Hv-k%1qeUdY@6;G|2t}5hAnEX+rckfT!Rd!kBMYJoY4{D z!Pa|_b?n22Hs-#}2`!Z2P#Bm5WDNJ{Uf2SiU}!hek8yxuZ1+}w3E~2PAdv>0|6fV) z9-|~xEFs7oq2L!G0&p{FL~ui!T#4;X;b&1C{OPkes>*m0+9DH4`V3?#deBrd#ybjC zt=!924t`SP7B6b-74b3?@8Rl*D+W4b<}F5E5Z_fuF0Gp6ypqBDZv=Y%yWHbx0mPg= zc$S_IEi51k6c3K|?s9KX8m{ZzUXB``4F!pu=-4>$n;A8|kHPKkusk4;2`WM}^MaFt z^K&YgPKt1HN5Mt5-@?|1anX z%H{M*%TX`sH8Q<*v}k0GNR_mTgv3?^l?eMjx#@qo?=Sbu^L(H0+w*;%&nLxzKrm{B zxhz4$f;1}Ss9sLt@lNeW;yx{wUo5|Fg6#A`ke(>rDu(WO0iewzJWkA%rV}o#{U7qE z5GC}}h?O(hj!lM`hI4CO!JBN5qETq)cet# z#83F$pAD~;n$54sHM9GbXe|Da>9PFjQr^3Gcy3sdkI!&M$3VWQOGgQ6y2)SlqF}Ql zv^A2y$M%>%&V#$kD%`GRd+*LDwsQ*RZXJU{%891)h#gR*^@9>(_ALJpRj&Vuzrgs; zugEnTFhFAtZB&Gudz*vHbGyz>zb3XNXQ~)A-_!piVmqY zDO?7qCE*}h{{3jp~LRyyd2I|iXsDLi%U0uMqM;|cY9WNNMuT)s#mhRwh{ z;P~GLDR%-sPm~3U3}7hOhAIanGl!T{8|VyA!rN(1V9*SBIAY}+GY&Uii6N+Oz=1Vk zZscEOQdFmlLPQ`*1BrKr)(`7#7MIbFDQ8p4EKHYQbRFI4+35!8K`Mah&UtR-s8x%E zn2tSYty-=2^c1w-z-c=MX@H0grNd7>pNTf@F3w#T{!R3dsYreecgtzFdx8Q~j7(bz zs?<>Ms_$d~oen;T$jeUmdDXKsn%s@B4w%Z^ggX{u#w{WH<>pk0DcqIfep$25a-P@o>4P>!n26h)AVg^U#W8LaGo;G!7F45y{l&B*cp@(4Kf=eLm`th;)<8 z1<5*e$HZg*a>bS+(liF~m!Zvcf?u;HG{s+PL`pP(#YWrFX|I2%d3$Nd_a0OwUFUnq zaJ->i!PJ1-)}c@o4~XN(%|x5e2o|s%L`R7U2Pm4TAb+VxC4!5F8o`SQkCaIS(>yGv zkP4%I3)cH8PD#hS-=TgK(3$FY*&JCIwUa#t|Mae4K877%Oc+F_yIiYz`H zr4Wj;KmvpZu=ij3iHpnI%_}Mf$t94y&5gZkX{WBvI1m8(_!<1lfRP$`mnDyWLYstR z(IW6p?9;k~cRy@Qu%jm(fn@?bEZHJDs)bmR-?d%b1uwFI7rAVI!;7_5S2OVYKxz7* zb%TuLgDnN}Xwlh8h|m$70&!ykvv*QjRUlk$d9dL`zI0${{vDfT5bRU literal 0 HcmV?d00001 diff --git a/docs/web-service/training-process.png b/docs/web-service/training-process.png new file mode 100644 index 0000000000000000000000000000000000000000..0fb895862e3e68dbb5fc12beff81a69ca2626775 GIT binary patch literal 62694 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 WebServer ++ : Create Model +WebServer -> Database ++ : Store Model +Database -->> WebServer -- +WebServer -->> Actor -- + +Actor -> WebServer ++ : Create Training +WebServer -> Database ++ : Store Training +Database -->> WebServer -- +WebServer -->> Actor -- + +Actor ->> WebServer --++ : Start Training +WebServer ->> Clients ++ : Start Training +deactivate Clients + +loop For n Updates + WebServer ->> Clients --++ : Start Training Round + + WebServer <- Clients ++ : Download Global Model + WebServer --> Clients -- : Global Model + + Clients -> Clients ++ : Train Local Model + Clients -[hidden]-> Clients -- + + Clients ->> WebServer --++ : Upload Local Model + WebServer -> Database ++ : Store Local Model + Database -->> WebServer -- + + note over WebServer,celery #eeeeee + continue if **all** __//model uploads//__ arrived + end note + + WebServer ->> celery --++ : Dispatch Aggregation Task + + celery -> Database ++ : Get Local Models + Database --> celery -- + + celery -> celery ++ : Do Aggregation + celery -[hidden]-> celery -- + + celery -> Database ++ : Store "New" Global Model + Database --> celery -- + + celery -> Database ++ : Clean Up Local Models + Database --> celery -- + + celery ->> Clients --++ : Start Model Test + + WebServer <- Clients ++ : Download Global Model + WebServer --> Clients -- : Global Model + + Clients -> Clients ++ : Test Global Model + Clients -[hidden]-> Clients -- + + Clients ->> WebServer --++ : Upload Global Model Test Metrics + WebServer -> Database ++ : Store Test Metrics + Database -->> WebServer -- + + note over WebServer,celery #eeeeee + continue if **all** __//test metrics//__ arrived + end note +end + +WebServer ->> Clients ++ : Training Finished +WebServer ->> Actor --++: Training Finished +deactivate Clients +deactivate Actor +@enduml \ No newline at end of file diff --git a/docs/web-service/training.drawio.png b/docs/web-service/training.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..c8a183e7167e0576b0079d5949c4f69b63d597e2 GIT binary patch literal 42093 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