Compare commits

..

2 Commits

Author SHA1 Message Date
izzy lyseggen 840424b488 feat(schemas): DRAFT upload generated schemas
there is loads of repetition since a whole class and it's references
are defined in the same file. need to figure out a way to get them to
generate with references. just uploading this for now so it is visible
for ppl to check out
2020-11-24 16:58:25 +00:00
izzy lyseggen dba362c230 feat(schemas): add generated classes from Objects
just geometry classes for now

TODO: consolidation after generating the classes.
this is the raw generation which creates duplicates as it defines all
required classes in the same file
2020-11-18 11:59:55 +00:00
114 changed files with 2625 additions and 6601 deletions
-78
View File
@@ -1,78 +0,0 @@
version: 2.1
orbs:
python: circleci/python@1.3.2
codecov: codecov/codecov@3.2.2
jobs:
test:
docker:
- image: "cimg/python:<<parameters.tag>>"
- image: 'cimg/node:14.18'
- image: 'circleci/redis:6'
- image: 'cimg/postgres:12.8'
environment:
POSTGRES_DB: speckle2_test
POSTGRES_PASSWORD: speckle
POSTGRES_USER: speckle
- image: "speckle/speckle-server"
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
parameters:
tag:
default: "3.8"
type: string
steps:
- checkout
- run: python --version
- run:
command: python -m pip install --upgrade pip
name: upgrade pip
- python/install-packages:
pkg-manager: poetry
- run: poetry run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
- store_test_results:
path: reports
- store_artifacts:
path: reports
- codecov/upload
deploy:
docker:
- image: "circleci/python:3.8"
steps:
- checkout
- run: python patch_version.py $CIRCLE_TAG
- run: poetry build
- run: poetry publish -u specklesystems -p $PYPI_PASSWORD
workflows:
main:
jobs:
- test:
matrix:
parameters:
tag: ["3.6", "3.7", "3.8", "3.9"]
filters:
tags:
only: /.*/
- deploy:
requires:
- test
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
+11 -12
View File
@@ -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).
🙌❤️💙💚💜🙌
-78
View File
@@ -1,78 +0,0 @@
name: Update issue Status
on:
issues:
types: [closed]
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
-50
View File
@@ -1,50 +0,0 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
-4
View File
@@ -1,7 +1,3 @@
.tool-versions
.envrc
reports/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
-6
View File
@@ -11,12 +11,6 @@
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "Python: Test debug config",
"type": "python",
"request": "test",
"console": "integratedTerminal",
}
]
}
+16 -69
View File
@@ -1,76 +1,22 @@
<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | specklepy 🐍
</h1>
<h3 align="center">
The Python SDK
</h3>
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
# 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&amp;style=flat-square&amp;logo=discourse&amp;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&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
<p align="center"><a href="https://github.com/specklesystems/specklepy/"><img src="https://circleci.com/gh/specklesystems/specklepy.svg?style=svg&amp;circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a><a href="https://codecov.io/gh/specklesystems/specklepy">
<img src="https://codecov.io/gh/specklesystems/specklepy/branch/main/graph/badge.svg?token=8KQFL5N0YF"/>
</a> </p>
[![Twitter Follow](https://img.shields.io/twitter/follow/SpeckleSystems?style=social)](https://twitter.com/SpeckleSystems) [![Discourse users](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.speckle.works&style=flat-square)](https://discourse.speckle.works)
[![Slack Invite](https://img.shields.io/badge/-slack-grey?style=flat-square&logo=slack)](https://speckle-works.slack.com/join/shared_invite/enQtNjY5Mzk2NTYxNTA4LTU4MWI5ZjdhMjFmMTIxZDIzOTAzMzRmMTZhY2QxMmM1ZjVmNzJmZGMzMDVlZmJjYWQxYWU0MWJkYmY3N2JjNGI) [![website](https://img.shields.io/badge/www-speckle.systems-royalblue?style=flat-square)](https://speckle.systems)
# About Speckle
## Introduction
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
### Features
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
- **Collaboration:** share your designs collaborate with others
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
- **Real time:** get real time updates and notifications and changes
- **GraphQL API:** get what you need anywhere you want it
- **Webhooks:** the base for a automation and next-gen pipelines
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
### Try Speckle now!
Give Speckle a try in no time by:
- [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz) ⇒ creating an account at our public server
- [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
# 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 python-poetry for dependency management, make sure you follow the official [docs](https://python-poetry.org/docs/#installation) to get poetry.
To bootstrap the project environment run `$ poetry install`. This will create a new virtual-env for the project and install both the package and dev dependencies.
If this is your first time using poetry and you're used to creating your venvs within the project directory, run `poetry config virtualenvs.in-project true` to configure poetry to do the same.
To execute any python script run `$ poetry run python my_script.py`
> Alternatively you may roll your own virtual-env with either venv, virtualenv, pyenv-virtualenv etc. Poetry will play along an recognize if it is invoked from inside a virtual environment.
### 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
@@ -78,11 +24,12 @@ Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md)
## 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
-59
View File
@@ -1,59 +0,0 @@
"""This is an example showcasing the usage of speckle `Base` class."""
# the speckle.objects module exposes all speckle provided classes
from specklepy.objects import Base
from specklepy.api import operations
from devtools import debug
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.json())
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.json())
-31
View File
@@ -1,31 +0,0 @@
import re
import sys
def patch(tag):
print(f"Patching version: {tag}")
with open("pyproject.toml", "r") as f:
lines = f.readlines()
if "version" not in lines[2]:
raise Exception(f"Invalid pyproject.toml. Could not patch version.")
lines[2] = f'version = "{tag}"\n'
with open("pyproject.toml", "w") as file:
file.writelines(lines)
def main():
if len(sys.argv) < 2:
return
tag = sys.argv[1]
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
raise ValueError(f"Invalid tag provided: {tag}")
patch(tag)
if __name__ == "__main__":
main()
Generated
-1076
View File
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
[tool.poetry]
name = "specklepy"
version = "2.4.0"
description = "The Python SDK for Speckle 2.0"
readme = "README.md"
authors = ["Speckle Systems <devops@speckle.systems>"]
license = "Apache-2.0"
repository = "https://github.com/specklesystems/speckle-py"
documentation = "https://speckle.guide/dev/py-examples.html"
homepage = "https://speckle.systems/"
[tool.poetry.dependencies]
python = "^3.6.5"
pydantic = "^1.8.2"
appdirs = "^1.4.4"
gql = {version = ">=3.0.0b1", extras = ["all"], allow-prereleases = true}
ujson = "^4.3.0"
[tool.poetry.dev-dependencies]
black = "^20.8b1"
isort = "^5.7.0"
pytest = "^6.2.2"
pytest-ordering = "^0.6"
pytest-cov = "^3.0.0"
[tool.black]
exclude = '''
/(
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''
include = '\.pyi?$'
line-length = 88
target-version = ["py36", "py37", "py38"]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+31
View File
@@ -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
@@ -1,19 +1,9 @@
import re
from gql.client import SyncClientSession
from specklepy.logging.exceptions import SpeckleException
from speckle.logging.exceptions import SpeckleException
from typing import Dict
from specklepy.api import resources
from specklepy.api.resources import (
branch,
commit,
stream,
object,
server,
user,
subscriptions,
)
from specklepy.api.models import ServerInfo
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
@@ -21,34 +11,7 @@ from gql.transport.websockets import WebsocketsTransport
class SpeckleClient:
"""
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 `speckle.xyz`.
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.api.credentials import get_default_account
# initialise the client
client = SpeckleClient(host="speckle.xyz") # or whatever your host is
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
# authenticate the client with a token (account has been added in Speckle Manager)
account = get_default_account()
client.authenticate(token=account.token)
# create a new stream. this returns the stream id
new_stream_id = client.stream.create(name="a shiny new stream")
# use that stream id to get the stream from the server
new_stream = client.stream.get(id=new_stream_id)
```
"""
DEFAULT_HOST = "speckle.xyz"
DEFAULT_HOST = "staging.speckle.dev"
USE_SSL = True
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
@@ -59,9 +22,6 @@ class SpeckleClient:
ws_protocol = "wss"
http_protocol = "https"
# sanitise host input by removing protocol and trailing slash
host = re.sub(r"((^\w+:|^)\/\/)|(\/$)", "", host)
self.url = f"{http_protocol}://{host}"
self.graphql = self.url + "/graphql"
self.ws_url = f"{ws_protocol}://{host}/graphql"
@@ -74,19 +34,6 @@ class SpeckleClient:
self._init_resources()
# Check compatibility with the server
try:
serverInfo = self.server.get()
if not isinstance(serverInfo, ServerInfo):
raise Exception("Couldn't get ServerInfo")
except Exception as ex:
raise SpeckleException(f"{self.url} is not a compatible Speckle Server", ex)
def __repr__(self):
return (
f"SpeckleClient( server: {self.url}, authenticated: {self.me is not None} )"
)
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
@@ -118,15 +65,6 @@ class SpeckleClient:
self.stream = stream.Resource(
me=self.me, basepath=self.url, client=self.httpclient
)
self.commit = commit.Resource(
me=self.me, basepath=self.url, client=self.httpclient
)
self.branch = branch.Resource(
me=self.me, basepath=self.url, client=self.httpclient
)
self.object = object.Resource(
me=self.me, basepath=self.url, client=self.httpclient
)
self.server = server.Resource(
me=self.me, basepath=self.url, client=self.httpclient
)
@@ -144,4 +82,4 @@ class SpeckleClient:
except:
raise SpeckleException(
f"Method {name} is not supported by the SpeckleClient class"
)
)
@@ -2,6 +2,8 @@
# 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
@@ -22,18 +24,8 @@ class Commit(BaseModel):
authorName: Optional[str]
authorId: Optional[str]
authorAvatar: Optional[str]
branchName: Optional[str]
createdAt: Optional[str]
sourceApplication: Optional[str]
referencedObject: Optional[str]
totalChildrenCount: Optional[int]
parents: Optional[List[str]]
def __repr__(self) -> str:
return f"Commit( id: {self.id}, message: {self.message}, referencedObject: {self.referencedObject}, authorName: {self.authorName}, branchName: {self.branchName}, createdAt: {self.createdAt} )"
def __str__(self) -> str:
return self.__repr__()
class Commits(BaseModel):
@@ -63,6 +55,12 @@ class Branches(BaseModel):
items: List[Branch] = []
class Streams(BaseModel):
totalCount: Optional[int]
cursor: Optional[datetime]
items: List[Stream] = []
class Stream(BaseModel):
id: Optional[str]
name: Optional[str]
@@ -75,18 +73,6 @@ class Stream(BaseModel):
commit: Optional[Commit]
object: Optional[Object]
def __repr__(self):
return f"Stream( id: {self.id}, name: {self.name}, description: {self.description}, isPublic: {self.isPublic})"
def __str__(self) -> str:
return self.__repr__()
class Streams(BaseModel):
totalCount: Optional[int]
cursor: Optional[datetime]
items: List[Stream] = []
class User(BaseModel):
id: Optional[str]
@@ -98,22 +84,3 @@ class User(BaseModel):
verified: Optional[bool]
role: Optional[str]
streams: Optional[Streams]
def __repr__(self):
return f"User( id: {self.id}, name: {self.name}, email: {self.email}, company: {self.company} )"
def __str__(self) -> str:
return self.__repr__()
class ServerInfo(BaseModel):
name: Optional[str]
company: Optional[str]
url: Optional[str]
description: Optional[str]
adminContact: Optional[str]
canonicalUrl: Optional[str]
roles: Optional[List[dict]]
scopes: Optional[List[dict]]
authStrategies: Optional[List[dict]]
version: Optional[str]
@@ -1,10 +1,9 @@
from specklepy.transports.sqlite import SQLiteTransport
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
from specklepy.logging.exceptions import GraphQLException, SpeckleException
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
class ResourceBase(object):
@@ -41,11 +40,7 @@ class ResourceBase(object):
if schema:
return schema.parse_obj(response)
elif self.schema:
try:
return self.schema.parse_obj(response)
except:
s = BaseObjectSerializer(read_transport=SQLiteTransport())
return s.recompose_base(response)
return self.schema.parse_obj(response)
else:
return response
@@ -5,9 +5,9 @@ import pkgutil
from importlib import import_module
for (_, name, _) in pkgutil.iter_modules(__path__):
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)
setattr(sys.modules[__name__], name, imported_module)
+48
View File
@@ -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)
+19
View File
@@ -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
+79
View File
@@ -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)
@@ -1,9 +1,10 @@
from gql import gql
from re import search
from typing import Dict, List, Optional
from specklepy.logging import metrics
from specklepy.api.models import Stream
from specklepy.api.resource import ResourceBase
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 = [
@@ -37,7 +38,6 @@ class Resource(ResourceBase):
Returns:
Stream -- the retrieved stream
"""
metrics.track(metrics.STREAM_GET)
query = gql(
"""
query Stream($id: String!, $branch_limit: Int!, $commit_limit: Int!) {
@@ -93,7 +93,6 @@ class Resource(ResourceBase):
Returns:
List[Stream] -- A list of Stream objects
"""
metrics.track(metrics.STREAM_LIST)
query = gql(
"""
query User($stream_limit: Int!) {
@@ -151,7 +150,6 @@ class Resource(ResourceBase):
Returns:
id {str} -- the id of the newly created stream
"""
metrics.track(metrics.STREAM_CREATE)
query = gql(
"""
mutation StreamCreate($stream: StreamCreateInput!) {
@@ -182,7 +180,6 @@ class Resource(ResourceBase):
Returns:
bool -- whether the stream update was successful
"""
metrics.track(metrics.STREAM_UPDATE)
query = gql(
"""
mutation StreamUpdate($stream: StreamUpdateInput!) {
@@ -213,7 +210,6 @@ class Resource(ResourceBase):
Returns:
bool -- whether the deletion was successful
"""
metrics.track(metrics.STREAM_DELETE)
query = gql(
"""
mutation StreamDelete($id: String!) {
@@ -246,7 +242,6 @@ class Resource(ResourceBase):
Returns:
List[Stream] -- a list of Streams that match the search query
"""
metrics.track(metrics.STREAM_SEARCH)
query = gql(
"""
query StreamSearch($search_query: String!,$limit: Int!, $branch_limit:Int!, $commit_limit:Int!) {
@@ -301,64 +296,3 @@ class Resource(ResourceBase):
return self.make_request(
query=query, params=params, return_type=["streams", "items"]
)
def grant_permission(self, stream_id: str, user_id: str, role: str):
"""Grant permissions to a user on a given stream
Arguments:
stream_id {str} -- the id of the stream to grant permissions to
user_id {str} -- the id of the user to grant permissions for
role {str} -- the role to grant the user
Returns:
bool -- True if the operation was successful
"""
query = gql(
"""
mutation StreamGrantPermission($permission_params: StreamGrantPermissionInput !) {
streamGrantPermission(permissionParams: $permission_params)
}
"""
)
params = {
"permission_params": {
"streamId": stream_id,
"userId": user_id,
"role": role,
}
}
return self.make_request(
query=query,
params=params,
return_type="streamGrantPermission",
parse_response=False,
)
def revoke_permission(self, stream_id: str, user_id: str):
"""Revoke permissions from a user on a given stream
Arguments:
stream_id {str} -- the id of the stream to revoke permissions from
user_id {str} -- the id of the user to revoke permissions from
Returns:
bool -- True if the operation was successful
"""
query = gql(
"""
mutation StreamRevokePermission($permission_params: StreamRevokePermissionInput !) {
streamRevokePermission(permissionParams: $permission_params)
}
"""
)
params = {"permission_params": {"streamId": stream_id, "userId": user_id}}
return self.make_request(
query=query,
params=params,
return_type="streamRevokePermission",
parse_response=False,
)
@@ -1,9 +1,9 @@
from typing import Callable, Dict, List, Optional, Any
from functools import wraps
from gql import gql
from specklepy.api.resource import ResourceBase
from specklepy.api.resources.stream import Stream
from specklepy.logging.exceptions import GraphQLException, SpeckleException
from speckle.api.resource import ResourceBase
from speckle.api.resources.stream import Stream
from speckle.logging.exceptions import GraphQLException, SpeckleException
NAME = "subscribe"
METHODS = [
@@ -122,4 +122,4 @@ class Resource(ResourceBase):
if callback is not None:
callback(res)
else:
return res
return res
@@ -1,9 +1,9 @@
from specklepy.logging.exceptions import SpeckleException
from speckle.logging.exceptions import SpeckleException
from typing import List, Optional
from gql import gql
from pydantic.main import BaseModel
from specklepy.api.resource import ResourceBase
from specklepy.api.models import User
from speckle.api.resource import ResourceBase
from speckle.api.models import User
NAME = "user"
METHODS = ["get"]
@@ -30,17 +30,17 @@ class Resource(ResourceBase):
query = gql(
"""
query User($id: String) {
user(id: $id) {
id
email
name
bio
company
avatar
verified
profiles
role
}
user(id: $id) {
id
email
name
bio
company
avatar
verified
profiles
role
}
}
"""
)
@@ -66,16 +66,16 @@ class Resource(ResourceBase):
query = gql(
"""
query UserSearch($search_query: String!, $limit: Int!) {
userSearch(query: $search_query, limit: $limit) {
items {
id
name
bio
company
avatar
verified
}
userSearch(query: $search_query, limit: $limit) {
items {
id
name
bio
company
avatar
verified
}
}
}
"""
)
@@ -84,37 +84,3 @@ class Resource(ResourceBase):
return self.make_request(
query=query, params=params, return_type=["userSearch", "items"]
)
def update(
self, name: str = None, company: str = None, bio: str = None, avatar: str = None
):
"""Updates your user profile. All arguments are optional.
Arguments:
name {str} -- your name
company {str} -- the company you may or may not work for
bio {str} -- tell us about yourself
avatar {str} -- a nice photo of yourself
Returns:
bool -- True if your profile was updated successfully
"""
query = gql(
"""
mutation UserUpdate($user: UserUpdateInput!) {
userUpdate(user: $user)
}
"""
)
params = {"name": name, "company": company, "bio": bio, "avatar": avatar}
params = {"user": {k: v for k, v in params.items() if v is not None}}
if not params["user"]:
return SpeckleException(
message="You must provide at least one field to update your user profile"
)
return self.make_request(
query=query, params=params, return_type="userUpdate", parse_response=False
)
+17
View File
@@ -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
+51
View File
@@ -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()
+51
View File
@@ -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()
+52
View File
@@ -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()
+57
View File
@@ -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()
+51
View File
@@ -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()
+56
View File
@@ -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()
+40
View File
@@ -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()
+51
View File
@@ -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()
+56
View File
@@ -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()
+59
View File
@@ -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
+52
View File
@@ -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()
+61
View File
@@ -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
+25
View File
@@ -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
+56
View File
@@ -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
+55
View File
@@ -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
+44
View File
@@ -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
+56
View File
@@ -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
+39
View File
@@ -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
+27
View File
@@ -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
+20
View File
@@ -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
+36
View File
@@ -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
+17
View File
@@ -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
+32
View File
@@ -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
+28
View File
@@ -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
+17
View File
@@ -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
+18
View File
@@ -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
+27
View File
@@ -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()
+22
View File
@@ -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
+94
View File
@@ -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()
+66
View File
@@ -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()
+22
View File
@@ -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
+53
View File
@@ -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()
+53
View File
@@ -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()
+47
View File
@@ -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
+57
View File
@@ -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()
+59
View File
@@ -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()
+57
View File
@@ -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()
+55
View File
@@ -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()
+68
View File
@@ -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()
+53
View File
@@ -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()
+28
View File
@@ -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
+11
View File
@@ -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()
+45
View File
@@ -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
-230
View File
@@ -1,230 +0,0 @@
import os
from warnings import warn
from pydantic import BaseModel
from typing import List, Optional
from urllib.parse import urlparse, unquote
from specklepy.logging import metrics
from specklepy.api.models import ServerInfo
from specklepy.api.client import SpeckleClient
from specklepy.transports.sqlite import SQLiteTransport
from specklepy.transports.server.server import ServerTransport
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
class UserInfo(BaseModel):
name: str
email: str
company: Optional[str]
id: str
class Account(BaseModel):
isDefault: bool = None
token: str
refreshToken: str = None
serverInfo: ServerInfo
userInfo: UserInfo
id: str = None
def __repr__(self) -> str:
return f"Account(email: {self.userInfo.email}, server: {self.serverInfo.url}, isDefault: {self.isDefault})"
def __str__(self) -> str:
return self.__repr__()
def get_local_accounts(base_path: 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
"""
metrics.track(metrics.ACCOUNT_LIST)
account_storage = SQLiteTransport(scope="Accounts", base_path=base_path)
json_path = os.path.join(account_storage._base_path, "Accounts")
os.makedirs(json_path, exist_ok=True)
json_acct_files = [file for file in os.listdir(json_path) if file.endswith(".json")]
accounts = []
res = account_storage.get_all_objects()
if res:
accounts.extend(Account.parse_raw(r[1]) for r in res)
if json_acct_files:
try:
accounts.extend(
Account.parse_file(os.path.join(json_path, json_file))
for json_file in json_acct_files
)
except Exception as ex:
raise SpeckleException(
"Invalid json accounts could not be read. Please fix or remove them.",
ex,
)
return accounts
def get_default_account(base_path: str = None) -> 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
"""
metrics.track(metrics.ACCOUNT_DEFAULT)
accounts = 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
return default
class StreamWrapper:
"""
The `StreamWrapper` gives you some handy helpers to deal with urls and get authenticated clients and transports.
Construct a `StreamWrapper` with a stream, branch, commit, or object URL. The corresponding ids will be stored
in the wrapper. If you have local accounts on the machine, you can use the `get_account` and `get_client` methods
to get a local account for the server. You can also pass a token into `get_client` if you don't have a corresponding
local account for the server.
```py
from specklepy.api.credentials import StreamWrapper
# provide any stream, branch, commit, object, or globals url
wrapper = StreamWrapper("https://speckle.xyz/streams/3073b96e86/commits/604bea8cc6")
# get an authenticated SpeckleClient if you have a local account for the server
client = wrapper.get_client()
# get an authenticated ServerTransport if you have a local account for the server
transport = wrapper.get_transport()
```
"""
stream_url: str = None
use_ssl: bool = True
host: str = None
stream_id: str = None
commit_id: str = None
object_id: str = None
branch_name: str = None
client: SpeckleClient = None
account: Account = None
def __repr__(self):
return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )"
def __str__(self) -> str:
return self.__repr__()
@property
def type(self) -> str:
if self.object_id:
return "object"
elif self.commit_id:
return "commit"
elif self.branch_name:
return "branch"
else:
return "stream" if self.stream_id else "invalid"
def __init__(self, url: str) -> None:
metrics.track("streamwrapper")
self.stream_url = url
parsed = urlparse(url)
self.host = parsed.netloc
self.use_ssl = parsed.scheme == "https"
segments = parsed.path.strip("/").split("/", 3)
if not segments or len(segments) < 2:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
)
while segments:
segment = segments.pop(0)
if segments and segment.lower() == "streams":
self.stream_id = segments.pop(0)
elif segments and segment.lower() == "commits":
self.commit_id = segments.pop(0)
elif segments and segment.lower() == "branches":
self.branch_name = unquote(segments.pop(0))
elif segments and segment.lower() == "objects":
self.object_id = segments.pop(0)
elif segment.lower() == "globals":
self.branch_name = "globals"
if segments:
self.commit_id = segments.pop(0)
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
)
if not self.stream_id:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - no stream id found."
)
def get_account(self) -> Account:
"""
Gets an account object for this server from the local accounts db (added via Speckle Manager or a json file)
"""
if self.account:
return self.account
self.account = next(
(a for a in get_local_accounts() if self.host in a.serverInfo.url),
None,
)
return self.account
def get_client(self, token: str = None) -> SpeckleClient:
"""
Gets an authenticated client for this server. You may provide a token if there aren't any local accounts on this
machine. If no account is found and no token is provided, an unauthenticated client is returned.
Arguments:
token {str} -- optional token if no local account is available (defaults to None)
Returns:
SpeckleClient -- authenticated with a corresponding local account or the provided token
"""
if self.client and token is None:
return self.client
if not self.account:
self.get_account()
if not self.client:
self.client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
if self.account is None and token is None:
warn(f"No local account found for server {self.host}", SpeckleWarning)
return self.client
self.client.authenticate(self.account.token if self.account else token)
return self.client
def get_transport(self, token: str = None) -> ServerTransport:
"""
Gets a server transport for this stream using an authenticated client. If there is no local account for this
server and the client was not authenticated with a token, this will throw an exception.
Returns:
ServerTransport -- constructed for this stream with a pre-authenticated client
"""
if not self.client or not self.client.me:
self.get_client(token)
return ServerTransport(self.stream_id, self.client)
-120
View File
@@ -1,120 +0,0 @@
from typing import List
from specklepy.logging import metrics
from specklepy.objects.base import Base
from specklepy.transports.sqlite import SQLiteTransport
from specklepy.logging.exceptions import SpeckleException
from specklepy.transports.abstract_transport import AbstractTransport
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
def send(
base: Base,
transports: List[AbstractTransport] = [],
use_default_cache: bool = True,
):
"""Sends an object via the provided transports. Defaults to the local cache.
Arguments:
obj {Base} -- the object you want to send
transports {list} -- where you want to send them
use_default_cache {bool} -- toggle for the default cache. If set to false, it will only send to the provided transports
Returns:
str -- the object id of the sent object
"""
metrics.track(metrics.SEND)
if not transports and not use_default_cache:
raise SpeckleException(
message="You need to provide at least one transport: cannot send with an empty transport list and no default cache"
)
if use_default_cache:
transports.insert(0, SQLiteTransport())
serializer = BaseObjectSerializer(write_transports=transports)
for t in transports:
t.begin_write()
hash, _ = serializer.write_json(base=base)
for t in transports:
t.end_write()
return hash
def receive(
obj_id: str,
remote_transport: AbstractTransport = None,
local_transport: AbstractTransport = None,
) -> Base:
"""Receives an object from a transport.
Arguments:
obj_id {str} -- the id of the object to receive
remote_transport {Transport} -- the transport to receive from
local_transport {Transport} -- the local cache to check for existing objects
(defaults to `SQLiteTransport`)
Returns:
Base -- the base object
"""
metrics.track(metrics.RECEIVE)
if not local_transport:
local_transport = SQLiteTransport()
serializer = BaseObjectSerializer(read_transport=local_transport)
# try local transport first. if the parent is there, we assume all the children are there and continue wth deserialisation using the local transport
obj_string = local_transport.get_object(obj_id)
if obj_string:
return serializer.read_json(obj_string=obj_string)
if not remote_transport:
raise SpeckleException(
message="Could not find the specified object using the local transport, and you didn't provide a fallback remote from which to pull it."
)
obj_string = remote_transport.copy_object_and_children(
id=obj_id, target_transport=local_transport
)
return serializer.read_json(obj_string=obj_string)
def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str:
"""
Serialize a base object. If no write transports are provided, the object will be serialized
without detaching or chunking any of the attributes.
Arguments:
base {Base} -- the object to serialize
write_transports {List[AbstractTransport]} -- optional: the transports to write to
Returns:
str -- the serialized object
"""
metrics.track(metrics.SERIALIZE)
serializer = BaseObjectSerializer(write_transports=write_transports)
return serializer.write_json(base)[1]
def deserialize(obj_string: str, read_transport: AbstractTransport = None) -> Base:
"""
Deserialize a string object into a Base object. If the object contains referenced child objects that are not stored in the local db, a read transport needs to be provided in order to recompose the base with the children objects.
Arguments:
obj_string {str} -- the string object to deserialize
read_transport {AbstractTransport} -- the transport to fetch children objects from
(defaults to SQLiteTransport)
Returns:
Base -- the deserialized object
"""
metrics.track(metrics.DESERIALIZE)
if not read_transport:
read_transport = SQLiteTransport()
serializer = BaseObjectSerializer(read_transport=read_transport)
return serializer.read_json(obj_string=obj_string)
-212
View File
@@ -1,212 +0,0 @@
from specklepy.api.resources import stream
from typing import List, Optional
from gql import gql
from pydantic.main import BaseModel
from specklepy.api.resource import ResourceBase
from specklepy.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, stream_id: 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": stream_id,
"name": name,
"description": description,
}
}
return self.make_request(
query=query, params=params, return_type="branchCreate", parse_response=False
)
def get(self, stream_id: str, name: str, commits_limit: int = 10):
"""Get a branch by name from a stream
Arguments:
stream_id {str} -- the id of the stream to get the branch from
name {str} -- the name of the branch to get
commits_limit {int} -- maximum number of commits to get
Returns:
Branch -- the fetched branch with its latest commits
"""
query = gql(
"""
query BranchGet($stream_id: String!, $name: String!, $commits_limit: Int!) {
stream(id: $stream_id) {
branch(name: $name) {
id,
name,
description,
commits (limit: $commits_limit) {
totalCount,
cursor,
items {
id,
referencedObject,
sourceApplication,
totalChildrenCount,
message,
authorName,
authorId,
branchName,
parents,
createdAt
}
}
}
}
}
"""
)
params = {"stream_id": stream_id, "name": name, "commits_limit": commits_limit}
return self.make_request(
query=query, params=params, return_type=["stream", "branch"]
)
def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10):
"""Get a list of branches from a given stream
Arguments:
stream_id {str} -- the id of the stream to get the branches from
branches_limit {int} -- maximum number of branches to get
commits_limit {int} -- maximum number of commits to get
Returns:
List[Branch] -- the branches on the stream
"""
query = gql(
"""
query BranchesGet($stream_id: String!, $branches_limit: Int!, $commits_limit: Int!) {
stream(id: $stream_id) {
branches(limit: $branches_limit) {
items {
id
name
description
commits(limit: $commits_limit) {
totalCount
items{
id
message
referencedObject
sourceApplication
parents
authorId
authorName
branchName
createdAt
}
}
}
}
}
}
"""
)
params = {
"stream_id": stream_id,
"branches_limit": branches_limit,
"commits_limit": commits_limit,
}
return self.make_request(
query=query, params=params, return_type=["stream", "branches", "items"]
)
def update(
self, stream_id: str, branch_id: str, name: str = None, description: str = None
):
"""Update a branch
Arguments:
stream_id {str} -- the id of the stream containing the branch to update
branch_id {str} -- the id of the branch to update
name {str} -- optional: the updated branch name
description {str} -- optional: the updated branch description
Returns:
bool -- True if update is successfull
"""
query = gql(
"""
mutation BranchUpdate($branch: BranchUpdateInput!) {
branchUpdate(branch: $branch)
}
"""
)
params = {
"branch": {
"streamId": stream_id,
"id": branch_id,
}
}
if name:
params["branch"]["name"] = name
if description:
params["branch"]["description"] = description
return self.make_request(
query=query, params=params, return_type="branchUpdate", parse_response=False
)
def delete(self, stream_id: str, branch_id: str):
"""Delete a branch
Arguments:
stream_id {str} -- the id of the stream containing the branch to delete
branch_id {str} -- the branch to delete
Returns:
bool -- True if deletion is successful
"""
query = gql(
"""
mutation BranchDelete($branch: BranchDeleteInput!) {
branchDelete(branch: $branch)
}
"""
)
params = {"branch": {"streamId": stream_id, "id": branch_id}}
return self.make_request(
query=query, params=params, return_type="branchDelete", parse_response=False
)
-225
View File
@@ -1,225 +0,0 @@
from typing import Optional, List
from gql import gql
from specklepy.api.resource import ResourceBase
from specklepy.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
def get(self, stream_id: str, commit_id: str) -> Commit:
"""
Gets a commit given a stream and the commit id
Arguments:
stream_id {str} -- the stream where we can find the commit
commit_id {str} -- the id of the commit you want to get
Returns:
Commit -- the retrieved commit object
"""
query = gql(
"""
query Commit($stream_id: String!, $commit_id: String!) {
stream(id: $stream_id) {
commit(id: $commit_id) {
id
message
referencedObject
authorId
authorName
authorAvatar
branchName
createdAt
sourceApplication
totalChildrenCount
parents
}
}
}
"""
)
params = {"stream_id": stream_id, "commit_id": commit_id}
return self.make_request(
query=query, params=params, return_type=["stream", "commit"]
)
def list(self, stream_id: str, limit: int = 10) -> List[Commit]:
"""
Get a list of commits on a given stream
Arguments:
stream_id {str} -- the stream where the commits are
limit {int} -- the maximum number of commits to fetch (default = 10)
Returns:
List[Commit] -- a list of the most recent commit objects
"""
query = gql(
"""
query Commits($stream_id: String!, $limit: Int!) {
stream(id: $stream_id) {
commits(limit: $limit) {
items {
id
message
referencedObject
authorName
authorId
authorName
authorAvatar
branchName
createdAt
sourceApplication
totalChildrenCount
parents
}
}
}
}
"""
)
params = {"stream_id": stream_id, "limit": limit}
return self.make_request(
query=query, params=params, return_type=["stream", "commits", "items"]
)
def create(
self,
stream_id: str,
object_id: str,
branch_name: str = "main",
message: str = "",
source_application: str = "python",
parents: List[str] = None,
) -> str:
"""
Creates a commit on a branch
Arguments:
stream_id {str} -- the stream you want to commit to
object_id {str} -- the hash of your commit object
branch_name {str} -- the name of the branch to commit to (defaults to "main")
message {str} -- optional: a message to give more information about the commit
source_application{str} -- optional: the application from which the commit was created (defaults to "python")
parents {List[str]} -- optional: the id of the parent commits
Returns:
str -- the id of the created commit
"""
query = gql(
"""
mutation CommitCreate ($commit: CommitCreateInput!){ commitCreate(commit: $commit)}
"""
)
params = {
"commit": {
"streamId": stream_id,
"branchName": branch_name,
"objectId": object_id,
"message": message,
"sourceApplication": source_application,
}
}
if parents:
params["commit"]["parents"] = parents
return self.make_request(
query=query, params=params, return_type="commitCreate", parse_response=False
)
def update(self, stream_id: str, commit_id: str, message: str) -> bool:
"""
Update a commit
Arguments:
stream_id {str} -- the id of the stream that contains the commit you'd like to update
commit_id {str} -- the id of the commit you'd like to update
message {str} -- the updated commit message
Returns:
bool -- True if the operation succeeded
"""
query = gql(
"""
mutation CommitUpdate($commit: CommitUpdateInput!){ commitUpdate(commit: $commit)}
"""
)
params = {
"commit": {"streamId": stream_id, "id": commit_id, "message": message}
}
return self.make_request(
query=query, params=params, return_type="commitUpdate", parse_response=False
)
def delete(self, stream_id: str, commit_id: str) -> bool:
"""
Delete a commit
Arguments:
stream_id {str} -- the id of the stream that contains the commit you'd like to delete
commit_id {str} -- the id of the commit you'd like to delete
Returns:
bool -- True if the operation succeeded
"""
query = gql(
"""
mutation CommitDelete($commit: CommitDeleteInput!){ commitDelete(commit: $commit)}
"""
)
params = {"commit": {"streamId": stream_id, "id": commit_id}}
return self.make_request(
query=query, params=params, return_type="commitDelete", parse_response=False
)
def received(
self,
stream_id: str,
commit_id: str,
source_application: str = "python",
message: Optional[str] = None,
) -> bool:
"""
Mark a commit object a received by the source application.
"""
query = gql(
"""
mutation CommitReceive($receivedInput:CommitReceivedInput!){
commitReceive(input:$receivedInput)
}
"""
)
params = {
"receivedInput": {
"sourceApplication": source_application,
"streamId": stream_id,
"commitId": commit_id,
"message": "message",
}
}
try:
return self.make_request(
query=query,
params=params,
return_type="commitReceive",
parse_response=False,
)
except Exception as ex:
print(ex.with_traceback)
return False
-80
View File
@@ -1,80 +0,0 @@
from typing import Dict, List
from gql import gql
from graphql.language import parser
from specklepy.api.resource import ResourceBase
from specklepy.objects.base import Base
NAME = "object"
METHODS = []
class Resource(ResourceBase):
"""API Access class for objects"""
def __init__(self, me, basepath, client) -> None:
super().__init__(
me=me, basepath=basepath, client=client, name=NAME, methods=METHODS
)
self.schema = Base
def get(self, stream_id: str, object_id: str) -> Base:
"""
Get a stream object
Arguments:
stream_id {str} -- the id of the stream for the object
object_id {str} -- the hash of the object you want to get
Returns:
Base -- the returned Base object
"""
query = gql(
"""
query Object($stream_id: String!, $object_id: String!) {
stream(id: $stream_id) {
id
name
object(id: $object_id) {
id
speckleType
applicationId
createdAt
totalChildrenCount
data
}
}
}
"""
)
params = {"stream_id": stream_id, "object_id": object_id}
return self.make_request(
query=query, params=params, return_type=["stream", "object", "data"]
)
def create(self, stream_id: str, objects: List[Dict]) -> str:
"""
Not advised - generally, you want to use `operations.send()`.
Create a new object on a stream. To send a base object, you can prepare it by running it through the
`BaseObjectSerializer.traverse_base()` function to get a valid (serialisable) object to send.
NOTE: this does not create a commit - you can create one with `SpeckleClient.commit.create`. Dynamic fields will be located in the 'data' dict of the received `Base` object
Arguments:
stream_id {str} -- the id of the stream you want to send the object to
objects {List[Dict]} -- a list of base dictionary objects (NOTE: must be json serialisable)
Returns:
str -- the id of the object
"""
query = gql(
"""
mutation ObjectCreate($object_input: ObjectCreateInput!) { objectCreate(objectInput: $object_input) }
"""
)
params = {"object_input": {"streamId": stream_id, "objects": objects}}
return self.make_request(
query=query, params=params, return_type="objectCreate", parse_response=False
)
-137
View File
@@ -1,137 +0,0 @@
from typing import Dict, List
from gql import gql
from gql.client import Client
from specklepy.api.models import ServerInfo
from specklepy.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) -> ServerInfo:
"""Get the server info
Returns:
dict -- the server info in dictionary form
"""
query = gql(
"""
query Server {
serverInfo {
name
company
description
adminContact
canonicalUrl
version
roles {
name
description
resourceTarget
}
scopes {
name
description
}
authStrategies{
id
name
icon
}
}
}
"""
)
return self.make_request(
query=query, return_type="serverInfo", schema=ServerInfo
)
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
trustByDefault
logo
author {
id
name
avatar
}
}
}
"""
)
return self.make_request(query=query, return_type="apps", parse_response=False)
def create_token(self, name: str, scopes: List[str], lifespan: int) -> str:
"""Create a personal API token
Arguments:
scopes {List[str]} -- the scopes to grant with this token
name {str} -- a name for your new token
lifespan {int} -- duration before the token expires
Returns:
str -- the new API token. note: this is the only time you'll see the token!
"""
query = gql(
"""
mutation TokenCreate($token: ApiTokenCreateInput!) {
apiTokenCreate(token: $token)
}
"""
)
params = {"token": {"scopes": scopes, "name": name, "lifespan": lifespan}}
return self.make_request(
query=query,
params=params,
return_type="apiTokenCreate",
parse_response=False,
)
def revoke_token(self, token: str) -> bool:
"""Revokes (deletes) a personal API token
Arguments:
token {str} -- the token to revoke (delete)
Returns:
bool -- True if the token was successfully deleted
"""
query = gql(
"""
mutation TokenRevoke($token: String!) {
apiTokenRevoke(token: $token)
}
"""
)
params = {"token": token}
return self.make_request(
query=query,
params=params,
return_type="apiTokenRevoke",
parse_response=False,
)
-35
View File
@@ -1,35 +0,0 @@
from typing import Any, List
class SpeckleException(Exception):
def __init__(self, message: str, exception: Exception = None) -> None:
self.message = message
self.exception = exception
def __str__(self) -> str:
return f"SpeckleException: {self.message}"
class SerializationException(SpeckleException):
def __init__(self, message: str, object: Any, exception: Exception = None) -> None:
super().__init__(message=message)
self.object = object
self.unhandled_type = type(object)
def __str__(self) -> str:
return f"SpeckleException: Could not serialize object of type {self.unhandled_type}"
class GraphQLException(SpeckleException):
def __init__(self, message: str, errors: List, data=None) -> None:
super().__init__(message=message)
self.errors = errors
self.data = data
def __str__(self) -> str:
return f"GraphQLException: {self.message}"
class SpeckleWarning(Warning):
def __init__(self, *args: object) -> None:
super().__init__(*args)
-125
View File
@@ -1,125 +0,0 @@
import os
import queue
import logging
import requests
import threading
from requests.sessions import session
from specklepy.transports.sqlite import SQLiteTransport
"""
Anonymous telemetry to help us understand how to make a better Speckle.
This really helps us to deliver a better open source project and product!
"""
TRACK = True
HOST_APP = "python"
LOG = logging.getLogger(__name__)
METRICS_TRACKER = None
# actions
RECEIVE = "receive"
SEND = "send"
STREAM_CREATE = "stream/create"
STREAM_GET = "stream/get"
STREAM_UPDATE = "stream/update"
STREAM_DELETE = "stream/delete"
STREAM_DETAILS = "stream/details"
STREAM_LIST = "stream/list"
STREAM_VIEW = "stream/view"
STREAM_SEARCH = "stream/search"
ACCOUNT_DEFAULT = "account/default"
ACCOUNT_DETAILS = "account/details"
ACCOUNT_LIST = "account/list"
SERIALIZE = "serialization/serialize"
DESERIALIZE = "serialization/deserialize"
def disable():
global TRACK
TRACK = False
def set_host_app(host_app: str):
global HOST_APP
HOST_APP = host_app
def track(action: str):
if not TRACK:
return
try:
global METRICS_TRACKER
if not METRICS_TRACKER:
METRICS_TRACKER = MetricsTracker()
page_params = {
"rec": 1,
"idsite": METRICS_TRACKER.site_id,
"uid": METRICS_TRACKER.suuid,
"action_name": action,
"url": f"http://connectors/{HOST_APP}/{action}",
"urlref": f"http://connectors/{HOST_APP}/{action}",
"_cvar": {"1": ["hostApplication", HOST_APP]},
}
event_params = {
"rec": 1,
"idsite": METRICS_TRACKER.site_id,
"uid": MetricsTracker.suuid,
"_cvar": {"1": ["hostApplication", HOST_APP]},
"e_c": HOST_APP,
"e_a": action,
}
METRICS_TRACKER.queue.put_nowait([event_params, page_params])
except Exception as ex:
# wrapping this whole thing in a try except as we never want a failure here to annoy users!
LOG.error("Error queueing metrics request: " + str(ex))
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MetricsTracker(metaclass=Singleton):
matomo_url = "https://speckle.matomo.cloud/matomo.php"
site_id = 2
host_app = "python"
suuid = None
sending_thread = None
queue = queue.Queue(1000)
def __init__(self) -> None:
self.sending_thread = threading.Thread(
target=self._send_tracking_requests, daemon=True
)
self.set_suuid()
self.sending_thread.start()
def set_suuid(self):
try:
file_path = os.path.join(SQLiteTransport.get_base_path("Speckle"), "suuid")
with open(file_path, "r") as file:
self.suuid = file.read()
except:
self.suuid = "unknown-suuid"
def _send_tracking_requests(self):
session = requests.Session()
while True:
params = self.queue.get()
try:
session.post(self.matomo_url, params=params[0])
session.post(self.matomo_url, params=params[1])
except Exception as ex:
LOG.error("Error sending metrics request: " + str(ex))
self.queue.task_done()
-5
View File
@@ -1,5 +0,0 @@
"""Builtin Speckle object kit."""
from specklepy.objects.base import Base
__all__ = ["Base"]
-404
View File
@@ -1,404 +0,0 @@
import typing
from typing import (
Any,
Callable,
ClassVar,
Dict,
List,
Optional,
Set,
Type,
get_type_hints,
)
from warnings import warn
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.units import get_units_from_string
from specklepy.transports.memory import MemoryTransport
PRIMITIVES = (int, float, str, bool)
# to remove from dir() when calling get_member_names()
REMOVE_FROM_DIR = {
"Config",
"_Base__dict_helper",
"__annotations__",
"__class__",
"__delattr__",
"__dict__",
"__dir__",
"__doc__",
"__eq__",
"__format__",
"__ge__",
"__getattribute__",
"__getitem__",
"__gt__",
"__hash__",
"__init__",
"__init_subclass__",
"__le__",
"__lt__",
"__module__",
"__ne__",
"__new__",
"__reduce__",
"__reduce_ex__",
"__repr__",
"__setattr__",
"__setitem__",
"__sizeof__",
"__str__",
"__subclasshook__",
"__weakref__",
"_chunk_size_default",
"_chunkable",
"_count_descendants",
"_attr_types",
"_detachable",
"_handle_object_count",
"_type_check",
"_type_registry",
"_units",
"add_chunkable_attrs",
"add_detachable_attrs",
"get_children_count",
"get_dynamic_member_names",
"get_id",
"get_member_names",
"get_registered_type",
"get_typed_member_names",
"to_dict",
"update_forward_refs",
"validate_prop_name",
"from_list",
"to_list",
}
class _RegisteringBase:
"""
Private Base model for Speckle types.
This is an implementation detail, please do not use this outside this module.
This class provides automatic registration of `speckle_type` into a global,
(class level) registry for each subclassing type.
The type registry is a base for accurate type based (de)serialization.
"""
speckle_type: ClassVar[str]
_type_registry: ClassVar[Dict[str, "Base"]] = {}
_attr_types: ClassVar[Dict[str, Type]] = {}
class Config:
validate_assignment = True
@classmethod
def get_registered_type(cls, speckle_type: str) -> Optional[Type["Base"]]:
"""Get the registered type from the protected mapping via the `speckle_type`"""
return cls._type_registry.get(speckle_type, None)
def __init_subclass__(
cls,
speckle_type: str = None,
chunkable: Dict[str, int] = None,
detachable: Set[str] = None,
serialize_ignore: Set[str] = None,
**kwargs: Dict[str, Any],
):
"""
Hook into subclass type creation.
This is provides a mechanism to hook into the event of the subclass type object
initialization. This is reused to register each subclassing type into a class
level dictionary.
"""
if speckle_type in cls._type_registry:
raise ValueError(
f"The speckle_type: {speckle_type} is already registered for type: "
f"{cls._type_registry[speckle_type].__name__}. "
f"Please choose a different type name."
)
cls.speckle_type = speckle_type or cls.__name__
cls._type_registry[cls.speckle_type] = cls # type: ignore
try:
cls._attr_types = get_type_hints(cls)
except Exception:
cls._attr_types = getattr(cls, "__annotations__", {})
if chunkable:
chunkable = {k: v for k, v in chunkable.items() if isinstance(v, int)}
cls._chunkable = dict(cls._chunkable, **chunkable)
if detachable:
cls._detachable = cls._detachable.union(detachable)
if serialize_ignore:
cls._serialize_ignore = cls._serialize_ignore.union(serialize_ignore)
super().__init_subclass__(**kwargs)
class Base(_RegisteringBase):
id: Optional[str] = None
totalChildrenCount: Optional[int] = None
applicationId: Optional[str] = None
_units: str = "m"
# dict of chunkable props and their max chunk size
_chunkable: Dict[str, int] = {}
_chunk_size_default: int = 1000
_detachable: Set[str] = set() # list of defined detachable props
_serialize_ignore: Set[str] = set()
def __init__(self, **kwargs) -> None:
super().__init__()
for k, v in kwargs.items():
self.__setattr__(k, v)
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(id: {self.id}, "
f"speckle_type: {self.speckle_type}, "
f"totalChildrenCount: {self.totalChildrenCount})"
)
def __str__(self) -> str:
return self.__repr__()
@classmethod
def of_type(cls, speckle_type: str, **kwargs) -> "Base":
"""
Get a plain Base object with a specified speckle_type.
The speckle_type is protected and cannot be overwritten on a class instance.
This is to prevent problems with receiving in other platforms or connectors.
However, if you really need a base with a different type, here is a helper
to do that for you.
This is used in the deserialisation of unknown types so their speckle_type
can be preserved.
"""
b = cls(**kwargs)
b.__dict__.update(speckle_type=speckle_type)
return b
def __setitem__(self, name: str, value: Any) -> None:
self.validate_prop_name(name)
self.__dict__[name] = value
def __getitem__(self, name: str) -> Any:
return self.__dict__[name]
def __setattr__(self, name: str, value: Any) -> None:
"""
Type checking, guard attribute, and property set mechanism.
The `speckle_type` is a protected class attribute it must not be overridden.
This also performs a type check if the attribute is type hinted.
"""
if name == "speckle_type":
# not sure if we should raise an exception here??
# raise SpeckleException(
# "Cannot override the `speckle_type`. This is set manually by the class or on deserialisation"
# )
return
# if value is not None:
value = self._type_check(name, value)
attr = getattr(self.__class__, name, None)
if isinstance(attr, property):
try:
attr.__set__(self, value)
except AttributeError:
pass # the prop probably doesn't have a setter
super().__setattr__(name, value)
@classmethod
def update_forward_refs(cls) -> None:
"""
Attempts to populate the internal defined types dict for type checking sometime after defining the class.
This is already done when defining the class, but can be called again if references to undefined types were
included.
See `objects.geometry` for an example of how this is used with the Brep class definitions
"""
try:
cls._attr_types = get_type_hints(cls)
except Exception as e:
warn(f"Could not update forward refs for class {cls.__name__}: {e}")
@classmethod
def validate_prop_name(cls, name: str) -> None:
"""Validator for dynamic attribute names."""
if name in {"", "@"}:
raise ValueError("Invalid Name: Base member names cannot be empty strings")
if name.startswith("@@"):
raise ValueError(
"Invalid Name: Base member names cannot start with more than one '@'",
)
if "." in name or "/" in name:
raise ValueError(
"Invalid Name: Base member names cannot contain characters '.' or '/'",
)
def _type_check(self, name: str, value: Any):
"""
Lightweight type checking of values before setting them
NOTE: Does not check subscripted types within generics as the performance hit of checking
each item within a given collection isn't worth it. Eg if you have a type Dict[str, float],
we will only check if the value you're trying to set is a dict.
"""
types = getattr(self, "_attr_types", {})
t = types.get(name, None)
if t is None:
return value
if t.__module__ == "typing":
origin = getattr(t, "__origin__")
t = (
tuple(getattr(sub_t, "__origin__", sub_t) for sub_t in t.__args__)
if origin is typing.Union
else origin
)
if not isinstance(t, (type, tuple)):
warn(
f"Unrecognised type '{t}' provided for attribute '{name}'. Type will not been validated."
)
return value
if isinstance(value, t):
return value
# to be friendly, we'll parse ints and strs into floats, but not the other way around
# (to avoid unexpected rounding)
if isinstance(t, tuple):
t = t[0]
try:
if t is float:
return float(value)
if t is str and value:
return str(value)
except ValueError:
pass
raise SpeckleException(
f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'"
)
def add_chunkable_attrs(self, **kwargs: int) -> None:
"""
Mark defined attributes as chunkable for serialisation
Arguments:
kwargs {int} -- the name of the attribute as the keyword and the chunk size as the arg
"""
chunkable = {k: v for k, v in kwargs.items() if isinstance(v, int)}
self._chunkable = dict(self._chunkable, **chunkable)
def add_detachable_attrs(self, names: Set[str]) -> None:
"""
Mark defined attributes as detachable for serialisation
Arguments:
names {Set[str]} -- the names of the attributes to detach as a set of strings
"""
self._detachable = self._detachable.union(names)
@property
def units(self):
return self._units
@units.setter
def units(self, value: str):
self._units = get_units_from_string(value)
def get_member_names(self) -> List[str]:
"""Get all of the property names on this object, dynamic or not"""
attr_dir = list(set(dir(self)) - REMOVE_FROM_DIR)
return [
name
for name in attr_dir
if not name.startswith("_") and not callable(getattr(self, name))
]
def get_serializable_attributes(self) -> List[str]:
"""Get the attributes that should be serialized"""
return list(set(self.get_member_names()) - self._serialize_ignore)
def get_typed_member_names(self) -> List[str]:
"""Get all of the names of the defined (typed) properties of this object"""
return list(self._attr_types.keys())
def get_dynamic_member_names(self) -> List[str]:
"""Get all of the names of the dynamic properties of this object"""
return list(set(self.__dict__.keys()) - set(self._attr_types.keys()))
def get_children_count(self) -> int:
"""Get the total count of children Base objects"""
parsed = []
return 1 + self._count_descendants(self, parsed)
def get_id(self, decompose: bool = False) -> str:
"""
Gets the id (a unique hash) of this object. ⚠️ This method fully serializes the object which,
in the case of large objects (with many sub-objects), has a tangible cost. Avoid using it!
Note: the hash of a decomposed object differs from that of a non-decomposed object
Arguments:
decompose {bool} -- if True, will decompose the object in the process of hashing it
Returns:
str -- the hash (id) of the fully serialized object
"""
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
serializer = BaseObjectSerializer()
if decompose:
serializer.write_transports = [MemoryTransport()]
return serializer.traverse_base(self)[0]
def _count_descendants(self, base: "Base", parsed: List) -> int:
if base in parsed:
return 0
parsed.append(base)
return sum(
self._handle_object_count(value, parsed)
for name, value in base.get_member_names()
if not name.startswith("@")
)
def _handle_object_count(self, obj: Any, parsed: List) -> int:
count = 0
if obj is None:
return count
if isinstance(obj, "Base"):
count += 1
count += self._count_descendants(obj, parsed)
return count
elif isinstance(obj, list):
for item in obj:
if isinstance(item, "Base"):
count += 1
count += self._count_descendants(item, parsed)
else:
count += self._handle_object_count(item, parsed)
elif isinstance(obj, dict):
for _, value in obj.items():
if isinstance(value, "Base"):
count += 1
count += self._count_descendants(value, parsed)
else:
count += self._handle_object_count(value, parsed)
return count
Base.update_forward_refs()
class DataChunk(Base, speckle_type="Speckle.Core.Models.DataChunk"):
data: List[Any] = None
def __init__(self) -> None:
self.data = []
-136
View File
@@ -1,136 +0,0 @@
from enum import Enum
from typing import Any, Callable, List, Type
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.base import Base
class CurveTypeEncoding(int, Enum):
Arc = 0
Circle = 1
Curve = 2
Ellipse = 3
Line = 4
Polyline = 5
Polycurve = 6
@property
def object_class(self) -> Type:
from . import geometry
if self == self.Arc:
return geometry.Arc
elif self == self.Circle:
return geometry.Circle
elif self == self.Curve:
return geometry.Curve
elif self == self.Ellipse:
return geometry.Ellipse
elif self == self.Line:
return geometry.Line
elif self == self.Polyline:
return geometry.Polyline
elif self == self.Polycurve:
return geometry.Polycurve
raise SpeckleException(
f"No corresponding object class for CurveTypeEncoding: {self}"
)
def curve_from_list(args: List[float]):
curve_type = CurveTypeEncoding(args[0])
return curve_type.object_class.from_list(args)
class ObjectArray:
def __init__(self) -> None:
self.data = []
@classmethod
def from_objects(cls, objects: List[Base]) -> "ObjectArray":
data_list = cls()
if not objects:
return data_list
speckle_type = objects[0].speckle_type
for obj in objects:
if speckle_type != obj.speckle_type:
raise SpeckleException(
"All objects in chunk should have the same speckle_type. "
f"Found {speckle_type} and {obj.speckle_type}"
)
data_list.encode_object(object=obj)
return data_list
@staticmethod
def decode_data(
data: List[Any], decoder: Callable[[List[Any]], Base]
) -> List[Base]:
bases = []
if not data:
return bases
index = 0
while index < len(data):
item_length = data[index]
item_start = index + 1
item_end = item_start + item_length
item_data = data[item_start:item_end]
index = item_end
# TODO: investigate what's going on w this fail
try:
decoded_data = decoder(item_data)
bases.append(decoded_data)
except ValueError:
continue
return bases
def decode(self, decoder: Callable[[List[Any]], Any]):
return self.decode_data(data=self.data, decoder=decoder)
def encode_object(self, object: Base):
encoded = object.to_list()
encoded.insert(0, len(encoded))
self.data.extend(encoded)
class CurveArray(ObjectArray):
@classmethod
def from_curve(cls, curve: Base) -> "CurveArray":
crv_array = cls()
crv_array.data = curve.to_list()
return crv_array
@classmethod
def from_curves(cls, curves: List[Base]) -> "CurveArray":
data = []
for curve in curves:
curve_list = curve.to_list()
curve_list.insert(0, len(curve_list))
data.extend(curve_list)
crv_array = cls()
crv_array.data = data
return crv_array
@staticmethod
def curve_from_list(args: List[float]) -> Base:
curve_type = CurveTypeEncoding(args[0])
return curve_type.object_class.from_list(args)
@property
def type(self) -> CurveTypeEncoding:
return CurveTypeEncoding(self.data[0])
def to_curve(self) -> Base:
return self.type.object_class.from_list(self.data)
@classmethod
def _curve_decoder(cls, data: List[float]) -> Base:
crv_array = cls()
crv_array.data = data
return crv_array.to_curve()
def to_curves(self) -> List[Base]:
return self.decode(decoder=self._curve_decoder)
-43
View File
@@ -1,43 +0,0 @@
from specklepy.objects.geometry import Point
from typing import List
from .base import Base
CHUNKABLE_PROPS = {
"vertices": 100,
"faces": 100,
"colors": 100,
"textureCoordinates": 100,
"test_bases": 10,
}
DETACHABLE = {"detach_this", "origin", "detached_list"}
class FakeGeo(Base, chunkable={"dots": 50}, detachable={"pointslist"}):
pointslist: List[Base] = None
dots: List[int] = None
class FakeMesh(FakeGeo, chunkable=CHUNKABLE_PROPS, detachable=DETACHABLE):
vertices: List[float] = None
faces: List[int] = None
colors: List[int] = None
textureCoordinates: List[float] = None
test_bases: List[Base] = None
detach_this: Base = None
detached_list: List[Base] = None
_origin: Point = None
# def __init__(self, **kwargs) -> None:
# super(FakeMesh, self).__init__(**kwargs)
# self.add_chunkable_attrs(**CHUNKABLE_PROPS)
# self.add_detachable_attrs(DETACHABLE)
@property
def origin(self):
return self._origin
@origin.setter
def origin(self, value: Point):
self._origin = value
-735
View File
@@ -1,735 +0,0 @@
from enum import Enum
from typing import Any, List, Optional
from .base import Base
from .encoding import CurveArray, CurveTypeEncoding, ObjectArray
from .units import get_encoding_from_units, get_units_from_encoding
GEOMETRY = "Objects.Geometry."
class Interval(Base, speckle_type="Objects.Primitive.Interval"):
start: float = 0.0
end: float = 0.0
def length(self):
return abs(self.start - self.end)
@classmethod
def from_list(cls, args: List[Any]) -> "Interval":
return cls(start=args[0], end=args[1])
def to_list(self) -> List[Any]:
return [self.start, self.end]
class Point(Base, speckle_type=GEOMETRY + "Point"):
x: float = 0.0
y: float = 0.0
z: float = 0.0
def __repr__(self) -> str:
return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, id: {self.id}, speckle_type: {self.speckle_type})"
@classmethod
def from_list(cls, args: List[float]) -> "Point":
return cls(x=args[0], y=args[1], z=args[2])
def to_list(self) -> List[Any]:
return [self.x, self.y, self.z]
@classmethod
def from_coords(cls, x: float = 0.0, y: float = 0.0, z: float = 0.0):
pt = Point()
pt.x, pt.y, pt.z = x, y, z
return pt
class Vector(Point, speckle_type=GEOMETRY + "Vector"):
pass
class ControlPoint(Point, speckle_type=GEOMETRY + "ControlPoint"):
weight: float = None
class Plane(Base, speckle_type=GEOMETRY + "Plane"):
origin: Point = Point()
normal: Vector = Vector()
xdir: Vector = Vector()
ydir: Vector = Vector()
@classmethod
def from_list(cls, args: List[Any]) -> "Plane":
return cls(
origin=Point.from_list(args[0:3]),
normal=Vector.from_list(args[3:6]),
xdir=Vector.from_list(args[6:9]),
ydir=Vector.from_list(args[9:12]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.extend(self.origin.to_list())
encoded.extend(self.normal.to_list())
encoded.extend(self.xdir.to_list())
encoded.extend(self.ydir.to_list())
return encoded
class Box(Base, speckle_type=GEOMETRY + "Box"):
basePlane: Plane = Plane()
ySize: Interval = Interval()
zSize: Interval = Interval()
xSize: Interval = Interval()
area: float = None
volume: float = None
class Line(Base, speckle_type=GEOMETRY + "Line"):
start: Point = Point()
end: Point = None
domain: Interval = None
bbox: Box = None
length: float = None
@classmethod
def from_list(cls, args: List[Any]) -> "Line":
return cls(
start=Point.from_list(args[0:3]),
end=Point.from_list(args[3:6]),
domain=Interval.from_list(args[6:9]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.extend(self.start.to_list())
encoded.extend(self.end.to_list())
encoded.extend(self.domain.to_list())
return encoded
class Arc(Base, speckle_type=GEOMETRY + "Arc"):
radius: float = None
startAngle: float = None
endAngle: float = None
angleRadians: float = None
plane: Plane = None
domain: Interval = None
startPoint: Point = None
midPoint: Point = None
endPoint: Point = None
bbox: Box = None
area: float = None
length: float = None
@classmethod
def from_list(cls, args: List[Any]) -> "Arc":
return cls(
radius=args[1],
startAngle=args[2],
endAngle=args[3],
angleRadians=args[4],
domain=Interval.from_list(args[5:7]),
plane=Plane.from_list(args[7:20]),
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(CurveTypeEncoding.Arc.value)
encoded.append(self.radius)
encoded.append(self.startAngle)
encoded.append(self.endAngle)
encoded.append(self.angleRadians)
encoded.extend(self.domain.to_list())
encoded.extend(self.plane.to_list())
encoded.append(get_encoding_from_units(self.units))
return encoded
class Circle(Base, speckle_type=GEOMETRY + "Circle"):
radius: float = None
plane: Plane = None
domain: Interval = None
bbox: Box = None
area: float = None
length: float = None
@classmethod
def from_list(cls, args: List[Any]) -> "Circle":
return cls(
radius=args[1],
domain=Interval.from_list(args[2:4]),
plane=Plane.from_list(args[4:17]),
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(CurveTypeEncoding.Circle.value)
encoded.append(self.radius),
encoded.extend(self.domain.to_list())
encoded.extend(self.plane.to_list())
encoded.append(get_encoding_from_units(self.units))
return encoded
class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"):
firstRadius: float = None
secondRadius: float = None
plane: Plane = None
domain: Interval = None
trimDomain: Interval = None
bbox: Box = None
area: float = None
length: float = None
@classmethod
def from_list(cls, args: List[Any]) -> "Ellipse":
return cls(
firstRadius=args[1],
secondRadius=args[2],
domain=Interval.from_list(args[3:5]),
plane=Plane.from_list(args[5:18]),
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(CurveTypeEncoding.Ellipse.value)
encoded.append(self.firstRadius)
encoded.append(self.secondRadius)
encoded.extend(self.domain.to_list())
encoded.extend(self.plane.to_list())
encoded.append(get_encoding_from_units(self.units))
return encoded
class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 20000}):
value: List[float] = None
closed: bool = None
domain: Interval = None
bbox: Box = None
area: float = None
length: float = None
@classmethod
def from_points(cls, points: List[Point]):
polyline = cls()
polyline.units = points[0].units
polyline.value = []
for point in points:
polyline.value.extend([point.x, point.y, point.z])
return polyline
@classmethod
def from_list(cls, args: List[Any]) -> "Polyline":
point_count = args[4]
return cls(
closed=bool(args[1]),
domain=Interval.from_list(args[2:4]),
value=args[5 : 5 + point_count],
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(CurveTypeEncoding.Polyline.value)
encoded.append(int(self.closed))
encoded.extend(self.domain.to_list())
encoded.append(len(self.value))
encoded.extend(self.value)
encoded.append(get_encoding_from_units(self.units))
return encoded
def as_points(self) -> List[Point]:
"""Converts the `value` attribute to a list of Points"""
if not self.value:
return
if len(self.value) % 3:
raise ValueError("Points array malformed: length%3 != 0.")
values = iter(self.value)
return [
Point(x=v, y=next(values), z=next(values), units=self.units) for v in values
]
class Curve(
Base,
speckle_type=GEOMETRY + "Curve",
chunkable={"points": 20000, "weights": 20000, "knots": 20000},
):
degree: int = None
periodic: bool = None
rational: bool = None
points: List[float] = None
weights: List[float] = None
knots: List[float] = None
domain: Interval = None
displayValue: Polyline = None
closed: bool = None
bbox: Box = None
area: float = None
length: float = None
def as_points(self) -> List[Point]:
"""Converts the `value` attribute to a list of Points"""
if not self.points:
return
if len(self.points) % 3:
raise ValueError("Points array malformed: length%3 != 0.")
values = iter(self.points)
return [
Point(x=v, y=next(values), z=next(values), units=self.units) for v in values
]
@classmethod
def from_list(cls, args: List[Any]) -> "Curve":
point_count = args[7]
weights_count = args[8]
knots_count = args[9]
points_start = 10
weights_start = 10 + point_count
knots_start = weights_start + weights_count
knots_end = knots_start + knots_count
return cls(
degree=args[1],
periodic=bool(args[2]),
rational=bool(args[3]),
closed=bool(args[4]),
domain=Interval.from_list(args[5:7]),
points=args[points_start:weights_start],
weights=args[weights_start:knots_start],
knots=args[knots_start:knots_end],
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(CurveTypeEncoding.Curve.value)
encoded.append(self.degree)
encoded.append(int(self.periodic))
encoded.append(int(self.rational))
encoded.append(int(self.closed))
encoded.extend(self.domain.to_list())
encoded.append(len(self.points))
encoded.append(len(self.weights))
encoded.append(len(self.knots))
encoded.extend(self.points)
encoded.extend(self.weights)
encoded.extend(self.knots)
encoded.append(get_encoding_from_units(self.units))
return encoded
class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"):
segments: List[Base] = None
domain: Interval = None
closed: bool = None
bbox: Box = None
area: float = None
length: float = None
@classmethod
def from_list(cls, args: List[Any]) -> "Polycurve":
curve_arrays = CurveArray()
curve_arrays.data = args[4:-1]
return cls(
closed=bool(args[1]),
domain=Interval.from_list(args[2:4]),
segments=curve_arrays.to_curves(),
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(CurveTypeEncoding.Polycurve.value)
encoded.append(int(self.closed))
encoded.extend(self.domain.to_list())
curve_array = CurveArray.from_curves(self.segments)
encoded.extend(curve_array.data)
encoded.append(get_encoding_from_units(self.units))
return encoded
class Extrusion(Base, speckle_type=GEOMETRY + "Extrusion"):
capped: bool = None
profile: Base = None
pathStart: Point = None
pathEnd: Point = None
pathCurve: Base = None
pathTangent: Base = None
profiles: List[Base] = None
length: float = None
area: float = None
volume: float = None
bbox: Box = None
class Mesh(
Base,
speckle_type=GEOMETRY + "Mesh",
chunkable={
"vertices": 2000,
"faces": 2000,
"colors": 2000,
"textureCoordinates": 2000,
},
):
vertices: List[float] = None
faces: List[int] = None
colors: List[int] = None
textureCoordinates: List[float] = None
bbox: Box = None
area: float = None
volume: float = None
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
degreeU: int = None
degreeV: int = None
rational: bool = None
area: float = None
pointData: List[float] = None
countU: int = None
countV: int = None
bbox: Box = None
closedU: bool = None
closedV: bool = None
domainU: Interval = None
domainV: Interval = None
knotsU: List[float] = None
knotsV: List[float] = None
@classmethod
def from_list(cls, args: List[Any]) -> "Surface":
point_count = int(args[11])
knots_u_count = int(args[12])
knots_v_count = int(args[13])
start_point_data = 14
start_knots_u = start_point_data + point_count
start_knots_v = start_knots_u + knots_u_count
return cls(
degreeU=int(args[0]),
degreeV=int(args[1]),
countU=int(args[2]),
countV=int(args[3]),
rational=bool(args[4]),
closedU=bool(args[5]),
closedV=bool(args[6]),
domainU=Interval(start=args[7], end=args[8]),
domainV=Interval(start=args[9], end=args[10]),
pointData=args[start_point_data:start_knots_u],
knotsU=args[start_knots_u:start_knots_v],
knotsV=args[start_knots_v : start_knots_v + knots_v_count],
units=get_units_from_encoding(args[-1]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(self.degreeU)
encoded.append(self.degreeV)
encoded.append(self.countU)
encoded.append(self.countV)
encoded.append(int(self.rational))
encoded.append(int(self.closedU))
encoded.append(int(self.closedV))
encoded.extend(self.domainU.to_list())
encoded.extend(self.domainV.to_list())
encoded.append(len(self.pointData))
encoded.append(len(self.knotsU))
encoded.append(len(self.knotsV))
encoded.extend(self.pointData)
encoded.extend(self.knotsU)
encoded.extend(self.knotsV)
encoded.append(get_encoding_from_units(self.units))
return encoded
class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"):
_Brep: "Brep" = None
SurfaceIndex: int = None
LoopIndices: List[int] = None
OuterLoopIndex: int = None
OrientationReversed: bool = None
@property
def _outer_loop(self):
return self._Brep.Loops[self.OuterLoopIndex]
@property
def _surface(self):
return self._Brep.Surfaces[self.SurfaceIndex]
@property
def _loops(self):
if self.LoopIndices:
return [self._Brep.Loops[i] for i in self.LoopIndices]
class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"):
_Brep: "Brep" = None
Curve3dIndex: int = None
TrimIndices: List[int] = None
StartIndex: int = None
EndIndex: int = None
ProxyCurveIsReversed: bool = None
Domain: Interval = None
@property
def _start_vertex(self):
return self._Brep.Vertices[self.StartIndex]
@property
def _end_vertex(self):
return self._Brep.Vertices[self.EndIndex]
@property
def _trims(self):
if self.TrimIndices:
return [self._Brep.Trims[i] for i in self.TrimIndices]
@property
def _curve(self):
return self._Brep.Curve3D[self.Curve3dIndex]
class BrepLoop(Base, speckle_type=GEOMETRY + "BrepLoop"):
_Brep: "Brep" = None
FaceIndex: int = None
TrimIndices: List[int] = None
Type: str = None
@property
def _face(self):
return self._Brep.Faces[self.FaceIndex]
@property
def _trims(self):
if self.TrimIndices:
return [self._Brep.Trims[i] for i in self.TrimIndices]
class BrepTrimTypeEnum(int, Enum):
Unknown = 0
Boundary = 1
Mated = 2
Seam = 3
Singular = 4
CurveOnSurface = 5
PointOnSurface = 6
Slit = 7
class BrepTrim(Base, speckle_type=GEOMETRY + "BrepTrim"):
_Brep: "Brep" = None
EdgeIndex: int = None
StartIndex: int = None
EndIndex: int = None
FaceIndex: int = None
LoopIndex: int = None
CurveIndex: int = None
IsoStatus: int = None
TrimType: str = None
IsReversed: bool = None
Domain: Interval = None
@property
def _face(self):
return self._Brep.Faces[self.FaceIndex]
@property
def _loop(self):
return self._Brep.Loops[self.LoopIndex]
@property
def _edge(self):
return self._Brep.Edges[self.EdgeIndex] if self.EdgeIndex != -1 else None
@property
def _curve_2d(self):
return self._Brep.Curve2D[self.CurveIndex]
@classmethod
def from_list(cls, args: List[Any]) -> "BrepTrim":
return cls(
EdgeIndex=args[0],
StartIndex=args[1],
EndIndex=args[2],
FaceIndex=args[3],
LoopIndex=args[4],
CurveIndex=args[5],
IsoStatus=args[6],
TrimType=BrepTrimTypeEnum(args[7]).name,
IsReversed=bool(args[8]),
)
def to_list(self) -> List[Any]:
encoded = []
encoded.append(self.EdgeIndex)
encoded.append(self.StartIndex)
encoded.append(self.EndIndex)
encoded.append(self.FaceIndex)
encoded.append(self.LoopIndex)
encoded.append(self.CurveIndex)
encoded.append(self.IsoStatus)
encoded.append(getattr(BrepTrimTypeEnum, self.TrimType).value)
encoded.append(self.IsReversed)
return encoded
class Brep(
Base,
speckle_type=GEOMETRY + "Brep",
chunkable={
"SurfacesValue": 200,
"Curve3DValues": 200,
"Curve2DValues": 200,
"VerticesValue": 5000,
"Edges": 5000,
"Loops": 5000,
"TrimsValue": 5000,
"Faces": 5000,
},
detachable={"displayValue"},
serialize_ignore={"Surfaces", "Curve3D", "Curve2D", "Vertices", "Trims"},
):
provenance: str = None
bbox: Box = None
area: float = None
volume: float = None
displayValue: Mesh = None
Surfaces: List[Surface] = None
Curve3D: List[Base] = None
Curve2D: List[Base] = None
Vertices: List[Point] = None
IsClosed: bool = None
Orientation: int = None
def _inject_self_into_children(self, children: Optional[List[Base]]) -> List[Base]:
if children is None:
return children
for child in children:
child._Brep = self
return children
@property
def Edges(self) -> List[BrepEdge]:
return self._inject_self_into_children(self._Edges)
@Edges.setter
def Edges(self, value: List[BrepEdge]):
self._Edges = value
@property
def Loops(self) -> List[BrepLoop]:
return self._inject_self_into_children(self._Loops)
@Loops.setter
def Loops(self, value: List[BrepLoop]):
self._Loops = value
@property
def Faces(self) -> List[BrepFace]:
return self._inject_self_into_children(self._Faces)
@Faces.setter
def Faces(self, value: List[BrepFace]):
self._Faces = value
@property
def SurfacesValue(self) -> List[float]:
if self.Surfaces is None:
return None
return ObjectArray.from_objects(self.Surfaces).data
@SurfacesValue.setter
def SurfacesValue(self, value: List[float]):
self.Surfaces = ObjectArray.decode_data(value, Surface.from_list)
@property
def Curve3DValues(self) -> List[float]:
if self.Curve3D is None:
return None
return CurveArray.from_curves(self.Curve3D).data
@Curve3DValues.setter
def Curve3DValues(self, value: List[float]):
crv_array = CurveArray()
crv_array.data = value
self.Curve3D = crv_array.to_curves()
@property
def Curve2DValues(self) -> List[Base]:
if self.Curve2D is None:
return None
return CurveArray.from_curves(self.Curve2D).data
@Curve2DValues.setter
def Curve2DValues(self, value: List[float]):
crv_array = CurveArray()
crv_array.data = value
self.Curve2D = crv_array.to_curves()
@property
def VerticesValue(self) -> List[Point]:
if self.Vertices is None:
return None
encoded_unit = get_encoding_from_units(self.Vertices[0].units)
values = [encoded_unit]
for vertex in self.Vertices:
values.extend(vertex.to_list())
return values
@VerticesValue.setter
def VerticesValue(self, value: List[float]):
value = value.copy()
units = get_units_from_encoding(value.pop(0))
vertices = []
for i in range(0, len(value), 3):
vertex = Point.from_list(value[i : i + 3])
vertex._units = units
vertices.append(vertex)
self.Vertices = vertices
@property
def Trims(self) -> List[BrepTrim]:
return self._inject_self_into_children(self._Trims)
@Trims.setter
def Trims(self, value: List[BrepTrim]):
self._Trims = value
@property
def TrimsValue(self) -> List[float]:
if self.Trims is None:
return None
values = []
for trim in self.Trims:
values.extend(trim.to_list())
return values
@TrimsValue.setter
def TrimsValue(self, value: List[float]):
self.Trims = [
BrepTrim.from_list(value[i : i + 9]) for i in range(0, len(value), 9)
]
BrepEdge.update_forward_refs()
BrepLoop.update_forward_refs()
BrepTrim.update_forward_refs()
BrepFace.update_forward_refs()
-12
View File
@@ -1,12 +0,0 @@
from .base import Base
OTHER = "Objects.Other."
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
name: str = None
opacity: float = 1
metalness: float = 0
roughness: float = 1
diffuse: int = -2894893 # light gray arbg
emissive: int = -16777216 # black arbg
-57
View File
@@ -1,57 +0,0 @@
from specklepy.logging.exceptions import SpeckleException
UNITS = ["mm", "cm", "m", "in", "ft", "yd", "mi"]
UNITS_STRINGS = {
"mm": ["mm", "mil", "millimeters", "millimetres"],
"cm": ["cm", "centimetre", "centimeter", "centimetres", "centimeters"],
"m": ["m", "meter", "meters", "metre", "metres"],
"km": ["km", "kilometer", "kilometre", "kilometers", "kilometres"],
"in": ["in", "inch", "inches"],
"ft": ["ft", "foot", "feet"],
"yd": ["yd", "yard", "yards"],
"mi": ["mi", "mile", "miles"],
"none": ["none", "null"],
}
UNITS_ENCODINGS = {
"none": 0,
"mm": 1,
"cm": 2,
"m": 3,
"km": 4,
"in": 5,
"ft": 6,
"yd": 7,
"mi": 8,
}
def get_units_from_string(unit: str):
unit = str.lower(unit)
for name, alternates in UNITS_STRINGS.items():
if unit in alternates:
return name
raise SpeckleException(
message=f"Could not understand what unit {unit} is referring to. Please enter a valid unit (eg {UNITS})."
)
def get_units_from_encoding(unit: int):
for name, encoding in UNITS_ENCODINGS.items():
if unit == encoding:
return name
raise SpeckleException(
message=f"Could not understand what unit {unit} is referring to. Please enter a valid unit encoding (eg {UNITS_ENCODINGS})."
)
def get_encoding_from_units(unit: str):
try:
return UNITS_ENCODINGS[unit]
except KeyError:
raise SpeckleException(
message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS})."
)
View File
@@ -1,353 +0,0 @@
import ujson
import hashlib
import re
from uuid import uuid4
from typing import Any, Dict, List, Tuple
from specklepy.objects.base import Base, DataChunk
from specklepy.logging.exceptions import SerializationException, SpeckleException
from specklepy.transports.abstract_transport import AbstractTransport
import specklepy.objects.geometry
import specklepy.objects.other
PRIMITIVES = (int, float, str, bool)
def hash_obj(obj: Any) -> str:
return hashlib.sha256(ujson.dumps(obj).encode()).hexdigest()[:32]
class BaseObjectSerializer:
read_transport: AbstractTransport
write_transports: List[AbstractTransport]
detach_lineage: List[bool] = [] # tracks depth and whether or not to detach
lineage: List[str] = [] # keeps track of hash chain through the object tree
family_tree: Dict[str, Dict[str, int]] = {}
closure_table: Dict[str, Dict[str, int]] = {}
def __init__(
self, write_transports: List[AbstractTransport] = [], read_transport=None
) -> None:
self.write_transports = write_transports
self.read_transport = read_transport
def write_json(self, base: Base):
self.__reset_writer()
self.detach_lineage = [True]
hash, obj = self.traverse_base(base)
return hash, ujson.dumps(obj)
def traverse_base(self, base: Base) -> Tuple[str, Dict]:
"""Decomposes the given base object and builds a serializable dictionary
Arguments:
base {Base} -- the base object to be decomposed and serialized
Returns:
(str, dict) -- a tuple containing the hash (id) of the base object and the constructed serializable dictionary
"""
if not self.detach_lineage:
self.detach_lineage = [True]
self.lineage.append(uuid4().hex)
object_builder = {"id": "", "speckle_type": "Base", "totalChildrenCount": 0}
object_builder.update(speckle_type=base.speckle_type)
obj, props = base, base.get_serializable_attributes()
while props:
prop = props.pop(0)
value = getattr(obj, prop, None)
chunkable = False
detach = False
# skip nulls or props marked to be ignored with "__" or "_"
if value is None or prop.startswith(("__", "_")):
continue
# don't prepopulate id as this will mess up hashing
if prop == "id":
continue
# only bother with chunking and detaching if there is a write transport
if self.write_transports:
dynamic_chunk_match = prop.startswith("@") and re.match(
r"^@\((\d*)\)", prop
)
if dynamic_chunk_match:
chunk_size = dynamic_chunk_match.groups()[0]
base._chunkable[prop] = (
int(chunk_size) if chunk_size else base._chunk_size_default
)
chunkable = prop in base._chunkable
detach = bool(
prop.startswith("@") or prop in base._detachable or chunkable
)
# 1. handle primitives (ints, floats, strings, and bools)
if isinstance(value, PRIMITIVES):
object_builder[prop] = value
continue
# 2. handle Base objects
elif isinstance(value, Base):
child_obj = self.traverse_value(value, detach=detach)
if detach and self.write_transports:
ref_hash = child_obj["id"]
object_builder[prop] = self.detach_helper(ref_hash=ref_hash)
else:
object_builder[prop] = child_obj
# 3. handle chunkable props
elif chunkable and self.write_transports:
chunks = []
max_size = base._chunkable[prop]
chunk = DataChunk()
for count, item in enumerate(value):
if count and count % max_size == 0:
chunks.append(chunk)
chunk = DataChunk()
chunk.data.append(item)
chunks.append(chunk)
chunk_refs = []
for c in chunks:
self.detach_lineage.append(detach)
ref_hash, _ = self.traverse_base(c)
ref_obj = self.detach_helper(ref_hash=ref_hash)
chunk_refs.append(ref_obj)
object_builder[prop] = chunk_refs
# 4. handle all other cases
else:
child_obj = self.traverse_value(value, detach)
object_builder[prop] = child_obj
closure = {}
# add closures & children count to the object
detached = self.detach_lineage.pop()
if self.lineage[-1] in self.family_tree:
closure = {
ref: depth - len(self.detach_lineage)
for ref, depth in self.family_tree[self.lineage[-1]].items()
}
object_builder["totalChildrenCount"] = len(closure)
hash = hash_obj(object_builder)
object_builder["id"] = hash
if closure:
object_builder["__closure"] = self.closure_table[hash] = closure
# write detached or root objects to transports
if detached and self.write_transports:
for t in self.write_transports:
t.save_object(id=hash, serialized_object=ujson.dumps(object_builder))
del self.lineage[-1]
return hash, object_builder
def traverse_value(self, obj: Any, detach: bool = False) -> Any:
"""Decomposes a given object and constructs a serializable object or dictionary
Arguments:
obj {Any} -- the value to decompose
Returns:
Any -- a serializable version of the given object
"""
if isinstance(obj, PRIMITIVES):
return obj
elif isinstance(obj, (list, tuple, set)):
if not detach:
return [self.traverse_value(o) for o in obj]
detached_list = []
for o in obj:
if isinstance(o, Base):
self.detach_lineage.append(detach)
hash, _ = self.traverse_base(o)
detached_list.append(self.detach_helper(ref_hash=hash))
else:
detached_list.append(self.traverse_value(o, detach))
return detached_list
elif isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, PRIMITIVES):
continue
else:
obj[k] = self.traverse_value(v)
return obj
elif isinstance(obj, Base):
self.detach_lineage.append(detach)
_, base_obj = self.traverse_base(obj)
return base_obj
else:
try:
return obj.dict()
except:
SerializationException(
message=f"Failed to handle {type(obj)} in `BaseObjectSerializer.traverse_value`",
object=obj,
)
return str(obj)
def detach_helper(self, ref_hash: str) -> Dict[str, str]:
"""Helper to keep track of detached objects and their depth in the family tree and create reference objects to place in the parent object
Arguments:
ref_hash {str} -- the hash of the fully traversed object
Returns:
dict -- a reference object to be inserted into the given object's parent
"""
for parent in self.lineage:
if parent not in self.family_tree:
self.family_tree[parent] = {}
if ref_hash not in self.family_tree[parent] or self.family_tree[parent][
ref_hash
] > len(self.detach_lineage):
self.family_tree[parent][ref_hash] = len(self.detach_lineage)
return {
"referencedId": ref_hash,
"speckle_type": "reference",
}
def __reset_writer(self) -> None:
"""Reinitializes the lineage, and other variables that get used during the json writing process"""
self.detach_lineage = []
self.lineage = []
self.family_tree = {}
self.closure_table = {}
def read_json(self, obj_string: str) -> Base:
"""Recomposes a Base object from the string representation of the object
Arguments:
obj_string {str} -- the string representation of the object
Returns:
Base -- the base object with all it's children attached
"""
if not obj_string:
return None
obj = ujson.loads(obj_string)
return self.recompose_base(obj=obj)
def recompose_base(self, obj: dict) -> Base:
"""Steps through a base object dictionary and recomposes the base object
Arguments:
obj {dict} -- the dictionary representation of the object
Returns:
Base -- the base object with all its children attached
"""
# make sure an obj was passed and create dict if string was somehow passed
if not obj:
return
if isinstance(obj, str):
obj = ujson.loads(obj)
if "speckle_type" in obj and obj["speckle_type"] == "reference":
obj = self.get_child(obj=obj)
speckle_type = obj.get("speckle_type")
# if speckle type is not in the object definition, it is treated as a dict
if not speckle_type:
return obj
# get the registered type from base register.
object_type = Base.get_registered_type(speckle_type)
# initialise the base object using `speckle_type` fall back to base if needed
base = object_type() if object_type else Base.of_type(speckle_type=speckle_type)
# get total children count
if "__closure" in obj:
if not self.read_transport:
raise SpeckleException(
message="Cannot resolve reference - no read transport is defined"
)
closure = obj.pop("__closure")
base.totalChildrenCount = len(closure)
for prop, value in obj.items():
# 1. handle primitives (ints, floats, strings, and bools) or None
if isinstance(value, PRIMITIVES) or value is None:
base.__setattr__(prop, value)
continue
# 2. handle referenced child objects
elif "referencedId" in value:
ref_hash = value["referencedId"]
ref_obj_str = self.read_transport.get_object(id=ref_hash)
if not ref_obj_str:
raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
)
ref_obj = ujson.loads(ref_obj_str)
base.__setattr__(prop, self.recompose_base(obj=ref_obj))
# 3. handle all other cases (base objects, lists, and dicts)
else:
base.__setattr__(prop, self.handle_value(value))
return base
def handle_value(self, obj: Any):
"""Helper for recomposing a base object by handling the dictionary representation's values
Arguments:
obj {Any} -- a value from the base object dictionary
Returns:
Any -- the handled value (primitive, list, dictionary, or Base)
"""
if not obj:
return obj
if isinstance(obj, PRIMITIVES):
return obj
# lists (regular and chunked)
if isinstance(obj, list):
obj_list = [self.handle_value(o) for o in obj]
if (
hasattr(obj_list[0], "speckle_type")
and "DataChunk" in obj_list[0].speckle_type
):
# handle chunked lists
data = []
for o in obj_list:
data.extend(o.data)
return data
return obj_list
# bases
if isinstance(obj, dict) and "speckle_type" in obj:
return self.recompose_base(obj=obj)
# dictionaries
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, PRIMITIVES):
continue
else:
obj[k] = self.handle_value(v)
return obj
def get_child(self, obj: Dict):
ref_hash = obj["referencedId"]
ref_obj_str = self.read_transport.get_object(id=ref_hash)
if not ref_obj_str:
raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
)
return ujson.loads(ref_obj_str)
View File
@@ -1,95 +0,0 @@
from abc import ABC, abstractmethod
from typing import Any, Optional, List, Dict
from pydantic import BaseModel
from pydantic.main import Extra
# __________________
# | |
# | this is v wip |
# | pls be careful |
# |__________________|
# (\__/) ||
# (•ㅅ•) ||
# /  
class AbstractTransport(ABC, BaseModel):
_name: str = "Abstract"
@property
def name(self):
return type(self)._name
@abstractmethod
def begin_write(self) -> None:
"""Optional: signals to the transport that writes are about to begin."""
pass
@abstractmethod
def end_write(self) -> None:
"""Optional: signals to the transport that no more items will need to be written."""
pass
@abstractmethod
def save_object(self, id: str, serialized_object: str) -> None:
"""Saves the given serialized object.
Arguments:
id {str} -- the hash of the object
serialized_object {str} -- the full string representation of the object
"""
pass
@abstractmethod
def save_object_from_transport(
self, id: str, source_transport: "AbstractTransport"
) -> None:
"""Saves an object from the given source transport.
Arguments:
id {str} -- the hash of the object
source_transport {AbstractTransport) -- the transport through which the object can be found
"""
pass
@abstractmethod
def get_object(self, id: str) -> Optional[str]:
"""Gets an object. Returns `None` if the object is not found.
Arguments:
id {str} -- the hash of the object
Returns:
str -- the full string representation of the object (or null if no object is found)
"""
pass
@abstractmethod
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
"""Checks the presence of multiple objects.
Arguments:
id_list -- List of object id to be checked
Returns:
Dict[str, bool] -- keys: input ids, values: whether the transport has that object
"""
pass
@abstractmethod
def copy_object_and_children(
self, id: str, target_transport: "AbstractTransport"
) -> str:
"""Copies the parent object and all its children to the provided transport.
Arguments:
id {str} -- the id of the object you want to copy
target_transport {AbstractTransport} -- the transport you want to copy the object to
Returns:
str -- the string representation of the root object
"""
pass
class Config:
extra = Extra.allow
arbitrary_types_allowed = True
-48
View File
@@ -1,48 +0,0 @@
import json
from typing import Any, List, Dict
from specklepy.logging.exceptions import SpeckleException
from specklepy.transports.abstract_transport import AbstractTransport
class MemoryTransport(AbstractTransport):
_name: str = "Memory"
objects: dict = {}
saved_object_count: int = 0
def __init__(self, name=None, **data: Any) -> None:
super().__init__(**data)
if name:
self._name = name
def __repr__(self) -> str:
return f"MemoryTransport(objects: {len(self.objects)})"
def save_object(self, id: str, serialized_object: str) -> None:
self.objects[id] = serialized_object
self.saved_object_count += 1
def save_object_from_transport(
self, id: str, source_transport: AbstractTransport
) -> None:
raise NotImplementedError
def get_object(self, id: str) -> str or None:
if id in self.objects:
return self.objects[id]
else:
return None
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
return {id: (id in self.objects) for id in id_list}
def begin_write(self) -> None:
self.saved_object_count = 0
def end_write(self) -> None:
pass
def copy_object_and_children(
self, id: str, target_transport: AbstractTransport
) -> str:
raise NotImplementedError
-1
View File
@@ -1 +0,0 @@
from .server import ServerTransport
-149
View File
@@ -1,149 +0,0 @@
import json
import logging
import threading
import queue
import time
import gzip
import requests
from specklepy.logging.exceptions import SpeckleException
LOG = logging.getLogger(__name__)
class BatchSender(object):
def __init__(
self,
server_url,
stream_id,
token,
max_batch_size_mb=1,
batch_buffer_length=10,
thread_count=4,
):
self.server_url = server_url
self.stream_id = stream_id
self._token = token
self.max_size = int(max_batch_size_mb * 1000 * 1000)
self._batches = queue.Queue(batch_buffer_length)
self._crt_batch = []
self._crt_batch_size = 0
self.thread_count = thread_count
self._send_threads = []
self._exception = None
def send_object(self, id: str, obj: str):
if not self._send_threads:
self._create_threads()
crt_obj_size = len(obj)
if not self._crt_batch or self._crt_batch_size + crt_obj_size < self.max_size:
self._crt_batch.append((id, obj))
self._crt_batch_size += crt_obj_size
return
self._batches.put(self._crt_batch)
self._crt_batch = [(id, obj)]
self._crt_batch_size = crt_obj_size
def flush(self):
# Add current non-complete batch
if self._crt_batch:
self._batches.put(self._crt_batch)
self._crt_batch = []
self._crt_batch_size = 0
# Wait for queued batches to be sent
self._batches.join()
# End the sending threads
self._delete_threads()
# If there was any error, throw the first exception that occurred during upload
if self._exception is not None:
ex = self._exception
self._exception = None
raise ex
def _sending_thread_main(self):
try:
session = requests.Session()
session.headers.update(
{"Authorization": f"Bearer {self._token}", "Accept": "text/plain"}
)
while True:
batch = self._batches.get()
# None is a sentinel value, meaning the thread should exit gracefully
if batch is None:
self._batches.task_done()
break
try:
self._bg_send_batch(session, batch)
except Exception as ex:
self._exception = self._exception or ex
LOG.error("Error sending batch of objects to server: " + str(ex))
self._batches.task_done()
except Exception as ex:
self._exception = self._exception or ex
LOG.error("ServerTransport sending thread error: " + str(ex))
def _bg_send_batch(self, session, batch):
object_ids = [obj[0] for obj in batch]
server_has_object = session.post(
url=f"{self.server_url}/api/diff/{self.stream_id}",
data={"objects": json.dumps(object_ids)},
).json()
new_object_ids = [x for x in object_ids if not server_has_object[x]]
new_object_ids = set(new_object_ids)
new_objects = [obj[1] for obj in batch if obj[0] in new_object_ids]
if not new_objects:
LOG.info(
f"Uploading batch of {len(batch)} objects: all objects are already in the server"
)
return
upload_data = "[" + ",".join(new_objects) + "]"
upload_data_gzip = gzip.compress(upload_data.encode())
LOG.info(
"Uploading batch of %s objects (%s new): (size: %s, compressed size: %s)"
% (len(batch), len(new_objects), len(upload_data), len(upload_data_gzip))
)
try:
r = session.post(
url=f"{self.server_url}/objects/{self.stream_id}",
files={"batch-1": ("batch-1", upload_data_gzip, "application/gzip")},
)
if r.status_code != 201:
LOG.warning("Upload server response: %s", r.text)
raise SpeckleException(
message=f"Could not save the object to the server - status code {r.status_code}"
)
except json.JSONDecodeError as error:
return SpeckleException(
f"Failed to send objects to {self.server_url}. Please ensure this stream ({self.stream_id}) exists on this server and that you have permission to send to it.",
error,
)
def _create_threads(self):
for _ in range(self.thread_count):
t = threading.Thread(target=self._sending_thread_main, daemon=True)
t.start()
self._send_threads.append(t)
def _delete_threads(self):
for _ in range(len(self._send_threads)):
self._batches.put(None)
for thread in self._send_threads:
thread.join()
self._send_threads = []
def __del__(self):
self._delete_threads()

Some files were not shown because too many files have changed in this diff Show More