Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 840424b488 | |||
| dba362c230 |
@@ -1,15 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
# Define the jobs we want to run for this project
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/base:2023.03
|
||||
steps:
|
||||
- run: echo "so long and thanks for all the fish"
|
||||
|
||||
# Orchestrate our job run sequence
|
||||
workflows:
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build
|
||||
@@ -1,27 +0,0 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
|
||||
ARG VARIANT="3.10"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
|
||||
|
||||
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
|
||||
ARG NODE_VERSION="16"
|
||||
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||
|
||||
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
|
||||
# COPY requirements.txt /tmp/pip-tmp/
|
||||
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
|
||||
# && rm -rf /tmp/pip-tmp
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
||||
|
||||
USER vscode
|
||||
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
ENV PATH=$PATH:$HOME/.poetry/env
|
||||
@@ -1,55 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/python-3
|
||||
{
|
||||
"name": "Python 3",
|
||||
// "build": {
|
||||
// "dockerfile": "Dockerfile",
|
||||
// "context": "..",
|
||||
// "args": {
|
||||
// // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
|
||||
// "VARIANT": "3.6",
|
||||
// // Options
|
||||
// "NODE_VERSION": "lts/*"
|
||||
// }
|
||||
// },
|
||||
"dockerComposeFile": "./docker-compose.yaml",
|
||||
"service": "specklepy",
|
||||
"workspaceFolder": "/workspaces/specklepy",
|
||||
"shutdownAction": "stopCompose",
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.pylintArgs": [
|
||||
"--max-line-length=120"
|
||||
],
|
||||
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
|
||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
|
||||
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
|
||||
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
|
||||
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
||||
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
|
||||
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
|
||||
"python.testing.pytestArgs": [
|
||||
"tests/",
|
||||
"-s"
|
||||
],
|
||||
"python.testing.pytestEnabled": true,
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "poetry config virtualenvs.create false && poetry install",
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
version: "3.3" # optional since v1.27.0
|
||||
services:
|
||||
postgres:
|
||||
image: cimg/postgres:14.2
|
||||
environment:
|
||||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
network_mode: host
|
||||
redis:
|
||||
image: cimg/redis:6.2
|
||||
network_mode: host
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
command: ["bash", "-c", "/wait && node bin/www"]
|
||||
environment:
|
||||
POSTGRES_URL: "localhost"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle2_test"
|
||||
REDIS_URL: "redis://localhost"
|
||||
SESSION_SECRET: "keyboard cat"
|
||||
STRATEGY_LOCAL: "true"
|
||||
CANONICAL_URL: "http://localhost:3000"
|
||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
||||
DISABLE_FILE_UPLOADS: "true"
|
||||
network_mode: host
|
||||
|
||||
specklepy:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
args:
|
||||
VARIANT: 3.9
|
||||
NODE_VERSION: lts/*
|
||||
volumes:
|
||||
# Mounts the project folder to '/workspace'. While this file is in .devcontainer,
|
||||
# mounts are relative to the first file in the list, which is a level up.
|
||||
- ..:/workspaces/specklepy:cached
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
network_mode: host
|
||||
# networks:
|
||||
# default:
|
||||
@@ -1,3 +0,0 @@
|
||||
* text=auto eol=lf
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
+11
-12
@@ -1,5 +1,4 @@
|
||||
# Speckle Contribution Guidelines
|
||||
|
||||
## Introduction
|
||||
|
||||
Thank you for reading this! Speckle's a rather wide network of parts that depend on each other, either directly, indirectly or even just cosmetically.
|
||||
@@ -10,41 +9,41 @@ This means that what might look like a simple quick change in one repo may have
|
||||
|
||||
## Bugs & Issues 🐞
|
||||
|
||||
### Found a new bug?
|
||||
### Found a new bug?
|
||||
|
||||
- First step is to check whether this is a new bug! We encourage you to search through the issues of the project in question **and** associated repos!
|
||||
|
||||
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
|
||||
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
|
||||
|
||||
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
|
||||
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
|
||||
|
||||
- Try to reference & note all potentially affected projects.
|
||||
|
||||
### Sending a PR for Bug Fixes
|
||||
|
||||
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
|
||||
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
|
||||
|
||||
## New Features 🎉
|
||||
|
||||
The golden rule is to Discuss First!
|
||||
|
||||
- Before embarking on adding a new feature, suggest it first as an issue with the `enhancement` label and/or title - this will allow relevant people to pitch in
|
||||
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
|
||||
- The last step is to actually start writing code & submit a PR so we can follow along!
|
||||
- All new features should, if and where possible, come with tests. We won't merge without!
|
||||
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
|
||||
- The last step is to actually start writing code & submit a PR so we can follow along!
|
||||
- All new features should, if and where possible, come with tests. We won't merge without!
|
||||
|
||||
> Many clients may potentially have overlapping scopes, some features might already be in dev somewhere else, or might have been postponed to the next major release due to api instability in that area. For example, adding a delete stream button in the accounts panel in rhino: this feature was planned for speckle admin, and the whole functionality of the accounts panel in rhino is to be greatly reduced!
|
||||
|
||||
## Cosmetic Patches ✨
|
||||
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
|
||||
|
||||
Why? However trivial the changes might seem, there might be subtle reasons for the original code to be as it is. Furthermore, there are a lot of potential hidden costs (that even maintainers themselves are not aware of fully!) and they eat up review time unncessarily.
|
||||
|
||||
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
|
||||
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
|
||||
|
||||
|
||||
## Wrap up
|
||||
|
||||
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion on our community [forum](https://discourse.speckle.works).
|
||||
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion either on our community [forum](https://discourse.speckle.works) or [chat](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI).
|
||||
|
||||
🙌❤️💙💚💜🙌
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: New issue
|
||||
about: Create a report to help us improve
|
||||
title:
|
||||
labels:
|
||||
assignees:
|
||||
---
|
||||
|
||||
If it's your first time here - or you forgot about them - make sure you read the [contribution guidelines](CONTRIBUTING.md), and then feel free to delete this line!
|
||||
|
||||
### Expected vs. Actual Behavior
|
||||
|
||||
Describe the problem here.
|
||||
|
||||
### Reproduction Steps & System Config (win, osx, web, etc.)
|
||||
|
||||
Let us know how we can reproduce this, and attach relevant files (if any).
|
||||
|
||||
### Proposed Solution (if any)
|
||||
|
||||
Let us know what how you would solve this.
|
||||
|
||||
#### Optional: Affected Projects
|
||||
|
||||
Does this issue propagate to other dependencies or dependents? If so, list them here!
|
||||
@@ -1,113 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Help improve Speckle!
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
Provide a short summary in the Title above. Examples of good Issue titles:
|
||||
|
||||
* "Bug: Error from server when reticulating splines"
|
||||
* "Bug: Revit crashes when installing connector"
|
||||
|
||||
-->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!---
|
||||
|
||||
Please answer the following questions before submitting an issue.
|
||||
|
||||
-->
|
||||
|
||||
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
|
||||
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
|
||||
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
|
||||
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
|
||||
- [ ] I'm reporting the issue to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
|
||||
|
||||
## What package are you referring to?
|
||||
|
||||
<!---
|
||||
Is it related to the server (backend) only, or does this bug relate to the frontend, viewer, objectloader or any other package?
|
||||
-->
|
||||
|
||||
## Describe the bug
|
||||
|
||||
<!---
|
||||
A clear and concise description of what the bug is.
|
||||
-->
|
||||
|
||||
## To Reproduce
|
||||
|
||||
<!---
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
-->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!---
|
||||
A clear and concise description of what you expected to happen.
|
||||
-->
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!---
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
-->
|
||||
|
||||
## System Info
|
||||
|
||||
If applicable, please fill in the below details - they help a lot!
|
||||
|
||||
### Desktop (please complete the following information):
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
### Smartphone (please complete the following information):
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
## Failure Logs
|
||||
|
||||
<!---
|
||||
Please include any relevant log snippets or files here, or upload as a file.
|
||||
|
||||
If including inline, please use markdown code block syntax. https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks
|
||||
For example:
|
||||
|
||||
```
|
||||
your log output here
|
||||
```
|
||||
-->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!---
|
||||
Add any other context about the problem here.
|
||||
-->
|
||||
|
||||
## Proposed Solution (if any)
|
||||
|
||||
<!---
|
||||
Let us know what how you would solve this.
|
||||
-->
|
||||
|
||||
#### Optional: Affected Projects
|
||||
|
||||
<!---
|
||||
Does this issue propagate to other dependencies or dependents? If so, list them here with links!
|
||||
-->
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for Speckle!
|
||||
title: ''
|
||||
labels: enhancement, question
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
Provide a short summary in the Title above. Examples of good Issue titles:
|
||||
|
||||
* "Enhancement: Connector for Minecraft"
|
||||
* "Enhancement: Web viewer should support tesseracts"
|
||||
|
||||
-->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!---
|
||||
|
||||
Please answer the following questions before submitting an issue.
|
||||
|
||||
-->
|
||||
|
||||
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
|
||||
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
|
||||
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
|
||||
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
|
||||
- [ ] I'm requesting the feature to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
|
||||
|
||||
## What package are you referring to?
|
||||
|
||||
<!---
|
||||
Is it related to the server (backend) only, or does this feature request relate to the frontend, viewer, objectloader or any other package?
|
||||
-->
|
||||
|
||||
## Is your feature request related to a problem? Please describe.
|
||||
|
||||
<!---
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
-->
|
||||
|
||||
## Describe the solution you'd like
|
||||
|
||||
<!---
|
||||
A clear and concise description of what you want to happen.
|
||||
-->
|
||||
|
||||
## Describe alternatives you've considered
|
||||
|
||||
<!---
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
-->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!---
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
Have you seen this feature implemented in any other software? Can you provide screenshots or links to video or documentation?
|
||||
What works well about these existing features in other software? What doesn't work well?
|
||||
-->
|
||||
|
||||
## Related issues or community discussions
|
||||
|
||||
<!---
|
||||
Is this feature request related to (but sufficiently distinct from) any existing issues?
|
||||
Does this feature request require other features to be available beforehand?
|
||||
Has this feature been discussed in the community forum, please link here? https://speckle.community/
|
||||
-->
|
||||
@@ -0,0 +1,25 @@
|
||||
Description of PR...
|
||||
|
||||
## Changes
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Unit tests
|
||||
- [ ] Documentation
|
||||
|
||||
## References
|
||||
|
||||
(optional)
|
||||
|
||||
Include **important** links regarding the implementation of this PR.
|
||||
This usually includes and RFC or an aggregation of issues and/or individual conversations
|
||||
that helped put this solution together. This helps ensure there is a good aggregation
|
||||
of resources regarding the implementation.
|
||||
|
||||
```text
|
||||
Fixes #85, Fixes #22, Fixes username/repo#123
|
||||
Connects #123
|
||||
```
|
||||
@@ -1,102 +0,0 @@
|
||||
<!---
|
||||
|
||||
Provide a short summary in the Title above. Examples of good PR titles:
|
||||
|
||||
* "Feature: adds metrics to component"
|
||||
|
||||
* "Fix: resolves duplication in comment thread"
|
||||
|
||||
* "Update: apollo v2.34.0"
|
||||
|
||||
-->
|
||||
|
||||
## Description & motivation
|
||||
|
||||
<!---
|
||||
|
||||
Describe your changes, and why you're making them. What benefit will this have to others?
|
||||
|
||||
Is this linked to an open Github issue, a thread in Speckle community,
|
||||
or another pull request? Link it here.
|
||||
|
||||
If it is related to a Github issue, and resolves it, please link to the issue number, e.g.:
|
||||
Fixes #85, Fixes #22, Fixes username/repo#123
|
||||
Connects #123
|
||||
|
||||
-->
|
||||
|
||||
## Changes:
|
||||
|
||||
<!---
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
|
||||
-->
|
||||
|
||||
## To-do before merge:
|
||||
|
||||
<!---
|
||||
|
||||
(Optional -- remove this section if not needed)
|
||||
|
||||
Include any notes about things that need to happen before this PR is merged, e.g.:
|
||||
|
||||
- [ ] Change the base branch
|
||||
|
||||
- [ ] Ensure PR #56 is merged
|
||||
|
||||
-->
|
||||
|
||||
## Screenshots:
|
||||
|
||||
<!---
|
||||
|
||||
Include a screenshot the before and after. This can be a screenshot of a plugin, web frontend, or output in a terminal.
|
||||
|
||||
-->
|
||||
|
||||
## Validation of changes:
|
||||
|
||||
<!---
|
||||
|
||||
Describe what tests have been added or amended, and why these demonstrate it works and will prevent this feature being accidentally broken by future changes.
|
||||
|
||||
-->
|
||||
|
||||
## Checklist:
|
||||
|
||||
<!---
|
||||
|
||||
This checklist is mostly useful as a reminder of small things that can easily be
|
||||
|
||||
forgotten – it is meant as a helpful tool rather than hoops to jump through.
|
||||
|
||||
Put an `x` between the square brackets, e.g. [x], for all the items that apply,
|
||||
|
||||
make notes next to any that haven't been addressed, and remove any items that are not relevant to this PR.
|
||||
|
||||
-->
|
||||
|
||||
- [ ] My pull request follows the guidelines in the [Contributing guide](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)?
|
||||
- [ ] My pull request does not duplicate any other open [Pull Requests](../../pulls) for the same update/change?
|
||||
- [ ] My commits are related to the pull request and do not amend unrelated code or documentation.
|
||||
- [ ] My code follows a similar style to existing code.
|
||||
- [ ] I have added appropriate tests.
|
||||
- [ ] I have updated or added relevant documentation.
|
||||
|
||||
## References
|
||||
|
||||
<!---
|
||||
|
||||
(Optional -- remove this section if not needed )
|
||||
|
||||
Include **important** links regarding the implementation of this PR.
|
||||
|
||||
This usually includes a RFC or an aggregation of issues and/or individual conversations
|
||||
|
||||
that helped put this solution together. This helps ensure we retain and share knowledge
|
||||
|
||||
regarding the implementation, and may help others understand motivation and design decisions etc..
|
||||
|
||||
-->
|
||||
@@ -1,56 +0,0 @@
|
||||
name: "Specklepy test and build"
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "v3-dev"
|
||||
push:
|
||||
branches:
|
||||
- "v3-dev"
|
||||
jobs:
|
||||
ci:
|
||||
name: continuous-integration
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pre-commit/
|
||||
key: ${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
|
||||
- name: Run pre-commit
|
||||
run: uv run pre-commit run --all-files
|
||||
|
||||
# - name: Run Speckle Server
|
||||
# run: docker compose up -d
|
||||
|
||||
# - name: Run tests
|
||||
# run: uv run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
|
||||
|
||||
# - uses: codecov/codecov-action@v5
|
||||
# if: matrix.python-version == 3.13
|
||||
# with:
|
||||
# fail_ci_if_error: true # optional (default = false)
|
||||
# files: ./reports/test-results.xml # optional
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Minimize uv cache
|
||||
run: uv cache prune --ci
|
||||
@@ -1,33 +0,0 @@
|
||||
# Publish a release to PyPI.
|
||||
name: "Publish to PyPI"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Specklepy test and build"]
|
||||
branches: [v3-dev]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
pypi-publish:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: testpypi
|
||||
permissions:
|
||||
# For PyPI's trusted publishing.
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# This is necessary so that we have the tags.
|
||||
fetch-depth: 0
|
||||
- name: "Build artifacts"
|
||||
run: uv build
|
||||
- name: Publish to PyPi
|
||||
run: uv publish --index test
|
||||
|
||||
- name: Test package install
|
||||
run: uv run --index test --with specklepy --no-project -- python -c "import specklepy"
|
||||
+1
-5
@@ -1,7 +1,3 @@
|
||||
.tool-versions
|
||||
.envrc
|
||||
reports/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -112,4 +108,4 @@ venv.bak/
|
||||
|
||||
# other
|
||||
scratch.py
|
||||
settings.json
|
||||
settings.json
|
||||
@@ -1,31 +0,0 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
name: ruff lint
|
||||
entry: uv run ruff check --force-exclude
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
name: ruff format
|
||||
entry: uv run ruff format --force-exclude
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
|
||||
|
||||
- repo: https://github.com/commitizen-tools/commitizen
|
||||
hooks:
|
||||
- id: commitizen
|
||||
- id: commitizen-branch
|
||||
stages:
|
||||
- pre-push
|
||||
rev: v3.13.0
|
||||
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
Vendored
+2
-11
@@ -6,20 +6,11 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "debugpy",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false
|
||||
},
|
||||
{
|
||||
"name": "Pytest",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "pytest",
|
||||
"args": [],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,35 @@
|
||||
<h1 align="center">
|
||||
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
|
||||
Speckle | specklepy 🐍
|
||||
</h1>
|
||||
# speckle-py 🥧
|
||||
|
||||
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
||||
[](https://twitter.com/SpeckleSystems) [](https://discourse.speckle.works)
|
||||
[](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI) [](https://speckle.systems)
|
||||
|
||||
> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better.
|
||||
## Introduction
|
||||
|
||||
<h3 align="center">
|
||||
The Python SDK
|
||||
</h3>
|
||||
|
||||
<p align="center"><a href="https://codecov.io/gh/specklesystems/specklepy"><img src="https://codecov.io/gh/specklesystems/specklepy/branch/main/graph/badge.svg?token=8KQFL5N0YF" alt="Codecov"></a></p>
|
||||
|
||||
# Repo structure
|
||||
|
||||
## Usage
|
||||
|
||||
Send and receive data from a Speckle Server with `operations`, interact with the Speckle API with the `SpeckleClient`, create and extend your own custom Speckle Objects with `Base`, and more!
|
||||
|
||||
Head to the [**📚 specklepy docs**](https://speckle.guide/dev/python.html) for more information and usage examples.
|
||||
> ⚠ This is the start of the Python client for Speckle 2.0. It is currently quite nebulous and may be trashed and rebuilt at any moment! It is compatible with Python 3.6+ ⚠
|
||||
|
||||
## Developing & Debugging
|
||||
|
||||
### Installation
|
||||
To get started, create a virtual environment and pip install the requirements.
|
||||
|
||||
This project uses uv for dependency management, make sure you follow the official [docs](https://docs.astral.sh/uv/) to get it.
|
||||
|
||||
To create a new virtual environment with uv run `$ uv venv` and follow the instructions on the screen to activate the virtual environment.
|
||||
To bootstrap the project environment run `$ uv sync`. This will install both the package and dev dependencies.
|
||||
|
||||
To execute any python script run `$ uv run python my_script.py`
|
||||
|
||||
> Alternatively you may roll your own virtual-env with either venv, virtualenv, pyenv-virtualenv etc. Uv will play along an recognize if it is invoked from inside a virtual environment.
|
||||
|
||||
### Style guide
|
||||
|
||||
All our repo wide styling linting and other rules are checked and enforced by `pre-commit`, which is included in the dev dependencies.
|
||||
It is recommended to set up `pre-commit` after installing the dependencies by running `$ pre-commit install`.
|
||||
Commiting code that doesn't adhere to the given rules, will fail the checks in our CI system.
|
||||
|
||||
### Local Data Paths
|
||||
|
||||
It may be helpful to know where the local accounts and object cache dbs are stored. Depending on on your OS, you can find the dbs at:
|
||||
|
||||
- Windows: `APPDATA` or `<USER>\AppData\Roaming\Speckle`
|
||||
- Linux: `$XDG_DATA_HOME` or by default `~/.local/share/Speckle`
|
||||
- Mac: `~/.config/Speckle`
|
||||
on windows:
|
||||
```
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) and [code of conduct](.github/CODE_OF_CONDUCT.md) for an overview of the practices we try to follow.
|
||||
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) for an overview of the best practices we try to follow.
|
||||
|
||||
## Community
|
||||
|
||||
The Speckle Community hangs out on [the forum](https://discourse.speckle.works), do join and introduce yourself & feel free to ask us questions!
|
||||
The Speckle Community hangs out in two main places, usually:
|
||||
|
||||
## Security
|
||||
- on [the forum](https://discourse.speckle.works)
|
||||
- on [the chat](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI)
|
||||
|
||||
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
||||
Do join and introduce yourself!
|
||||
|
||||
## License
|
||||
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.2.+ | :white_check_mark: |
|
||||
| < 2.2 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Hi! If you've found something off, we'd be more than happy if you would report it via security@speckle.systems. We will work together with you to correctly identify the cause and implement a fix. Thanks for helping make Speckle safer!
|
||||
@@ -1,111 +0,0 @@
|
||||
version: "3.9"
|
||||
name: "speckle-server"
|
||||
|
||||
services:
|
||||
####
|
||||
# Speckle Server dependencies
|
||||
#######
|
||||
postgres:
|
||||
image: "postgres:16.4-alpine3.20@sha256:d898b0b78a2627cb4ee63464a14efc9d296884f1b28c841b0ab7d7c42f1fffdf"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: speckle
|
||||
POSTGRES_USER: speckle
|
||||
POSTGRES_PASSWORD: speckle
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data/
|
||||
healthcheck:
|
||||
# the -U user has to match the POSTGRES_USER value
|
||||
test: ["CMD-SHELL", "pg_isready -U speckle"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
redis:
|
||||
image: "redis:6.0-alpine"
|
||||
restart: always
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
minio:
|
||||
image: "minio/minio:RELEASE.2023-10-25T06-33-25Z"
|
||||
command: server /data --console-address ":9001"
|
||||
restart: always
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"curl -s -o /dev/null http://127.0.0.1:9000/minio/index.html",
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
restart: always
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /nodejs/bin/node
|
||||
- -e
|
||||
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(res.statusCode != 200 || body.toLowerCase().includes('error'));}); }).end(); } catch { process.exit(1); }"
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
ports:
|
||||
- "0.0.0.0:3000:3000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# TODO: Change this to the URL of the speckle server, as accessed from the network
|
||||
CANONICAL_URL: "http://127.0.0.1:8080"
|
||||
SPECKLE_AUTOMATE_URL: "http://127.0.0.1:3030"
|
||||
FRONTEND_ORIGIN: "http://127.0.0.1:8081"
|
||||
|
||||
# TODO: Change thvolumes:
|
||||
REDIS_URL: "redis://redis"
|
||||
|
||||
S3_ENDPOINT: "http://minio:9000"
|
||||
S3_ACCESS_KEY: "minioadmin"
|
||||
S3_SECRET_KEY: "minioadmin"
|
||||
S3_BUCKET: "speckle-server"
|
||||
S3_CREATE_BUCKET: "true"
|
||||
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
MAX_PROJECT_MODELS_PER_PAGE: 500
|
||||
|
||||
# TODO: Change this to a unique secret for this server
|
||||
SESSION_SECRET: "TODO:ReplaceWithLongString"
|
||||
|
||||
STRATEGY_LOCAL: "true"
|
||||
DEBUG: "speckle:*"
|
||||
|
||||
POSTGRES_URL: "postgres"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle"
|
||||
ENABLE_MP: "false"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: speckle-server
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
minio-data:
|
||||
@@ -1,63 +0,0 @@
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.objects import Base
|
||||
from specklepy.transports.sqlite import SQLiteTransport
|
||||
|
||||
|
||||
class Sub(Base):
|
||||
bar: List[str]
|
||||
|
||||
|
||||
def random_string():
|
||||
letters = string.ascii_lowercase
|
||||
return "".join(random.choice(letters) for _ in range(10))
|
||||
|
||||
|
||||
BASE_PATH = SQLiteTransport.get_base_path("Speckle")
|
||||
|
||||
|
||||
def clean_db():
|
||||
os.remove(Path(BASE_PATH, "Objects.db"))
|
||||
|
||||
|
||||
def one_pass(clean: bool, randomize: bool, child_count: int):
|
||||
foo = Base()
|
||||
for i in range(child_count):
|
||||
stuff = random_string() if randomize else "stuff"
|
||||
foo[f"@child_{i}"] = Sub(bar=["asdf", "bar", i, stuff])
|
||||
|
||||
if clean:
|
||||
clean_db()
|
||||
transport = SQLiteTransport()
|
||||
start = time.time()
|
||||
hash = operations.send(base=foo, transports=[transport])
|
||||
send_time = time.time() - start
|
||||
|
||||
receive_start = time.time()
|
||||
operations.receive(hash, transport)
|
||||
receive_time = time.time() - receive_start
|
||||
return send_time, receive_time
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sample_size = 4
|
||||
|
||||
test_permutations = [
|
||||
(True, True),
|
||||
(False, False),
|
||||
(False, True),
|
||||
(True, False),
|
||||
]
|
||||
for clean, randomize in test_permutations:
|
||||
print(f"CLEAN: {clean}, RANDOMIZE: {randomize}")
|
||||
for child_count in [10, 100, 1000, 10000]:
|
||||
print(f"\tCHILD COUNT: {child_count}")
|
||||
for _ in range(sample_size):
|
||||
send_time, receive_time = one_pass(clean, randomize, child_count)
|
||||
print(f"\t\tSend: {send_time} Receive: {receive_time}")
|
||||
@@ -1,15 +0,0 @@
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
|
||||
if __name__ == "__main__":
|
||||
stream_url = "https://latest.speckle.dev/streams/7d051a6449"
|
||||
wrapper = StreamWrapper(stream_url)
|
||||
|
||||
transport = wrapper.get_transport()
|
||||
|
||||
rec = operations.receive("98396753f8bf7fe1cb60c5193e9f9d86", transport)
|
||||
|
||||
# hash = operations.send(base=foo, transports=[transport], use_default_cache=False)
|
||||
debug(rec)
|
||||
@@ -1,39 +0,0 @@
|
||||
import random
|
||||
import string
|
||||
from typing import List
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
from specklepy.objects import Base
|
||||
|
||||
|
||||
class Sub(Base):
|
||||
bar: List[str]
|
||||
|
||||
|
||||
def random_string():
|
||||
letters = string.ascii_lowercase
|
||||
return "".join(random.choice(letters) for _ in range(10))
|
||||
|
||||
|
||||
def create_object(child_count: int) -> Base:
|
||||
foo = Base()
|
||||
for i in range(child_count):
|
||||
stuff = random_string()
|
||||
foo[f"@child_{i}"] = Sub(bar=["asdf", "bar", i, stuff])
|
||||
return foo
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
stream_url = "http://hyperion:3000/streams/2372b54c35"
|
||||
|
||||
child_count = 10
|
||||
foo = create_object(child_count)
|
||||
|
||||
wrapper = StreamWrapper(stream_url)
|
||||
transport = wrapper.get_transport()
|
||||
|
||||
hash = operations.send(base=foo, transports=[transport], use_default_cache=False)
|
||||
|
||||
rec = operations.receive(hash, transport)
|
||||
print(rec)
|
||||
@@ -1,36 +0,0 @@
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.objects_v2.geometry import Base
|
||||
from specklepy.objects_v2.units import Units
|
||||
|
||||
dct = {
|
||||
"id": "1234abcd",
|
||||
"units": None,
|
||||
"speckle_type": "Base",
|
||||
"applicationId": None,
|
||||
"totalChildrenCount": 0,
|
||||
}
|
||||
base = Base()
|
||||
for prop, value in dct.items():
|
||||
base.__setattr__(prop, value)
|
||||
|
||||
|
||||
debug(base)
|
||||
debug(base.units)
|
||||
|
||||
base.units = "m"
|
||||
debug(base.units)
|
||||
base.units = None
|
||||
|
||||
debug(base.units)
|
||||
|
||||
foo = operations.serialize(base)
|
||||
|
||||
base.units = 10
|
||||
|
||||
debug(base.units)
|
||||
debug(foo)
|
||||
|
||||
base.units = Units.mm
|
||||
debug(base.units)
|
||||
@@ -1,60 +0,0 @@
|
||||
"""This is an example showcasing the usage of speckle `Base` class."""
|
||||
|
||||
# the speckle.objects module exposes all speckle provided classes
|
||||
from devtools import debug
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.objects import Base
|
||||
|
||||
|
||||
class ExampleSub(Base):
|
||||
"""
|
||||
Inheriting from `Base` is done with in the standard way by default.
|
||||
|
||||
The syntax is similar to the stdlib dataclass syntax.
|
||||
No __init__ method definition is required, that is done automatically by the base
|
||||
type. Also the attributes defined this way are instance attributes despite they
|
||||
might look like class attributes.
|
||||
|
||||
The speckle Base uses the pydantic BaseModel in the background, but ideally that
|
||||
is not the consumers concern.
|
||||
|
||||
**Important note:** currently the way how serialization works, requires
|
||||
each attribute to have a valid default value, just like `foo` has. This includes
|
||||
default values for all primitives and complex datastructures.
|
||||
Failing to provide a default, breaks the receiving end of the transport.
|
||||
"""
|
||||
|
||||
foo: str = "bar"
|
||||
|
||||
|
||||
class SpeckleSub(ExampleSub, speckle_type="custom_speckle_sub"):
|
||||
"""
|
||||
Example custom type name registration.
|
||||
|
||||
This is an optional feature.
|
||||
The default value of the speckle_type is generated from the name of the class, but
|
||||
optionally it may be overridden. This is useful, since the speckle_type has to be
|
||||
unique for each subclass of speckle Base.
|
||||
"""
|
||||
|
||||
magic: str = "trick"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# example usage
|
||||
custom_sub = SpeckleSub(
|
||||
foo=123,
|
||||
magic="trick",
|
||||
bar="baric",
|
||||
extra=123,
|
||||
)
|
||||
# support for dynamic attributes
|
||||
custom_sub.extra_extra = "what is this?"
|
||||
debug(custom_sub)
|
||||
|
||||
serialized = operations.serialize(custom_sub)
|
||||
deserialized = operations.deserialize(serialized)
|
||||
# the only difference should be between the two data is that the deserialized
|
||||
# instance id attribute is not None.
|
||||
debug(deserialized)
|
||||
@@ -1,87 +0,0 @@
|
||||
[project]
|
||||
dynamic = ["version"]
|
||||
# version = "3.0.0a1"
|
||||
name = "specklepy"
|
||||
description = "The Python SDK for Speckle 2.0"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "Speckle Systems", email = "devops@speckle.systems" }]
|
||||
license = { text = "Apache-2.0" }
|
||||
requires-python = ">=3.10.0, <4.0"
|
||||
dependencies = [
|
||||
"appdirs>=1.4.4",
|
||||
"attrs>=24.3.0",
|
||||
"deprecated>=1.2.15",
|
||||
"gql[requests,websockets]>=3.5.0",
|
||||
"httpx>=0.28.1",
|
||||
"pydantic>=2.10.5",
|
||||
"pydantic-settings>=2.7.1",
|
||||
"stringcase>=1.2.0",
|
||||
"ujson>=5.10.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"commitizen>=4.1.0",
|
||||
"devtools>=0.12.2",
|
||||
"hatch>=1.14.0",
|
||||
"hatch-vcs>=0.4.0",
|
||||
"pre-commit>=4.0.1",
|
||||
"pytest>=8.3.4",
|
||||
"pytest-asyncio>=0.25.2",
|
||||
"pytest-cov>=6.0.0",
|
||||
"pytest-ordering>=0.6",
|
||||
"ruff>=0.9.2",
|
||||
"types-deprecated>=1.2.15.20241117",
|
||||
"types-requests>=2.32.0.20241016",
|
||||
"types-ujson>=5.10.0.20240515",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/specklesystems/specklepy"
|
||||
documentation = "https://speckle.guide/dev/py-examples.html"
|
||||
homepage = "https://speckle.systems/"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling", "hatch-vcs"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.version]
|
||||
source = "vcs"
|
||||
|
||||
[tool.hatch.version.raw-options]
|
||||
local_scheme = "no-local-version"
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
version = "2.9.2"
|
||||
tag_format = "$version"
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [".venv", "**/*.yml"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# pycodestyle
|
||||
"E",
|
||||
# Pyflakes
|
||||
"F",
|
||||
# pyupgrade
|
||||
"UP",
|
||||
# flake8-bugbear
|
||||
"B",
|
||||
# flake8-simplify
|
||||
"SIM",
|
||||
# isort
|
||||
"I",
|
||||
]
|
||||
ignore = ["UP006", "UP007", "UP035"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple/"
|
||||
publish-url = "https://upload.pypi.org/legacy/"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "test"
|
||||
url = "https://test.pypi.org/simple/"
|
||||
publish-url = "https://test.pypi.org/legacy/"
|
||||
@@ -0,0 +1,31 @@
|
||||
aiohttp==3.7.1
|
||||
appdirs==1.4.4
|
||||
astroid==2.4.2
|
||||
async-timeout==3.0.1
|
||||
attrs==20.3.0
|
||||
black==20.8b1
|
||||
certifi==2020.11.8
|
||||
chardet==3.0.4
|
||||
click==7.1.2
|
||||
colorama==0.4.4
|
||||
gql==3.0.0a4
|
||||
graphql-core==3.1.2
|
||||
idna==2.10
|
||||
isort==5.6.4
|
||||
lazy-object-proxy==1.4.3
|
||||
mccabe==0.6.1
|
||||
multidict==5.0.0
|
||||
mypy-extensions==0.4.3
|
||||
pathspec==0.8.1
|
||||
pydantic==1.7.2
|
||||
pylint==2.6.0
|
||||
regex==2020.11.11
|
||||
requests==2.24.0
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
urllib3==1.25.11
|
||||
websockets==8.1
|
||||
wrapt==1.12.1
|
||||
yarl==1.5.1
|
||||
@@ -0,0 +1,85 @@
|
||||
from gql.client import SyncClientSession
|
||||
from speckle.logging.exceptions import SpeckleException
|
||||
from typing import Dict
|
||||
|
||||
from speckle.api import resources
|
||||
from speckle.api.resources import stream, server, user, subscriptions
|
||||
from gql import Client, gql
|
||||
from gql.transport.requests import RequestsHTTPTransport
|
||||
from gql.transport.aiohttp import AIOHTTPTransport
|
||||
from gql.transport.websockets import WebsocketsTransport
|
||||
|
||||
|
||||
class SpeckleClient:
|
||||
DEFAULT_HOST = "staging.speckle.dev"
|
||||
USE_SSL = True
|
||||
|
||||
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
|
||||
ws_protocol = "ws"
|
||||
http_protocol = "http"
|
||||
|
||||
if use_ssl:
|
||||
ws_protocol = "wss"
|
||||
http_protocol = "https"
|
||||
|
||||
self.url = f"{http_protocol}://{host}"
|
||||
self.graphql = self.url + "/graphql"
|
||||
self.ws_url = f"{ws_protocol}://{host}/graphql"
|
||||
self.me = None
|
||||
|
||||
self.httpclient = Client(
|
||||
transport=RequestsHTTPTransport(url=self.graphql, verify=True, retries=3)
|
||||
)
|
||||
self.wsclient = None
|
||||
|
||||
self._init_resources()
|
||||
|
||||
def authenticate(self, token: str) -> None:
|
||||
"""Authenticate the client using a personal access token
|
||||
The token is saved in the client object and a synchronous GraphQL entrypoint is created
|
||||
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
self.me = {"token": token}
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.me['token']}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
httptransport = RequestsHTTPTransport(
|
||||
url=self.graphql, headers=headers, verify=True, retries=3
|
||||
)
|
||||
wstransport = WebsocketsTransport(
|
||||
url=self.ws_url,
|
||||
init_payload={"Authorization": f"Bearer {self.me['token']}"},
|
||||
)
|
||||
self.httpclient = Client(transport=httptransport)
|
||||
self.wsclient = Client(transport=wstransport)
|
||||
|
||||
self._init_resources()
|
||||
|
||||
def execute_query(self, query: str) -> Dict:
|
||||
return self.httpclient.execute(query)
|
||||
|
||||
def _init_resources(self) -> None:
|
||||
self.stream = stream.Resource(
|
||||
me=self.me, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.server = server.Resource(
|
||||
me=self.me, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.user = user.Resource(me=self.me, basepath=self.url, client=self.httpclient)
|
||||
self.subscribe = subscriptions.Resource(
|
||||
me=self.me,
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
attr = getattr(resources, name)
|
||||
return attr.Resource(me=self.me, basepath=self.url, client=self.httpclient)
|
||||
except:
|
||||
raise SpeckleException(
|
||||
f"Method {name} is not supported by the SpeckleClient class"
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: stream_schema.json
|
||||
# timestamp: 2020-11-17T14:33:13+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Collaborator(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
role: Optional[str]
|
||||
avatar: Optional[str]
|
||||
|
||||
|
||||
class Commit(BaseModel):
|
||||
id: Optional[str]
|
||||
message: Optional[str]
|
||||
authorName: Optional[str]
|
||||
authorId: Optional[str]
|
||||
authorAvatar: Optional[str]
|
||||
createdAt: Optional[str]
|
||||
referencedObject: Optional[str]
|
||||
|
||||
|
||||
class Commits(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[Any]
|
||||
items: List[Commit] = []
|
||||
|
||||
|
||||
class Object(BaseModel):
|
||||
id: Optional[str]
|
||||
speckleType: Optional[str]
|
||||
applicationId: Optional[str]
|
||||
totalChildrenCount: Optional[int]
|
||||
createdAt: Optional[str]
|
||||
|
||||
|
||||
class Branch(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
commits: Optional[Commits]
|
||||
|
||||
|
||||
class Branches(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Branch] = []
|
||||
|
||||
|
||||
class Streams(BaseModel):
|
||||
totalCount: Optional[int]
|
||||
cursor: Optional[datetime]
|
||||
items: List[Stream] = []
|
||||
|
||||
|
||||
class Stream(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
isPublic: Optional[bool]
|
||||
createdAt: Optional[str]
|
||||
updatedAt: Optional[str]
|
||||
collaborators: List[Collaborator] = []
|
||||
branches: Optional[Branches]
|
||||
commit: Optional[Commit]
|
||||
object: Optional[Object]
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: Optional[str]
|
||||
email: Optional[str]
|
||||
name: Optional[str]
|
||||
bio: Optional[str]
|
||||
company: Optional[str]
|
||||
avatar: Optional[str]
|
||||
verified: Optional[bool]
|
||||
role: Optional[str]
|
||||
streams: Optional[Streams]
|
||||
@@ -0,0 +1,76 @@
|
||||
from logging import error
|
||||
from speckle.logging.exceptions import GraphQLException, SpeckleException
|
||||
from typing import Dict, List
|
||||
from gql.client import Client
|
||||
from gql.gql import gql
|
||||
from gql.transport.exceptions import TransportQueryError
|
||||
|
||||
|
||||
class ResourceBase(object):
|
||||
def __init__(
|
||||
self,
|
||||
me: Dict,
|
||||
basepath: str,
|
||||
client: Client,
|
||||
name: str,
|
||||
methods: list,
|
||||
) -> None:
|
||||
self.me = me
|
||||
self.basepath = basepath
|
||||
self.client = client
|
||||
self.name = name
|
||||
self.methods = methods
|
||||
self.schema = None
|
||||
|
||||
def _step_into_response(self, response: dict, return_type: str or List):
|
||||
"""Step into the dict to get the relevant data"""
|
||||
if return_type is None:
|
||||
return response
|
||||
elif isinstance(return_type, str):
|
||||
return response[return_type]
|
||||
elif isinstance(return_type, List):
|
||||
for key in return_type:
|
||||
response = response[key]
|
||||
return response
|
||||
|
||||
def _parse_response(self, response: dict or list, schema=None):
|
||||
"""Try to create a class instance from the response"""
|
||||
if isinstance(response, list):
|
||||
return [self._parse_response(response=r, schema=schema) for r in response]
|
||||
if schema:
|
||||
return schema.parse_obj(response)
|
||||
elif self.schema:
|
||||
return self.schema.parse_obj(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
def make_request(
|
||||
self,
|
||||
query: gql,
|
||||
params: Dict = None,
|
||||
return_type: str or List = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
) -> Dict or GraphQLException:
|
||||
"""Executes the GraphQL query"""
|
||||
try:
|
||||
response = self.client.execute(query, variable_values=params)
|
||||
except Exception as e:
|
||||
if isinstance(e, TransportQueryError):
|
||||
return GraphQLException(
|
||||
message=f"Failed to execute the GraphQL {self.name} request. Errors: {e.errors}",
|
||||
errors=e.errors,
|
||||
data=e.data,
|
||||
)
|
||||
else:
|
||||
return SpeckleException(
|
||||
message=f"Failed to execute the GraphQL {self.name} request. Inner exception: {e}",
|
||||
exception=e,
|
||||
)
|
||||
|
||||
response = self._step_into_response(response=response, return_type=return_type)
|
||||
|
||||
if parse_response:
|
||||
return self._parse_response(response=response, schema=schema)
|
||||
else:
|
||||
return response
|
||||
@@ -0,0 +1,13 @@
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import inspect
|
||||
import pkgutil
|
||||
from importlib import import_module
|
||||
|
||||
|
||||
for (_, name, _) in pkgutil.iter_modules([Path(__file__).parent]):
|
||||
|
||||
imported_module = import_module("." + name, package=__name__)
|
||||
|
||||
if hasattr(imported_module, "Resource"):
|
||||
setattr(sys.modules[__name__], name, imported_module)
|
||||
@@ -0,0 +1,48 @@
|
||||
from typing import List, Optional
|
||||
from gql import gql
|
||||
from pydantic.main import BaseModel
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import Branch
|
||||
|
||||
NAME = "branch"
|
||||
METHODS = ["create"]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for branches"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
self.schema = Branch
|
||||
|
||||
def create(
|
||||
self, streamId: str, name: str, description: str = "No description provided"
|
||||
) -> str:
|
||||
"""Create a new branch on this stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the new branch
|
||||
description {str} -- a short description of the branch
|
||||
|
||||
Returns:
|
||||
id {str} -- the newly created branch's id
|
||||
"""
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchCreate($branch: BranchCreateInput!){
|
||||
branchCreate(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"branch": {
|
||||
"streamId": streamId,
|
||||
"name": name,
|
||||
"description": description,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(query=query, params=params, parse_response=False)
|
||||
@@ -0,0 +1,19 @@
|
||||
from typing import Optional, List
|
||||
from gql import gql
|
||||
from pydantic.main import BaseModel
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import Commit
|
||||
|
||||
|
||||
NAME = "commit"
|
||||
METHODS = []
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for commits"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
self.schema = Commit
|
||||
@@ -0,0 +1,79 @@
|
||||
from typing import Dict
|
||||
from gql import gql
|
||||
from gql.client import Client
|
||||
from speckle.api.resource import ResourceBase
|
||||
|
||||
|
||||
NAME = "server"
|
||||
METHODS = ["get", "apps"]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for the server"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
|
||||
def get(self) -> Dict:
|
||||
"""Get the server info
|
||||
|
||||
Returns:
|
||||
dict -- the server info in dictionary form
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Server {
|
||||
serverInfo {
|
||||
name
|
||||
company
|
||||
description
|
||||
adminContact
|
||||
canonicalUrl
|
||||
roles {
|
||||
name
|
||||
description
|
||||
resourceTarget
|
||||
}
|
||||
scopes {
|
||||
name
|
||||
description
|
||||
}
|
||||
authStrategies{
|
||||
id
|
||||
name
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(query=query)
|
||||
|
||||
def apps(self) -> Dict:
|
||||
"""Get the apps registered on the server
|
||||
|
||||
Returns:
|
||||
dict -- a dictionary of apps registered on the server
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Apps {
|
||||
apps {
|
||||
id
|
||||
name
|
||||
description
|
||||
termsAndConditionsLink
|
||||
logo
|
||||
author {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(query=query)
|
||||
@@ -0,0 +1,298 @@
|
||||
from re import search
|
||||
from typing import Dict, List, Optional
|
||||
from pydantic import BaseModel
|
||||
from gql import gql
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import Stream
|
||||
from speckle.logging.exceptions import GraphQLException
|
||||
|
||||
NAME = "stream"
|
||||
METHODS = [
|
||||
"list",
|
||||
"create",
|
||||
"get",
|
||||
"update",
|
||||
"delete",
|
||||
"search",
|
||||
]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for streams"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
|
||||
self.schema = Stream
|
||||
|
||||
def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream:
|
||||
"""Get the specified stream from the server
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
Stream -- the retrieved stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Stream($id: String!, $branch_limit: Int!, $commit_limit: Int!) {
|
||||
stream(id: $id) {
|
||||
id
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
avatar
|
||||
}
|
||||
branches(limit: $branch_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commit_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
authorName
|
||||
authorId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id, "branch_limit": branch_limit, "commit_limit": commit_limit}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="stream")
|
||||
|
||||
def list(self, stream_limit: int = 10) -> List[Stream]:
|
||||
"""Get a list of the user's streams
|
||||
|
||||
Arguments:
|
||||
stream_limit {int} -- The maximum number of streams to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- A list of Stream objects
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query User($stream_limit: Int!) {
|
||||
user {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
streams(limit: $stream_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"stream_limit": stream_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["user", "streams", "items"]
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
name: str = "Anonymous Python Stream",
|
||||
description: str = "No description provided",
|
||||
is_public: bool = True,
|
||||
) -> str:
|
||||
"""Create a new stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
id {str} -- the id of the newly created stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamCreate($stream: StreamCreateInput!) {
|
||||
streamCreate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"stream": {"name": name, "description": description, "isPublic": is_public}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamCreate", parse_response=False
|
||||
)
|
||||
|
||||
def update(
|
||||
self, id: str, name: str = None, description: str = None, is_public: bool = None
|
||||
) -> bool:
|
||||
"""Update an existing stream
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to be updated
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
bool -- whether the stream update was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamUpdate($stream: StreamUpdateInput!) {
|
||||
streamUpdate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"isPublic": is_public,
|
||||
}
|
||||
# remove None values so graphql doesn't cry
|
||||
params = {"stream": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamUpdate", parse_response=False
|
||||
)
|
||||
|
||||
def delete(self, id: str) -> bool:
|
||||
"""Delete a stream given its id
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to delete
|
||||
|
||||
Returns:
|
||||
bool -- whether the deletion was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamDelete($id: String!) {
|
||||
streamDelete(id: $id)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamDelete", parse_response=False
|
||||
)
|
||||
|
||||
def search(
|
||||
self,
|
||||
search_query: str,
|
||||
limit: int = 25,
|
||||
branch_limit: int = 10,
|
||||
commit_limit: int = 10,
|
||||
):
|
||||
"""Search for streams by name, description, or id
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- a list of Streams that match the search query
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query StreamSearch($search_query: String!,$limit: Int!, $branch_limit:Int!, $commit_limit:Int!) {
|
||||
streams(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
avatar
|
||||
}
|
||||
branches(limit: $branch_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commit_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
authorName
|
||||
authorId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"search_query": search_query,
|
||||
"limit": limit,
|
||||
"branch_limit": branch_limit,
|
||||
"commit_limit": commit_limit,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["streams", "items"]
|
||||
)
|
||||
@@ -0,0 +1,125 @@
|
||||
from typing import Callable, Dict, List, Optional, Any
|
||||
from functools import wraps
|
||||
from gql import gql
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.resources.stream import Stream
|
||||
from speckle.logging.exceptions import GraphQLException, SpeckleException
|
||||
|
||||
NAME = "subscribe"
|
||||
METHODS = [
|
||||
"stream_added",
|
||||
"stream_updated",
|
||||
"stream_removed",
|
||||
]
|
||||
|
||||
|
||||
def check_wsclient(function):
|
||||
@wraps(function)
|
||||
async def check_wsclient_wrapper(self, *args, **kwargs):
|
||||
if self.client is None:
|
||||
raise SpeckleException(
|
||||
"You must authenticate before you can subscribe to events"
|
||||
)
|
||||
else:
|
||||
return await function(self, *args, **kwargs)
|
||||
|
||||
return check_wsclient_wrapper
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for subscriptions"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def stream_added(self, callback: Callable = None):
|
||||
"""Subscribes to new stream added event for your profile. Use this to display an up-to-date list of streams.
|
||||
|
||||
Arguments:
|
||||
callback {Callable[Stream]} -- a function that takes the updated stream as an argument and executes each time a stream is added
|
||||
|
||||
Returns:
|
||||
Stream -- the update stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
subscription { userStreamAdded }
|
||||
"""
|
||||
)
|
||||
return await self.subscribe(
|
||||
query=query, callback=callback, return_type="userStreamAdded", schema=Stream
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def stream_updated(self, id: str, callback: Callable = None):
|
||||
"""Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id of the stream to subscribe to
|
||||
callback {Callable[Stream]} -- a function that takes the updated stream as an argument and executes each time the stream is updated
|
||||
|
||||
Returns:
|
||||
Stream -- the update stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
subscription Update($id: String!) { streamUpdated(streamId: $id) }
|
||||
"""
|
||||
)
|
||||
params = {"id": id}
|
||||
|
||||
return await self.subscribe(
|
||||
query=query,
|
||||
params=params,
|
||||
callback=callback,
|
||||
return_type="streamUpdated",
|
||||
schema=Stream,
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def stream_removed(self, callback: Callable = None):
|
||||
"""Subscribes to stream removed event for your profile. Use this to display an up-to-date list of streams for your profile. NOTE: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of revokedBy in the payload.
|
||||
|
||||
Arguments:
|
||||
callback {Callable[Dict]} -- a function that takes the returned dict as an argument and executes each time a stream is removed
|
||||
|
||||
Returns:
|
||||
dict -- dict containing 'id' of stream removed and optionally 'revokedBy'
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
subscription { userStreamRemoved }
|
||||
"""
|
||||
)
|
||||
|
||||
return await self.subscribe(
|
||||
query=query,
|
||||
callback=callback,
|
||||
return_type="userStreamRemoved",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def subscribe(
|
||||
self,
|
||||
query: gql,
|
||||
params: Dict = None,
|
||||
callback: Callable = None,
|
||||
return_type: str or List = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
):
|
||||
# if self.client.transport.websocket is None:
|
||||
# TODO: add multiple subs to the same ws connection
|
||||
async with self.client as session:
|
||||
async for res in session.subscribe(query, variable_values=params):
|
||||
res = self._step_into_response(response=res, return_type=return_type)
|
||||
if parse_response:
|
||||
res = self._parse_response(response=res, schema=schema)
|
||||
if callback is not None:
|
||||
callback(res)
|
||||
else:
|
||||
return res
|
||||
@@ -0,0 +1,86 @@
|
||||
from speckle.logging.exceptions import SpeckleException
|
||||
from typing import List, Optional
|
||||
from gql import gql
|
||||
from pydantic.main import BaseModel
|
||||
from speckle.api.resource import ResourceBase
|
||||
from speckle.api.models import User
|
||||
|
||||
NAME = "user"
|
||||
METHODS = ["get"]
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for users"""
|
||||
|
||||
def __init__(self, me, basepath, client) -> None:
|
||||
super().__init__(
|
||||
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
|
||||
)
|
||||
self.schema = User
|
||||
|
||||
def get(self, id: str = None) -> User:
|
||||
"""Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
|
||||
Returns:
|
||||
User -- the retrieved user
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query User($id: String) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="user")
|
||||
|
||||
def search(self, search_query: str, limit: int = 25) -> List[User]:
|
||||
"""Searches for user by name or email. The search query must be at least 3 characters long
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[User] -- a list of User objects that match the search query
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserSearch($search_query: String!, $limit: Int!) {
|
||||
userSearch(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"search_query": search_query, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["userSearch", "items"]
|
||||
)
|
||||
@@ -0,0 +1,640 @@
|
||||
|
||||
|
||||
scalar DateTime
|
||||
|
||||
scalar EmailAddress
|
||||
|
||||
scalar BigInt
|
||||
|
||||
scalar JSONObject
|
||||
|
||||
|
||||
directive @hasScope(scope: String!) on FIELD_DEFINITION
|
||||
directive @hasRole(role: String!) on FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
"""
|
||||
Stare into the void.
|
||||
"""
|
||||
_: String
|
||||
}
|
||||
type Mutation{
|
||||
"""
|
||||
The void stares back.
|
||||
"""
|
||||
_: String
|
||||
}
|
||||
type Subscription{
|
||||
"""
|
||||
It's lonely in the void.
|
||||
"""
|
||||
_: String
|
||||
},extend type Query {
|
||||
"""
|
||||
Gets a specific app from the server.
|
||||
"""
|
||||
app( id: String! ): ServerApp
|
||||
|
||||
"""
|
||||
Returns all the publicly available apps on this server.
|
||||
"""
|
||||
apps: [ServerAppListItem]
|
||||
}
|
||||
|
||||
type ServerApp {
|
||||
id: String!
|
||||
secret: String!
|
||||
name: String!
|
||||
description: String
|
||||
termsAndConditionsLink: String
|
||||
logo: String
|
||||
public: Boolean
|
||||
trustByDefault: Boolean
|
||||
author: AppAuthor
|
||||
createdAt: DateTime!
|
||||
redirectUrl: String!
|
||||
scopes: [Scope]!
|
||||
}
|
||||
|
||||
type ServerAppListItem {
|
||||
id: String!
|
||||
name: String!
|
||||
description: String
|
||||
termsAndConditionsLink: String
|
||||
logo: String
|
||||
author: AppAuthor
|
||||
}
|
||||
|
||||
type AppAuthor {
|
||||
name: String
|
||||
id: String
|
||||
}
|
||||
|
||||
extend type User {
|
||||
"""
|
||||
Returns the apps you have authorized.
|
||||
"""
|
||||
authorizedApps: [ServerAppListItem]
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "apps:read")
|
||||
|
||||
"""
|
||||
Returns the apps you have created.
|
||||
"""
|
||||
createdApps: [ServerAppListItem]
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "apps:read")
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
"""
|
||||
Register a new third party application.
|
||||
"""
|
||||
appCreate(app: AppCreateInput!): String!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "apps:write")
|
||||
|
||||
"""
|
||||
Update an existing third party application. **Note: This will invalidate all existing tokens, refresh tokens and access codes and will require existing users to re-authorize it.**
|
||||
"""
|
||||
appUpdate(app: AppUpdateInput!): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "apps:write")
|
||||
|
||||
"""
|
||||
Deletes a thirty party application.
|
||||
"""
|
||||
appDelete(appId: String!): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "apps:write")
|
||||
|
||||
"""
|
||||
Revokes (de-authorizes) an application that you have previously authorized.
|
||||
"""
|
||||
appRevokeAccess(appId: String!): Boolean
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "apps:write")
|
||||
|
||||
}
|
||||
|
||||
input AppCreateInput {
|
||||
name: String!
|
||||
description: String!
|
||||
termsAndConditionsLink: String
|
||||
logo: String
|
||||
public: Boolean
|
||||
redirectUrl: String!
|
||||
scopes: [String]!
|
||||
}
|
||||
|
||||
input AppUpdateInput {
|
||||
id: String!
|
||||
name: String!
|
||||
description: String!
|
||||
termsAndConditionsLink: String
|
||||
logo: String
|
||||
public: Boolean
|
||||
redirectUrl: String!
|
||||
scopes: [String]!
|
||||
}
|
||||
,extend type ServerInfo {
|
||||
"""
|
||||
The authentication strategies available on this server.
|
||||
"""
|
||||
authStrategies: [AuthStrategy]
|
||||
}
|
||||
|
||||
type AuthStrategy {
|
||||
id: String!,
|
||||
name: String!,
|
||||
icon: String!,
|
||||
url: String!,
|
||||
color: String
|
||||
}
|
||||
,extend type User{
|
||||
"""
|
||||
Returns a list of your personal api tokens.
|
||||
"""
|
||||
apiTokens: [ApiToken]
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "tokens:read")
|
||||
}
|
||||
|
||||
type ApiToken {
|
||||
id: String!
|
||||
name: String!
|
||||
lastChars: String!
|
||||
scopes: [String]!
|
||||
createdAt: DateTime! #date
|
||||
lifespan: BigInt!
|
||||
lastUsed: String! #date
|
||||
}
|
||||
|
||||
input ApiTokenCreateInput {
|
||||
scopes: [String!]!,
|
||||
name: String!,
|
||||
lifespan: BigInt
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
"""
|
||||
Creates an personal api token.
|
||||
"""
|
||||
apiTokenCreate(token: ApiTokenCreateInput!):String!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "tokens:write")
|
||||
"""
|
||||
Revokes (deletes) an personal api token.
|
||||
"""
|
||||
apiTokenRevoke(token: String!):Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "tokens:write")
|
||||
}
|
||||
,extend type Stream {
|
||||
commits(limit: Int! = 25, cursor: String): CommitCollection
|
||||
commit(id: String!): Commit
|
||||
branches(limit: Int! = 25, cursor: String): BranchCollection
|
||||
branch(name: String!): Branch
|
||||
}
|
||||
|
||||
extend type User {
|
||||
commits(limit: Int! = 25, cursor: String): CommitCollectionUser
|
||||
}
|
||||
|
||||
type Branch {
|
||||
id: String!
|
||||
name: String!
|
||||
author: User!
|
||||
description: String
|
||||
commits(limit: Int! = 25, cursor: String): CommitCollection
|
||||
}
|
||||
|
||||
type Commit {
|
||||
id: String!
|
||||
referencedObject: String!
|
||||
message: String
|
||||
authorName: String
|
||||
authorId: String
|
||||
createdAt: DateTime
|
||||
}
|
||||
|
||||
type CommitCollectionUserNode {
|
||||
id: String!
|
||||
referencedObject: String!
|
||||
message: String
|
||||
streamId: String
|
||||
streamName: String
|
||||
createdAt: DateTime
|
||||
}
|
||||
|
||||
type BranchCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [Branch]
|
||||
}
|
||||
|
||||
type CommitCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [Commit]
|
||||
}
|
||||
|
||||
type CommitCollectionUser {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [CommitCollectionUserNode]
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
branchCreate(branch: BranchCreateInput!): String!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
branchUpdate(branch: BranchUpdateInput!): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
branchDelete(branch: BranchDeleteInput!): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
|
||||
commitCreate(commit: CommitCreateInput!): String!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
commitUpdate(commit: CommitUpdateInput!): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
commitDelete(commit: CommitDeleteInput!): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
}
|
||||
|
||||
extend type Subscription {
|
||||
# TODO: auth for these subscriptions
|
||||
"""
|
||||
Subscribe to branch created event
|
||||
"""
|
||||
branchCreated(streamId: String!): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
"""
|
||||
Subscribe to branch updated event.
|
||||
"""
|
||||
branchUpdated(streamId: String!, branchId: String): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
"""
|
||||
Subscribe to branch deleted event
|
||||
"""
|
||||
branchDeleted(streamId: String!): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
|
||||
"""
|
||||
Subscribe to commit created event
|
||||
"""
|
||||
commitCreated(streamId: String!): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
"""
|
||||
Subscribe to commit updated event.
|
||||
"""
|
||||
commitUpdated(streamId: String!, commitId: String): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
"""
|
||||
Subscribe to commit deleted event
|
||||
"""
|
||||
commitDeleted(streamId: String!): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
}
|
||||
|
||||
input BranchCreateInput {
|
||||
streamId: String!
|
||||
name: String!
|
||||
description: String
|
||||
}
|
||||
|
||||
input BranchUpdateInput {
|
||||
streamId: String!
|
||||
id: String!
|
||||
name: String
|
||||
description: String
|
||||
}
|
||||
|
||||
input BranchDeleteInput {
|
||||
streamId: String!
|
||||
id: String!
|
||||
}
|
||||
|
||||
input CommitCreateInput {
|
||||
streamId: String!
|
||||
branchName: String!
|
||||
objectId: String!
|
||||
message: String
|
||||
previousCommitIds: [String]
|
||||
}
|
||||
|
||||
input CommitUpdateInput {
|
||||
streamId: String!
|
||||
id: String!
|
||||
message: String!
|
||||
}
|
||||
|
||||
input CommitDeleteInput {
|
||||
streamId: String!
|
||||
id: String!
|
||||
}
|
||||
,extend type Stream {
|
||||
object( id: String! ): Object
|
||||
}
|
||||
|
||||
type Object {
|
||||
id: String!
|
||||
speckleType: String!
|
||||
applicationId: String
|
||||
createdAt: DateTime
|
||||
totalChildrenCount: Int
|
||||
"""
|
||||
The full object, with all its props & other things. **NOTE:** If you're requesting objects for the purpose of recreating & displaying, you probably only want to request this specific field.
|
||||
"""
|
||||
data: JSONObject
|
||||
"""
|
||||
Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
|
||||
**NOTE**: Providing any of the two last arguments ( `query`, `orderBy` ) will trigger a different code branch that executes a much more expensive SQL query. It is not recommended to do so for basic clients that are interested in purely getting all the objects of a given commit.
|
||||
"""
|
||||
children(
|
||||
limit: Int! = 100,
|
||||
depth: Int! = 50,
|
||||
select: [String],
|
||||
cursor: String,
|
||||
query: [JSONObject!],
|
||||
orderBy: JSONObject ): ObjectCollection!
|
||||
}
|
||||
|
||||
type ObjectCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
objects: [Object]!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
objectCreate( objectInput: ObjectCreateInput! ): [String]!
|
||||
}
|
||||
|
||||
input ObjectCreateInput {
|
||||
"""
|
||||
The stream against which these objects will be created.
|
||||
"""
|
||||
streamId: String!
|
||||
"""
|
||||
The objects you want to create.
|
||||
"""
|
||||
objects: [JSONObject]!
|
||||
},extend type Query {
|
||||
serverInfo: ServerInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
Information about this server.
|
||||
"""
|
||||
type ServerInfo {
|
||||
name: String!
|
||||
company: String
|
||||
description: String
|
||||
adminContact: String
|
||||
canonicalUrl: String
|
||||
termsOfService: String
|
||||
roles: [Role]!
|
||||
scopes: [Scope]!
|
||||
}
|
||||
|
||||
"""
|
||||
Available roles.
|
||||
"""
|
||||
type Role {
|
||||
name: String!
|
||||
description: String!
|
||||
resourceTarget: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Available scopes.
|
||||
"""
|
||||
type Scope {
|
||||
name: String!
|
||||
description: String!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
serverInfoUpdate(info: ServerInfoUpdateInput!): Boolean
|
||||
@hasRole(role: "server:admin")
|
||||
@hasScope(scope: "server:setup")
|
||||
}
|
||||
|
||||
input ServerInfoUpdateInput {
|
||||
name: String!
|
||||
company: String
|
||||
description: String
|
||||
adminContact: String
|
||||
termsOfService: String
|
||||
}
|
||||
,extend type Query {
|
||||
"""
|
||||
Returns a specific stream.
|
||||
"""
|
||||
stream( id: String! ): Stream
|
||||
|
||||
"""
|
||||
All the streams of the current user, pass in the `query` parameter to seach by name, description or ID.
|
||||
"""
|
||||
streams( query: String, limit: Int = 25, cursor: String ): StreamCollection
|
||||
@hasScope(scope: "streams:read")
|
||||
}
|
||||
|
||||
type Stream {
|
||||
id: String!
|
||||
name: String!
|
||||
description: String
|
||||
isPublic: Boolean!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
collaborators: [ StreamCollaborator ]!
|
||||
}
|
||||
|
||||
extend type User {
|
||||
"""
|
||||
All the streams that a user has access to.
|
||||
"""
|
||||
streams( limit: Int! = 25, cursor: String ): StreamCollection
|
||||
}
|
||||
|
||||
type StreamCollaborator {
|
||||
id: String!
|
||||
name: String!
|
||||
role: String!
|
||||
company: String
|
||||
avatar: String
|
||||
}
|
||||
|
||||
type StreamCollection {
|
||||
totalCount: Int!
|
||||
cursor: String
|
||||
items: [ Stream ]
|
||||
}
|
||||
|
||||
|
||||
extend type Mutation {
|
||||
"""
|
||||
Creates a new stream.
|
||||
"""
|
||||
streamCreate( stream: StreamCreateInput! ): String
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
"""
|
||||
Updates an existing stream.
|
||||
"""
|
||||
streamUpdate( stream: StreamUpdateInput! ): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
"""
|
||||
Deletes an existing stream.
|
||||
"""
|
||||
streamDelete( id: String! ): Boolean!
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
"""
|
||||
Grants permissions to a user on a given stream.
|
||||
"""
|
||||
streamGrantPermission( permissionParams: StreamGrantPermissionInput! ): Boolean
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
"""
|
||||
Revokes the permissions of a user on a given stream.
|
||||
"""
|
||||
streamRevokePermission( permissionParams: StreamRevokePermissionInput! ): Boolean
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:write")
|
||||
}
|
||||
|
||||
extend type Subscription {
|
||||
|
||||
#
|
||||
# User bound subscriptions that operate on the stream collection of an user
|
||||
# Example relevant view/usecase: updating the list of streams for a user.
|
||||
#
|
||||
|
||||
"""
|
||||
Subscribes to new stream added event for your profile. Use this to display an up-to-date list of streams.
|
||||
**NOTE**: If someone shares a stream with you, this subscription will be triggered with an extra value of `sharedBy` in the payload.
|
||||
"""
|
||||
userStreamAdded: JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "profile:read")
|
||||
|
||||
"""
|
||||
Subscribes to stream removed event for your profile. Use this to display an up-to-date list of streams for your profile.
|
||||
**NOTE**: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of `revokedBy` in the payload.
|
||||
"""
|
||||
userStreamRemoved: JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "profile:read")
|
||||
|
||||
#
|
||||
# Stream bound subscriptions that operate on the stream itself.
|
||||
# Example relevant view/usecase: a single stream connector, or view, or component in a web app
|
||||
#
|
||||
|
||||
"""
|
||||
Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
||||
"""
|
||||
streamUpdated( streamId: String ): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
|
||||
"""
|
||||
Subscribes to stream deleted event. Use this in clients/components that pertain only to this stream.
|
||||
"""
|
||||
streamDeleted( streamId: String ): JSONObject
|
||||
@hasRole(role: "server:user")
|
||||
@hasScope(scope: "streams:read")
|
||||
|
||||
}
|
||||
|
||||
input StreamCreateInput {
|
||||
name: String
|
||||
description: String
|
||||
isPublic: Boolean
|
||||
}
|
||||
|
||||
input StreamUpdateInput {
|
||||
id: String!
|
||||
name: String
|
||||
description: String
|
||||
isPublic: Boolean
|
||||
}
|
||||
|
||||
input StreamGrantPermissionInput {
|
||||
streamId: String!,
|
||||
userId: String!,
|
||||
role: String!
|
||||
}
|
||||
|
||||
input StreamRevokePermissionInput {
|
||||
streamId: String!,
|
||||
userId: String!
|
||||
}
|
||||
,extend type Query {
|
||||
"""
|
||||
Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
||||
"""
|
||||
user(id: String): User
|
||||
userSearch(
|
||||
query: String!
|
||||
limit: Int! = 25
|
||||
cursor: String
|
||||
): UserSearchResultCollection
|
||||
userPwdStrength(pwd: String!): JSONObject
|
||||
}
|
||||
|
||||
"""
|
||||
Base user type.
|
||||
"""
|
||||
type User {
|
||||
id: String!
|
||||
suuid: String
|
||||
email: String
|
||||
name: String
|
||||
bio: String
|
||||
company: String
|
||||
avatar: String
|
||||
verified: Boolean
|
||||
profiles: JSONObject
|
||||
role: String
|
||||
}
|
||||
|
||||
type UserSearchResultCollection {
|
||||
cursor: String
|
||||
items: [UserSearchResult]
|
||||
}
|
||||
|
||||
type UserSearchResult {
|
||||
id: String!
|
||||
name: String
|
||||
bio: String
|
||||
company: String
|
||||
avatar: String
|
||||
verified: Boolean
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
"""
|
||||
Edits a user's profile.
|
||||
"""
|
||||
userUpdate(user: UserUpdateInput!): Boolean!
|
||||
}
|
||||
|
||||
input UserUpdateInput {
|
||||
name: String
|
||||
company: String
|
||||
bio: String
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
class SpeckleException(Exception):
|
||||
def __init__(self, message: str, exception: Exception = None) -> None:
|
||||
self.message = message
|
||||
self.exception = exception
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"SpeckleException: {self.message}"
|
||||
|
||||
|
||||
class GraphQLException(SpeckleException):
|
||||
def __init__(self, message: str, errors: List, data=None) -> None:
|
||||
super().__init__(message=message)
|
||||
self.errors = errors
|
||||
self.data = data
|
||||
@@ -0,0 +1,51 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Beam.json
|
||||
# timestamp: 2020-11-24T16:33:00+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Beam(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,51 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Brace.json
|
||||
# timestamp: 2020-11-24T16:33:02+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Brace(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,52 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Column.json
|
||||
# timestamp: 2020-11-24T16:33:04+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Column(BaseModel):
|
||||
height: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,57 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Duct.json
|
||||
# timestamp: 2020-11-24T16:33:07+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Duct(BaseModel):
|
||||
width: Optional[float] = None
|
||||
height: Optional[float] = None
|
||||
diameter: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
level: Optional[Level] = None
|
||||
velocity: Optional[float] = None
|
||||
system: Optional[Optional[str]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,51 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Element.json
|
||||
# timestamp: 2020-11-24T16:33:08+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,56 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Floor.json
|
||||
# timestamp: 2020-11-24T16:33:10+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Floor(BaseModel):
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,67 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: GridLine.json
|
||||
# timestamp: 2020-11-24T16:33:12+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class GridLine(BaseModel):
|
||||
baseLine: Optional[Line] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,40 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Level.json
|
||||
# timestamp: 2020-11-24T16:33:15+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,51 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Opening.json
|
||||
# timestamp: 2020-11-24T16:33:18+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Opening(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,56 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Roof.json
|
||||
# timestamp: 2020-11-24T16:33:33+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Roof(BaseModel):
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,59 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Room.json
|
||||
# timestamp: 2020-11-24T16:33:34+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Room(BaseModel):
|
||||
number: Optional[Optional[str]] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,28 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Topography.json
|
||||
# timestamp: 2020-11-24T16:33:35+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Topography(BaseModel):
|
||||
baseGeometry: Optional[Mesh] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,52 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Wall.json
|
||||
# timestamp: 2020-11-24T16:33:37+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Wall(BaseModel):
|
||||
height: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,61 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Arc.json
|
||||
# timestamp: 2020-11-24T16:32:59+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Arc(BaseModel):
|
||||
radius: Optional[Optional[float]] = None
|
||||
startAngle: Optional[Optional[float]] = None
|
||||
endAngle: Optional[Optional[float]] = None
|
||||
angleRadians: Optional[Optional[float]] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
startPoint: Optional[Point] = None
|
||||
midPoint: Optional[Point] = None
|
||||
endPoint: Optional[Point] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,25 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Block.json
|
||||
# timestamp: 2020-11-24T16:33:01+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Block(BaseModel):
|
||||
description: Optional[Optional[str]] = None
|
||||
objects: Optional[List[Base]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,56 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Box.json
|
||||
# timestamp: 2020-11-24T16:33:02+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Box(BaseModel):
|
||||
basePlane: Optional[Plane] = None
|
||||
xSize: Optional[Interval] = None
|
||||
ySize: Optional[Interval] = None
|
||||
zSize: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,55 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Circle.json
|
||||
# timestamp: 2020-11-24T16:33:04+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Circle(BaseModel):
|
||||
radius: Optional[Optional[float]] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,44 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Curve.json
|
||||
# timestamp: 2020-11-24T16:33:05+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Polyline(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
closed: Optional[bool] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Curve(BaseModel):
|
||||
degree: Optional[int] = None
|
||||
periodic: Optional[bool] = None
|
||||
rational: Optional[bool] = None
|
||||
points: Optional[List[float]] = None
|
||||
weights: Optional[List[float]] = None
|
||||
knots: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
displayValue: Optional[Polyline] = None
|
||||
closed: Optional[bool] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,56 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Ellipse.json
|
||||
# timestamp: 2020-11-24T16:33:08+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Ellipse(BaseModel):
|
||||
firstRadius: Optional[Optional[float]] = None
|
||||
secondRadius: Optional[Optional[float]] = None
|
||||
plane: Optional[Plane] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,39 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Extrusion.json
|
||||
# timestamp: 2020-11-24T16:33:09+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Extrusion(BaseModel):
|
||||
length: Optional[Optional[float]] = None
|
||||
capped: Optional[Optional[bool]] = None
|
||||
profile: Optional[Base] = None
|
||||
pathStart: Optional[Point] = None
|
||||
pathEnd: Optional[Point] = None
|
||||
pathCurve: Optional[Base] = None
|
||||
pathTangent: Optional[Base] = None
|
||||
profiles: Optional[List[Base]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,27 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Line.json
|
||||
# timestamp: 2020-11-24T16:33:16+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,20 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Mesh.json
|
||||
# timestamp: 2020-11-24T16:33:16+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,36 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Plane.json
|
||||
# timestamp: 2020-11-24T16:33:19+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Plane(BaseModel):
|
||||
origin: Optional[Point] = None
|
||||
normal: Optional[Vector] = None
|
||||
xdir: Optional[Vector] = None
|
||||
ydir: Optional[Vector] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,17 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Point.json
|
||||
# timestamp: 2020-11-24T16:33:20+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,32 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Polycurve.json
|
||||
# timestamp: 2020-11-24T16:33:20+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Polycurve(BaseModel):
|
||||
segments: Optional[List[ICurve]] = None
|
||||
domain: Optional[Interval] = None
|
||||
closed: Optional[bool] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,28 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Polyline.json
|
||||
# timestamp: 2020-11-24T16:33:21+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Polyline(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
closed: Optional[bool] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,17 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Vector.json
|
||||
# timestamp: 2020-11-24T16:33:36+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Vector(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,18 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Interval.json
|
||||
# timestamp: 2020-11-24T16:33:14+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,27 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: Interval2d.json
|
||||
# timestamp: 2020-11-24T16:33:14+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Interval2d(BaseModel):
|
||||
u: Optional[Interval] = None
|
||||
v: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,54 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: AdaptiveComponent.json
|
||||
# timestamp: 2020-11-24T16:32:59+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class AdaptiveComponent(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
flipped: Optional[bool] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,22 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: DetailCurve.json
|
||||
# timestamp: 2020-11-24T16:33:06+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class DetailCurve(BaseModel):
|
||||
baseCurve: Optional[ICurve] = None
|
||||
lineStyle: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,94 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: DirectShape.json
|
||||
# timestamp: 2020-11-24T16:33:06+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Category(Enum):
|
||||
integer_0 = 0
|
||||
integer_1 = 1
|
||||
integer_2 = 2
|
||||
integer_3 = 3
|
||||
integer_4 = 4
|
||||
integer_5 = 5
|
||||
integer_6 = 6
|
||||
integer_7 = 7
|
||||
integer_8 = 8
|
||||
integer_9 = 9
|
||||
integer_10 = 10
|
||||
integer_11 = 11
|
||||
integer_12 = 12
|
||||
integer_13 = 13
|
||||
integer_14 = 14
|
||||
integer_15 = 15
|
||||
integer_16 = 16
|
||||
integer_17 = 17
|
||||
integer_18 = 18
|
||||
integer_19 = 19
|
||||
integer_20 = 20
|
||||
integer_21 = 21
|
||||
integer_22 = 22
|
||||
integer_23 = 23
|
||||
integer_24 = 24
|
||||
integer_25 = 25
|
||||
integer_26 = 26
|
||||
integer_27 = 27
|
||||
integer_28 = 28
|
||||
integer_29 = 29
|
||||
integer_30 = 30
|
||||
integer_31 = 31
|
||||
integer_32 = 32
|
||||
integer_33 = 33
|
||||
integer_34 = 34
|
||||
integer_35 = 35
|
||||
integer_36 = 36
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class DirectShape(BaseModel):
|
||||
category: Optional[Category] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,66 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: FamilyInstance.json
|
||||
# timestamp: 2020-11-24T16:33:10+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class FamilyInstance(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
flipped: Optional[bool] = None
|
||||
host: Optional[Element] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,22 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: ModelCurve.json
|
||||
# timestamp: 2020-11-24T16:33:17+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class ModelCurve(BaseModel):
|
||||
baseCurve: Optional[ICurve] = None
|
||||
lineStyle: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,53 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitBeam.json
|
||||
# timestamp: 2020-11-24T16:33:22+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitBeam(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,53 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitBrace.json
|
||||
# timestamp: 2020-11-24T16:33:22+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitBrace(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,47 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitCategory.json
|
||||
# timestamp: 2020-11-24T16:33:23+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class RevitCategory(Enum):
|
||||
integer_0 = 0
|
||||
integer_1 = 1
|
||||
integer_2 = 2
|
||||
integer_3 = 3
|
||||
integer_4 = 4
|
||||
integer_5 = 5
|
||||
integer_6 = 6
|
||||
integer_7 = 7
|
||||
integer_8 = 8
|
||||
integer_9 = 9
|
||||
integer_10 = 10
|
||||
integer_11 = 11
|
||||
integer_12 = 12
|
||||
integer_13 = 13
|
||||
integer_14 = 14
|
||||
integer_15 = 15
|
||||
integer_16 = 16
|
||||
integer_17 = 17
|
||||
integer_18 = 18
|
||||
integer_19 = 19
|
||||
integer_20 = 20
|
||||
integer_21 = 21
|
||||
integer_22 = 22
|
||||
integer_23 = 23
|
||||
integer_24 = 24
|
||||
integer_25 = 25
|
||||
integer_26 = 26
|
||||
integer_27 = 27
|
||||
integer_28 = 28
|
||||
integer_29 = 29
|
||||
integer_30 = 30
|
||||
integer_31 = 31
|
||||
integer_32 = 32
|
||||
integer_33 = 33
|
||||
integer_34 = 34
|
||||
integer_35 = 35
|
||||
integer_36 = 36
|
||||
@@ -0,0 +1,57 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitColumn.json
|
||||
# timestamp: 2020-11-24T16:33:24+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitColumn(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
topLevel: Optional[Level] = None
|
||||
baseOffset: Optional[float] = None
|
||||
topOffset: Optional[float] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
height: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,59 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitDuct.json
|
||||
# timestamp: 2020-11-24T16:33:25+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitDuct(BaseModel):
|
||||
family: Optional[Optional[str]] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
width: Optional[float] = None
|
||||
height: Optional[float] = None
|
||||
diameter: Optional[float] = None
|
||||
length: Optional[float] = None
|
||||
level: Optional[Level] = None
|
||||
velocity: Optional[float] = None
|
||||
system: Optional[Optional[str]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,78 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitExtrusionRoof.json
|
||||
# timestamp: 2020-11-24T16:33:25+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Interval(BaseModel):
|
||||
start: Optional[Optional[float]] = None
|
||||
end: Optional[Optional[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
domain: Optional[Interval] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitExtrusionRoof(BaseModel):
|
||||
start: Optional[float] = None
|
||||
end: Optional[float] = None
|
||||
referenceLine: Optional[Line] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,57 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitFloor.json
|
||||
# timestamp: 2020-11-24T16:33:26+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitFloor(BaseModel):
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,58 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitFootprintRoof.json
|
||||
# timestamp: 2020-11-24T16:33:27+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitFootprintRoof(BaseModel):
|
||||
cutOffLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,55 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitLevel.json
|
||||
# timestamp: 2020-11-24T16:33:28+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitLevel(BaseModel):
|
||||
createView: Optional[bool] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,68 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitRoom.json
|
||||
# timestamp: 2020-11-24T16:33:28+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
value: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitRoom(BaseModel):
|
||||
basePoint: Optional[Point] = None
|
||||
number: Optional[Optional[str]] = None
|
||||
area: Optional[float] = None
|
||||
volume: Optional[float] = None
|
||||
holes: Optional[List[ICurve]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,53 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitShaft.json
|
||||
# timestamp: 2020-11-24T16:33:29+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitShaft(BaseModel):
|
||||
topLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,28 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitTopography.json
|
||||
# timestamp: 2020-11-24T16:33:30+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitTopography(BaseModel):
|
||||
baseGeometry: Optional[Mesh] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -0,0 +1,11 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitUtils.json
|
||||
# timestamp: 2020-11-24T16:33:30+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RevitUtils(BaseModel):
|
||||
pass
|
||||
@@ -0,0 +1,64 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitVerticalOpening.json
|
||||
# timestamp: 2020-11-24T16:33:31+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitVerticalOpening(BaseModel):
|
||||
host: Optional[Element] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,45 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitWall.json
|
||||
# timestamp: 2020-11-24T16:33:32+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitWall(BaseModel):
|
||||
topLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,58 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RevitWallOpening.json
|
||||
# timestamp: 2020-11-24T16:33:33+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IGeometry(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class Mesh(BaseModel):
|
||||
vertices: Optional[List[float]] = None
|
||||
faces: Optional[List[int]] = None
|
||||
colors: Optional[List[int]] = None
|
||||
textureCoordinates: Optional[List[float]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class Level(BaseModel):
|
||||
name: Optional[Optional[str]] = None
|
||||
elevation: Optional[float] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
class RevitWall(BaseModel):
|
||||
topLevel: Optional[Level] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
|
||||
|
||||
class RevitWallOpening(BaseModel):
|
||||
host: Optional[RevitWall] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
baseGeometry: Optional[IGeometry] = None
|
||||
displayMesh: Optional[Mesh] = None
|
||||
type: Optional[Optional[str]] = None
|
||||
level: Optional[Level] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
|
||||
|
||||
Level.update_forward_refs()
|
||||
@@ -0,0 +1,22 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: RoomBoundaryLine.json
|
||||
# timestamp: 2020-11-24T16:33:35+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ICurve(BaseModel):
|
||||
__root__: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class RoomBoundaryLine(BaseModel):
|
||||
baseCurve: Optional[ICurve] = None
|
||||
parameters: Optional[Optional[Dict[str, Any]]] = None
|
||||
id: Optional[Optional[str]] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
applicationId: Optional[Optional[str]] = None
|
||||
speckle_type: Optional[Optional[str]] = None
|
||||
@@ -1,24 +0,0 @@
|
||||
"""This module contains an SDK for working with Speckle Automate."""
|
||||
|
||||
from speckle_automate.automation_context import AutomationContext
|
||||
from speckle_automate.runner import execute_automate_function, run_function
|
||||
from speckle_automate.schema import (
|
||||
AutomateBase,
|
||||
AutomationResult,
|
||||
AutomationRunData,
|
||||
AutomationStatus,
|
||||
ObjectResultLevel,
|
||||
ResultCase,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AutomationContext",
|
||||
"AutomateBase",
|
||||
"AutomationStatus",
|
||||
"AutomationResult",
|
||||
"AutomationRunData",
|
||||
"ResultCase",
|
||||
"ObjectResultLevel",
|
||||
"run_function",
|
||||
"execute_automate_function",
|
||||
]
|
||||
@@ -1,447 +0,0 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# ruff: noqa: E501
|
||||
"""This module provides an abstraction layer above the Speckle Automate runtime."""
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import httpx
|
||||
from gql import gql
|
||||
|
||||
from speckle_automate.schema import (
|
||||
AutomateBase,
|
||||
AutomationResult,
|
||||
AutomationRunData,
|
||||
AutomationStatus,
|
||||
ObjectResultLevel,
|
||||
ResultCase,
|
||||
)
|
||||
from specklepy.api import operations
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.model_inputs import CreateModelInput
|
||||
from specklepy.core.api.inputs.version_inputs import CreateVersionInput
|
||||
from specklepy.core.api.models.current import Model, Version
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.transports.memory import MemoryTransport
|
||||
from specklepy.transports.server import ServerTransport
|
||||
|
||||
|
||||
@dataclass
|
||||
class AutomationContext:
|
||||
"""A context helper class.
|
||||
|
||||
This class exposes methods to work with the Speckle Automate context inside
|
||||
Speckle Automate functions.
|
||||
|
||||
An instance of AutomationContext is injected into every run of a function.
|
||||
"""
|
||||
|
||||
automation_run_data: AutomationRunData
|
||||
speckle_client: SpeckleClient
|
||||
_server_transport: ServerTransport
|
||||
_speckle_token: str
|
||||
|
||||
#: keep a memory transponrt at hand, to speed up things if needed
|
||||
_memory_transport: MemoryTransport = field(default_factory=MemoryTransport)
|
||||
|
||||
#: added for performance measuring
|
||||
_init_time: float = field(default_factory=time.perf_counter)
|
||||
_automation_result: AutomationResult = field(default_factory=AutomationResult)
|
||||
|
||||
@classmethod
|
||||
def initialize(
|
||||
cls, automation_run_data: Union[str, AutomationRunData], speckle_token: str
|
||||
) -> "AutomationContext":
|
||||
"""Bootstrap the AutomateSDK from raw data.
|
||||
|
||||
Todo:
|
||||
----
|
||||
* bootstrap a structlog logger instance
|
||||
* expose a logger, that ppl can use instead of print
|
||||
"""
|
||||
# parse the json value if its not an initialized project data instance
|
||||
automation_run_data = (
|
||||
automation_run_data
|
||||
if isinstance(automation_run_data, AutomationRunData)
|
||||
else AutomationRunData.model_validate_json(automation_run_data)
|
||||
)
|
||||
speckle_client = SpeckleClient(
|
||||
automation_run_data.speckle_server_url,
|
||||
automation_run_data.speckle_server_url.startswith("https"),
|
||||
)
|
||||
speckle_client.authenticate_with_token(speckle_token)
|
||||
if not speckle_client.account:
|
||||
msg = (
|
||||
f"Could not autenticate to {automation_run_data.speckle_server_url}",
|
||||
"with the provided token",
|
||||
)
|
||||
raise ValueError(msg)
|
||||
server_transport = ServerTransport(
|
||||
automation_run_data.project_id, speckle_client
|
||||
)
|
||||
return cls(automation_run_data, speckle_client, server_transport, speckle_token)
|
||||
|
||||
@property
|
||||
def run_status(self) -> AutomationStatus:
|
||||
"""Get the status of the automation run."""
|
||||
return self._automation_result.run_status
|
||||
|
||||
@property
|
||||
def status_message(self) -> Optional[str]:
|
||||
"""Get the current status message."""
|
||||
return self._automation_result.status_message
|
||||
|
||||
def elapsed(self) -> float:
|
||||
"""Return the elapsed time in seconds since the initialization time."""
|
||||
return time.perf_counter() - self._init_time
|
||||
|
||||
def receive_version(self) -> Base:
|
||||
"""Receive the Speckle project version that triggered this automation run."""
|
||||
# TODO: this is a quick hack to keep implementation consistency.
|
||||
# Move to proper receive many versions
|
||||
version_id = self.automation_run_data.triggers[0].payload.version_id
|
||||
try:
|
||||
version = self.speckle_client.version.get(
|
||||
version_id, self.automation_run_data.project_id
|
||||
)
|
||||
except SpeckleException as err:
|
||||
raise ValueError(
|
||||
f"""\
|
||||
Could not receive specified version.
|
||||
Is your environment configured correctly?
|
||||
project_id: {self.automation_run_data.project_id}
|
||||
model_id: {self.automation_run_data.triggers[0].payload.model_id}
|
||||
version_id: {self.automation_run_data.triggers[0].payload.version_id}
|
||||
"""
|
||||
) from err
|
||||
|
||||
base = operations.receive(
|
||||
version.referenced_object, self._server_transport, self._memory_transport
|
||||
)
|
||||
print(
|
||||
f"It took {self.elapsed():.2f} seconds to receive",
|
||||
f" the speckle version {version_id}",
|
||||
)
|
||||
return base
|
||||
|
||||
def create_new_model_in_project(
|
||||
self, model_name: str, model_description: Optional[str] = None
|
||||
) -> Model:
|
||||
input = CreateModelInput(
|
||||
name=model_name,
|
||||
description=model_description,
|
||||
project_id=self.automation_run_data.project_id,
|
||||
)
|
||||
|
||||
return self.speckle_client.model.create(input)
|
||||
|
||||
def get_model(self, model_id: str) -> Model:
|
||||
"""
|
||||
Args:
|
||||
model_id (str): The id of the model to get
|
||||
"""
|
||||
return self.speckle_client.model.get(
|
||||
model_id, self.automation_run_data.project_id
|
||||
)
|
||||
|
||||
def create_new_version_in_project(
|
||||
self, root_object: Base, model_id: str, version_message: str = ""
|
||||
) -> Version:
|
||||
"""Save a base model to a new version on the project.
|
||||
|
||||
Args:
|
||||
root_object (Base): The Speckle base object for the new version.
|
||||
model_id (str): Id of model to create the new version on.
|
||||
version_message (str): The message for the new version.
|
||||
"""
|
||||
|
||||
matching_trigger = [
|
||||
t
|
||||
for t in self.automation_run_data.triggers
|
||||
if t.payload.model_id == model_id
|
||||
]
|
||||
if matching_trigger:
|
||||
raise ValueError(
|
||||
f"The target model: {model_id} cannot match the model"
|
||||
f" that triggered this automation:"
|
||||
f" {matching_trigger[0].payload.model_id}"
|
||||
)
|
||||
|
||||
root_object_id = operations.send(
|
||||
root_object,
|
||||
[self._server_transport, self._memory_transport],
|
||||
use_default_cache=False,
|
||||
)
|
||||
|
||||
create_version_input = CreateVersionInput(
|
||||
object_id=root_object_id,
|
||||
model_id=model_id,
|
||||
project_id=self.automation_run_data.project_id,
|
||||
message=version_message,
|
||||
source_application="SpeckleAutomate",
|
||||
)
|
||||
version = self.speckle_client.version.create(create_version_input)
|
||||
|
||||
self._automation_result.result_versions.append(version.id)
|
||||
return version
|
||||
|
||||
@property
|
||||
def context_view(self) -> Optional[str]:
|
||||
return self._automation_result.result_view
|
||||
|
||||
def set_context_view(
|
||||
self,
|
||||
# f"{model_id}@{version_id} or {model_id} "
|
||||
resource_ids: Optional[List[str]] = None,
|
||||
include_source_model_version: bool = True,
|
||||
) -> None:
|
||||
link_resources = (
|
||||
[
|
||||
f"{t.payload.model_id}@{t.payload.version_id}"
|
||||
for t in self.automation_run_data.triggers
|
||||
]
|
||||
if include_source_model_version
|
||||
else []
|
||||
)
|
||||
if resource_ids:
|
||||
link_resources.extend(resource_ids)
|
||||
if not link_resources:
|
||||
raise Exception(
|
||||
"We do not have enough resource ids to compose a context view"
|
||||
)
|
||||
self._automation_result.result_view = (
|
||||
f"/projects/{self.automation_run_data.project_id}"
|
||||
f"/models/{','.join(link_resources)}"
|
||||
)
|
||||
|
||||
def report_run_status(self) -> None:
|
||||
"""Report the current run status to the project of this automation."""
|
||||
query = gql(
|
||||
"""
|
||||
mutation AutomateFunctionRunStatusReport(
|
||||
$projectId: String!
|
||||
$functionRunId: String!
|
||||
$status: AutomateRunStatus!
|
||||
$statusMessage: String
|
||||
$results: JSONObject
|
||||
$contextView: String
|
||||
){
|
||||
automateFunctionRunStatusReport(input: {
|
||||
projectId: $projectId
|
||||
functionRunId: $functionRunId
|
||||
status: $status
|
||||
statusMessage: $statusMessage
|
||||
contextView: $contextView
|
||||
results: $results
|
||||
})
|
||||
}
|
||||
"""
|
||||
)
|
||||
if self.run_status in [AutomationStatus.SUCCEEDED, AutomationStatus.FAILED]:
|
||||
object_results = {
|
||||
"version": 1,
|
||||
"values": {
|
||||
"objectResults": self._automation_result.model_dump(by_alias=True)[
|
||||
"objectResults"
|
||||
],
|
||||
"blobIds": self._automation_result.blobs,
|
||||
},
|
||||
}
|
||||
else:
|
||||
object_results = None
|
||||
|
||||
params = {
|
||||
"projectId": self.automation_run_data.project_id,
|
||||
"functionRunId": self.automation_run_data.function_run_id,
|
||||
"status": self.run_status.value,
|
||||
"statusMessage": self._automation_result.status_message,
|
||||
"results": object_results,
|
||||
"contextView": self._automation_result.result_view,
|
||||
}
|
||||
print(f"Reporting run status with content: {params}")
|
||||
self.speckle_client.httpclient.execute(query, params)
|
||||
|
||||
def store_file_result(self, file_path: Union[Path, str]) -> None:
|
||||
"""Save a file attached to the project of this automation."""
|
||||
path_obj = (
|
||||
Path(file_path).resolve() if isinstance(file_path, str) else file_path
|
||||
)
|
||||
|
||||
class UploadResult(AutomateBase):
|
||||
blob_id: str
|
||||
file_name: str
|
||||
upload_status: int
|
||||
|
||||
class BlobUploadResponse(AutomateBase):
|
||||
upload_results: list[UploadResult]
|
||||
|
||||
if not path_obj.exists():
|
||||
raise ValueError("The given file path doesn't exist")
|
||||
|
||||
files = {path_obj.name: path_obj.open("rb")}
|
||||
|
||||
url = (
|
||||
f"{self.automation_run_data.speckle_server_url}api/stream/"
|
||||
f"{self.automation_run_data.project_id}/blob"
|
||||
)
|
||||
data = (
|
||||
httpx.post(
|
||||
url,
|
||||
files=files,
|
||||
headers={"authorization": f"Bearer {self._speckle_token}"},
|
||||
)
|
||||
.raise_for_status()
|
||||
.json()
|
||||
)
|
||||
|
||||
upload_response = BlobUploadResponse.model_validate(data)
|
||||
|
||||
if len(upload_response.upload_results) != 1:
|
||||
raise ValueError("Expecting one upload result.")
|
||||
|
||||
self._automation_result.blobs.extend(
|
||||
[upload_result.blob_id for upload_result in upload_response.upload_results]
|
||||
)
|
||||
|
||||
def mark_run_failed(self, status_message: str) -> None:
|
||||
"""Mark the current run a failure."""
|
||||
self._mark_run(AutomationStatus.FAILED, status_message)
|
||||
|
||||
def mark_run_exception(self, status_message: str) -> None:
|
||||
"""Mark the current run a failure."""
|
||||
self._mark_run(AutomationStatus.EXCEPTION, status_message)
|
||||
|
||||
def mark_run_success(self, status_message: Optional[str]) -> None:
|
||||
"""Mark the current run a success with an optional message."""
|
||||
self._mark_run(AutomationStatus.SUCCEEDED, status_message)
|
||||
|
||||
def _mark_run(
|
||||
self, status: AutomationStatus, status_message: Optional[str]
|
||||
) -> None:
|
||||
duration = self.elapsed()
|
||||
self._automation_result.status_message = status_message
|
||||
self._automation_result.run_status = status
|
||||
self._automation_result.elapsed = duration
|
||||
|
||||
msg = f"Automation run {status.value} after {duration:.2f} seconds."
|
||||
print("\n".join([msg, status_message]) if status_message else msg)
|
||||
|
||||
def attach_error_to_objects(
|
||||
self,
|
||||
category: str,
|
||||
objects: Union[Base, List[Base]],
|
||||
message: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
visual_overrides: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Add a new error case to the run results.
|
||||
|
||||
If the error cause has already created an error case,
|
||||
the error will be extended with a new case refering to the causing objects.
|
||||
Args:
|
||||
error_tag (str): A short tag for the error type.
|
||||
causing_object_ids (str[]): A list of object_id-s that are causing the error
|
||||
error_messagge (Optional[str]): Optional error message.
|
||||
metadata: User provided metadata key value pairs
|
||||
visual_overrides: Case specific 3D visual overrides.
|
||||
"""
|
||||
self.attach_result_to_objects(
|
||||
ObjectResultLevel.ERROR,
|
||||
category,
|
||||
objects,
|
||||
message,
|
||||
metadata,
|
||||
visual_overrides,
|
||||
)
|
||||
|
||||
def attach_warning_to_objects(
|
||||
self,
|
||||
category: str,
|
||||
objects: Union[Base, List[Base]],
|
||||
message: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
visual_overrides: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Add a new warning case to the run results."""
|
||||
self.attach_result_to_objects(
|
||||
ObjectResultLevel.WARNING,
|
||||
category,
|
||||
objects,
|
||||
message,
|
||||
metadata,
|
||||
visual_overrides,
|
||||
)
|
||||
|
||||
def attach_success_to_objects(
|
||||
self,
|
||||
category: str,
|
||||
objects: Union[Base, List[Base]],
|
||||
message: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
visual_overrides: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Add a new success case to the run results."""
|
||||
self.attach_result_to_objects(
|
||||
ObjectResultLevel.SUCCESS,
|
||||
category,
|
||||
objects,
|
||||
message,
|
||||
metadata,
|
||||
visual_overrides,
|
||||
)
|
||||
|
||||
def attach_info_to_objects(
|
||||
self,
|
||||
category: str,
|
||||
objects: Union[Base, List[Base]],
|
||||
message: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
visual_overrides: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Add a new info case to the run results."""
|
||||
self.attach_result_to_objects(
|
||||
ObjectResultLevel.INFO,
|
||||
category,
|
||||
objects,
|
||||
message,
|
||||
metadata,
|
||||
visual_overrides,
|
||||
)
|
||||
|
||||
def attach_result_to_objects(
|
||||
self,
|
||||
level: ObjectResultLevel,
|
||||
category: str,
|
||||
objects: Union[Base, List[Base]],
|
||||
message: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
visual_overrides: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
if isinstance(objects, list):
|
||||
if len(objects) < 1:
|
||||
raise ValueError(
|
||||
f"Need atleast one object_id to report a(n) {level.value.upper()}"
|
||||
)
|
||||
id_list = [o.id for o in objects]
|
||||
application_ids = [o.applicationId for o in objects]
|
||||
else:
|
||||
id_list = [objects.id]
|
||||
application_ids = [objects.applicationId]
|
||||
metadata["applicationIds"] = application_ids
|
||||
print(
|
||||
f"Created new {level.value.upper()}"
|
||||
f" category: {category} caused by: {message}"
|
||||
)
|
||||
self._automation_result.object_results.append(
|
||||
ResultCase(
|
||||
category=category,
|
||||
level=level,
|
||||
object_ids=id_list,
|
||||
message=message,
|
||||
metadata=metadata,
|
||||
visual_overrides=visual_overrides,
|
||||
)
|
||||
)
|
||||
@@ -1,155 +0,0 @@
|
||||
"""Some useful helpers for working with automation data."""
|
||||
|
||||
import secrets
|
||||
import string
|
||||
|
||||
import pytest
|
||||
from gql import gql
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
from speckle_automate.schema import AutomationRunData, TestAutomationRunData
|
||||
from specklepy.api.client import SpeckleClient
|
||||
|
||||
|
||||
class TestAutomationEnvironment(BaseSettings):
|
||||
"""Get known environment variables from local `.env` file"""
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
env_prefix="speckle_",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
token: str = Field()
|
||||
server_url: str = Field()
|
||||
project_id: str = Field()
|
||||
automation_id: str = Field()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def test_automation_environment() -> TestAutomationEnvironment:
|
||||
return TestAutomationEnvironment()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def test_automation_token(
|
||||
test_automation_environment: TestAutomationEnvironment,
|
||||
) -> str:
|
||||
"""Provide a speckle token for the test suite."""
|
||||
|
||||
return test_automation_environment.token
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def speckle_client(
|
||||
test_automation_environment: TestAutomationEnvironment,
|
||||
) -> SpeckleClient:
|
||||
"""Initialize a SpeckleClient for testing."""
|
||||
speckle_client = SpeckleClient(
|
||||
test_automation_environment.server_url,
|
||||
test_automation_environment.server_url.startswith("https"),
|
||||
)
|
||||
speckle_client.authenticate_with_token(test_automation_environment.token)
|
||||
return speckle_client
|
||||
|
||||
|
||||
def create_test_automation_run(
|
||||
speckle_client: SpeckleClient, project_id: str, test_automation_id: str
|
||||
) -> TestAutomationRunData:
|
||||
"""Create test run to report local test results to"""
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation CreateTestRun(
|
||||
$projectId: ID!,
|
||||
$automationId: ID!
|
||||
) {
|
||||
projectMutations {
|
||||
automationMutations(projectId: $projectId) {
|
||||
createTestAutomationRun(automationId: $automationId) {
|
||||
automationRunId
|
||||
functionRunId
|
||||
triggers {
|
||||
payload {
|
||||
modelId
|
||||
versionId
|
||||
}
|
||||
triggerType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"automationId": test_automation_id, "projectId": project_id}
|
||||
|
||||
result = speckle_client.httpclient.execute(query, params)
|
||||
|
||||
print(result)
|
||||
|
||||
return (
|
||||
result.get("projectMutations")
|
||||
.get("automationMutations")
|
||||
.get("createTestAutomationRun")
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def test_automation_run(
|
||||
speckle_client: SpeckleClient,
|
||||
test_automation_environment: TestAutomationEnvironment,
|
||||
) -> TestAutomationRunData:
|
||||
return create_test_automation_run(
|
||||
speckle_client,
|
||||
test_automation_environment.project_id,
|
||||
test_automation_environment.automation_id,
|
||||
)
|
||||
|
||||
|
||||
def create_test_automation_run_data(
|
||||
speckle_client: SpeckleClient,
|
||||
test_automation_environment: TestAutomationEnvironment,
|
||||
) -> AutomationRunData:
|
||||
"""Create automation run data for a new run for a given test automation"""
|
||||
|
||||
test_automation_run_data = create_test_automation_run(
|
||||
speckle_client,
|
||||
test_automation_environment.project_id,
|
||||
test_automation_environment.automation_id,
|
||||
)
|
||||
|
||||
return AutomationRunData(
|
||||
project_id=test_automation_environment.project_id,
|
||||
speckle_server_url=test_automation_environment.server_url,
|
||||
automation_id=test_automation_environment.automation_id,
|
||||
automation_run_id=test_automation_run_data["automationRunId"],
|
||||
function_run_id=test_automation_run_data["functionRunId"],
|
||||
triggers=test_automation_run_data["triggers"],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def test_automation_run_data(
|
||||
speckle_client: SpeckleClient,
|
||||
test_automation_environment: TestAutomationEnvironment,
|
||||
) -> AutomationRunData:
|
||||
return create_test_automation_run_data(speckle_client, test_automation_environment)
|
||||
|
||||
|
||||
def crypto_random_string(length: int) -> str:
|
||||
"""Generate a semi crypto random string of a given length."""
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return "".join(secrets.choice(alphabet) for _ in range(length)).lower()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"test_automation_environment",
|
||||
"test_automation_token",
|
||||
"speckle_client",
|
||||
"test_automation_run",
|
||||
"test_automation_run_data",
|
||||
]
|
||||
@@ -1,194 +0,0 @@
|
||||
"""Function execution module.
|
||||
|
||||
Provides mechanisms to execute any function,
|
||||
that conforms to the AutomateFunction "interface"
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, Tuple, TypeVar, Union, overload
|
||||
|
||||
from pydantic import create_model
|
||||
from pydantic.json_schema import GenerateJsonSchema
|
||||
|
||||
from speckle_automate.automation_context import AutomationContext
|
||||
from speckle_automate.schema import AutomateBase, AutomationRunData, AutomationStatus
|
||||
|
||||
T = TypeVar("T", bound=AutomateBase)
|
||||
|
||||
AutomateFunction = Callable[[AutomationContext, T], None]
|
||||
AutomateFunctionWithoutInputs = Callable[[AutomationContext], None]
|
||||
|
||||
|
||||
def _read_input_data(inputs_location: str) -> str:
|
||||
input_path = Path(inputs_location)
|
||||
if not input_path.exists():
|
||||
raise ValueError(f"Cannot find the function inputs file at {input_path}")
|
||||
|
||||
return input_path.read_text()
|
||||
|
||||
|
||||
def _parse_input_data(
|
||||
input_location: str, input_schema: Optional[type[T]]
|
||||
) -> Tuple[AutomationRunData, Optional[T], str]:
|
||||
input_json_string = _read_input_data(input_location)
|
||||
|
||||
class FunctionRunData(AutomateBase):
|
||||
speckle_token: str
|
||||
automation_run_data: AutomationRunData
|
||||
function_inputs: None = None
|
||||
|
||||
parser_model = FunctionRunData
|
||||
|
||||
if input_schema:
|
||||
parser_model = create_model(
|
||||
"FunctionRunDataWithInputs",
|
||||
function_inputs=(input_schema, ...),
|
||||
__base__=FunctionRunData,
|
||||
)
|
||||
|
||||
input_data = parser_model.model_validate_json(input_json_string)
|
||||
return (
|
||||
input_data.automation_run_data,
|
||||
input_data.function_inputs,
|
||||
input_data.speckle_token,
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
def execute_automate_function(
|
||||
automate_function: AutomateFunction[T],
|
||||
input_schema: type[T],
|
||||
) -> None: ...
|
||||
|
||||
|
||||
@overload
|
||||
def execute_automate_function(
|
||||
automate_function: AutomateFunctionWithoutInputs,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
class AutomateGenerateJsonSchema(GenerateJsonSchema):
|
||||
def generate(self, schema, mode="validation"):
|
||||
json_schema = super().generate(schema, mode=mode)
|
||||
json_schema["$schema"] = self.schema_dialect
|
||||
return json_schema
|
||||
|
||||
|
||||
def execute_automate_function(
|
||||
automate_function: Union[AutomateFunction[T], AutomateFunctionWithoutInputs],
|
||||
input_schema: Optional[type[T]] = None,
|
||||
):
|
||||
"""Runs the provided automate function with the input schema."""
|
||||
# first arg is the python file name, we do not need that
|
||||
args = sys.argv[1:]
|
||||
|
||||
if len(args) != 2:
|
||||
raise ValueError("Incorrect number of arguments specified need 2")
|
||||
|
||||
# we rely on a command name convention to decide what to do.
|
||||
# this is here, so that the function authors do not see any of this
|
||||
command, argument = args
|
||||
|
||||
if command == "generate_schema":
|
||||
path = Path(argument)
|
||||
schema = json.dumps(
|
||||
input_schema.model_json_schema(
|
||||
by_alias=True, schema_generator=AutomateGenerateJsonSchema
|
||||
)
|
||||
if input_schema
|
||||
else {}
|
||||
)
|
||||
path.write_text(schema)
|
||||
|
||||
elif command == "run":
|
||||
automation_run_data, function_inputs, speckle_token = _parse_input_data(
|
||||
argument, input_schema
|
||||
)
|
||||
|
||||
automation_context = AutomationContext.initialize(
|
||||
automation_run_data, speckle_token
|
||||
)
|
||||
|
||||
if function_inputs:
|
||||
automation_context = run_function(
|
||||
automation_context,
|
||||
automate_function, # type: ignore
|
||||
function_inputs, # type: ignore
|
||||
)
|
||||
|
||||
else:
|
||||
automation_context = AutomationContext.initialize(
|
||||
automation_run_data, speckle_token
|
||||
)
|
||||
automation_context = run_function(
|
||||
automation_context,
|
||||
automate_function, # type: ignore
|
||||
)
|
||||
|
||||
# if we've gotten this far,
|
||||
# the execution should technically be completed as expected
|
||||
# thus exiting with 0 is the schemantically correct thing to do
|
||||
exit_code = (
|
||||
1 if automation_context.run_status == AutomationStatus.EXCEPTION else 0
|
||||
)
|
||||
exit(exit_code)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(f"Command: '{command}' is not supported.")
|
||||
|
||||
|
||||
@overload
|
||||
def run_function(
|
||||
automation_context: AutomationContext,
|
||||
automate_function: AutomateFunction[T],
|
||||
inputs: T,
|
||||
) -> AutomationContext: ...
|
||||
|
||||
|
||||
@overload
|
||||
def run_function(
|
||||
automation_context: AutomationContext,
|
||||
automate_function: AutomateFunctionWithoutInputs,
|
||||
) -> AutomationContext: ...
|
||||
|
||||
|
||||
def run_function(
|
||||
automation_context: AutomationContext,
|
||||
automate_function: Union[AutomateFunction[T], AutomateFunctionWithoutInputs],
|
||||
inputs: Optional[T] = None,
|
||||
) -> AutomationContext:
|
||||
"""Run the provided function with the automate sdk context."""
|
||||
automation_context.report_run_status()
|
||||
|
||||
try:
|
||||
# avoiding complex type gymnastics here on the internals.
|
||||
# the external type overloads make this correct
|
||||
if inputs:
|
||||
automate_function(automation_context, inputs) # type: ignore
|
||||
else:
|
||||
automate_function(automation_context) # type: ignore
|
||||
|
||||
# the function author forgot to mark the function success
|
||||
if automation_context.run_status not in [
|
||||
AutomationStatus.FAILED,
|
||||
AutomationStatus.SUCCEEDED,
|
||||
AutomationStatus.EXCEPTION,
|
||||
]:
|
||||
automation_context.mark_run_success(
|
||||
"WARNING: Automate assumed a success status,"
|
||||
" but it was not marked as so by the function."
|
||||
)
|
||||
except Exception:
|
||||
trace = traceback.format_exc()
|
||||
print(trace)
|
||||
automation_context.mark_run_exception(
|
||||
"Function error. Check the automation run logs for details."
|
||||
)
|
||||
finally:
|
||||
if not automation_context.context_view:
|
||||
automation_context.set_context_view()
|
||||
automation_context.report_run_status()
|
||||
return automation_context
|
||||
@@ -1,98 +0,0 @@
|
||||
""""""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from stringcase import camelcase
|
||||
|
||||
|
||||
class AutomateBase(BaseModel):
|
||||
"""Use this class as a base model for automate related DTO."""
|
||||
|
||||
model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True)
|
||||
|
||||
|
||||
class VersionCreationTriggerPayload(AutomateBase):
|
||||
"""Represents the version creation trigger payload."""
|
||||
|
||||
model_id: str
|
||||
version_id: str
|
||||
|
||||
|
||||
class VersionCreationTrigger(AutomateBase):
|
||||
"""Represents a single version creation trigger for the automation run."""
|
||||
|
||||
trigger_type: Literal["versionCreation"]
|
||||
payload: VersionCreationTriggerPayload
|
||||
|
||||
|
||||
class AutomationRunData(BaseModel):
|
||||
"""Values of the project / model that triggered the run of this function."""
|
||||
|
||||
project_id: str
|
||||
speckle_server_url: str
|
||||
automation_id: str
|
||||
automation_run_id: str
|
||||
function_run_id: str
|
||||
|
||||
triggers: List[VersionCreationTrigger]
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=camelcase, populate_by_name=True, protected_namespaces=()
|
||||
)
|
||||
|
||||
|
||||
class TestAutomationRunData(BaseModel):
|
||||
"""Values of the run created in the test automation for local test results."""
|
||||
|
||||
automation_run_id: str
|
||||
function_run_id: str
|
||||
|
||||
triggers: List[VersionCreationTrigger]
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=camelcase, populate_by_name=True, protected_namespaces=()
|
||||
)
|
||||
|
||||
|
||||
class AutomationStatus(str, Enum):
|
||||
"""Set the status of the automation."""
|
||||
|
||||
INITIALIZING = "INITIALIZING"
|
||||
RUNNING = "RUNNING"
|
||||
FAILED = "FAILED"
|
||||
SUCCEEDED = "SUCCEEDED"
|
||||
EXCEPTION = "EXCEPTION"
|
||||
|
||||
|
||||
class ObjectResultLevel(str, Enum):
|
||||
"""Possible status message levels for object reports."""
|
||||
|
||||
SUCCESS = "SUCCESS"
|
||||
INFO = "INFO"
|
||||
WARNING = "WARNING"
|
||||
ERROR = "ERROR"
|
||||
|
||||
|
||||
class ResultCase(AutomateBase):
|
||||
"""A result case."""
|
||||
|
||||
category: str
|
||||
level: ObjectResultLevel
|
||||
object_ids: List[str]
|
||||
message: Optional[str]
|
||||
metadata: Optional[Dict[str, Any]]
|
||||
visual_overrides: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class AutomationResult(AutomateBase):
|
||||
"""Schema accepted by the Speckle server as a result for an automation run."""
|
||||
|
||||
elapsed: float = 0
|
||||
result_view: Optional[str] = None
|
||||
result_versions: List[str] = Field(default_factory=list)
|
||||
blobs: List[str] = Field(default_factory=list)
|
||||
run_status: AutomationStatus = AutomationStatus.RUNNING
|
||||
status_message: Optional[str] = None
|
||||
object_results: list[ResultCase] = Field(default_factory=list)
|
||||
@@ -1,3 +0,0 @@
|
||||
# from specklepy import objects
|
||||
|
||||
# __all__ = ["objects"]
|
||||
@@ -1,147 +0,0 @@
|
||||
import contextlib
|
||||
|
||||
from specklepy.api.credentials import Account
|
||||
from specklepy.api.resources import (
|
||||
ActiveUserResource,
|
||||
ModelResource,
|
||||
OtherUserResource,
|
||||
ProjectInviteResource,
|
||||
ProjectResource,
|
||||
ServerResource,
|
||||
SubscriptionResource,
|
||||
VersionResource,
|
||||
)
|
||||
from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient
|
||||
from specklepy.logging import metrics
|
||||
|
||||
|
||||
class SpeckleClient(CoreSpeckleClient):
|
||||
"""
|
||||
The `SpeckleClient` is your entry point for interacting with
|
||||
your Speckle Server's GraphQL API.
|
||||
You'll need to have access to a server to use it,
|
||||
or you can use our public server `app.speckle.systems`.
|
||||
|
||||
To authenticate the client, you'll need to have downloaded
|
||||
the [Speckle Manager](https://speckle.guide/#speckle-manager)
|
||||
and added your account.
|
||||
|
||||
```py
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
|
||||
from specklepy.api.credentials import get_default_account
|
||||
|
||||
# initialise the client
|
||||
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
|
||||
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
|
||||
|
||||
# authenticate the client with an account
|
||||
# (account has been added in Speckle Manager)
|
||||
account = get_default_account()
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
# create a new project
|
||||
input = ProjectCreateInput(name="a shiny new project")
|
||||
project = self.project.create(input)
|
||||
|
||||
# or, use a project id to get an existing project from the server
|
||||
new_stream = client.project.get("abcdefghij")
|
||||
```
|
||||
"""
|
||||
|
||||
DEFAULT_HOST = "app.speckle.systems"
|
||||
USE_SSL = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = DEFAULT_HOST,
|
||||
use_ssl: bool = USE_SSL,
|
||||
verify_certificate: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
host=host,
|
||||
use_ssl=use_ssl,
|
||||
verify_certificate=verify_certificate,
|
||||
)
|
||||
self.account = Account()
|
||||
|
||||
def _init_resources(self) -> None:
|
||||
self.server = ServerResource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
|
||||
server_version = None
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
server_version = self.server.version()
|
||||
|
||||
self.other_user = OtherUserResource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.active_user = ActiveUserResource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.project = ProjectResource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.project_invite = ProjectInviteResource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.model = ModelResource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.version = VersionResource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.subscription = SubscriptionResource(
|
||||
account=self.account,
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
# todo: why doesn't this take a server version
|
||||
)
|
||||
|
||||
def authenticate_with_token(self, token: str) -> None:
|
||||
"""
|
||||
Authenticate the client using a personal access token.
|
||||
The token is saved in the client object and a synchronous GraphQL
|
||||
entrypoint is created
|
||||
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "Client Authenticate With Token"}
|
||||
)
|
||||
return super().authenticate_with_token(token)
|
||||
|
||||
def authenticate_with_account(self, account: Account) -> None:
|
||||
"""Authenticate the client using an Account object
|
||||
The account is saved in the client object and a synchronous GraphQL
|
||||
entrypoint is created
|
||||
|
||||
Arguments:
|
||||
account {Account} -- the account object which can be found with
|
||||
`get_default_account` or `get_local_accounts`
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "Client Authenticate With Account"}
|
||||
)
|
||||
return super().authenticate_with_account(account)
|
||||
@@ -1,76 +0,0 @@
|
||||
from typing import List, Optional
|
||||
|
||||
# following imports seem to be unnecessary, but they need to stay
|
||||
# to not break the scripts using these functions as non-core
|
||||
from specklepy.core.api.credentials import ( # noqa: F401
|
||||
Account,
|
||||
StreamWrapper, # noqa: F401
|
||||
UserInfo,
|
||||
)
|
||||
from specklepy.core.api.credentials import (
|
||||
get_account_from_token as core_get_account_from_token,
|
||||
)
|
||||
from specklepy.core.api.credentials import get_local_accounts as core_get_local_accounts
|
||||
from specklepy.logging import metrics
|
||||
|
||||
|
||||
def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
|
||||
"""Gets all the accounts present in this environment
|
||||
|
||||
Arguments:
|
||||
base_path {str} -- custom base path if you are not using the system default
|
||||
|
||||
Returns:
|
||||
List[Account] -- list of all local accounts or an empty list if
|
||||
no accounts were found
|
||||
"""
|
||||
accounts = core_get_local_accounts(base_path)
|
||||
|
||||
metrics.track(
|
||||
metrics.SDK,
|
||||
next(
|
||||
(acc for acc in accounts if acc.isDefault),
|
||||
accounts[0] if accounts else None,
|
||||
),
|
||||
{"name": "Get Local Accounts"},
|
||||
)
|
||||
|
||||
return accounts
|
||||
|
||||
|
||||
def get_default_account(base_path: Optional[str] = None) -> Optional[Account]:
|
||||
"""
|
||||
Gets this environment's default account if any. If there is no default,
|
||||
the first found will be returned and set as default.
|
||||
Arguments:
|
||||
base_path {str} -- custom base path if you are not using the system default
|
||||
|
||||
Returns:
|
||||
Account -- the default account or None if no local accounts were found
|
||||
"""
|
||||
accounts = core_get_local_accounts(base_path=base_path)
|
||||
if not accounts:
|
||||
return None
|
||||
|
||||
default = next((acc for acc in accounts if acc.isDefault), None)
|
||||
if not default:
|
||||
default = accounts[0]
|
||||
default.isDefault = True
|
||||
metrics.initialise_tracker(default)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def get_account_from_token(token: str, server_url: str = None) -> Account:
|
||||
"""Gets the local account for the token if it exists
|
||||
Arguments:
|
||||
token {str} -- the api token
|
||||
|
||||
Returns:
|
||||
Account -- the local account with this token or a shell account containing
|
||||
just the token and url if no local account is found
|
||||
"""
|
||||
account = core_get_account_from_token(token, server_url)
|
||||
|
||||
metrics.track(metrics.SDK, account, {"name": "Get Account From Token"})
|
||||
return account
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user