Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac6ba87c68 | |||
| 3db8565f57 | |||
| a32822f4e3 | |||
| 40956927c8 | |||
| 4628f111ba | |||
| 9c952b432d | |||
| f075988e4b | |||
| 65c829404a | |||
| 85e7a72524 | |||
| 0533aa0139 | |||
| 04b733a241 | |||
| 01036c0f2e | |||
| 4d8ca534fe | |||
| e941efd95c | |||
| be9defbfa9 | |||
| dd54c69554 | |||
| 93c1ec9556 | |||
| 659c57e840 | |||
| 1cdd4ffc9c | |||
| 5ae022d2ed | |||
| 31ca59cea8 | |||
| c91f673dba | |||
| e2c8ef1b3d | |||
| b6b0a5a3a0 | |||
| f36d63a2cf | |||
| afb9065fb9 | |||
| fcc33f8989 | |||
| 2cf9b64221 | |||
| 990cf4eb2f | |||
| b25f2ab4bc | |||
| a8786c126d | |||
| a35f8936ca | |||
| d965cc0988 | |||
| 59a6950eed | |||
| 6804282fac | |||
| a3a22a43d5 | |||
| 08aaa41b6c | |||
| 07a3213ee9 | |||
| 7a46176803 | |||
| b75501addd | |||
| 15636dbe62 | |||
| 9a2061d900 | |||
| 8dc51fb936 | |||
| e805b8ac6e | |||
| 5a1d624979 | |||
| c8808b07b3 | |||
| 1a9b847c44 | |||
| f01fcb8e66 | |||
| 67499ab20c | |||
| ebb703e68d | |||
| ef5d41da5d | |||
| 851dd9c482 | |||
| 78074cf691 | |||
| be0daa419d | |||
| a7d1b9ce30 | |||
| 05756a8e9e | |||
| b50e658333 | |||
| 88248353ab | |||
| aec94f8f7f | |||
| e6b1604bc3 | |||
| de29b93b8b | |||
| 10aa8b59b6 | |||
| b86faa6a14 | |||
| 7430611c52 | |||
| ddd52f4af9 | |||
| 35bc6b0350 | |||
| 9585d46c4e | |||
| 1900fece8b | |||
| 344b7de557 | |||
| fd09e97a53 | |||
| 459bd0901f | |||
| ae7c4bc14d | |||
| 41f1823aae | |||
| 625bd5cd84 | |||
| 8812985c67 | |||
| c838835a65 | |||
| 361ba6bfcd | |||
| 8078a4b596 | |||
| 08b106464f | |||
| 0cfe5db674 | |||
| d9dbca2c68 | |||
| ab5b55871b | |||
| 2f87956154 | |||
| 6fc4ab1539 | |||
| 7f432e768d | |||
| d0eb364b3e | |||
| 936b2d8b5a | |||
| 5a9aec80bd | |||
| 6616526279 | |||
| 6e00de58b9 | |||
| 0ec404bbec | |||
| 506aaf68ca |
@@ -18,15 +18,15 @@ jobs:
|
|||||||
- image: "speckle/speckle-server"
|
- image: "speckle/speckle-server"
|
||||||
command: ["bash", "-c", "/wait && node bin/www"]
|
command: ["bash", "-c", "/wait && node bin/www"]
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_URL: "localhost"
|
POSTGRES_URL: "127.0.0.1"
|
||||||
POSTGRES_USER: "speckle"
|
POSTGRES_USER: "speckle"
|
||||||
POSTGRES_PASSWORD: "speckle"
|
POSTGRES_PASSWORD: "speckle"
|
||||||
POSTGRES_DB: "speckle2_test"
|
POSTGRES_DB: "speckle2_test"
|
||||||
REDIS_URL: "redis://localhost"
|
REDIS_URL: "redis://127.0.0.1"
|
||||||
SESSION_SECRET: "keyboard cat"
|
SESSION_SECRET: "keyboard cat"
|
||||||
STRATEGY_LOCAL: "true"
|
STRATEGY_LOCAL: "true"
|
||||||
CANONICAL_URL: "http://localhost:3000"
|
CANONICAL_URL: "http://localhost:3000"
|
||||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
WAIT_HOSTS: 127.0.0.1:5432, 127.0.0.1:6379
|
||||||
DISABLE_FILE_UPLOADS: "true"
|
DISABLE_FILE_UPLOADS: "true"
|
||||||
parameters:
|
parameters:
|
||||||
tag:
|
tag:
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
- run: python patch_version.py $CIRCLE_TAG
|
- run: python patch_version.py $CIRCLE_TAG
|
||||||
- run: poetry build
|
- run: poetry build
|
||||||
- run: poetry publish -u specklesystems -p $PYPI_PASSWORD
|
- run: poetry publish -u __token__ -p $PYPI_TOKEN
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
main:
|
main:
|
||||||
@@ -65,11 +65,12 @@ workflows:
|
|||||||
- test:
|
- test:
|
||||||
matrix:
|
matrix:
|
||||||
parameters:
|
parameters:
|
||||||
tag: ["3.7", "3.8", "3.9", "3.10"]
|
tag: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /.*/
|
only: /.*/
|
||||||
- deploy:
|
- deploy:
|
||||||
|
context: pypi
|
||||||
requires:
|
requires:
|
||||||
- test
|
- test
|
||||||
filters:
|
filters:
|
||||||
|
|||||||
@@ -52,4 +52,4 @@
|
|||||||
"postCreateCommand": "poetry config virtualenvs.create false && poetry install",
|
"postCreateCommand": "poetry config virtualenvs.create false && poetry install",
|
||||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
"remoteUser": "vscode"
|
"remoteUser": "vscode"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,4 +41,4 @@ services:
|
|||||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||||
network_mode: host
|
network_mode: host
|
||||||
# networks:
|
# networks:
|
||||||
# default:
|
# default:
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
name: New issue
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title:
|
|
||||||
labels:
|
|
||||||
assignees:
|
|
||||||
---
|
|
||||||
|
|
||||||
If it's your first time here - or you forgot about them - make sure you read the [contribution guidelines](CONTRIBUTING.md), and then feel free to delete this line!
|
|
||||||
|
|
||||||
### Expected vs. Actual Behavior
|
|
||||||
|
|
||||||
Describe the problem here.
|
|
||||||
|
|
||||||
### Reproduction Steps & System Config (win, osx, web, etc.)
|
|
||||||
|
|
||||||
Let us know how we can reproduce this, and attach relevant files (if any).
|
|
||||||
|
|
||||||
### Proposed Solution (if any)
|
|
||||||
|
|
||||||
Let us know what how you would solve this.
|
|
||||||
|
|
||||||
#### Optional: Affected Projects
|
|
||||||
|
|
||||||
Does this issue propagate to other dependencies or dependents? If so, list them here!
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Help improve Speckle!
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Provide a short summary in the Title above. Examples of good Issue titles:
|
||||||
|
|
||||||
|
* "Bug: Error from server when reticulating splines"
|
||||||
|
* "Bug: Revit crashes when installing connector"
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Please answer the following questions before submitting an issue.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
|
||||||
|
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
|
||||||
|
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
|
||||||
|
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
|
||||||
|
- [ ] I'm reporting the issue to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
|
||||||
|
|
||||||
|
## What package are you referring to?
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Is it related to the server (backend) only, or does this bug relate to the frontend, viewer, objectloader or any other package?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Describe the bug
|
||||||
|
|
||||||
|
<!---
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## To Reproduce
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Expected behavior
|
||||||
|
|
||||||
|
<!---
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<!---
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## System Info
|
||||||
|
|
||||||
|
If applicable, please fill in the below details - they help a lot!
|
||||||
|
|
||||||
|
### Desktop (please complete the following information):
|
||||||
|
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
### Smartphone (please complete the following information):
|
||||||
|
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
## Failure Logs
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Please include any relevant log snippets or files here, or upload as a file.
|
||||||
|
|
||||||
|
If including inline, please use markdown code block syntax. https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
your log output here
|
||||||
|
```
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Additional context
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Add any other context about the problem here.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Proposed Solution (if any)
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Let us know what how you would solve this.
|
||||||
|
-->
|
||||||
|
|
||||||
|
#### Optional: Affected Projects
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Does this issue propagate to other dependencies or dependents? If so, list them here with links!
|
||||||
|
-->
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for Speckle!
|
||||||
|
title: ''
|
||||||
|
labels: enhancement, question
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Provide a short summary in the Title above. Examples of good Issue titles:
|
||||||
|
|
||||||
|
* "Enhancement: Connector for Minecraft"
|
||||||
|
* "Enhancement: Web viewer should support tesseracts"
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Please answer the following questions before submitting an issue.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
|
||||||
|
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
|
||||||
|
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
|
||||||
|
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
|
||||||
|
- [ ] I'm requesting the feature to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
|
||||||
|
|
||||||
|
## What package are you referring to?
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Is it related to the server (backend) only, or does this feature request relate to the frontend, viewer, objectloader or any other package?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Is your feature request related to a problem? Please describe.
|
||||||
|
|
||||||
|
<!---
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Describe the solution you'd like
|
||||||
|
|
||||||
|
<!---
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Describe alternatives you've considered
|
||||||
|
|
||||||
|
<!---
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Additional context
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|
||||||
|
Have you seen this feature implemented in any other software? Can you provide screenshots or links to video or documentation?
|
||||||
|
What works well about these existing features in other software? What doesn't work well?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Related issues or community discussions
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Is this feature request related to (but sufficiently distinct from) any existing issues?
|
||||||
|
Does this feature request require other features to be available beforehand?
|
||||||
|
Has this feature been discussed in the community forum, please link here? https://speckle.community/
|
||||||
|
-->
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
Description of PR...
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Item 1
|
|
||||||
- Item 2
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
- [ ] Unit tests
|
|
||||||
- [ ] Documentation
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
(optional)
|
|
||||||
|
|
||||||
Include **important** links regarding the implementation of this PR.
|
|
||||||
This usually includes and RFC or an aggregation of issues and/or individual conversations
|
|
||||||
that helped put this solution together. This helps ensure there is a good aggregation
|
|
||||||
of resources regarding the implementation.
|
|
||||||
|
|
||||||
```text
|
|
||||||
Fixes #85, Fixes #22, Fixes username/repo#123
|
|
||||||
Connects #123
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<!---
|
||||||
|
|
||||||
|
Provide a short summary in the Title above. Examples of good PR titles:
|
||||||
|
|
||||||
|
* "Feature: adds metrics to component"
|
||||||
|
|
||||||
|
* "Fix: resolves duplication in comment thread"
|
||||||
|
|
||||||
|
* "Update: apollo v2.34.0"
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description & motivation
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Describe your changes, and why you're making them. What benefit will this have to others?
|
||||||
|
|
||||||
|
Is this linked to an open Github issue, a thread in Speckle community,
|
||||||
|
or another pull request? Link it here.
|
||||||
|
|
||||||
|
If it is related to a Github issue, and resolves it, please link to the issue number, e.g.:
|
||||||
|
Fixes #85, Fixes #22, Fixes username/repo#123
|
||||||
|
Connects #123
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Changes:
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
- Item 1
|
||||||
|
- Item 2
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## To-do before merge:
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
(Optional -- remove this section if not needed)
|
||||||
|
|
||||||
|
Include any notes about things that need to happen before this PR is merged, e.g.:
|
||||||
|
|
||||||
|
- [ ] Change the base branch
|
||||||
|
|
||||||
|
- [ ] Ensure PR #56 is merged
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Screenshots:
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Include a screenshot the before and after. This can be a screenshot of a plugin, web frontend, or output in a terminal.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Validation of changes:
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
Describe what tests have been added or amended, and why these demonstrate it works and will prevent this feature being accidentally broken by future changes.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Checklist:
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
This checklist is mostly useful as a reminder of small things that can easily be
|
||||||
|
|
||||||
|
forgotten – it is meant as a helpful tool rather than hoops to jump through.
|
||||||
|
|
||||||
|
Put an `x` between the square brackets, e.g. [x], for all the items that apply,
|
||||||
|
|
||||||
|
make notes next to any that haven't been addressed, and remove any items that are not relevant to this PR.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [ ] My pull request follows the guidelines in the [Contributing guide](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)?
|
||||||
|
- [ ] My pull request does not duplicate any other open [Pull Requests](../../pulls) for the same update/change?
|
||||||
|
- [ ] My commits are related to the pull request and do not amend unrelated code or documentation.
|
||||||
|
- [ ] My code follows a similar style to existing code.
|
||||||
|
- [ ] I have added appropriate tests.
|
||||||
|
- [ ] I have updated or added relevant documentation.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
<!---
|
||||||
|
|
||||||
|
(Optional -- remove this section if not needed )
|
||||||
|
|
||||||
|
Include **important** links regarding the implementation of this PR.
|
||||||
|
|
||||||
|
This usually includes a RFC or an aggregation of issues and/or individual conversations
|
||||||
|
|
||||||
|
that helped put this solution together. This helps ensure we retain and share knowledge
|
||||||
|
|
||||||
|
regarding the implementation, and may help others understand motivation and design decisions etc..
|
||||||
|
|
||||||
|
-->
|
||||||
@@ -6,73 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_issue:
|
update_issue:
|
||||||
runs-on: ubuntu-latest
|
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
|
||||||
steps:
|
secrets: inherit
|
||||||
- name: Get project data
|
with:
|
||||||
env:
|
issue-id: ${{ github.event.issue.node_id }}
|
||||||
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 }}
|
|
||||||
|
|
||||||
@@ -6,45 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
track_issue:
|
track_issue:
|
||||||
runs-on: ubuntu-latest
|
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
|
||||||
steps:
|
secrets: inherit
|
||||||
- name: Get project data
|
with:
|
||||||
env:
|
issue-id: ${{ github.event.issue.node_id }}
|
||||||
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
|
|
||||||
+1
-1
@@ -112,4 +112,4 @@ venv.bak/
|
|||||||
|
|
||||||
# other
|
# other
|
||||||
scratch.py
|
scratch.py
|
||||||
settings.json
|
settings.json
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
rev: v0.0.186
|
||||||
|
|
||||||
|
- repo: https://github.com/commitizen-tools/commitizen
|
||||||
|
hooks:
|
||||||
|
- id: commitizen
|
||||||
|
- id: commitizen-branch
|
||||||
|
stages:
|
||||||
|
- push
|
||||||
|
rev: v2.38.0
|
||||||
|
|
||||||
|
- repo: https://github.com/pycqa/isort
|
||||||
|
rev: v5.11.3
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 22.12.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
# It is recommended to specify the latest version of Python
|
||||||
|
# supported by your project here, or alternatively use
|
||||||
|
# pre-commit's default_language_version, see
|
||||||
|
# https://pre-commit.com/#top_level-default_language_version
|
||||||
|
# language_version: python3.11
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
Vendored
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Python: Current File",
|
"name": "Python: Current File",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
@@ -23,4 +23,4 @@
|
|||||||
"justMyCode": true
|
"justMyCode": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ What is Speckle? Check our ](https://speckle.xyz) ⇒ creating an account at our public server
|
- [](https://speckle.xyz) ⇒ creating an account at our public server
|
||||||
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ Give Speckle a try in no time by:
|
|||||||
|
|
||||||
## Usage
|
## 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!
|
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.
|
Head to the [**📚 specklepy docs**](https://speckle.guide/dev/python.html) for more information and usage examples.
|
||||||
|
|
||||||
@@ -65,6 +65,12 @@ 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.
|
> 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.
|
||||||
|
|
||||||
|
### Style guide
|
||||||
|
|
||||||
|
All our repo wide styling linting and other rules are checked and enforced by `pre-commit`, which is included in the dev dependencies.
|
||||||
|
It is recommended to set up `pre-commit` after installing the dependencies by running `$ pre-commit install`.
|
||||||
|
Commiting code that doesn't adhere to the given rules, will fail the checks in our CI system.
|
||||||
|
|
||||||
### Local Data Paths
|
### 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:
|
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:
|
||||||
@@ -74,7 +80,7 @@ It may be helpful to know where the local accounts and object cache dbs are stor
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) for an overview of the best practices we try to follow.
|
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) and [code of conduct](.github/CODE_OF_CONDUCT.md) for an overview of the practices we try to follow.
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ The Speckle Community hangs out on [the forum](https://discourse.speckle.works),
|
|||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 2.2.+ | :white_check_mark: |
|
||||||
|
| < 2.2 | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Hi! If you've found something off, we'd be more than happy if you would report it via security@speckle.systems. We will work together with you to correctly identify the cause and implement a fix. Thanks for helping make Speckle safer!
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
from typing import List
|
import os
|
||||||
from specklepy.objects import Base
|
import random
|
||||||
from specklepy.api import operations
|
import string
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
from typing import List
|
||||||
import string
|
|
||||||
import random
|
from specklepy.api import operations
|
||||||
|
from specklepy.objects import Base
|
||||||
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
|
|
||||||
|
|
||||||
class Sub(Base):
|
class Sub(Base):
|
||||||
@@ -26,7 +27,6 @@ def clean_db():
|
|||||||
|
|
||||||
|
|
||||||
def one_pass(clean: bool, randomize: bool, child_count: int):
|
def one_pass(clean: bool, randomize: bool, child_count: int):
|
||||||
|
|
||||||
foo = Base()
|
foo = Base()
|
||||||
for i in range(child_count):
|
for i in range(child_count):
|
||||||
stuff = random_string() if randomize else "stuff"
|
stuff = random_string() if randomize else "stuff"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from devtools import debug
|
||||||
|
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.api.wrapper import StreamWrapper
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
stream_url = "https://latest.speckle.dev/streams/7d051a6449"
|
||||||
|
wrapper = StreamWrapper(stream_url)
|
||||||
|
|
||||||
|
transport = wrapper.get_transport()
|
||||||
|
|
||||||
|
rec = operations.receive("98396753f8bf7fe1cb60c5193e9f9d86", transport)
|
||||||
|
|
||||||
|
# hash = operations.send(base=foo, transports=[transport], use_default_cache=False)
|
||||||
|
debug(rec)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import random
|
||||||
|
import string
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.api.wrapper import StreamWrapper
|
||||||
|
from specklepy.objects import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Sub(Base):
|
||||||
|
bar: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
def random_string():
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
return "".join(random.choice(letters) for _ in range(10))
|
||||||
|
|
||||||
|
|
||||||
|
def create_object(child_count: int) -> Base:
|
||||||
|
foo = Base()
|
||||||
|
for i in range(child_count):
|
||||||
|
stuff = random_string()
|
||||||
|
foo[f"@child_{i}"] = Sub(bar=["asdf", "bar", i, stuff])
|
||||||
|
return foo
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
stream_url = "http://hyperion:3000/streams/2372b54c35"
|
||||||
|
|
||||||
|
child_count = 10
|
||||||
|
foo = create_object(child_count)
|
||||||
|
|
||||||
|
wrapper = StreamWrapper(stream_url)
|
||||||
|
transport = wrapper.get_transport()
|
||||||
|
|
||||||
|
hash = operations.send(base=foo, transports=[transport], use_default_cache=False)
|
||||||
|
|
||||||
|
rec = operations.receive(hash, transport)
|
||||||
|
print(rec)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from devtools import debug
|
||||||
|
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.objects.geometry import Base
|
||||||
|
from specklepy.objects.units import Units
|
||||||
|
|
||||||
|
dct = {
|
||||||
|
"id": "1234abcd",
|
||||||
|
"units": None,
|
||||||
|
"speckle_type": "Base",
|
||||||
|
"applicationId": None,
|
||||||
|
"totalChildrenCount": 0,
|
||||||
|
}
|
||||||
|
base = Base()
|
||||||
|
for prop, value in dct.items():
|
||||||
|
base.__setattr__(prop, value)
|
||||||
|
|
||||||
|
|
||||||
|
debug(base)
|
||||||
|
debug(base.units)
|
||||||
|
|
||||||
|
base.units = "m"
|
||||||
|
debug(base.units)
|
||||||
|
base.units = None
|
||||||
|
|
||||||
|
debug(base.units)
|
||||||
|
|
||||||
|
foo = operations.serialize(base)
|
||||||
|
|
||||||
|
base.units = 10
|
||||||
|
|
||||||
|
debug(base.units)
|
||||||
|
debug(foo)
|
||||||
|
|
||||||
|
base.units = Units.mm
|
||||||
|
debug(base.units)
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
"""This is an example showcasing the usage of speckle `Base` class."""
|
"""This is an example showcasing the usage of speckle `Base` class."""
|
||||||
|
|
||||||
# the speckle.objects module exposes all speckle provided classes
|
# the speckle.objects module exposes all speckle provided classes
|
||||||
from specklepy.objects import Base
|
|
||||||
from specklepy.api import operations
|
|
||||||
from devtools import debug
|
from devtools import debug
|
||||||
|
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.objects import Base
|
||||||
|
|
||||||
|
|
||||||
class ExampleSub(Base):
|
class ExampleSub(Base):
|
||||||
"""
|
"""
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ def patch(tag):
|
|||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
if "version" not in lines[2]:
|
if "version" not in lines[2]:
|
||||||
raise Exception(f"Invalid pyproject.toml. Could not patch version.")
|
raise Exception("Invalid pyproject.toml. Could not patch version.")
|
||||||
|
|
||||||
lines[2] = f'version = "{tag}"\n'
|
lines[2] = f'version = "{tag}"\n'
|
||||||
with open("pyproject.toml", "w") as file:
|
with open("pyproject.toml", "w") as file:
|
||||||
|
|||||||
Generated
+1242
-726
File diff suppressed because it is too large
Load Diff
+24
-7
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "specklepy"
|
name = "specklepy"
|
||||||
version = "2.4.0"
|
version = "2.9.1"
|
||||||
description = "The Python SDK for Speckle 2.0"
|
description = "The Python SDK for Speckle 2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = ["Speckle Systems <devops@speckle.systems>"]
|
authors = ["Speckle Systems <devops@speckle.systems>"]
|
||||||
@@ -8,25 +8,35 @@ license = "Apache-2.0"
|
|||||||
repository = "https://github.com/specklesystems/speckle-py"
|
repository = "https://github.com/specklesystems/speckle-py"
|
||||||
documentation = "https://speckle.guide/dev/py-examples.html"
|
documentation = "https://speckle.guide/dev/py-examples.html"
|
||||||
homepage = "https://speckle.systems/"
|
homepage = "https://speckle.systems/"
|
||||||
|
packages = [
|
||||||
|
{ include = "specklepy", from = "src" },
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.7.2, <4.0"
|
python = ">=3.7.2, <4.0"
|
||||||
pydantic = "^1.8.2"
|
pydantic = "^1.9"
|
||||||
appdirs = "^1.4.4"
|
appdirs = "^1.4.4"
|
||||||
gql = {extras = ["requests", "websockets"], version = "^3.3.0"}
|
gql = {extras = ["requests", "websockets"], version = "^3.3.0"}
|
||||||
ujson = "^5.3.0"
|
ujson = "^5.3.0"
|
||||||
Deprecated = "^1.2.13"
|
Deprecated = "^1.2.13"
|
||||||
|
stringcase = "^1.2.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^20.8b1"
|
black = "^22.8.0"
|
||||||
isort = "^5.7.0"
|
isort = "^5.7.0"
|
||||||
pytest = "^6.2.2"
|
pytest = "^7.1.3"
|
||||||
pytest-ordering = "^0.6"
|
pytest-ordering = "^0.6"
|
||||||
pytest-cov = "^3.0.0"
|
pytest-cov = "^3.0.0"
|
||||||
devtools = "^0.8.0"
|
devtools = "^0.8.0"
|
||||||
pylint = "^2.14.4"
|
pylint = "^2.14.4"
|
||||||
|
mypy = "^0.982"
|
||||||
|
pre-commit = "^2.20.0"
|
||||||
|
commitizen = "^2.38.0"
|
||||||
|
ruff = "^0.0.187"
|
||||||
|
types-deprecated = "^1.2.9"
|
||||||
|
types-ujson = "^5.6.0.0"
|
||||||
|
types-requests = "^2.28.11.5"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
exclude = '''
|
exclude = '''
|
||||||
@@ -45,9 +55,16 @@ exclude = '''
|
|||||||
'''
|
'''
|
||||||
include = '\.pyi?$'
|
include = '\.pyi?$'
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = ["py37", "py38", "py39", "py310"]
|
target-version = ["py37", "py38", "py39", "py310", "py311"]
|
||||||
|
|
||||||
|
|
||||||
|
[tool.commitizen]
|
||||||
|
name = "cz_conventional_commits"
|
||||||
|
version = "2.9.2"
|
||||||
|
tag_format = "$version"
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
|||||||
@@ -1,640 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
scalar DateTime
|
|
||||||
|
|
||||||
scalar EmailAddress
|
|
||||||
|
|
||||||
scalar BigInt
|
|
||||||
|
|
||||||
scalar JSONObject
|
|
||||||
|
|
||||||
|
|
||||||
directive @hasScope(scope: String!) on FIELD_DEFINITION
|
|
||||||
directive @hasRole(role: String!) on FIELD_DEFINITION
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
"""
|
|
||||||
Stare into the void.
|
|
||||||
"""
|
|
||||||
_: String
|
|
||||||
}
|
|
||||||
type Mutation{
|
|
||||||
"""
|
|
||||||
The void stares back.
|
|
||||||
"""
|
|
||||||
_: String
|
|
||||||
}
|
|
||||||
type Subscription{
|
|
||||||
"""
|
|
||||||
It's lonely in the void.
|
|
||||||
"""
|
|
||||||
_: String
|
|
||||||
},extend type Query {
|
|
||||||
"""
|
|
||||||
Gets a specific app from the server.
|
|
||||||
"""
|
|
||||||
app( id: String! ): ServerApp
|
|
||||||
|
|
||||||
"""
|
|
||||||
Returns all the publicly available apps on this server.
|
|
||||||
"""
|
|
||||||
apps: [ServerAppListItem]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerApp {
|
|
||||||
id: String!
|
|
||||||
secret: String!
|
|
||||||
name: String!
|
|
||||||
description: String
|
|
||||||
termsAndConditionsLink: String
|
|
||||||
logo: String
|
|
||||||
public: Boolean
|
|
||||||
trustByDefault: Boolean
|
|
||||||
author: AppAuthor
|
|
||||||
createdAt: DateTime!
|
|
||||||
redirectUrl: String!
|
|
||||||
scopes: [Scope]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerAppListItem {
|
|
||||||
id: String!
|
|
||||||
name: String!
|
|
||||||
description: String
|
|
||||||
termsAndConditionsLink: String
|
|
||||||
logo: String
|
|
||||||
author: AppAuthor
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppAuthor {
|
|
||||||
name: String
|
|
||||||
id: String
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type User {
|
|
||||||
"""
|
|
||||||
Returns the apps you have authorized.
|
|
||||||
"""
|
|
||||||
authorizedApps: [ServerAppListItem]
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "apps:read")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Returns the apps you have created.
|
|
||||||
"""
|
|
||||||
createdApps: [ServerAppListItem]
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "apps:read")
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
"""
|
|
||||||
Register a new third party application.
|
|
||||||
"""
|
|
||||||
appCreate(app: AppCreateInput!): String!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "apps:write")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Update an existing third party application. **Note: This will invalidate all existing tokens, refresh tokens and access codes and will require existing users to re-authorize it.**
|
|
||||||
"""
|
|
||||||
appUpdate(app: AppUpdateInput!): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "apps:write")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Deletes a thirty party application.
|
|
||||||
"""
|
|
||||||
appDelete(appId: String!): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "apps:write")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Revokes (de-authorizes) an application that you have previously authorized.
|
|
||||||
"""
|
|
||||||
appRevokeAccess(appId: String!): Boolean
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "apps:write")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
input AppCreateInput {
|
|
||||||
name: String!
|
|
||||||
description: String!
|
|
||||||
termsAndConditionsLink: String
|
|
||||||
logo: String
|
|
||||||
public: Boolean
|
|
||||||
redirectUrl: String!
|
|
||||||
scopes: [String]!
|
|
||||||
}
|
|
||||||
|
|
||||||
input AppUpdateInput {
|
|
||||||
id: String!
|
|
||||||
name: String!
|
|
||||||
description: String!
|
|
||||||
termsAndConditionsLink: String
|
|
||||||
logo: String
|
|
||||||
public: Boolean
|
|
||||||
redirectUrl: String!
|
|
||||||
scopes: [String]!
|
|
||||||
}
|
|
||||||
,extend type ServerInfo {
|
|
||||||
"""
|
|
||||||
The authentication strategies available on this server.
|
|
||||||
"""
|
|
||||||
authStrategies: [AuthStrategy]
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthStrategy {
|
|
||||||
id: String!,
|
|
||||||
name: String!,
|
|
||||||
icon: String!,
|
|
||||||
url: String!,
|
|
||||||
color: String
|
|
||||||
}
|
|
||||||
,extend type User{
|
|
||||||
"""
|
|
||||||
Returns a list of your personal api tokens.
|
|
||||||
"""
|
|
||||||
apiTokens: [ApiToken]
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "tokens:read")
|
|
||||||
}
|
|
||||||
|
|
||||||
type ApiToken {
|
|
||||||
id: String!
|
|
||||||
name: String!
|
|
||||||
lastChars: String!
|
|
||||||
scopes: [String]!
|
|
||||||
createdAt: DateTime! #date
|
|
||||||
lifespan: BigInt!
|
|
||||||
lastUsed: String! #date
|
|
||||||
}
|
|
||||||
|
|
||||||
input ApiTokenCreateInput {
|
|
||||||
scopes: [String!]!,
|
|
||||||
name: String!,
|
|
||||||
lifespan: BigInt
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
"""
|
|
||||||
Creates an personal api token.
|
|
||||||
"""
|
|
||||||
apiTokenCreate(token: ApiTokenCreateInput!):String!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "tokens:write")
|
|
||||||
"""
|
|
||||||
Revokes (deletes) an personal api token.
|
|
||||||
"""
|
|
||||||
apiTokenRevoke(token: String!):Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "tokens:write")
|
|
||||||
}
|
|
||||||
,extend type Stream {
|
|
||||||
commits(limit: Int! = 25, cursor: String): CommitCollection
|
|
||||||
commit(id: String!): Commit
|
|
||||||
branches(limit: Int! = 25, cursor: String): BranchCollection
|
|
||||||
branch(name: String!): Branch
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type User {
|
|
||||||
commits(limit: Int! = 25, cursor: String): CommitCollectionUser
|
|
||||||
}
|
|
||||||
|
|
||||||
type Branch {
|
|
||||||
id: String!
|
|
||||||
name: String!
|
|
||||||
author: User!
|
|
||||||
description: String
|
|
||||||
commits(limit: Int! = 25, cursor: String): CommitCollection
|
|
||||||
}
|
|
||||||
|
|
||||||
type Commit {
|
|
||||||
id: String!
|
|
||||||
referencedObject: String!
|
|
||||||
message: String
|
|
||||||
authorName: String
|
|
||||||
authorId: String
|
|
||||||
createdAt: DateTime
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommitCollectionUserNode {
|
|
||||||
id: String!
|
|
||||||
referencedObject: String!
|
|
||||||
message: String
|
|
||||||
streamId: String
|
|
||||||
streamName: String
|
|
||||||
createdAt: DateTime
|
|
||||||
}
|
|
||||||
|
|
||||||
type BranchCollection {
|
|
||||||
totalCount: Int!
|
|
||||||
cursor: String
|
|
||||||
items: [Branch]
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommitCollection {
|
|
||||||
totalCount: Int!
|
|
||||||
cursor: String
|
|
||||||
items: [Commit]
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommitCollectionUser {
|
|
||||||
totalCount: Int!
|
|
||||||
cursor: String
|
|
||||||
items: [CommitCollectionUserNode]
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
branchCreate(branch: BranchCreateInput!): String!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
branchUpdate(branch: BranchUpdateInput!): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
branchDelete(branch: BranchDeleteInput!): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
|
|
||||||
commitCreate(commit: CommitCreateInput!): String!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
commitUpdate(commit: CommitUpdateInput!): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
commitDelete(commit: CommitDeleteInput!): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Subscription {
|
|
||||||
# TODO: auth for these subscriptions
|
|
||||||
"""
|
|
||||||
Subscribe to branch created event
|
|
||||||
"""
|
|
||||||
branchCreated(streamId: String!): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
"""
|
|
||||||
Subscribe to branch updated event.
|
|
||||||
"""
|
|
||||||
branchUpdated(streamId: String!, branchId: String): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
"""
|
|
||||||
Subscribe to branch deleted event
|
|
||||||
"""
|
|
||||||
branchDeleted(streamId: String!): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Subscribe to commit created event
|
|
||||||
"""
|
|
||||||
commitCreated(streamId: String!): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
"""
|
|
||||||
Subscribe to commit updated event.
|
|
||||||
"""
|
|
||||||
commitUpdated(streamId: String!, commitId: String): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
"""
|
|
||||||
Subscribe to commit deleted event
|
|
||||||
"""
|
|
||||||
commitDeleted(streamId: String!): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
}
|
|
||||||
|
|
||||||
input BranchCreateInput {
|
|
||||||
streamId: String!
|
|
||||||
name: String!
|
|
||||||
description: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input BranchUpdateInput {
|
|
||||||
streamId: String!
|
|
||||||
id: String!
|
|
||||||
name: String
|
|
||||||
description: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input BranchDeleteInput {
|
|
||||||
streamId: String!
|
|
||||||
id: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
input CommitCreateInput {
|
|
||||||
streamId: String!
|
|
||||||
branchName: String!
|
|
||||||
objectId: String!
|
|
||||||
message: String
|
|
||||||
previousCommitIds: [String]
|
|
||||||
}
|
|
||||||
|
|
||||||
input CommitUpdateInput {
|
|
||||||
streamId: String!
|
|
||||||
id: String!
|
|
||||||
message: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
input CommitDeleteInput {
|
|
||||||
streamId: String!
|
|
||||||
id: String!
|
|
||||||
}
|
|
||||||
,extend type Stream {
|
|
||||||
object( id: String! ): Object
|
|
||||||
}
|
|
||||||
|
|
||||||
type Object {
|
|
||||||
id: String!
|
|
||||||
speckleType: String!
|
|
||||||
applicationId: String
|
|
||||||
createdAt: DateTime
|
|
||||||
totalChildrenCount: Int
|
|
||||||
"""
|
|
||||||
The full object, with all its props & other things. **NOTE:** If you're requesting objects for the purpose of recreating & displaying, you probably only want to request this specific field.
|
|
||||||
"""
|
|
||||||
data: JSONObject
|
|
||||||
"""
|
|
||||||
Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
|
|
||||||
**NOTE**: Providing any of the two last arguments ( `query`, `orderBy` ) will trigger a different code branch that executes a much more expensive SQL query. It is not recommended to do so for basic clients that are interested in purely getting all the objects of a given commit.
|
|
||||||
"""
|
|
||||||
children(
|
|
||||||
limit: Int! = 100,
|
|
||||||
depth: Int! = 50,
|
|
||||||
select: [String],
|
|
||||||
cursor: String,
|
|
||||||
query: [JSONObject!],
|
|
||||||
orderBy: JSONObject ): ObjectCollection!
|
|
||||||
}
|
|
||||||
|
|
||||||
type ObjectCollection {
|
|
||||||
totalCount: Int!
|
|
||||||
cursor: String
|
|
||||||
objects: [Object]!
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
objectCreate( objectInput: ObjectCreateInput! ): [String]!
|
|
||||||
}
|
|
||||||
|
|
||||||
input ObjectCreateInput {
|
|
||||||
"""
|
|
||||||
The stream against which these objects will be created.
|
|
||||||
"""
|
|
||||||
streamId: String!
|
|
||||||
"""
|
|
||||||
The objects you want to create.
|
|
||||||
"""
|
|
||||||
objects: [JSONObject]!
|
|
||||||
},extend type Query {
|
|
||||||
serverInfo: ServerInfo!
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Information about this server.
|
|
||||||
"""
|
|
||||||
type ServerInfo {
|
|
||||||
name: String!
|
|
||||||
company: String
|
|
||||||
description: String
|
|
||||||
adminContact: String
|
|
||||||
canonicalUrl: String
|
|
||||||
termsOfService: String
|
|
||||||
roles: [Role]!
|
|
||||||
scopes: [Scope]!
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Available roles.
|
|
||||||
"""
|
|
||||||
type Role {
|
|
||||||
name: String!
|
|
||||||
description: String!
|
|
||||||
resourceTarget: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Available scopes.
|
|
||||||
"""
|
|
||||||
type Scope {
|
|
||||||
name: String!
|
|
||||||
description: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
serverInfoUpdate(info: ServerInfoUpdateInput!): Boolean
|
|
||||||
@hasRole(role: "server:admin")
|
|
||||||
@hasScope(scope: "server:setup")
|
|
||||||
}
|
|
||||||
|
|
||||||
input ServerInfoUpdateInput {
|
|
||||||
name: String!
|
|
||||||
company: String
|
|
||||||
description: String
|
|
||||||
adminContact: String
|
|
||||||
termsOfService: String
|
|
||||||
}
|
|
||||||
,extend type Query {
|
|
||||||
"""
|
|
||||||
Returns a specific stream.
|
|
||||||
"""
|
|
||||||
stream( id: String! ): Stream
|
|
||||||
|
|
||||||
"""
|
|
||||||
All the streams of the current user, pass in the `query` parameter to search by name, description or ID.
|
|
||||||
"""
|
|
||||||
streams( query: String, limit: Int = 25, cursor: String ): StreamCollection
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stream {
|
|
||||||
id: String!
|
|
||||||
name: String!
|
|
||||||
description: String
|
|
||||||
isPublic: Boolean!
|
|
||||||
createdAt: DateTime!
|
|
||||||
updatedAt: DateTime!
|
|
||||||
collaborators: [ StreamCollaborator ]!
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type User {
|
|
||||||
"""
|
|
||||||
All the streams that a user has access to.
|
|
||||||
"""
|
|
||||||
streams( limit: Int! = 25, cursor: String ): StreamCollection
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamCollaborator {
|
|
||||||
id: String!
|
|
||||||
name: String!
|
|
||||||
role: String!
|
|
||||||
company: String
|
|
||||||
avatar: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamCollection {
|
|
||||||
totalCount: Int!
|
|
||||||
cursor: String
|
|
||||||
items: [ Stream ]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
"""
|
|
||||||
Creates a new stream.
|
|
||||||
"""
|
|
||||||
streamCreate( stream: StreamCreateInput! ): String
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
"""
|
|
||||||
Updates an existing stream.
|
|
||||||
"""
|
|
||||||
streamUpdate( stream: StreamUpdateInput! ): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
"""
|
|
||||||
Deletes an existing stream.
|
|
||||||
"""
|
|
||||||
streamDelete( id: String! ): Boolean!
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
"""
|
|
||||||
Grants permissions to a user on a given stream.
|
|
||||||
"""
|
|
||||||
streamGrantPermission( permissionParams: StreamGrantPermissionInput! ): Boolean
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
"""
|
|
||||||
Revokes the permissions of a user on a given stream.
|
|
||||||
"""
|
|
||||||
streamRevokePermission( permissionParams: StreamRevokePermissionInput! ): Boolean
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:write")
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Subscription {
|
|
||||||
|
|
||||||
#
|
|
||||||
# User bound subscriptions that operate on the stream collection of an user
|
|
||||||
# Example relevant view/usecase: updating the list of streams for a user.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Subscribes to new stream added event for your profile. Use this to display an up-to-date list of streams.
|
|
||||||
**NOTE**: If someone shares a stream with you, this subscription will be triggered with an extra value of `sharedBy` in the payload.
|
|
||||||
"""
|
|
||||||
userStreamAdded: JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "profile:read")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Subscribes to stream removed event for your profile. Use this to display an up-to-date list of streams for your profile.
|
|
||||||
**NOTE**: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of `revokedBy` in the payload.
|
|
||||||
"""
|
|
||||||
userStreamRemoved: JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "profile:read")
|
|
||||||
|
|
||||||
#
|
|
||||||
# Stream bound subscriptions that operate on the stream itself.
|
|
||||||
# Example relevant view/usecase: a single stream connector, or view, or component in a web app
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
|
||||||
"""
|
|
||||||
streamUpdated( streamId: String ): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Subscribes to stream deleted event. Use this in clients/components that pertain only to this stream.
|
|
||||||
"""
|
|
||||||
streamDeleted( streamId: String ): JSONObject
|
|
||||||
@hasRole(role: "server:user")
|
|
||||||
@hasScope(scope: "streams:read")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
input StreamCreateInput {
|
|
||||||
name: String
|
|
||||||
description: String
|
|
||||||
isPublic: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
input StreamUpdateInput {
|
|
||||||
id: String!
|
|
||||||
name: String
|
|
||||||
description: String
|
|
||||||
isPublic: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
input StreamGrantPermissionInput {
|
|
||||||
streamId: String!,
|
|
||||||
userId: String!,
|
|
||||||
role: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
input StreamRevokePermissionInput {
|
|
||||||
streamId: String!,
|
|
||||||
userId: String!
|
|
||||||
}
|
|
||||||
,extend type Query {
|
|
||||||
"""
|
|
||||||
Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
|
||||||
"""
|
|
||||||
user(id: String): User
|
|
||||||
userSearch(
|
|
||||||
query: String!
|
|
||||||
limit: Int! = 25
|
|
||||||
cursor: String
|
|
||||||
): UserSearchResultCollection
|
|
||||||
userPwdStrength(pwd: String!): JSONObject
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Base user type.
|
|
||||||
"""
|
|
||||||
type User {
|
|
||||||
id: String!
|
|
||||||
suuid: String
|
|
||||||
email: String
|
|
||||||
name: String
|
|
||||||
bio: String
|
|
||||||
company: String
|
|
||||||
avatar: String
|
|
||||||
verified: Boolean
|
|
||||||
profiles: JSONObject
|
|
||||||
role: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserSearchResultCollection {
|
|
||||||
cursor: String
|
|
||||||
items: [UserSearchResult]
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserSearchResult {
|
|
||||||
id: String!
|
|
||||||
name: String
|
|
||||||
bio: String
|
|
||||||
company: String
|
|
||||||
avatar: String
|
|
||||||
verified: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
"""
|
|
||||||
Edits a user's profile.
|
|
||||||
"""
|
|
||||||
userUpdate(user: UserUpdateInput!): Boolean!
|
|
||||||
}
|
|
||||||
|
|
||||||
input UserUpdateInput {
|
|
||||||
name: String
|
|
||||||
company: String
|
|
||||||
bio: String
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
"""Builtin Speckle object kit."""
|
|
||||||
|
|
||||||
from specklepy.objects.base import Base
|
|
||||||
|
|
||||||
__all__ = ["Base"]
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
"""Builtin Speckle object kit."""
|
|
||||||
|
|
||||||
from specklepy.objects.structural.analysis import *
|
|
||||||
from specklepy.objects.structural.properties import *
|
|
||||||
from specklepy.objects.structural.material import *
|
|
||||||
from specklepy.objects.structural.geometry import *
|
|
||||||
from specklepy.objects.structural.loading import *
|
|
||||||
from specklepy.objects.structural.axis import Axis
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Element1D",
|
|
||||||
"Element2D",
|
|
||||||
"Element3D",
|
|
||||||
"Axis",
|
|
||||||
"Node",
|
|
||||||
"Restraint",
|
|
||||||
"Load",
|
|
||||||
"LoadBeam",
|
|
||||||
"LoadCase",
|
|
||||||
"LoadCombinations",
|
|
||||||
"LoadFace",
|
|
||||||
"LoadGravity",
|
|
||||||
"LoadNode",
|
|
||||||
"Model",
|
|
||||||
"ModelInfo",
|
|
||||||
"ModelSettings",
|
|
||||||
"ModelUnits",
|
|
||||||
"Concrete",
|
|
||||||
"Material",
|
|
||||||
"Steel",
|
|
||||||
"Timber",
|
|
||||||
"Property",
|
|
||||||
"Property1D",
|
|
||||||
"Property2D",
|
|
||||||
"Property3D",
|
|
||||||
"PropertyDamper",
|
|
||||||
"PropertyMass",
|
|
||||||
"PropertySpring",
|
|
||||||
"SectionProfile",
|
|
||||||
]
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
|
|
||||||
from ..base import Base
|
|
||||||
from ..geometry import *
|
|
||||||
from .properties import *
|
|
||||||
|
|
||||||
STRUCTURAL_ANALYSIS = "Objects.Structural.Analysis."
|
|
||||||
|
|
||||||
|
|
||||||
class ModelUnits(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelUnits"):
|
|
||||||
length: str = None
|
|
||||||
sections: str = None
|
|
||||||
displacements: str = None
|
|
||||||
stress: str = None
|
|
||||||
force: str = None
|
|
||||||
mass: str = None
|
|
||||||
time: str = None
|
|
||||||
temperature: str = None
|
|
||||||
velocity: str = None
|
|
||||||
acceleration: str = None
|
|
||||||
energy: str = None
|
|
||||||
angle: str = None
|
|
||||||
strain: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class ModelSettings(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelSettings"):
|
|
||||||
modelUnits: ModelUnits = None
|
|
||||||
steelCode: str = None
|
|
||||||
concreteCode: str = None
|
|
||||||
coincidenceTolerance: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class ModelInfo(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelInfo"):
|
|
||||||
name: str = None
|
|
||||||
description: str = None
|
|
||||||
projectNumber: str = None
|
|
||||||
projectName: str = None
|
|
||||||
settings: ModelSettings = None
|
|
||||||
initials: str = None
|
|
||||||
application: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Model(Base, speckle_type=STRUCTURAL_ANALYSIS + "Model"):
|
|
||||||
specs: ModelInfo = None
|
|
||||||
nodes: List = None
|
|
||||||
elements: List = None
|
|
||||||
loads: List = None
|
|
||||||
restraints: List = None
|
|
||||||
properties: List = None
|
|
||||||
materials: List = None
|
|
||||||
layerDescription: str = None
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from ..base import Base
|
|
||||||
from ..geometry import Plane
|
|
||||||
|
|
||||||
|
|
||||||
class Axis(Base, speckle_type="Objects.Structural.Geometry.Axis"):
|
|
||||||
name: str = None
|
|
||||||
axisType: str = None
|
|
||||||
plane: Plane = None
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from ..base import Base
|
|
||||||
from ..geometry import *
|
|
||||||
from .properties import *
|
|
||||||
from .axis import Axis
|
|
||||||
|
|
||||||
STRUCTURAL_GEOMETRY = "Objects.Structural.Geometry"
|
|
||||||
|
|
||||||
|
|
||||||
class ElementType1D(int, Enum):
|
|
||||||
Beam = 0
|
|
||||||
Brace = 1
|
|
||||||
Bar = 2
|
|
||||||
Column = 3
|
|
||||||
Rod = 4
|
|
||||||
Spring = 5
|
|
||||||
Tie = 6
|
|
||||||
Strut = 7
|
|
||||||
Link = 8
|
|
||||||
Damper = 9
|
|
||||||
Cable = 10
|
|
||||||
Spacer = 11
|
|
||||||
Other = 12
|
|
||||||
Null = 13
|
|
||||||
|
|
||||||
|
|
||||||
class ElementType2D(int, Enum):
|
|
||||||
Quad4 = 0
|
|
||||||
Quad8 = 1
|
|
||||||
Triangle3 = 2
|
|
||||||
Triangle6 = 3
|
|
||||||
|
|
||||||
|
|
||||||
class ElementType3D(int, Enum):
|
|
||||||
Brick8 = 0
|
|
||||||
Wedge6 = 1
|
|
||||||
Pyramid5 = 2
|
|
||||||
Tetra4 = 3
|
|
||||||
|
|
||||||
|
|
||||||
class Restraint(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Restraint"):
|
|
||||||
code: str = None
|
|
||||||
stiffnessX: float = 0.0
|
|
||||||
stiffnessY: float = 0.0
|
|
||||||
stiffnessZ: float = 0.0
|
|
||||||
stiffnessXX: float = 0.0
|
|
||||||
stiffnessYY: float = 0.0
|
|
||||||
stiffnessZZ: float = 0.0
|
|
||||||
units: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Node(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Node"):
|
|
||||||
name: str = None
|
|
||||||
basePoint: Point = None
|
|
||||||
constraintAxis: Axis = None
|
|
||||||
restraint: Restraint = None
|
|
||||||
springProperty: PropertySpring = None
|
|
||||||
massProperty: PropertyMass = None
|
|
||||||
damperProperty: PropertyDamper = None
|
|
||||||
units: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Element1D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element1D"):
|
|
||||||
name: str = None
|
|
||||||
baseLine: Line = None
|
|
||||||
property: Property1D = None
|
|
||||||
type: ElementType1D = None
|
|
||||||
end1Releases: Restraint = None
|
|
||||||
end2Releases: Restraint = None
|
|
||||||
end1Offset: Vector = None
|
|
||||||
end2Offset: Vector = None
|
|
||||||
orientationNode: Node = None
|
|
||||||
orinetationAngle: float = 0.0
|
|
||||||
localAxis: Plane = None
|
|
||||||
parent: Base = None
|
|
||||||
end1Node: Node = Node
|
|
||||||
end2Node: Node = Node
|
|
||||||
topology: List = None
|
|
||||||
displayMesh: Mesh = None
|
|
||||||
units: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Element2D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element2D"):
|
|
||||||
name: str = None
|
|
||||||
property: Property2D = None
|
|
||||||
type: ElementType2D = None
|
|
||||||
offset: float = 0.0
|
|
||||||
orientationAngle: float = 0.0
|
|
||||||
parent: Base = None
|
|
||||||
topology: List = None
|
|
||||||
displayMesh: Mesh = None
|
|
||||||
units: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Element3D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element3D"):
|
|
||||||
name: str = None
|
|
||||||
baseMesh: Mesh = None
|
|
||||||
property: Property3D = None
|
|
||||||
type: ElementType3D = None
|
|
||||||
orientationAngle: float = 0.0
|
|
||||||
parent: Base = None
|
|
||||||
topology: List
|
|
||||||
units: str = None
|
|
||||||
|
|
||||||
|
|
||||||
# class Storey needs ependency on built elements first
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
|
|
||||||
from ..base import Base
|
|
||||||
from ..geometry import *
|
|
||||||
from .loading import *
|
|
||||||
from .geometry import *
|
|
||||||
from .analysis import Model
|
|
||||||
|
|
||||||
STRUCTURAL_RESULTS = "Objects.Structural.Results."
|
|
||||||
|
|
||||||
|
|
||||||
class Result(Base, speckle_type=STRUCTURAL_RESULTS + "Result"):
|
|
||||||
resultCase: Base = None
|
|
||||||
permutation: str = None
|
|
||||||
description: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"):
|
|
||||||
results1D: List
|
|
||||||
|
|
||||||
|
|
||||||
class Result1D(Result, speckle_type=STRUCTURAL_RESULTS + "Result1D"):
|
|
||||||
element: Element1D = None
|
|
||||||
position: float = 0.0
|
|
||||||
dispX: float = 0.0
|
|
||||||
dispY: float = 0.0
|
|
||||||
dispZ: float = 0.0
|
|
||||||
rotXX: float = 0.0
|
|
||||||
rotYY: float = 0.0
|
|
||||||
rotZZ: float = 0.0
|
|
||||||
forceX: float = 0.0
|
|
||||||
forceY: float = 0.0
|
|
||||||
forceZ: float = 0.0
|
|
||||||
momentXX: float = 0.0
|
|
||||||
momentYY: float = 0.0
|
|
||||||
momentZZ: float = 0.0
|
|
||||||
axialStress: float = 0.0
|
|
||||||
shearStressY: float = 0.0
|
|
||||||
shearStressZ: float = 0.0
|
|
||||||
bendingStressYPos: float = 0.0
|
|
||||||
bendingStressYNeg: float = 0.0
|
|
||||||
bendingStressZPos: float = 0.0
|
|
||||||
bendingStressZNeg: float = 0.0
|
|
||||||
combinedStressMax: float = 0.0
|
|
||||||
combinedStressMin: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class ResultSet2D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet2D"):
|
|
||||||
results2D: List
|
|
||||||
|
|
||||||
|
|
||||||
class Result2D(Result, speckle_type=STRUCTURAL_RESULTS + "Result2D"):
|
|
||||||
element: Element2D = None
|
|
||||||
position: List
|
|
||||||
dispX: float = 0.0
|
|
||||||
dispY: float = 0.0
|
|
||||||
dispZ: float = 0.0
|
|
||||||
forceXX: float = 0.0
|
|
||||||
forceYY: float = 0.0
|
|
||||||
forceXY: float = 0.0
|
|
||||||
momentXX: float = 0.0
|
|
||||||
momentYY: float = 0.0
|
|
||||||
momentXY: float = 0.0
|
|
||||||
shearX: float = 0.0
|
|
||||||
shearY: float = 0.0
|
|
||||||
stressTopXX: float = 0.0
|
|
||||||
stressTopYY: float = 0.0
|
|
||||||
stressTopZZ: float = 0.0
|
|
||||||
stressTopXY: float = 0.0
|
|
||||||
stressTopYZ: float = 0.0
|
|
||||||
stressTopZX: float = 0.0
|
|
||||||
stressMidXX: float = 0.0
|
|
||||||
stressMidYY: float = 0.0
|
|
||||||
stressMidZZ: float = 0.0
|
|
||||||
stressMidXY: float = 0.0
|
|
||||||
stressMidYZ: float = 0.0
|
|
||||||
stressMidZX: float = 0.0
|
|
||||||
stressBotXX: float = 0.0
|
|
||||||
stressBotYY: float = 0.0
|
|
||||||
stressBotZZ: float = 0.0
|
|
||||||
stressBotXY: float = 0.0
|
|
||||||
stressBotYZ: float = 0.0
|
|
||||||
stressBotZX: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class ResultSet3D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet3D"):
|
|
||||||
results3D: List
|
|
||||||
|
|
||||||
|
|
||||||
class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"):
|
|
||||||
element: Element3D = None
|
|
||||||
position: List
|
|
||||||
dispX: float = 0.0
|
|
||||||
dispY: float = 0.0
|
|
||||||
dispZ: float = 0.0
|
|
||||||
stressXX: float = 0.0
|
|
||||||
stressYY: float = 0.0
|
|
||||||
stressZZ: float = 0.0
|
|
||||||
stressXY: float = 0.0
|
|
||||||
stressYZ: float = 0.0
|
|
||||||
stressZX: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class ResultGlobal(Result, speckle_type=STRUCTURAL_RESULTS + "ResultGlobal"):
|
|
||||||
model: Model = None
|
|
||||||
loadX: float = 0.0
|
|
||||||
loadY: float = 0.0
|
|
||||||
loadZ: float = 0.0
|
|
||||||
loadXX: float = 0.0
|
|
||||||
loadYY: float = 0.0
|
|
||||||
loadZZ: float = 0.0
|
|
||||||
reactionX: float = 0.0
|
|
||||||
reactionY: float = 0.0
|
|
||||||
reactionZ: float = 0.0
|
|
||||||
reactionXX: float = 0.0
|
|
||||||
reactionYY: float = 0.0
|
|
||||||
reactionZZ: float = 0.0
|
|
||||||
mode: float = 0.0
|
|
||||||
frequency: float = 0.0
|
|
||||||
loadFactor: float = 0.0
|
|
||||||
modalStiffness: float = 0.0
|
|
||||||
modalGeoStiffness: float = 0.0
|
|
||||||
effMassX: float = 0.0
|
|
||||||
effMassY: float = 0.0
|
|
||||||
effMassZ: float = 0.0
|
|
||||||
effMassXX: float = 0.0
|
|
||||||
effMassYY: float = 0.0
|
|
||||||
effMassZZ: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class ResultSetNode(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSetNode"):
|
|
||||||
resultsNode: List
|
|
||||||
|
|
||||||
|
|
||||||
class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"):
|
|
||||||
node: Node = None
|
|
||||||
dispX: float = 0.0
|
|
||||||
dispY: float = 0.0
|
|
||||||
dispZ: float = 0.0
|
|
||||||
rotXX: float = 0.0
|
|
||||||
rotYY: float = 0.0
|
|
||||||
rotZZ: float = 0.0
|
|
||||||
reactionX: float = 0.0
|
|
||||||
reactionY: float = 0.0
|
|
||||||
reactionZ: float = 0.0
|
|
||||||
reactionXX: float = 0.0
|
|
||||||
reactionYY: float = 0.0
|
|
||||||
reactionZZ: float = 0.0
|
|
||||||
constraintX: float = 0.0
|
|
||||||
constraintY: float = 0.0
|
|
||||||
constraintZ: float = 0.0
|
|
||||||
constraintXX: float = 0.0
|
|
||||||
constraintYY: float = 0.0
|
|
||||||
constraintZZ: float = 0.0
|
|
||||||
velX: float = 0.0
|
|
||||||
velY: float = 0.0
|
|
||||||
velZ: float = 0.0
|
|
||||||
velXX: float = 0.0
|
|
||||||
velYY: float = 0.0
|
|
||||||
velZZ: float = 0.0
|
|
||||||
accX: float = 0.0
|
|
||||||
accY: float = 0.0
|
|
||||||
accZ: float = 0.0
|
|
||||||
accXX: float = 0.0
|
|
||||||
accYY: float = 0.0
|
|
||||||
accZZ: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class ResultSetAll(Base, speckle_type=None):
|
|
||||||
resultSet1D: ResultSet1D = None
|
|
||||||
resultSet2D: ResultSet2D = None
|
|
||||||
resultSet3D: ResultSet3D = None
|
|
||||||
resultsGlobal: ResultGlobal = None
|
|
||||||
resultsNode: ResultSetNode = None
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
from warnings import warn
|
|
||||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
|
||||||
|
|
||||||
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,
|
|
||||||
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):
|
|
||||||
if not isinstance(unit, str):
|
|
||||||
warn(
|
|
||||||
f"Invalid units: expected type str but received {type(unit)} ({unit}). Skipping - no units will be set.",
|
|
||||||
SpeckleWarning,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
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 as e:
|
|
||||||
raise SpeckleException(message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS}).") from e
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .server import ServerTransport
|
|
||||||
@@ -1,36 +1,38 @@
|
|||||||
import re
|
import re
|
||||||
from warnings import warn
|
|
||||||
from deprecated import deprecated
|
|
||||||
from specklepy.api.credentials import Account, get_account_from_token
|
|
||||||
from specklepy.logging import metrics
|
|
||||||
from specklepy.logging.exceptions import (
|
|
||||||
SpeckleException,
|
|
||||||
SpeckleWarning,
|
|
||||||
)
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
from specklepy.api import resources
|
from deprecated import deprecated
|
||||||
from specklepy.api.resources import (
|
|
||||||
branch,
|
|
||||||
commit,
|
|
||||||
stream,
|
|
||||||
object,
|
|
||||||
server,
|
|
||||||
user,
|
|
||||||
subscriptions,
|
|
||||||
)
|
|
||||||
from specklepy.api.models import ServerInfo
|
|
||||||
from gql import Client
|
from gql import Client
|
||||||
from gql.transport.requests import RequestsHTTPTransport
|
from gql.transport.requests import RequestsHTTPTransport
|
||||||
from gql.transport.websockets import WebsocketsTransport
|
from gql.transport.websockets import WebsocketsTransport
|
||||||
|
|
||||||
|
from specklepy.api import resources
|
||||||
|
from specklepy.api.credentials import Account, get_account_from_token
|
||||||
|
from specklepy.api.resources import (
|
||||||
|
active_user,
|
||||||
|
branch,
|
||||||
|
commit,
|
||||||
|
object,
|
||||||
|
other_user,
|
||||||
|
server,
|
||||||
|
stream,
|
||||||
|
subscriptions,
|
||||||
|
user,
|
||||||
|
)
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||||
|
|
||||||
|
|
||||||
class SpeckleClient:
|
class SpeckleClient:
|
||||||
"""
|
"""
|
||||||
The `SpeckleClient` is your entry point for interacting with your Speckle Server's GraphQL API.
|
The `SpeckleClient` is your entry point for interacting with
|
||||||
You'll need to have access to a server to use it, or you can use our public server `speckle.xyz`.
|
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)
|
To authenticate the client, you'll need to have downloaded
|
||||||
|
the [Speckle Manager](https://speckle.guide/#speckle-manager)
|
||||||
and added your account.
|
and added your account.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -93,15 +95,22 @@ class SpeckleClient:
|
|||||||
# ) from ex
|
# ) from ex
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"SpeckleClient( server: {self.url}, authenticated: {self.account.token is not None} )"
|
return (
|
||||||
|
f"SpeckleClient( server: {self.url}, authenticated:"
|
||||||
|
f" {self.account.token is not None} )"
|
||||||
|
)
|
||||||
|
|
||||||
@deprecated(
|
@deprecated(
|
||||||
version="2.6.0",
|
version="2.6.0",
|
||||||
reason="Renamed: please use `authenticate_with_account` or `authenticate_with_token` instead.",
|
reason=(
|
||||||
|
"Renamed: please use `authenticate_with_account` or"
|
||||||
|
" `authenticate_with_token` instead."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
def authenticate(self, token: str) -> None:
|
def authenticate(self, token: str) -> None:
|
||||||
"""Authenticate the client using a personal access token
|
"""Authenticate the client using a personal access token
|
||||||
The token is saved in the client object and a synchronous GraphQL entrypoint is created
|
The token is saved in the client object and a synchronous GraphQL
|
||||||
|
entrypoint is created
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
token {str} -- an api token
|
token {str} -- an api token
|
||||||
@@ -110,8 +119,10 @@ class SpeckleClient:
|
|||||||
self._set_up_client()
|
self._set_up_client()
|
||||||
|
|
||||||
def authenticate_with_token(self, token: str) -> None:
|
def authenticate_with_token(self, token: str) -> None:
|
||||||
"""Authenticate the client using a personal access token
|
"""
|
||||||
The token is saved in the client object and a synchronous GraphQL entrypoint is created
|
Authenticate the client using a personal access token.
|
||||||
|
The token is saved in the client object and a synchronous GraphQL
|
||||||
|
entrypoint is created
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
token {str} -- an api token
|
token {str} -- an api token
|
||||||
@@ -122,10 +133,12 @@ class SpeckleClient:
|
|||||||
|
|
||||||
def authenticate_with_account(self, account: Account) -> None:
|
def authenticate_with_account(self, account: Account) -> None:
|
||||||
"""Authenticate the client using an Account object
|
"""Authenticate the client using an Account object
|
||||||
The account is saved in the client object and a synchronous GraphQL entrypoint is created
|
The account is saved in the client object and a synchronous GraphQL
|
||||||
|
entrypoint is created
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
account {Account} -- the account object which can be found with `get_default_account` or `get_local_accounts`
|
account {Account} -- the account object which can be found with
|
||||||
|
`get_default_account` or `get_local_accounts`
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.CLIENT, account, {"name": "authenticate with account"})
|
metrics.track(metrics.CLIENT, account, {"name": "authenticate with account"})
|
||||||
self.account = account
|
self.account = account
|
||||||
@@ -136,6 +149,8 @@ class SpeckleClient:
|
|||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.account.token}",
|
"Authorization": f"Bearer {self.account.token}",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"apollographql-client-name": metrics.HOST_APP,
|
||||||
|
"apollographql-client-version": metrics.HOST_APP_VERSION,
|
||||||
}
|
}
|
||||||
httptransport = RequestsHTTPTransport(
|
httptransport = RequestsHTTPTransport(
|
||||||
url=self.graphql, headers=headers, verify=True, retries=3
|
url=self.graphql, headers=headers, verify=True, retries=3
|
||||||
@@ -152,7 +167,8 @@ class SpeckleClient:
|
|||||||
if self.user.get() is None:
|
if self.user.get() is None:
|
||||||
warn(
|
warn(
|
||||||
SpeckleWarning(
|
SpeckleWarning(
|
||||||
f"Possibly invalid token - could not authenticate Speckle Client for server {self.url}"
|
"Possibly invalid token - could not authenticate Speckle Client"
|
||||||
|
f" for server {self.url}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,7 +182,7 @@ class SpeckleClient:
|
|||||||
server_version = None
|
server_version = None
|
||||||
try:
|
try:
|
||||||
server_version = self.server.version()
|
server_version = self.server.version()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.user = user.Resource(
|
self.user = user.Resource(
|
||||||
account=self.account,
|
account=self.account,
|
||||||
@@ -174,6 +190,18 @@ class SpeckleClient:
|
|||||||
client=self.httpclient,
|
client=self.httpclient,
|
||||||
server_version=server_version,
|
server_version=server_version,
|
||||||
)
|
)
|
||||||
|
self.other_user = other_user.Resource(
|
||||||
|
account=self.account,
|
||||||
|
basepath=self.url,
|
||||||
|
client=self.httpclient,
|
||||||
|
server_version=server_version,
|
||||||
|
)
|
||||||
|
self.active_user = active_user.Resource(
|
||||||
|
account=self.account,
|
||||||
|
basepath=self.url,
|
||||||
|
client=self.httpclient,
|
||||||
|
server_version=server_version,
|
||||||
|
)
|
||||||
self.stream = stream.Resource(
|
self.stream = stream.Resource(
|
||||||
account=self.account,
|
account=self.account,
|
||||||
basepath=self.url,
|
basepath=self.url,
|
||||||
@@ -201,7 +229,7 @@ class SpeckleClient:
|
|||||||
return attr.Resource(
|
return attr.Resource(
|
||||||
account=self.account, basepath=self.url, client=self.httpclient
|
account=self.account, basepath=self.url, client=self.httpclient
|
||||||
)
|
)
|
||||||
except:
|
except AttributeError:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Method {name} is not supported by the SpeckleClient class"
|
f"Method {name} is not supported by the SpeckleClient class"
|
||||||
)
|
)
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
import os
|
import os
|
||||||
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from specklepy.logging import metrics
|
|
||||||
|
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
||||||
|
|
||||||
from specklepy.api.models import ServerInfo
|
from specklepy.api.models import ServerInfo
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
from specklepy.core.helpers import speckle_path_provider
|
||||||
|
from specklepy.logging import metrics
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
|
|
||||||
|
|
||||||
class UserInfo(BaseModel):
|
class UserInfo(BaseModel):
|
||||||
name: Optional[str]
|
name: Optional[str] = None
|
||||||
email: Optional[str]
|
email: Optional[str] = None
|
||||||
company: Optional[str]
|
company: Optional[str] = None
|
||||||
id: Optional[str]
|
id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class Account(BaseModel):
|
class Account(BaseModel):
|
||||||
@@ -23,7 +26,10 @@ class Account(BaseModel):
|
|||||||
id: Optional[str] = None
|
id: Optional[str] = None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Account(email: {self.userInfo.email}, server: {self.serverInfo.url}, isDefault: {self.isDefault})"
|
return (
|
||||||
|
f"Account(email: {self.userInfo.email}, server: {self.serverInfo.url},"
|
||||||
|
f" isDefault: {self.isDefault})"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
@@ -35,27 +41,40 @@ class Account(BaseModel):
|
|||||||
return acct
|
return acct
|
||||||
|
|
||||||
|
|
||||||
def get_local_accounts(base_path: str = None) -> List[Account]:
|
def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
|
||||||
"""Gets all the accounts present in this environment
|
"""Gets all the accounts present in this environment
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
base_path {str} -- custom base path if you are not using the system default
|
base_path {str} -- custom base path if you are not using the system default
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Account] -- list of all local accounts or an empty list if no accounts were found
|
List[Account] -- list of all local accounts or an empty list if
|
||||||
|
no accounts were found
|
||||||
"""
|
"""
|
||||||
account_storage = SQLiteTransport(scope="Accounts", base_path=base_path)
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
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: List[Account] = []
|
accounts: List[Account] = []
|
||||||
res = account_storage.get_all_objects()
|
try:
|
||||||
account_storage.close()
|
account_storage = SQLiteTransport(scope="Accounts", base_path=base_path)
|
||||||
|
res = account_storage.get_all_objects()
|
||||||
|
account_storage.close()
|
||||||
|
if res:
|
||||||
|
accounts.extend(Account.parse_raw(r[1]) for r in res)
|
||||||
|
except SpeckleException:
|
||||||
|
# cannot open SQLiteTransport, probably because of the lack
|
||||||
|
# of disk write permissions
|
||||||
|
pass
|
||||||
|
|
||||||
|
json_acct_files = []
|
||||||
|
json_path = str(speckle_path_provider.accounts_folder_path())
|
||||||
|
try:
|
||||||
|
os.makedirs(json_path, exist_ok=True)
|
||||||
|
json_acct_files.extend(
|
||||||
|
file for file in os.listdir(json_path) if file.endswith(".json")
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# cannot find or get the json account paths
|
||||||
|
pass
|
||||||
|
|
||||||
if res:
|
|
||||||
accounts.extend(Account.parse_raw(r[1]) for r in res)
|
|
||||||
if json_acct_files:
|
if json_acct_files:
|
||||||
try:
|
try:
|
||||||
accounts.extend(
|
accounts.extend(
|
||||||
@@ -79,8 +98,10 @@ def get_local_accounts(base_path: str = None) -> List[Account]:
|
|||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
def get_default_account(base_path: str = None) -> Account:
|
def get_default_account(base_path: Optional[str] = None) -> Optional[Account]:
|
||||||
"""Gets this environment's default account if any. If there is no default, the first found will be returned and set as default.
|
"""
|
||||||
|
Gets this environment's default account if any. If there is no default,
|
||||||
|
the first found will be returned and set as default.
|
||||||
Arguments:
|
Arguments:
|
||||||
base_path {str} -- custom base path if you are not using the system default
|
base_path {str} -- custom base path if you are not using the system default
|
||||||
|
|
||||||
@@ -106,7 +127,8 @@ def get_account_from_token(token: str, server_url: str = None) -> Account:
|
|||||||
token {str} -- the api token
|
token {str} -- the api token
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Account -- the local account with this token or a shell account containing just the token and url if no local account is found
|
Account -- the local account with this token or a shell account containing
|
||||||
|
just the token and url if no local account is found
|
||||||
"""
|
"""
|
||||||
accounts = get_local_accounts()
|
accounts = get_local_accounts()
|
||||||
if not accounts:
|
if not accounts:
|
||||||
@@ -130,6 +152,9 @@ def get_account_from_token(token: str, server_url: str = None) -> Account:
|
|||||||
class StreamWrapper:
|
class StreamWrapper:
|
||||||
def __init__(self, url: str = None) -> None:
|
def __init__(self, url: str = None) -> None:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
message="The StreamWrapper has moved as of v2.6.0! Please import from specklepy.api.wrapper",
|
message=(
|
||||||
exception=DeprecationWarning,
|
"The StreamWrapper has moved as of v2.6.0! Please import from"
|
||||||
|
" specklepy.api.wrapper"
|
||||||
|
),
|
||||||
|
exception=DeprecationWarning(),
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from unicodedata import name
|
||||||
|
|
||||||
|
|
||||||
|
class HostAppVersion(Enum):
|
||||||
|
v = "v"
|
||||||
|
v6 = "v6"
|
||||||
|
v7 = "v7"
|
||||||
|
v2019 = "v2019"
|
||||||
|
v2020 = "v2020"
|
||||||
|
v2021 = "v2021"
|
||||||
|
v2022 = "v2022"
|
||||||
|
v2023 = "v2023"
|
||||||
|
v2024 = "v2024"
|
||||||
|
v2025 = "v2025"
|
||||||
|
vSandbox = "vSandbox"
|
||||||
|
vRevit = "vRevit"
|
||||||
|
vRevit2021 = "vRevit2021"
|
||||||
|
vRevit2022 = "vRevit2022"
|
||||||
|
vRevit2023 = "vRevit2023"
|
||||||
|
vRevit2024 = "vRevit2024"
|
||||||
|
vRevit2025 = "vRevit2025"
|
||||||
|
v25 = "v25"
|
||||||
|
v26 = "v26"
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HostApplication:
|
||||||
|
name: str
|
||||||
|
slug: str
|
||||||
|
|
||||||
|
def get_version(self, version: HostAppVersion) -> str:
|
||||||
|
return f"{name.replace(' ', '')}{str(version).strip('v')}"
|
||||||
|
|
||||||
|
|
||||||
|
RHINO = HostApplication("Rhino", "rhino")
|
||||||
|
GRASSHOPPER = HostApplication("Grasshopper", "grasshopper")
|
||||||
|
REVIT = HostApplication("Revit", "revit")
|
||||||
|
DYNAMO = HostApplication("Dynamo", "dynamo")
|
||||||
|
UNITY = HostApplication("Unity", "unity")
|
||||||
|
GSA = HostApplication("GSA", "gsa")
|
||||||
|
CIVIL = HostApplication("Civil 3D", "civil3d")
|
||||||
|
AUTOCAD = HostApplication("AutoCAD", "autocad")
|
||||||
|
MICROSTATION = HostApplication("MicroStation", "microstation")
|
||||||
|
OPENROADS = HostApplication("OpenRoads", "openroads")
|
||||||
|
OPENRAIL = HostApplication("OpenRail", "openrail")
|
||||||
|
OPENBUILDINGS = HostApplication("OpenBuildings", "openbuildings")
|
||||||
|
ETABS = HostApplication("ETABS", "etabs")
|
||||||
|
SAP2000 = HostApplication("SAP2000", "sap2000")
|
||||||
|
CSIBRIDGE = HostApplication("CSIBridge", "csibridge")
|
||||||
|
SAFE = HostApplication("SAFE", "safe")
|
||||||
|
TEKLASTRUCTURES = HostApplication("Tekla Structures", "teklastructures")
|
||||||
|
DXF = HostApplication("DXF Converter", "dxf")
|
||||||
|
EXCEL = HostApplication("Excel", "excel")
|
||||||
|
UNREAL = HostApplication("Unreal", "unreal")
|
||||||
|
POWERBI = HostApplication("Power BI", "powerbi")
|
||||||
|
BLENDER = HostApplication("Blender", "blender")
|
||||||
|
QGIS = HostApplication("QGIS", "qgis")
|
||||||
|
ARCGIS = HostApplication("ArcGIS", "arcgis")
|
||||||
|
SKETCHUP = HostApplication("SketchUp", "sketchup")
|
||||||
|
ARCHICAD = HostApplication("Archicad", "archicad")
|
||||||
|
TOPSOLID = HostApplication("TopSolid", "topsolid")
|
||||||
|
PYTHON = HostApplication("Python", "python")
|
||||||
|
NET = HostApplication(".NET", "net")
|
||||||
|
OTHER = HostApplication("Other", "other")
|
||||||
|
|
||||||
|
_app_name_host_app_mapping = {
|
||||||
|
"dynamo": DYNAMO,
|
||||||
|
"revit": REVIT,
|
||||||
|
"autocad": AUTOCAD,
|
||||||
|
"civil": CIVIL,
|
||||||
|
"rhino": RHINO,
|
||||||
|
"grasshopper": GRASSHOPPER,
|
||||||
|
"unity": UNITY,
|
||||||
|
"gsa": GSA,
|
||||||
|
"microstation": MICROSTATION,
|
||||||
|
"openroads": OPENROADS,
|
||||||
|
"openrail": OPENRAIL,
|
||||||
|
"openbuildings": OPENBUILDINGS,
|
||||||
|
"etabs": ETABS,
|
||||||
|
"sap": SAP2000,
|
||||||
|
"csibridge": CSIBRIDGE,
|
||||||
|
"safe": SAFE,
|
||||||
|
"teklastructures": TEKLASTRUCTURES,
|
||||||
|
"dxf": DXF,
|
||||||
|
"excel": EXCEL,
|
||||||
|
"unreal": UNREAL,
|
||||||
|
"powerbi": POWERBI,
|
||||||
|
"blender": BLENDER,
|
||||||
|
"qgis": QGIS,
|
||||||
|
"arcgis": ARCGIS,
|
||||||
|
"sketchup": SKETCHUP,
|
||||||
|
"archicad": ARCHICAD,
|
||||||
|
"topsolid": TOPSOLID,
|
||||||
|
"python": PYTHON,
|
||||||
|
"net": NET,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_host_app_from_string(app_name: str) -> HostApplication:
|
||||||
|
app_name = app_name.lower().replace(" ", "")
|
||||||
|
for partial_app_name, host_app in _app_name_host_app_mapping.items():
|
||||||
|
if partial_app_name in app_name:
|
||||||
|
return host_app
|
||||||
|
return HostApplication(app_name, app_name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(HostAppVersion.v)
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
# generated by datamodel-codegen:
|
|
||||||
# filename: stream_schema.json
|
|
||||||
# timestamp: 2020-11-17T14:33:13+00:00
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
from pydantic import BaseModel # pylint: disable=no-name-in-module
|
|
||||||
|
|
||||||
|
|
||||||
class Collaborator(BaseModel):
|
class Collaborator(BaseModel):
|
||||||
@@ -30,7 +25,11 @@ class Commit(BaseModel):
|
|||||||
parents: Optional[List[str]]
|
parents: Optional[List[str]]
|
||||||
|
|
||||||
def __repr__(self) -> 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} )"
|
return (
|
||||||
|
f"Commit( id: {self.id}, message: {self.message}, referencedObject:"
|
||||||
|
f" {self.referencedObject}, authorName: {self.authorName}, branchName:"
|
||||||
|
f" {self.branchName}, createdAt: {self.createdAt} )"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
@@ -64,23 +63,26 @@ class Branches(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Stream(BaseModel):
|
class Stream(BaseModel):
|
||||||
id: Optional[str]
|
id: Optional[str] = None
|
||||||
name: Optional[str]
|
name: Optional[str]
|
||||||
role: Optional[str]
|
role: Optional[str] = None
|
||||||
isPublic: Optional[bool]
|
isPublic: Optional[bool] = None
|
||||||
description: Optional[str]
|
description: Optional[str] = None
|
||||||
createdAt: Optional[datetime]
|
createdAt: Optional[datetime] = None
|
||||||
updatedAt: Optional[datetime]
|
updatedAt: Optional[datetime] = None
|
||||||
collaborators: List[Collaborator] = []
|
collaborators: List[Collaborator] = Field(default_factory=list)
|
||||||
branches: Optional[Branches]
|
branches: Optional[Branches] = None
|
||||||
commit: Optional[Commit]
|
commit: Optional[Commit] = None
|
||||||
object: Optional[Object]
|
object: Optional[Object] = None
|
||||||
commentCount: Optional[int]
|
commentCount: Optional[int] = None
|
||||||
favoritedDate: Optional[datetime]
|
favoritedDate: Optional[datetime] = None
|
||||||
favoritesCount: Optional[int]
|
favoritesCount: Optional[int] = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Stream( id: {self.id}, name: {self.name}, description: {self.description}, isPublic: {self.isPublic})"
|
return (
|
||||||
|
f"Stream( id: {self.id}, name: {self.name}, description:"
|
||||||
|
f" {self.description}, isPublic: {self.isPublic})"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
@@ -104,12 +106,27 @@ class User(BaseModel):
|
|||||||
streams: Optional[Streams]
|
streams: Optional[Streams]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"User( id: {self.id}, name: {self.name}, email: {self.email}, company: {self.company} )"
|
return (
|
||||||
|
f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:"
|
||||||
|
f" {self.company} )"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
|
class LimitedUser(BaseModel):
|
||||||
|
"""Limited user type, for showing public info about a user to another user."""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: Optional[str]
|
||||||
|
bio: Optional[str]
|
||||||
|
company: Optional[str]
|
||||||
|
avatar: Optional[str]
|
||||||
|
verified: Optional[bool]
|
||||||
|
role: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class PendingStreamCollaborator(BaseModel):
|
class PendingStreamCollaborator(BaseModel):
|
||||||
id: Optional[str]
|
id: Optional[str]
|
||||||
inviteId: Optional[str]
|
inviteId: Optional[str]
|
||||||
@@ -122,7 +139,11 @@ class PendingStreamCollaborator(BaseModel):
|
|||||||
token: Optional[str]
|
token: Optional[str]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId: {self.streamId}, role: {self.role}, title: {self.title}, invitedBy: {self.user.name if self.user else None})"
|
return (
|
||||||
|
f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId:"
|
||||||
|
f" {self.streamId}, role: {self.role}, title: {self.title}, invitedBy:"
|
||||||
|
f" {self.user.name if self.user else None})"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
@@ -139,7 +160,10 @@ class Activity(BaseModel):
|
|||||||
time: Optional[datetime]
|
time: Optional[datetime]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Activity( streamId: {self.streamId}, actionType: {self.actionType}, message: {self.message}, userId: {self.userId} )"
|
return (
|
||||||
|
f"Activity( streamId: {self.streamId}, actionType: {self.actionType},"
|
||||||
|
f" message: {self.message}, userId: {self.userId} )"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
@@ -151,20 +175,24 @@ class ActivityCollection(BaseModel):
|
|||||||
cursor: Optional[datetime]
|
cursor: Optional[datetime]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ActivityCollection( totalCount: {self.totalCount}, items: {len(self.items) if self.items else 0}, cursor: {self.cursor.isoformat() if self.cursor else None} )"
|
return (
|
||||||
|
f"ActivityCollection( totalCount: {self.totalCount}, items:"
|
||||||
|
f" {len(self.items) if self.items else 0}, cursor:"
|
||||||
|
f" {self.cursor.isoformat() if self.cursor else None} )"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
class ServerInfo(BaseModel):
|
class ServerInfo(BaseModel):
|
||||||
name: Optional[str]
|
name: Optional[str] = None
|
||||||
company: Optional[str]
|
company: Optional[str] = None
|
||||||
url: Optional[str]
|
url: Optional[str] = None
|
||||||
description: Optional[str]
|
description: Optional[str] = None
|
||||||
adminContact: Optional[str]
|
adminContact: Optional[str] = None
|
||||||
canonicalUrl: Optional[str]
|
canonicalUrl: Optional[str] = None
|
||||||
roles: Optional[List[dict]]
|
roles: Optional[List[dict]] = None
|
||||||
scopes: Optional[List[dict]]
|
scopes: Optional[List[dict]] = None
|
||||||
authStrategies: Optional[List[dict]]
|
authStrategies: Optional[List[dict]] = None
|
||||||
version: Optional[str]
|
version: Optional[str] = None
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
from specklepy.objects.base import Base
|
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
|
||||||
from specklepy.transports.server import ServerTransport
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from specklepy.objects.base import Base
|
||||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||||
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
|
|
||||||
|
|
||||||
def send(
|
def send(
|
||||||
base: Base,
|
base: Base,
|
||||||
transports: List[AbstractTransport] = None,
|
transports: Optional[List[AbstractTransport]] = None,
|
||||||
use_default_cache: bool = True,
|
use_default_cache: bool = True,
|
||||||
):
|
):
|
||||||
"""Sends an object via the provided transports. Defaults to the local cache.
|
"""Sends an object via the provided transports. Defaults to the local cache.
|
||||||
@@ -18,7 +18,8 @@ def send(
|
|||||||
Arguments:
|
Arguments:
|
||||||
obj {Base} -- the object you want to send
|
obj {Base} -- the object you want to send
|
||||||
transports {list} -- where you want to send them
|
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
|
use_default_cache {bool} -- toggle for the default cache.
|
||||||
|
If set to false, it will only send to the provided transports
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str -- the object id of the sent object
|
str -- the object id of the sent object
|
||||||
@@ -26,7 +27,10 @@ def send(
|
|||||||
|
|
||||||
if not transports and not use_default_cache:
|
if not transports and not use_default_cache:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
message="You need to provide at least one transport: cannot send with an empty transport list and no default cache"
|
message=(
|
||||||
|
"You need to provide at least one transport: cannot send with an empty"
|
||||||
|
" transport list and no default cache"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(transports, AbstractTransport):
|
if isinstance(transports, AbstractTransport):
|
||||||
@@ -50,8 +54,17 @@ def send(
|
|||||||
|
|
||||||
def receive(
|
def receive(
|
||||||
obj_id: str,
|
obj_id: str,
|
||||||
remote_transport: AbstractTransport = None,
|
remote_transport: Optional[AbstractTransport] = None,
|
||||||
local_transport: AbstractTransport = None,
|
local_transport: Optional[AbstractTransport] = None,
|
||||||
|
) -> Base:
|
||||||
|
metrics.track(metrics.RECEIVE, getattr(remote_transport, "account", None))
|
||||||
|
return _untracked_receive(obj_id, remote_transport, local_transport)
|
||||||
|
|
||||||
|
|
||||||
|
def _untracked_receive(
|
||||||
|
obj_id: str,
|
||||||
|
remote_transport: Optional[AbstractTransport] = None,
|
||||||
|
local_transport: Optional[AbstractTransport] = None,
|
||||||
) -> Base:
|
) -> Base:
|
||||||
"""Receives an object from a transport.
|
"""Receives an object from a transport.
|
||||||
|
|
||||||
@@ -64,20 +77,22 @@ def receive(
|
|||||||
Returns:
|
Returns:
|
||||||
Base -- the base object
|
Base -- the base object
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.RECEIVE, getattr(remote_transport, "account", None))
|
|
||||||
if not local_transport:
|
if not local_transport:
|
||||||
local_transport = SQLiteTransport()
|
local_transport = SQLiteTransport()
|
||||||
|
|
||||||
serializer = BaseObjectSerializer(read_transport=local_transport)
|
serializer = BaseObjectSerializer(read_transport=local_transport)
|
||||||
|
|
||||||
# try local transport first. if the parent is there, we assume all the children are there and continue with deserialisation using the local transport
|
# try local transport first. if the parent is there, we assume all the children are there and continue with deserialization using the local transport
|
||||||
obj_string = local_transport.get_object(obj_id)
|
obj_string = local_transport.get_object(obj_id)
|
||||||
if obj_string:
|
if obj_string:
|
||||||
return serializer.read_json(obj_string=obj_string)
|
return serializer.read_json(obj_string=obj_string)
|
||||||
|
|
||||||
if not remote_transport:
|
if not remote_transport:
|
||||||
raise SpeckleException(
|
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."
|
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(
|
obj_string = remote_transport.copy_object_and_children(
|
||||||
@@ -89,12 +104,14 @@ def receive(
|
|||||||
|
|
||||||
def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str:
|
def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str:
|
||||||
"""
|
"""
|
||||||
Serialize a base object. If no write transports are provided, the object will be serialized
|
Serialize a base object. If no write transports are provided,
|
||||||
|
the object will be serialized
|
||||||
without detaching or chunking any of the attributes.
|
without detaching or chunking any of the attributes.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
base {Base} -- the object to serialize
|
base {Base} -- the object to serialize
|
||||||
write_transports {List[AbstractTransport]} -- optional: the transports to write to
|
write_transports {List[AbstractTransport]}
|
||||||
|
-- optional: the transports to write to
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str -- the serialized object
|
str -- the serialized object
|
||||||
@@ -105,14 +122,21 @@ def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str
|
|||||||
return serializer.write_json(base)[1]
|
return serializer.write_json(base)[1]
|
||||||
|
|
||||||
|
|
||||||
def deserialize(obj_string: str, read_transport: AbstractTransport = None) -> Base:
|
def deserialize(
|
||||||
|
obj_string: str, read_transport: Optional[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.
|
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:
|
Arguments:
|
||||||
obj_string {str} -- the string object to deserialize
|
obj_string {str} -- the string object to deserialize
|
||||||
read_transport {AbstractTransport} -- the transport to fetch children objects from
|
read_transport {AbstractTransport}
|
||||||
(defaults to SQLiteTransport)
|
-- the transport to fetch children objects from
|
||||||
|
(defaults to SQLiteTransport)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Base -- the deserialized object
|
Base -- the deserialized object
|
||||||
@@ -124,3 +148,6 @@ def deserialize(obj_string: str, read_transport: AbstractTransport = None) -> Ba
|
|||||||
serializer = BaseObjectSerializer(read_transport=read_transport)
|
serializer = BaseObjectSerializer(read_transport=read_transport)
|
||||||
|
|
||||||
return serializer.read_json(obj_string=obj_string)
|
return serializer.read_json(obj_string=obj_string)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["receive", "send", "serialize", "deserialize"]
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
from graphql import DocumentNode
|
|
||||||
from specklepy.api.credentials import Account
|
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
from gql.client import Client
|
from gql.client import Client
|
||||||
from gql.transport.exceptions import TransportQueryError
|
from gql.transport.exceptions import TransportQueryError
|
||||||
|
from graphql import DocumentNode
|
||||||
|
|
||||||
|
from specklepy.api.credentials import Account
|
||||||
from specklepy.logging.exceptions import (
|
from specklepy.logging.exceptions import (
|
||||||
GraphQLException,
|
GraphQLException,
|
||||||
SpeckleException,
|
SpeckleException,
|
||||||
UnsupportedException,
|
UnsupportedException,
|
||||||
)
|
)
|
||||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||||
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
|
|
||||||
|
|
||||||
class ResourceBase(object):
|
class ResourceBase(object):
|
||||||
@@ -50,7 +52,7 @@ class ResourceBase(object):
|
|||||||
elif self.schema:
|
elif self.schema:
|
||||||
try:
|
try:
|
||||||
return self.schema.parse_obj(response)
|
return self.schema.parse_obj(response)
|
||||||
except:
|
except Exception:
|
||||||
s = BaseObjectSerializer(read_transport=SQLiteTransport())
|
s = BaseObjectSerializer(read_transport=SQLiteTransport())
|
||||||
return s.recompose_base(response)
|
return s.recompose_base(response)
|
||||||
else:
|
else:
|
||||||
@@ -59,7 +61,7 @@ class ResourceBase(object):
|
|||||||
def make_request(
|
def make_request(
|
||||||
self,
|
self,
|
||||||
query: DocumentNode,
|
query: DocumentNode,
|
||||||
params: Dict = None,
|
params: Optional[Dict] = None,
|
||||||
return_type: Union[str, List, None] = None,
|
return_type: Union[str, List, None] = None,
|
||||||
schema=None,
|
schema=None,
|
||||||
parse_response: bool = True,
|
parse_response: bool = True,
|
||||||
@@ -70,13 +72,19 @@ class ResourceBase(object):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if isinstance(ex, TransportQueryError):
|
if isinstance(ex, TransportQueryError):
|
||||||
return GraphQLException(
|
return GraphQLException(
|
||||||
message=f"Failed to execute the GraphQL {self.name} request. Errors: {ex.errors}",
|
message=(
|
||||||
|
f"Failed to execute the GraphQL {self.name} request. Errors:"
|
||||||
|
f" {ex.errors}"
|
||||||
|
),
|
||||||
errors=ex.errors,
|
errors=ex.errors,
|
||||||
data=ex.data,
|
data=ex.data,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return SpeckleException(
|
return SpeckleException(
|
||||||
message=f"Failed to execute the GraphQL {self.name} request. Inner exception: {ex}",
|
message=(
|
||||||
|
f"Failed to execute the GraphQL {self.name} request. Inner"
|
||||||
|
f" exception: {ex}"
|
||||||
|
),
|
||||||
exception=ex,
|
exception=ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,16 +96,23 @@ class ResourceBase(object):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def _check_server_version_at_least(
|
def _check_server_version_at_least(
|
||||||
self, target_version: Tuple[Any, ...], unsupported_message: str = None
|
self, target_version: Tuple[Any, ...], unsupported_message: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""Use this check to guard against making unsupported requests on older servers.
|
"""Use this check to guard against making unsupported requests on older servers.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
target_version {tuple} -- the minimum server version in the format (major, minor, patch, (tag, build))
|
target_version {tuple}
|
||||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
the minimum server version in the format (major, minor, patch, (tag, build))
|
||||||
|
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||||
"""
|
"""
|
||||||
if not unsupported_message:
|
if not unsupported_message:
|
||||||
unsupported_message = f"The client method used is not supported on Speckle Server versios prior to v{'.'.join(target_version)}"
|
unsupported_message = (
|
||||||
|
"The client method used is not supported on Speckle Server versions"
|
||||||
|
f" prior to v{'.'.join(target_version)}"
|
||||||
|
)
|
||||||
|
# if version is dev, it should be supported... (or not)
|
||||||
|
if self.server_version == ("dev",):
|
||||||
|
return
|
||||||
if self.server_version and self.server_version < target_version:
|
if self.server_version and self.server_version < target_version:
|
||||||
raise UnsupportedException(unsupported_message)
|
raise UnsupportedException(unsupported_message)
|
||||||
|
|
||||||
@@ -107,8 +122,7 @@ class ResourceBase(object):
|
|||||||
"""
|
"""
|
||||||
self._check_server_version_at_least(
|
self._check_server_version_at_least(
|
||||||
(2, 6, 4),
|
(2, 6, 4),
|
||||||
(
|
"Stream invites are only supported as of Speckle Server v2.6.4. Please"
|
||||||
"Stream invites are only supported as of Speckle Server v2.6.4. "
|
" update your Speckle Server to use this method or use the"
|
||||||
"Please update your Speckle Server to use this method or use the `grant_permission` flow instead."
|
" `grant_permission` flow instead.",
|
||||||
),
|
|
||||||
)
|
)
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
import sys
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
for _, name, _ in pkgutil.iter_modules(__path__):
|
||||||
for (_, name, _) in pkgutil.iter_modules(__path__):
|
|
||||||
|
|
||||||
imported_module = import_module("." + name, package=__name__)
|
imported_module = import_module("." + name, package=__name__)
|
||||||
|
|
||||||
if hasattr(imported_module, "Resource"):
|
if hasattr(imported_module, "Resource"):
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from gql import gql
|
||||||
|
|
||||||
|
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
||||||
|
from specklepy.api.resource import ResourceBase
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
|
||||||
|
NAME = "active_user"
|
||||||
|
|
||||||
|
|
||||||
|
class Resource(ResourceBase):
|
||||||
|
"""API Access class for users"""
|
||||||
|
|
||||||
|
def __init__(self, account, basepath, client, server_version) -> None:
|
||||||
|
super().__init__(
|
||||||
|
account=account,
|
||||||
|
basepath=basepath,
|
||||||
|
client=client,
|
||||||
|
name=NAME,
|
||||||
|
server_version=server_version,
|
||||||
|
)
|
||||||
|
self.schema = User
|
||||||
|
|
||||||
|
def get(self) -> User:
|
||||||
|
"""Gets the profile of a user. If no id argument is provided,
|
||||||
|
will return the current authenticated user's profile
|
||||||
|
(as extracted from the authorization header).
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
id {str} -- the user id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User -- the retrieved user
|
||||||
|
"""
|
||||||
|
metrics.track(metrics.USER, self.account, {"name": "get"})
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query User {
|
||||||
|
activeUser {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
name
|
||||||
|
bio
|
||||||
|
company
|
||||||
|
avatar
|
||||||
|
verified
|
||||||
|
profiles
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
|
||||||
|
return self.make_request(query=query, params=params, return_type="activeUser")
|
||||||
|
|
||||||
|
def update(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
company: Optional[str] = None,
|
||||||
|
bio: Optional[str] = None,
|
||||||
|
avatar: Optional[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 @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT):
|
||||||
|
bool -- True if your profile was updated successfully
|
||||||
|
"""
|
||||||
|
metrics.track(metrics.USER, self.account, {"name": "update"})
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
def activity(
|
||||||
|
self,
|
||||||
|
limit: int = 20,
|
||||||
|
action_type: Optional[str] = None,
|
||||||
|
before: Optional[datetime] = None,
|
||||||
|
after: Optional[datetime] = None,
|
||||||
|
cursor: Optional[datetime] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get the activity from a given stream in an Activity collection.
|
||||||
|
Step into the activity `items` for the list of activity.
|
||||||
|
If no id argument is provided, will return the current authenticated user's
|
||||||
|
activity (as extracted from the authorization header).
|
||||||
|
|
||||||
|
Note: all timestamps arguments should be `datetime` of any tz as they will be
|
||||||
|
converted to UTC ISO format strings
|
||||||
|
|
||||||
|
user_id {str} -- the id of the user to get the activity from
|
||||||
|
action_type {str} -- filter results to a single action type
|
||||||
|
(eg: `commit_create` or `commit_receive`)
|
||||||
|
limit {int} -- max number of Activity items to return
|
||||||
|
before {datetime} -- latest cutoff for activity
|
||||||
|
(ie: return all activity _before_ this time)
|
||||||
|
after {datetime} -- oldest cutoff for activity
|
||||||
|
(ie: return all activity _after_ this time)
|
||||||
|
cursor {datetime} -- timestamp cursor for pagination
|
||||||
|
"""
|
||||||
|
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query UserActivity(
|
||||||
|
$action_type: String,
|
||||||
|
$before:DateTime,
|
||||||
|
$after: DateTime,
|
||||||
|
$cursor: DateTime,
|
||||||
|
$limit: Int
|
||||||
|
){
|
||||||
|
activeUser {
|
||||||
|
activity(
|
||||||
|
actionType: $action_type,
|
||||||
|
before: $before,
|
||||||
|
after: $after,
|
||||||
|
cursor: $cursor,
|
||||||
|
limit: $limit
|
||||||
|
) {
|
||||||
|
totalCount
|
||||||
|
cursor
|
||||||
|
items {
|
||||||
|
actionType
|
||||||
|
info
|
||||||
|
userId
|
||||||
|
streamId
|
||||||
|
resourceId
|
||||||
|
resourceType
|
||||||
|
message
|
||||||
|
time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"limit": limit,
|
||||||
|
"action_type": action_type,
|
||||||
|
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
||||||
|
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
||||||
|
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.make_request(
|
||||||
|
query=query,
|
||||||
|
params=params,
|
||||||
|
return_type=["activeUser", "activity"],
|
||||||
|
schema=ActivityCollection,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||||
|
"""Get all of the active user's pending stream invites
|
||||||
|
|
||||||
|
Requires Speckle Server version >= 2.6.4
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[PendingStreamCollaborator]
|
||||||
|
-- a list of pending invites for the current user
|
||||||
|
"""
|
||||||
|
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||||
|
self._check_invites_supported()
|
||||||
|
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query StreamInvites {
|
||||||
|
streamInvites{
|
||||||
|
id
|
||||||
|
token
|
||||||
|
inviteId
|
||||||
|
streamId
|
||||||
|
streamName
|
||||||
|
title
|
||||||
|
role
|
||||||
|
invitedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
company
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.make_request(
|
||||||
|
query=query,
|
||||||
|
return_type="streamInvites",
|
||||||
|
schema=PendingStreamCollaborator,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_pending_invite(
|
||||||
|
self, stream_id: str, token: Optional[str] = None
|
||||||
|
) -> Optional[PendingStreamCollaborator]:
|
||||||
|
"""Get a particular pending invite for the active user on a given stream.
|
||||||
|
If no invite_id is provided, any valid invite will be returned.
|
||||||
|
|
||||||
|
Requires Speckle Server version >= 2.6.4
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
stream_id {str} -- the id of the stream to look for invites on
|
||||||
|
token {str} -- the token of the invite to look for (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PendingStreamCollaborator
|
||||||
|
-- the invite for the given stream (or None if it isn't found)
|
||||||
|
"""
|
||||||
|
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||||
|
self._check_invites_supported()
|
||||||
|
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query StreamInvite($streamId: String!, $token: String) {
|
||||||
|
streamInvite(streamId: $streamId, token: $token) {
|
||||||
|
id
|
||||||
|
token
|
||||||
|
streamId
|
||||||
|
streamName
|
||||||
|
title
|
||||||
|
role
|
||||||
|
invitedBy {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
company
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {"streamId": stream_id}
|
||||||
|
if token:
|
||||||
|
params["token"] = token
|
||||||
|
|
||||||
|
return self.make_request(
|
||||||
|
query=query,
|
||||||
|
params=params,
|
||||||
|
return_type="streamInvite",
|
||||||
|
schema=PendingStreamCollaborator,
|
||||||
|
)
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
from gql import gql
|
from gql import gql
|
||||||
from specklepy.api.resource import ResourceBase
|
|
||||||
from specklepy.api.models import Branch
|
from specklepy.api.models import Branch
|
||||||
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
NAME = "branch"
|
NAME = "branch"
|
||||||
@@ -86,7 +89,7 @@ class Resource(ResourceBase):
|
|||||||
createdAt
|
createdAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -112,7 +115,11 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(metrics.BRANCH, self.account, {"name": "get"})
|
metrics.track(metrics.BRANCH, self.account, {"name": "get"})
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
query BranchesGet($stream_id: String!, $branches_limit: Int!, $commits_limit: Int!) {
|
query BranchesGet(
|
||||||
|
$stream_id: String!,
|
||||||
|
$branches_limit: Int!,
|
||||||
|
$commits_limit: Int!
|
||||||
|
) {
|
||||||
stream(id: $stream_id) {
|
stream(id: $stream_id) {
|
||||||
branches(limit: $branches_limit) {
|
branches(limit: $branches_limit) {
|
||||||
items {
|
items {
|
||||||
@@ -151,7 +158,11 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self, stream_id: str, branch_id: str, name: str = None, description: str = None
|
self,
|
||||||
|
stream_id: str,
|
||||||
|
branch_id: str,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""Update a branch
|
"""Update a branch
|
||||||
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
from typing import Optional, List
|
from typing import List, Optional
|
||||||
from gql import gql
|
|
||||||
from specklepy.api.resource import ResourceBase
|
|
||||||
from specklepy.api.models import Commit
|
|
||||||
from specklepy.logging import metrics
|
|
||||||
|
|
||||||
|
from gql import gql
|
||||||
|
|
||||||
|
from specklepy.api.models import Commit
|
||||||
|
from specklepy.api.resource import ResourceBase
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
|
||||||
NAME = "commit"
|
NAME = "commit"
|
||||||
|
|
||||||
@@ -115,9 +116,13 @@ class Resource(ResourceBase):
|
|||||||
Arguments:
|
Arguments:
|
||||||
stream_id {str} -- the stream you want to commit to
|
stream_id {str} -- the stream you want to commit to
|
||||||
object_id {str} -- the hash of your commit object
|
object_id {str} -- the hash of your commit object
|
||||||
branch_name {str} -- the name of the branch to commit to (defaults to "main")
|
branch_name {str}
|
||||||
message {str} -- optional: a message to give more information about the commit
|
-- the name of the branch to commit to (defaults to "main")
|
||||||
source_application{str} -- optional: the application from which the commit was created (defaults to "python")
|
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
|
parents {List[str]} -- optional: the id of the parent commits
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -126,7 +131,8 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(metrics.COMMIT, self.account, {"name": "create"})
|
metrics.track(metrics.COMMIT, self.account, {"name": "create"})
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation CommitCreate ($commit: CommitCreateInput!){ commitCreate(commit: $commit)}
|
mutation CommitCreate ($commit: CommitCreateInput!)
|
||||||
|
{ commitCreate(commit: $commit)}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
params = {
|
params = {
|
||||||
@@ -150,7 +156,8 @@ class Resource(ResourceBase):
|
|||||||
Update a commit
|
Update a commit
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
stream_id {str} -- the id of the stream that contains the commit you'd like to update
|
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
|
commit_id {str} -- the id of the commit you'd like to update
|
||||||
message {str} -- the updated commit message
|
message {str} -- the updated commit message
|
||||||
|
|
||||||
@@ -160,7 +167,8 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(metrics.COMMIT, self.account, {"name": "update"})
|
metrics.track(metrics.COMMIT, self.account, {"name": "update"})
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation CommitUpdate($commit: CommitUpdateInput!){ commitUpdate(commit: $commit)}
|
mutation CommitUpdate($commit: CommitUpdateInput!)
|
||||||
|
{ commitUpdate(commit: $commit)}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
params = {
|
params = {
|
||||||
@@ -176,7 +184,8 @@ class Resource(ResourceBase):
|
|||||||
Delete a commit
|
Delete a commit
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
stream_id {str} -- the id of the stream that contains the commit you'd like to delete
|
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
|
commit_id {str} -- the id of the commit you'd like to delete
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -185,7 +194,8 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(metrics.COMMIT, self.account, {"name": "delete"})
|
metrics.track(metrics.COMMIT, self.account, {"name": "delete"})
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation CommitDelete($commit: CommitDeleteInput!){ commitDelete(commit: $commit)}
|
mutation CommitDelete($commit: CommitDeleteInput!)
|
||||||
|
{ commitDelete(commit: $commit)}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
params = {"commit": {"streamId": stream_id, "id": commit_id}}
|
params = {"commit": {"streamId": stream_id, "id": commit_id}}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from gql import gql
|
from gql import gql
|
||||||
|
|
||||||
from specklepy.api.resource import ResourceBase
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
|
|
||||||
@@ -59,21 +61,28 @@ class Resource(ResourceBase):
|
|||||||
"""
|
"""
|
||||||
Not advised - generally, you want to use `operations.send()`.
|
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
|
Create a new object on a stream.
|
||||||
`BaseObjectSerializer.traverse_base()` function to get a valid (serialisable) object to send.
|
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
|
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:
|
Arguments:
|
||||||
stream_id {str} -- the id of the stream you want to send the object to
|
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)
|
objects {List[Dict]}
|
||||||
|
-- a list of base dictionary objects (NOTE: must be json serialisable)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str -- the id of the object
|
str -- the id of the object
|
||||||
"""
|
"""
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation ObjectCreate($object_input: ObjectCreateInput!) { objectCreate(objectInput: $object_input) }
|
mutation ObjectCreate($object_input: ObjectCreateInput!) {
|
||||||
|
objectCreate(objectInput: $object_input)
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
params = {"object_input": {"streamId": stream_id, "objects": objects}}
|
params = {"object_input": {"streamId": stream_id, "objects": objects}}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
from gql import gql
|
||||||
|
|
||||||
|
from specklepy.api.models import ActivityCollection, LimitedUser
|
||||||
|
from specklepy.api.resource import ResourceBase
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
|
||||||
|
NAME = "other_user"
|
||||||
|
|
||||||
|
|
||||||
|
class Resource(ResourceBase):
|
||||||
|
"""API Access class for other users, that are not the currently active user."""
|
||||||
|
|
||||||
|
def __init__(self, account, basepath, client, server_version) -> None:
|
||||||
|
super().__init__(
|
||||||
|
account=account,
|
||||||
|
basepath=basepath,
|
||||||
|
client=client,
|
||||||
|
name=NAME,
|
||||||
|
server_version=server_version,
|
||||||
|
)
|
||||||
|
self.schema = LimitedUser
|
||||||
|
|
||||||
|
def get(self, id: str) -> LimitedUser:
|
||||||
|
"""
|
||||||
|
Gets the profile of another user.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
id {str} -- the user id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LimitedUser -- the retrieved profile of another user
|
||||||
|
"""
|
||||||
|
metrics.track(metrics.OTHER_USER, self.account, {"name": "get"})
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query OtherUser($id: String!) {
|
||||||
|
otherUser(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
bio
|
||||||
|
company
|
||||||
|
avatar
|
||||||
|
verified
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {"id": id}
|
||||||
|
|
||||||
|
return self.make_request(query=query, params=params, return_type="otherUser")
|
||||||
|
|
||||||
|
def search(
|
||||||
|
self, search_query: str, limit: int = 25
|
||||||
|
) -> Union[List[LimitedUser], SpeckleException]:
|
||||||
|
"""Searches for user by name or email. The search query must be at least
|
||||||
|
3 characters long
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
search_query {str} -- a string to search for
|
||||||
|
limit {int} -- the maximum number of results to return
|
||||||
|
Returns:
|
||||||
|
List[LimitedUser] -- a list of User objects that match the search query
|
||||||
|
"""
|
||||||
|
if len(search_query) < 3:
|
||||||
|
return SpeckleException(
|
||||||
|
message="User search query must be at least 3 characters"
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics.track(metrics.OTHER_USER, self.account, {"name": "search"})
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query UserSearch($search_query: String!, $limit: Int!) {
|
||||||
|
userSearch(query: $search_query, limit: $limit) {
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
bio
|
||||||
|
company
|
||||||
|
avatar
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
params = {"search_query": search_query, "limit": limit}
|
||||||
|
|
||||||
|
return self.make_request(
|
||||||
|
query=query, params=params, return_type=["userSearch", "items"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def activity(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
limit: int = 20,
|
||||||
|
action_type: Optional[str] = None,
|
||||||
|
before: Optional[datetime] = None,
|
||||||
|
after: Optional[datetime] = None,
|
||||||
|
cursor: Optional[datetime] = None,
|
||||||
|
) -> ActivityCollection:
|
||||||
|
"""
|
||||||
|
Get the activity from a given stream in an Activity collection.
|
||||||
|
Step into the activity `items` for the list of activity.
|
||||||
|
|
||||||
|
Note: all timestamps arguments should be `datetime` of
|
||||||
|
any tz as they will be converted to UTC ISO format strings
|
||||||
|
|
||||||
|
user_id {str} -- the id of the user to get the activity from
|
||||||
|
action_type {str} -- filter results to a single action type
|
||||||
|
(eg: `commit_create` or `commit_receive`)
|
||||||
|
limit {int} -- max number of Activity items to return
|
||||||
|
before {datetime} -- latest cutoff for activity
|
||||||
|
(ie: return all activity _before_ this time)
|
||||||
|
after {datetime} -- oldest cutoff for activity
|
||||||
|
(ie: return all activity _after_ this time)
|
||||||
|
cursor {datetime} -- timestamp cursor for pagination
|
||||||
|
"""
|
||||||
|
|
||||||
|
query = gql(
|
||||||
|
"""
|
||||||
|
query UserActivity(
|
||||||
|
$user_id: String!,
|
||||||
|
$action_type: String,
|
||||||
|
$before:DateTime,
|
||||||
|
$after: DateTime,
|
||||||
|
$cursor: DateTime,
|
||||||
|
$limit: Int
|
||||||
|
){
|
||||||
|
otherUser(id: $user_id) {
|
||||||
|
activity(
|
||||||
|
actionType: $action_type,
|
||||||
|
before: $before,
|
||||||
|
after: $after,
|
||||||
|
cursor: $cursor,
|
||||||
|
limit: $limit
|
||||||
|
) {
|
||||||
|
totalCount
|
||||||
|
cursor
|
||||||
|
items {
|
||||||
|
actionType
|
||||||
|
info
|
||||||
|
userId
|
||||||
|
streamId
|
||||||
|
resourceId
|
||||||
|
resourceType
|
||||||
|
message
|
||||||
|
time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"limit": limit,
|
||||||
|
"action_type": action_type,
|
||||||
|
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
||||||
|
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
||||||
|
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.make_request(
|
||||||
|
query=query,
|
||||||
|
params=params,
|
||||||
|
return_type=["otherUser", "activity"],
|
||||||
|
schema=ActivityCollection,
|
||||||
|
)
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any, Dict, List, Tuple
|
||||||
|
|
||||||
from gql import gql
|
from gql import gql
|
||||||
|
|
||||||
from specklepy.api.models import ServerInfo
|
from specklepy.api.models import ServerInfo
|
||||||
from specklepy.api.resource import ResourceBase
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
from specklepy.logging.exceptions import GraphQLException
|
from specklepy.logging.exceptions import GraphQLException
|
||||||
|
|
||||||
|
|
||||||
NAME = "server"
|
NAME = "server"
|
||||||
|
|
||||||
|
|
||||||
@@ -65,8 +66,8 @@ class Resource(ResourceBase):
|
|||||||
"""Get the server version
|
"""Get the server version
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple -- the server version in the format (major, minor, patch, (tag, build))
|
the server version in the format (major, minor, patch, (tag, build))
|
||||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||||
"""
|
"""
|
||||||
# not tracking as it will be called along with other mutations / queries as a check
|
# not tracking as it will be called along with other mutations / queries as a check
|
||||||
query = gql(
|
query = gql(
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from deprecated import deprecated
|
from deprecated import deprecated
|
||||||
from gql import gql
|
from gql import gql
|
||||||
from specklepy.logging import metrics
|
|
||||||
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, Stream
|
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, Stream
|
||||||
from specklepy.api.resource import ResourceBase
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.logging.exceptions import UnsupportedException, SpeckleException
|
from specklepy.logging import metrics
|
||||||
|
from specklepy.logging.exceptions import SpeckleException, UnsupportedException
|
||||||
|
|
||||||
NAME = "stream"
|
NAME = "stream"
|
||||||
|
|
||||||
@@ -152,7 +153,8 @@ class Resource(ResourceBase):
|
|||||||
Arguments:
|
Arguments:
|
||||||
name {str} -- the name of the string
|
name {str} -- the name of the string
|
||||||
description {str} -- a short description of the stream
|
description {str} -- a short description of the stream
|
||||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
is_public {bool}
|
||||||
|
-- whether or not the stream can be viewed by anyone with the id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
id {str} -- the id of the newly created stream
|
id {str} -- the id of the newly created stream
|
||||||
@@ -175,7 +177,11 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self, id: str, name: str = None, description: str = None, is_public: bool = None
|
self,
|
||||||
|
id: str,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
is_public: Optional[bool] = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Update an existing stream
|
"""Update an existing stream
|
||||||
|
|
||||||
@@ -183,7 +189,8 @@ class Resource(ResourceBase):
|
|||||||
id {str} -- the id of the stream to be updated
|
id {str} -- the id of the stream to be updated
|
||||||
name {str} -- the name of the string
|
name {str} -- the name of the string
|
||||||
description {str} -- a short description of the stream
|
description {str} -- a short description of the stream
|
||||||
is_public {bool} -- whether or not the stream can be viewed by anyone with the id
|
is_public {bool}
|
||||||
|
-- whether or not the stream can be viewed by anyone with the id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool -- whether the stream update was successful
|
bool -- whether the stream update was successful
|
||||||
@@ -255,7 +262,12 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(metrics.STREAM, self.account, {"name": "search"})
|
metrics.track(metrics.STREAM, self.account, {"name": "search"})
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
query StreamSearch($search_query: String!,$limit: Int!, $branch_limit:Int!, $commit_limit:Int!) {
|
query StreamSearch(
|
||||||
|
$search_query: String!,
|
||||||
|
$limit: Int!,
|
||||||
|
$branch_limit:Int!,
|
||||||
|
$commit_limit:Int!
|
||||||
|
) {
|
||||||
streams(query: $search_query, limit: $limit) {
|
streams(query: $search_query, limit: $limit) {
|
||||||
items {
|
items {
|
||||||
id
|
id
|
||||||
@@ -314,7 +326,8 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
stream_id {str} -- the id of the stream to favorite / unfavorite
|
stream_id {str} -- the id of the stream to favorite / unfavorite
|
||||||
favorited {bool} -- whether to favorite (True) or unfavorite (False) the stream
|
favorited {bool}
|
||||||
|
-- whether to favorite (True) or unfavorite (False) the stream
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Stream -- the stream with its `id`, `name`, and `favoritedDate`
|
Stream -- the stream with its `id`, `name`, and `favoritedDate`
|
||||||
@@ -345,8 +358,8 @@ class Resource(ResourceBase):
|
|||||||
@deprecated(
|
@deprecated(
|
||||||
version="2.6.4",
|
version="2.6.4",
|
||||||
reason=(
|
reason=(
|
||||||
"As of Speckle Server v2.6.4, this method is deprecated. "
|
"As of Speckle Server v2.6.4, this method is deprecated. Users need to be"
|
||||||
"Users need to be invited and accept the invite before being added to a stream"
|
" invited and accept the invite before being added to a stream"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def grant_permission(self, stream_id: str, user_id: str, role: str):
|
def grant_permission(self, stream_id: str, user_id: str, role: str):
|
||||||
@@ -363,18 +376,23 @@ class Resource(ResourceBase):
|
|||||||
bool -- True if the operation was successful
|
bool -- True if the operation was successful
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.PERMISSION, self.account, {"name": "add", "role": role})
|
metrics.track(metrics.PERMISSION, self.account, {"name": "add", "role": role})
|
||||||
if self.server_version and self.server_version >= (2, 6, 4):
|
# we're checking for the actual version info, and if the version is 'dev' we treat it
|
||||||
|
# as an up to date instance
|
||||||
|
if self.server_version and (
|
||||||
|
self.server_version == ("dev",) or self.server_version >= (2, 6, 4)
|
||||||
|
):
|
||||||
raise UnsupportedException(
|
raise UnsupportedException(
|
||||||
(
|
"Server mutation `grant_permission` is no longer supported as of"
|
||||||
"Server mutation `grant_permission` is no longer supported as of Speckle Server v2.6.4. "
|
" Speckle Server v2.6.4. Please use the new `update_permission` method"
|
||||||
"Please use the new `update_permission` method to change an existing user's permission "
|
" to change an existing user's permission or use the `invite` method to"
|
||||||
"or use the `invite` method to invite a user to a stream."
|
" invite a user to a stream."
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation StreamGrantPermission($permission_params: StreamGrantPermissionInput !) {
|
mutation StreamGrantPermission(
|
||||||
|
$permission_params: StreamGrantPermissionInput !
|
||||||
|
) {
|
||||||
streamGrantPermission(permissionParams: $permission_params)
|
streamGrantPermission(permissionParams: $permission_params)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -407,7 +425,8 @@ class Resource(ResourceBase):
|
|||||||
stream_id {str} -- the stream id from which to get the pending invites
|
stream_id {str} -- the stream id from which to get the pending invites
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[PendingStreamCollaborator] -- a list of pending invites for the specified stream
|
List[PendingStreamCollaborator]
|
||||||
|
-- a list of pending invites for the specified stream
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||||
self._check_invites_supported()
|
self._check_invites_supported()
|
||||||
@@ -453,10 +472,10 @@ class Resource(ResourceBase):
|
|||||||
def invite(
|
def invite(
|
||||||
self,
|
self,
|
||||||
stream_id: str,
|
stream_id: str,
|
||||||
email: str = None,
|
email: Optional[str] = None,
|
||||||
user_id: str = None,
|
user_id: Optional[str] = None,
|
||||||
role: str = "stream:contributor", # should default be reviewer?
|
role: str = "stream:contributor", # should default be reviewer?
|
||||||
message: str = None,
|
message: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""Invite someone to a stream using either their email or user id
|
"""Invite someone to a stream using either their email or user id
|
||||||
|
|
||||||
@@ -466,8 +485,10 @@ class Resource(ResourceBase):
|
|||||||
stream_id {str} -- the id of the stream to invite the user to
|
stream_id {str} -- the id of the stream to invite the user to
|
||||||
email {str} -- the email of the user to invite (use this OR `user_id`)
|
email {str} -- the email of the user to invite (use this OR `user_id`)
|
||||||
user_id {str} -- the id of the user to invite (use this OR `email`)
|
user_id {str} -- the id of the user to invite (use this OR `email`)
|
||||||
role {str} -- the role to assing to the user (defaults to `stream:contributor`)
|
role {str}
|
||||||
message {str} -- a message to send along with this invite to the specified user
|
-- the role to assign to the user (defaults to `stream:contributor`)
|
||||||
|
message {str}
|
||||||
|
-- a message to send along with this invite to the specified user
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool -- True if the operation was successful
|
bool -- True if the operation was successful
|
||||||
@@ -477,7 +498,8 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
if email is None and user_id is None:
|
if email is None and user_id is None:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
"You must provide either an email or a user id to use the `stream.invite` method"
|
"You must provide either an email or a user id to use the"
|
||||||
|
" `stream.invite` method"
|
||||||
)
|
)
|
||||||
|
|
||||||
query = gql(
|
query = gql(
|
||||||
@@ -507,9 +529,9 @@ class Resource(ResourceBase):
|
|||||||
def invite_batch(
|
def invite_batch(
|
||||||
self,
|
self,
|
||||||
stream_id: str,
|
stream_id: str,
|
||||||
emails: List[str] = None,
|
emails: Optional[List[str]] = None,
|
||||||
user_ids: List[None] = None,
|
user_ids: Optional[List[None]] = None,
|
||||||
message: str = None,
|
message: Optional[str] = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Invite a batch of users to a specified stream.
|
"""Invite a batch of users to a specified stream.
|
||||||
|
|
||||||
@@ -517,9 +539,12 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
stream_id {str} -- the id of the stream to invite the user to
|
stream_id {str} -- the id of the stream to invite the user to
|
||||||
emails {List[str]} -- the email of the user to invite (use this and/or `user_ids`)
|
emails {List[str]}
|
||||||
user_id {List[str]} -- the id of the user to invite (use this and/or `emails`)
|
-- the email of the user to invite (use this and/or `user_ids`)
|
||||||
message {str} -- a message to send along with this invite to the specified user
|
user_id {List[str]}
|
||||||
|
-- the id of the user to invite (use this and/or `emails`)
|
||||||
|
message {str}
|
||||||
|
-- a message to send along with this invite to the specified user
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool -- True if the operation was successful
|
bool -- True if the operation was successful
|
||||||
@@ -528,7 +553,8 @@ class Resource(ResourceBase):
|
|||||||
self._check_invites_supported()
|
self._check_invites_supported()
|
||||||
if emails is None and user_ids is None:
|
if emails is None and user_ids is None:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
"You must provide either an email or a user id to use the `stream.invite` method"
|
"You must provide either an email or a user id to use the"
|
||||||
|
" `stream.invite` method"
|
||||||
)
|
)
|
||||||
|
|
||||||
query = gql(
|
query = gql(
|
||||||
@@ -598,7 +624,8 @@ class Resource(ResourceBase):
|
|||||||
Requires Speckle Server version >= 2.6.4
|
Requires Speckle Server version >= 2.6.4
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
stream_id {str} -- the id of the stream for which the user has a pending invite
|
stream_id {str}
|
||||||
|
-- the id of the stream for which the user has a pending invite
|
||||||
token {str} -- the token of the invite to use
|
token {str} -- the token of the invite to use
|
||||||
accept {bool} -- whether or not to accept the invite (defaults to True)
|
accept {bool} -- whether or not to accept the invite (defaults to True)
|
||||||
|
|
||||||
@@ -610,7 +637,11 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation StreamInviteUse($accept: Boolean!, $streamId: String!, $token: String!) {
|
mutation StreamInviteUse(
|
||||||
|
$accept: Boolean!,
|
||||||
|
$streamId: String!,
|
||||||
|
$token: String!
|
||||||
|
) {
|
||||||
streamInviteUse(accept: $accept, streamId: $streamId, token: $token)
|
streamInviteUse(accept: $accept, streamId: $streamId, token: $token)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -641,16 +672,19 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(
|
metrics.track(
|
||||||
metrics.PERMISSION, self.account, {"name": "update", "role": role}
|
metrics.PERMISSION, self.account, {"name": "update", "role": role}
|
||||||
)
|
)
|
||||||
if self.server_version and self.server_version < (2, 6, 4):
|
if self.server_version and (
|
||||||
|
self.server_version != ("dev",) and self.server_version < (2, 6, 4)
|
||||||
|
):
|
||||||
raise UnsupportedException(
|
raise UnsupportedException(
|
||||||
(
|
"Server mutation `update_permission` is only supported as of Speckle"
|
||||||
"Server mutation `update_permission` is only supported as of Speckle Server v2.6.4. "
|
" Server v2.6.4. Please update your Speckle Server to use this method"
|
||||||
"Please update your Speckle Server to use this method or use the `grant_permission` method instead."
|
" or use the `grant_permission` method instead."
|
||||||
)
|
|
||||||
)
|
)
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation StreamUpdatePermission($permission_params: StreamUpdatePermissionInput !) {
|
mutation StreamUpdatePermission(
|
||||||
|
$permission_params: StreamUpdatePermissionInput!
|
||||||
|
) {
|
||||||
streamUpdatePermission(permissionParams: $permission_params)
|
streamUpdatePermission(permissionParams: $permission_params)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -684,7 +718,9 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(metrics.PERMISSION, self.account, {"name": "revoke"})
|
metrics.track(metrics.PERMISSION, self.account, {"name": "revoke"})
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
mutation StreamRevokePermission($permission_params: StreamRevokePermissionInput !) {
|
mutation StreamRevokePermission(
|
||||||
|
$permission_params: StreamRevokePermissionInput!
|
||||||
|
) {
|
||||||
streamRevokePermission(permissionParams: $permission_params)
|
streamRevokePermission(permissionParams: $permission_params)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -702,29 +738,48 @@ class Resource(ResourceBase):
|
|||||||
def activity(
|
def activity(
|
||||||
self,
|
self,
|
||||||
stream_id: str,
|
stream_id: str,
|
||||||
action_type: str = None,
|
action_type: Optional[str] = None,
|
||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
before: datetime = None,
|
before: Optional[datetime] = None,
|
||||||
after: datetime = None,
|
after: Optional[datetime] = None,
|
||||||
cursor: datetime = None,
|
cursor: Optional[datetime] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
|
Get the activity from a given stream in an Activity collection.
|
||||||
|
Step into the activity `items` for the list of activity.
|
||||||
|
|
||||||
Note: all timestamps arguments should be `datetime` of any tz as they will be converted to UTC ISO format strings
|
Note: all timestamps arguments should be `datetime` of any tz
|
||||||
|
as they will be converted to UTC ISO format strings
|
||||||
|
|
||||||
stream_id {str} -- the id of the stream to get activity from
|
stream_id {str} -- the id of the stream to get activity from
|
||||||
action_type {str} -- filter results to a single action type (eg: `commit_create` or `commit_receive`)
|
action_type {str}
|
||||||
|
-- filter results to a single action type
|
||||||
|
(eg: `commit_create` or `commit_receive`)
|
||||||
limit {int} -- max number of Activity items to return
|
limit {int} -- max number of Activity items to return
|
||||||
before {datetime} -- latest cutoff for activity (ie: return all activity _before_ this time)
|
before {datetime}
|
||||||
after {datetime} -- oldest cutoff for activity (ie: return all activity _after_ this time)
|
-- latest cutoff for activity (ie: return all activity _before_ this time)
|
||||||
|
after {datetime}
|
||||||
|
-- oldest cutoff for activity (ie: return all activity _after_ this time)
|
||||||
cursor {datetime} -- timestamp cursor for pagination
|
cursor {datetime} -- timestamp cursor for pagination
|
||||||
"""
|
"""
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
query StreamActivity($stream_id: String!, $action_type: String, $before:DateTime, $after: DateTime, $cursor: DateTime, $limit: Int){
|
query StreamActivity(
|
||||||
|
$stream_id: String!,
|
||||||
|
$action_type: String,
|
||||||
|
$before:DateTime,
|
||||||
|
$after: DateTime,
|
||||||
|
$cursor: DateTime,
|
||||||
|
$limit: Int
|
||||||
|
){
|
||||||
stream(id: $stream_id) {
|
stream(id: $stream_id) {
|
||||||
activity(actionType: $action_type, before: $before, after: $after, cursor: $cursor, limit: $limit) {
|
activity(
|
||||||
|
actionType: $action_type,
|
||||||
|
before: $before,
|
||||||
|
after: $after,
|
||||||
|
cursor: $cursor,
|
||||||
|
limit: $limit
|
||||||
|
) {
|
||||||
totalCount
|
totalCount
|
||||||
cursor
|
cursor
|
||||||
items {
|
items {
|
||||||
@@ -757,8 +812,9 @@ class Resource(ResourceBase):
|
|||||||
}
|
}
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
"Could not get stream activity - `before`, `after`, and `cursor` must be in `datetime` format if provided",
|
"Could not get stream activity - `before`, `after`, and `cursor` must"
|
||||||
ValueError,
|
" be in `datetime` format if provided",
|
||||||
|
ValueError(),
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
return self.make_request(
|
return self.make_request(
|
||||||
+27
-13
@@ -1,7 +1,9 @@
|
|||||||
from typing import Callable, Dict, List, Union
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from typing import Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
from gql import gql
|
from gql import gql
|
||||||
from graphql import DocumentNode
|
from graphql import DocumentNode
|
||||||
|
|
||||||
from specklepy.api.resource import ResourceBase
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.api.resources.stream import Stream
|
from specklepy.api.resources.stream import Stream
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
@@ -34,11 +36,13 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@check_wsclient
|
@check_wsclient
|
||||||
async def stream_added(self, callback: Callable = None):
|
async def stream_added(self, callback: Optional[Callable] = None):
|
||||||
"""Subscribes to new stream added event for your profile. Use this to display an up-to-date list of streams.
|
"""Subscribes to new stream added event for your profile.
|
||||||
|
Use this to display an up-to-date list of streams.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
callback {Callable[Stream]} -- a function that takes the updated stream as an argument and executes each time a stream is added
|
callback {Callable[Stream]} -- a function that takes the updated stream
|
||||||
|
as an argument and executes each time a stream is added
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Stream -- the update stream
|
Stream -- the update stream
|
||||||
@@ -53,12 +57,16 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@check_wsclient
|
@check_wsclient
|
||||||
async def stream_updated(self, id: str, callback: Callable = None):
|
async def stream_updated(self, id: str, callback: Optional[Callable] = None):
|
||||||
"""Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
"""
|
||||||
|
Subscribes to stream updated event.
|
||||||
|
Use this in clients/components that pertain only to this stream.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the stream id of the stream to subscribe to
|
id {str} -- the stream id of the stream to subscribe to
|
||||||
callback {Callable[Stream]} -- a function that takes the updated stream as an argument and executes each time the stream is updated
|
callback {Callable[Stream]}
|
||||||
|
-- a function that takes the updated stream
|
||||||
|
as an argument and executes each time the stream is updated
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Stream -- the update stream
|
Stream -- the update stream
|
||||||
@@ -79,11 +87,17 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@check_wsclient
|
@check_wsclient
|
||||||
async def stream_removed(self, callback: Callable = None):
|
async def stream_removed(self, callback: Optional[Callable] = None):
|
||||||
"""Subscribes to stream removed event for your profile. Use this to display an up-to-date list of streams for your profile. NOTE: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of revokedBy in the payload.
|
"""Subscribes to stream removed event for your profile.
|
||||||
|
Use this to display an up-to-date list of streams for your profile.
|
||||||
|
NOTE: If someone revokes your permissions on a stream,
|
||||||
|
this subscription will be triggered with an extra value of revokedBy
|
||||||
|
in the payload.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
callback {Callable[Dict]} -- a function that takes the returned dict as an argument and executes each time a stream is removed
|
callback {Callable[Dict]}
|
||||||
|
-- a function that takes the returned dict as an argument
|
||||||
|
and executes each time a stream is removed
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict -- dict containing 'id' of stream removed and optionally 'revokedBy'
|
dict -- dict containing 'id' of stream removed and optionally 'revokedBy'
|
||||||
@@ -105,9 +119,9 @@ class Resource(ResourceBase):
|
|||||||
async def subscribe(
|
async def subscribe(
|
||||||
self,
|
self,
|
||||||
query: DocumentNode,
|
query: DocumentNode,
|
||||||
params: Dict = None,
|
params: Optional[Dict] = None,
|
||||||
callback: Callable = None,
|
callback: Optional[Callable] = None,
|
||||||
return_type: Union[str, List] = None,
|
return_type: Optional[Union[str, List]] = None,
|
||||||
schema=None,
|
schema=None,
|
||||||
parse_response: bool = True,
|
parse_response: bool = True,
|
||||||
):
|
):
|
||||||
@@ -1,13 +1,22 @@
|
|||||||
from typing import List, Optional, Union
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
from deprecated import deprecated
|
||||||
from gql import gql
|
from gql import gql
|
||||||
|
|
||||||
|
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
||||||
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.api.resource import ResourceBase
|
|
||||||
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
|
||||||
|
|
||||||
NAME = "user"
|
NAME = "user"
|
||||||
|
|
||||||
|
DEPRECATION_VERSION = "2.9.0"
|
||||||
|
DEPRECATION_TEXT = (
|
||||||
|
"The user resource is deprecated, please use the active_user or other_user"
|
||||||
|
" resources"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Resource(ResourceBase):
|
class Resource(ResourceBase):
|
||||||
"""API Access class for users"""
|
"""API Access class for users"""
|
||||||
@@ -22,8 +31,12 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
self.schema = User
|
self.schema = User
|
||||||
|
|
||||||
def get(self, id: str = None) -> User:
|
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||||
"""Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
def get(self, id: Optional[str] = None) -> User:
|
||||||
|
"""
|
||||||
|
Gets the profile of a user.
|
||||||
|
If no id argument is provided, will return the current authenticated
|
||||||
|
user's profile (as extracted from the authorization header).
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the user id
|
id {str} -- the user id
|
||||||
@@ -54,10 +67,13 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
return self.make_request(query=query, params=params, return_type="user")
|
return self.make_request(query=query, params=params, return_type="user")
|
||||||
|
|
||||||
|
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||||
def search(
|
def search(
|
||||||
self, search_query: str, limit: int = 25
|
self, search_query: str, limit: int = 25
|
||||||
) -> Union[List[User], SpeckleException]:
|
) -> Union[List[User], SpeckleException]:
|
||||||
"""Searches for user by name or email. The search query must be at least 3 characters long
|
"""
|
||||||
|
Searches for user by name or email.
|
||||||
|
The search query must be at least 3 characters long
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
search_query {str} -- a string to search for
|
search_query {str} -- a string to search for
|
||||||
@@ -93,8 +109,13 @@ class Resource(ResourceBase):
|
|||||||
query=query, params=params, return_type=["userSearch", "items"]
|
query=query, params=params, return_type=["userSearch", "items"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||||
def update(
|
def update(
|
||||||
self, name: str = None, company: str = None, bio: str = None, avatar: str = None
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
company: Optional[str] = None,
|
||||||
|
bio: Optional[str] = None,
|
||||||
|
avatar: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""Updates your user profile. All arguments are optional.
|
"""Updates your user profile. All arguments are optional.
|
||||||
|
|
||||||
@@ -121,41 +142,63 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
if not params["user"]:
|
if not params["user"]:
|
||||||
return SpeckleException(
|
return SpeckleException(
|
||||||
message="You must provide at least one field to update your user profile"
|
message=(
|
||||||
|
"You must provide at least one field to update your user profile"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.make_request(
|
return self.make_request(
|
||||||
query=query, params=params, return_type="userUpdate", parse_response=False
|
query=query, params=params, return_type="userUpdate", parse_response=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||||
def activity(
|
def activity(
|
||||||
self,
|
self,
|
||||||
user_id: str = None,
|
user_id: Optional[str] = None,
|
||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
action_type: str = None,
|
action_type: Optional[str] = None,
|
||||||
before: datetime = None,
|
before: Optional[datetime] = None,
|
||||||
after: datetime = None,
|
after: Optional[datetime] = None,
|
||||||
cursor: datetime = None,
|
cursor: Optional[datetime] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
|
Get the activity from a given stream in an Activity collection.
|
||||||
If no id argument is provided, will return the current authenticated user's activity (as extracted from the authorization header).
|
Step into the activity `items` for the list of activity.
|
||||||
|
If no id argument is provided, will return the current authenticated
|
||||||
|
user's activity (as extracted from the authorization header).
|
||||||
|
|
||||||
Note: all timestamps arguments should be `datetime` of any tz as they will be converted to UTC ISO format strings
|
Note: all timestamps arguments should be `datetime` of any tz as
|
||||||
|
they will be converted to UTC ISO format strings
|
||||||
|
|
||||||
user_id {str} -- the id of the user to get the activity from
|
user_id {str} -- the id of the user to get the activity from
|
||||||
action_type {str} -- filter results to a single action type (eg: `commit_create` or `commit_receive`)
|
action_type {str} -- filter results to a single action type
|
||||||
|
(eg: `commit_create` or `commit_receive`)
|
||||||
limit {int} -- max number of Activity items to return
|
limit {int} -- max number of Activity items to return
|
||||||
before {datetime} -- latest cutoff for activity (ie: return all activity _before_ this time)
|
before {datetime}
|
||||||
after {datetime} -- oldest cutoff for activity (ie: return all activity _after_ this time)
|
-- latest cutoff for activity (ie: return all activity _before_ this time)
|
||||||
|
after {datetime}
|
||||||
|
-- oldest cutoff for activity (ie: return all activity _after_ this time)
|
||||||
cursor {datetime} -- timestamp cursor for pagination
|
cursor {datetime} -- timestamp cursor for pagination
|
||||||
"""
|
"""
|
||||||
|
|
||||||
query = gql(
|
query = gql(
|
||||||
"""
|
"""
|
||||||
query UserActivity($user_id: String, $action_type: String, $before:DateTime, $after: DateTime, $cursor: DateTime, $limit: Int){
|
query UserActivity(
|
||||||
|
$user_id: String,
|
||||||
|
$action_type: String,
|
||||||
|
$before:DateTime,
|
||||||
|
$after: DateTime,
|
||||||
|
$cursor: DateTime,
|
||||||
|
$limit: Int
|
||||||
|
){
|
||||||
user(id: $user_id) {
|
user(id: $user_id) {
|
||||||
activity(actionType: $action_type, before: $before, after: $after, cursor: $cursor, limit: $limit) {
|
activity(
|
||||||
|
actionType: $action_type,
|
||||||
|
before: $before,
|
||||||
|
after: $after,
|
||||||
|
cursor: $cursor,
|
||||||
|
limit: $limit
|
||||||
|
) {
|
||||||
totalCount
|
totalCount
|
||||||
cursor
|
cursor
|
||||||
items {
|
items {
|
||||||
@@ -190,13 +233,15 @@ class Resource(ResourceBase):
|
|||||||
schema=ActivityCollection,
|
schema=ActivityCollection,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||||
"""Get all of the active user's pending stream invites
|
"""Get all of the active user's pending stream invites
|
||||||
|
|
||||||
Requires Speckle Server version >= 2.6.4
|
Requires Speckle Server version >= 2.6.4
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[PendingStreamCollaborator] -- a list of pending invites for the current user
|
List[PendingStreamCollaborator]
|
||||||
|
-- a list of pending invites for the current user
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||||
self._check_invites_supported()
|
self._check_invites_supported()
|
||||||
@@ -229,8 +274,9 @@ class Resource(ResourceBase):
|
|||||||
schema=PendingStreamCollaborator,
|
schema=PendingStreamCollaborator,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||||
def get_pending_invite(
|
def get_pending_invite(
|
||||||
self, stream_id: str, token: str = None
|
self, stream_id: str, token: Optional[str] = None
|
||||||
) -> Optional[PendingStreamCollaborator]:
|
) -> Optional[PendingStreamCollaborator]:
|
||||||
"""Get a particular pending invite for the active user on a given stream.
|
"""Get a particular pending invite for the active user on a given stream.
|
||||||
If no invite_id is provided, any valid invite will be returned.
|
If no invite_id is provided, any valid invite will be returned.
|
||||||
@@ -242,7 +288,8 @@ class Resource(ResourceBase):
|
|||||||
token {str} -- the token of the invite to look for (optional)
|
token {str} -- the token of the invite to look for (optional)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PendingStreamCollaborator -- the invite for the given stream (or None if it isn't found)
|
PendingStreamCollaborator
|
||||||
|
-- the invite for the given stream (or None if it isn't found)
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||||
self._check_invites_supported()
|
self._check_invites_supported()
|
||||||
@@ -1,23 +1,28 @@
|
|||||||
|
from urllib.parse import unquote, urlparse
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
from urllib.parse import urlparse, unquote
|
|
||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
from specklepy.api.credentials import (
|
from specklepy.api.credentials import (
|
||||||
Account,
|
Account,
|
||||||
get_account_from_token,
|
get_account_from_token,
|
||||||
get_local_accounts,
|
get_local_accounts,
|
||||||
)
|
)
|
||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.transports.server.server import ServerTransport
|
|
||||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||||
|
from specklepy.transports.server.server import ServerTransport
|
||||||
|
|
||||||
|
|
||||||
class StreamWrapper:
|
class StreamWrapper:
|
||||||
"""
|
"""
|
||||||
The `StreamWrapper` gives you some handy helpers to deal with urls and get authenticated clients and transports.
|
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
|
Construct a `StreamWrapper` with a stream, branch, commit, or object URL.
|
||||||
in the wrapper. If you have local accounts on the machine, you can use the `get_account` and `get_client` methods
|
The corresponding ids will be stored
|
||||||
to get a local account for the server. You can also pass a token into `get_client` if you don't have a corresponding
|
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.
|
local account for the server.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -45,7 +50,10 @@ class StreamWrapper:
|
|||||||
_account: Account = None
|
_account: Account = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )"
|
return (
|
||||||
|
f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type:"
|
||||||
|
f" {self.type} )"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
@@ -71,7 +79,8 @@ class StreamWrapper:
|
|||||||
|
|
||||||
if not segments or len(segments) < 2:
|
if not segments or len(segments) < 2:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
|
f"Cannot parse {url} into a stream wrapper class - invalid URL"
|
||||||
|
" provided."
|
||||||
)
|
)
|
||||||
|
|
||||||
while segments:
|
while segments:
|
||||||
@@ -90,7 +99,8 @@ class StreamWrapper:
|
|||||||
self.commit_id = segments.pop(0)
|
self.commit_id = segments.pop(0)
|
||||||
else:
|
else:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
|
f"Cannot parse {url} into a stream wrapper class - invalid URL"
|
||||||
|
" provided."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.stream_id:
|
if not self.stream_id:
|
||||||
@@ -104,13 +114,18 @@ class StreamWrapper:
|
|||||||
|
|
||||||
def get_account(self, token: str = None) -> Account:
|
def get_account(self, token: str = None) -> Account:
|
||||||
"""
|
"""
|
||||||
Gets an account object for this server from the local accounts db (added via Speckle Manager or a json file)
|
Gets an account object for this server from the local accounts db
|
||||||
|
(added via Speckle Manager or a json file)
|
||||||
"""
|
"""
|
||||||
if self._account and self._account.token:
|
if self._account and self._account.token:
|
||||||
return self._account
|
return self._account
|
||||||
|
|
||||||
self._account = next(
|
self._account = next(
|
||||||
(a for a in get_local_accounts() if self.host in a.serverInfo.url),
|
(
|
||||||
|
a
|
||||||
|
for a in get_local_accounts()
|
||||||
|
if self.host == urlparse(a.serverInfo.url).netloc
|
||||||
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,14 +139,18 @@ class StreamWrapper:
|
|||||||
|
|
||||||
def get_client(self, token: str = None) -> SpeckleClient:
|
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
|
Gets an authenticated client for this server.
|
||||||
machine. If no account is found and no token is provided, an unauthenticated client is returned.
|
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:
|
Arguments:
|
||||||
token {str} -- optional token if no local account is available (defaults to None)
|
token {str}
|
||||||
|
-- optional token if no local account is available (defaults to None)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SpeckleClient -- authenticated with a corresponding local account or the provided token
|
SpeckleClient
|
||||||
|
-- authenticated with a corresponding local account or the provided token
|
||||||
"""
|
"""
|
||||||
if self._client and token is None:
|
if self._client and token is None:
|
||||||
return self._client
|
return self._client
|
||||||
@@ -155,11 +174,14 @@ class StreamWrapper:
|
|||||||
|
|
||||||
def get_transport(self, token: str = None) -> ServerTransport:
|
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
|
Gets a server transport for this stream using an authenticated client.
|
||||||
server and the client was not authenticated with a token, this will throw an exception.
|
If there is no local account for this
|
||||||
|
server and the client was not authenticated with a token,
|
||||||
|
this will throw an exception.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ServerTransport -- constructed for this stream with a pre-authenticated client
|
ServerTransport -- constructed for this stream
|
||||||
|
with a pre-authenticated client
|
||||||
"""
|
"""
|
||||||
if not self._account or not self._account.token:
|
if not self._account or not self._account.token:
|
||||||
self.get_account(token)
|
self.get_account(token)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""
|
||||||
|
This is the Core SDK module of `specklepy`.
|
||||||
|
|
||||||
|
This module should be kept in sync with the functionalities of our other SDKs especially
|
||||||
|
C# Core https://github.com/specklesystems/speckle-sharp/tree/main/Core/Core
|
||||||
|
"""
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Common helpers module for Core."""
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
"""
|
||||||
|
Provides uniform and consistent path helpers for `specklepy`
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
|
||||||
|
_user_data_env_var = "SPECKLE_USERDATA_PATH"
|
||||||
|
|
||||||
|
|
||||||
|
def _path() -> Optional[Path]:
|
||||||
|
"""Read the user data path override setting."""
|
||||||
|
path_override = os.environ.get(_user_data_env_var)
|
||||||
|
if path_override:
|
||||||
|
return Path(path_override)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
_application_name = "Speckle"
|
||||||
|
|
||||||
|
|
||||||
|
def override_application_name(application_name: str) -> None:
|
||||||
|
"""Override the global Speckle application name."""
|
||||||
|
global _application_name
|
||||||
|
_application_name = application_name
|
||||||
|
|
||||||
|
|
||||||
|
def override_application_data_path(path: Optional[str]) -> None:
|
||||||
|
"""
|
||||||
|
Override the global Speckle application data path.
|
||||||
|
|
||||||
|
If the value of path is `None` the environment variable gets deleted.
|
||||||
|
"""
|
||||||
|
if path:
|
||||||
|
os.environ[_user_data_env_var] = path
|
||||||
|
else:
|
||||||
|
os.environ.pop(_user_data_env_var, None)
|
||||||
|
|
||||||
|
|
||||||
|
_blob_folder_name = "Blobs"
|
||||||
|
|
||||||
|
|
||||||
|
def override_blob_storage_folder(blob_folder_name: str) -> None:
|
||||||
|
"""Override the global Blob storage folder name."""
|
||||||
|
global _blob_folder_name
|
||||||
|
_blob_folder_name = blob_folder_name
|
||||||
|
|
||||||
|
|
||||||
|
_accounts_folder_name = "Accounts"
|
||||||
|
|
||||||
|
|
||||||
|
def override_accounts_folder_name(accounts_folder_name: str) -> None:
|
||||||
|
"""Override the global Accounts folder name."""
|
||||||
|
global _accounts_folder_name
|
||||||
|
_accounts_folder_name = accounts_folder_name
|
||||||
|
|
||||||
|
|
||||||
|
_objects_folder_name = "Objects"
|
||||||
|
|
||||||
|
|
||||||
|
def override_objects_folder_name(objects_folder_name: str) -> None:
|
||||||
|
"""Override global Objects folder name."""
|
||||||
|
global _objects_folder_name
|
||||||
|
_objects_folder_name = objects_folder_name
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_folder_exists(base_path: Path, folder_name: str) -> Path:
|
||||||
|
path = base_path.joinpath(folder_name)
|
||||||
|
path.mkdir(exist_ok=True, parents=True)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def user_application_data_path() -> Path:
|
||||||
|
"""Get the platform specific user configuration folder path"""
|
||||||
|
path_override = _path()
|
||||||
|
if path_override:
|
||||||
|
return path_override
|
||||||
|
|
||||||
|
try:
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
app_data_path = os.getenv("APPDATA")
|
||||||
|
if not app_data_path:
|
||||||
|
raise SpeckleException(
|
||||||
|
message="Cannot get appdata path from environment."
|
||||||
|
)
|
||||||
|
return Path(app_data_path)
|
||||||
|
else:
|
||||||
|
# try getting the standard XDG_DATA_HOME value
|
||||||
|
# as that is used as an override
|
||||||
|
app_data_path = os.getenv("XDG_DATA_HOME")
|
||||||
|
if app_data_path:
|
||||||
|
return Path(app_data_path)
|
||||||
|
else:
|
||||||
|
return _ensure_folder_exists(Path.home(), ".config")
|
||||||
|
except Exception as ex:
|
||||||
|
raise SpeckleException(
|
||||||
|
message="Failed to initialize user application data path.", exception=ex
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def user_speckle_folder_path() -> Path:
|
||||||
|
"""Get the folder where the user's Speckle data should be stored."""
|
||||||
|
return _ensure_folder_exists(user_application_data_path(), _application_name)
|
||||||
|
|
||||||
|
|
||||||
|
def accounts_folder_path() -> Path:
|
||||||
|
"""Get the folder where the Speckle accounts data should be stored."""
|
||||||
|
return _ensure_folder_exists(user_speckle_folder_path(), _accounts_folder_name)
|
||||||
|
|
||||||
|
|
||||||
|
def blob_storage_path(path: Optional[Path] = None) -> Path:
|
||||||
|
return _ensure_folder_exists(path or user_speckle_folder_path(), _blob_folder_name)
|
||||||
@@ -11,6 +11,17 @@ class SpeckleException(Exception):
|
|||||||
return f"SpeckleException: {self.message}"
|
return f"SpeckleException: {self.message}"
|
||||||
|
|
||||||
|
|
||||||
|
class SpeckleInvalidUnitException(SpeckleException):
|
||||||
|
def __init__(self, invalid_unit: Any) -> None:
|
||||||
|
super().__init__(
|
||||||
|
message=(
|
||||||
|
"Invalid units: expected type str but received"
|
||||||
|
f" {type(invalid_unit)} ({invalid_unit})."
|
||||||
|
),
|
||||||
|
exception=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SerializationException(SpeckleException):
|
class SerializationException(SpeckleException):
|
||||||
def __init__(self, message: str, obj: Any, exception: Exception = None) -> None:
|
def __init__(self, message: str, obj: Any, exception: Exception = None) -> None:
|
||||||
super().__init__(message=message, exception=exception)
|
super().__init__(message=message, exception=exception)
|
||||||
@@ -18,7 +29,10 @@ class SerializationException(SpeckleException):
|
|||||||
self.unhandled_type = type(obj)
|
self.unhandled_type = type(obj)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"SpeckleException: Could not serialize object of type {self.unhandled_type}"
|
return (
|
||||||
|
"SpeckleException: Could not serialize object of type"
|
||||||
|
f" {self.unhandled_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GraphQLException(SpeckleException):
|
class GraphQLException(SpeckleException):
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import sys
|
|
||||||
import queue
|
|
||||||
import hashlib
|
|
||||||
import getpass
|
|
||||||
import logging
|
|
||||||
import requests
|
|
||||||
import threading
|
|
||||||
import platform
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import getpass
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import platform
|
||||||
|
import queue
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Anonymous telemetry to help us understand how to make a better Speckle.
|
Anonymous telemetry to help us understand how to make a better Speckle.
|
||||||
@@ -30,6 +31,7 @@ INVITE = "Invite Action"
|
|||||||
COMMIT = "Commit Action"
|
COMMIT = "Commit Action"
|
||||||
BRANCH = "Branch Action"
|
BRANCH = "Branch Action"
|
||||||
USER = "User Action"
|
USER = "User Action"
|
||||||
|
OTHER_USER = "Other User Action"
|
||||||
SERVER = "Server Action"
|
SERVER = "Server Action"
|
||||||
CLIENT = "Speckle Client"
|
CLIENT = "Speckle Client"
|
||||||
STREAM_WRAPPER = "Stream Wrapper"
|
STREAM_WRAPPER = "Stream Wrapper"
|
||||||
@@ -50,13 +52,17 @@ def enable():
|
|||||||
TRACK = True
|
TRACK = True
|
||||||
|
|
||||||
|
|
||||||
def set_host_app(host_app: str, host_app_version: str = None):
|
def set_host_app(host_app: str, host_app_version: Optional[str] = None):
|
||||||
global HOST_APP, HOST_APP_VERSION
|
global HOST_APP, HOST_APP_VERSION
|
||||||
HOST_APP = host_app
|
HOST_APP = host_app
|
||||||
HOST_APP_VERSION = host_app_version or HOST_APP_VERSION
|
HOST_APP_VERSION = host_app_version or HOST_APP_VERSION
|
||||||
|
|
||||||
|
|
||||||
def track(action: str, account: "Account" = None, custom_props: dict = None):
|
def track(
|
||||||
|
action: str,
|
||||||
|
account=None,
|
||||||
|
custom_props: Optional[dict] = None,
|
||||||
|
):
|
||||||
if not TRACK:
|
if not TRACK:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -79,10 +85,10 @@ def track(action: str, account: "Account" = None, custom_props: dict = None):
|
|||||||
METRICS_TRACKER.queue.put_nowait(event_params)
|
METRICS_TRACKER.queue.put_nowait(event_params)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# wrapping this whole thing in a try except as we never want a failure here to annoy users!
|
# wrapping this whole thing in a try except as we never want a failure here to annoy users!
|
||||||
LOG.error(f"Error queueing metrics request: {str(ex)}")
|
LOG.debug(f"Error queueing metrics request: {str(ex)}")
|
||||||
|
|
||||||
|
|
||||||
def initialise_tracker(account: "Account" = None):
|
def initialise_tracker(account=None):
|
||||||
global METRICS_TRACKER
|
global METRICS_TRACKER
|
||||||
if not METRICS_TRACKER:
|
if not METRICS_TRACKER:
|
||||||
METRICS_TRACKER = MetricsTracker()
|
METRICS_TRACKER = MetricsTracker()
|
||||||
@@ -143,6 +149,6 @@ class MetricsTracker(metaclass=Singleton):
|
|||||||
try:
|
try:
|
||||||
session.post(self.analytics_url, json=event_params)
|
session.post(self.analytics_url, json=event_params)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Error sending metrics request: {str(ex)}")
|
LOG.debug(f"Error sending metrics request: {str(ex)}")
|
||||||
|
|
||||||
self.queue.task_done()
|
self.queue.task_done()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""Builtin Speckle object kit."""
|
||||||
|
|
||||||
|
from specklepy.objects import encoding, geometry, other, primitive, structural, units
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
|
||||||
|
__all__ = ["Base", "encoding", "geometry", "other", "units", "structural", "primitive"]
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
|
import contextlib
|
||||||
|
from enum import Enum
|
||||||
|
from inspect import isclass
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Union,
|
|
||||||
Set,
|
Set,
|
||||||
|
Tuple,
|
||||||
Type,
|
Type,
|
||||||
|
Union,
|
||||||
get_type_hints,
|
get_type_hints,
|
||||||
)
|
)
|
||||||
|
|
||||||
import contextlib
|
|
||||||
from enum import EnumMeta
|
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
|
from stringcase import pascalcase
|
||||||
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.objects.units import get_units_from_string
|
from specklepy.objects.units import Units, get_units_from_string
|
||||||
from specklepy.transports.memory import MemoryTransport
|
from specklepy.transports.memory import MemoryTransport
|
||||||
|
|
||||||
PRIMITIVES = (int, float, str, bool)
|
PRIMITIVES = (int, float, str, bool)
|
||||||
@@ -90,7 +93,9 @@ class _RegisteringBase:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
speckle_type: ClassVar[str]
|
speckle_type: ClassVar[str]
|
||||||
_type_registry: ClassVar[Dict[str, "Base"]] = {}
|
_speckle_type_override: ClassVar[Optional[str]] = None
|
||||||
|
_speckle_namespace: ClassVar[Optional[str]] = None
|
||||||
|
_type_registry: ClassVar[Dict[str, Type["Base"]]] = {}
|
||||||
_attr_types: ClassVar[Dict[str, Type]] = {}
|
_attr_types: ClassVar[Dict[str, Type]] = {}
|
||||||
# dict of chunkable props and their max chunk size
|
# dict of chunkable props and their max chunk size
|
||||||
_chunkable: Dict[str, int] = {}
|
_chunkable: Dict[str, int] = {}
|
||||||
@@ -98,22 +103,61 @@ class _RegisteringBase:
|
|||||||
_detachable: Set[str] = set() # list of defined detachable props
|
_detachable: Set[str] = set() # list of defined detachable props
|
||||||
_serialize_ignore: Set[str] = set()
|
_serialize_ignore: Set[str] = set()
|
||||||
|
|
||||||
class Config:
|
@classmethod
|
||||||
validate_assignment = True
|
def get_registered_type(cls, speckle_type: str) -> Optional[Type["Base"]]:
|
||||||
|
"""Get the registered type from the protected mapping via the `speckle_type`"""
|
||||||
|
for full_name in reversed(speckle_type.split(":")):
|
||||||
|
maybe_type = cls._type_registry.get(full_name, None)
|
||||||
|
if maybe_type:
|
||||||
|
return maybe_type
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_registered_type(
|
def _determine_speckle_type(cls) -> str:
|
||||||
cls, speckle_type: str
|
"""
|
||||||
) -> Union["Base", Type["Base"], None]:
|
This method brings the speckle_type construction in par with peckle-sharp/Core.
|
||||||
"""Get the registered type from the protected mapping via the `speckle_type`"""
|
|
||||||
return cls._type_registry.get(speckle_type, None)
|
The implementation differs, because in Core the basis of the speckle_type if
|
||||||
|
type.FullName, which includes the dotnet namespace name too.
|
||||||
|
Copying that behavior is hard in python, where the concept of namespaces
|
||||||
|
means something entirely different.
|
||||||
|
|
||||||
|
So we enabled a speckle_type override mechanism, that enables
|
||||||
|
"""
|
||||||
|
base_name = "Base"
|
||||||
|
if cls.__name__ == base_name:
|
||||||
|
return base_name
|
||||||
|
|
||||||
|
bases = [
|
||||||
|
b._full_name()
|
||||||
|
for b in reversed(cls.mro())
|
||||||
|
if issubclass(b, Base) and b.__name__ != base_name
|
||||||
|
]
|
||||||
|
return ":".join(bases)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _full_name(cls) -> str:
|
||||||
|
base_name = "Base"
|
||||||
|
if cls.__name__ == base_name:
|
||||||
|
return base_name
|
||||||
|
|
||||||
|
if cls._speckle_type_override:
|
||||||
|
return cls._speckle_type_override
|
||||||
|
|
||||||
|
# convert the module names to PascalCase to match c# namespace naming convention
|
||||||
|
# also drop specklepy from the beginning
|
||||||
|
namespace = ".".join(
|
||||||
|
pascalcase(m)
|
||||||
|
for m in filter(lambda name: name != "specklepy", cls.__module__.split("."))
|
||||||
|
)
|
||||||
|
return f"{namespace}.{cls.__name__}"
|
||||||
|
|
||||||
def __init_subclass__(
|
def __init_subclass__(
|
||||||
cls,
|
cls,
|
||||||
speckle_type: str = None,
|
speckle_type: Optional[str] = None,
|
||||||
chunkable: Dict[str, int] = None,
|
chunkable: Optional[Dict[str, int]] = None,
|
||||||
detachable: Set[str] = None,
|
detachable: Optional[Set[str]] = None,
|
||||||
serialize_ignore: Set[str] = None,
|
serialize_ignore: Optional[Set[str]] = None,
|
||||||
**kwargs: Dict[str, Any],
|
**kwargs: Dict[str, Any],
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -123,14 +167,15 @@ class _RegisteringBase:
|
|||||||
initialization. This is reused to register each subclassing type into a class
|
initialization. This is reused to register each subclassing type into a class
|
||||||
level dictionary.
|
level dictionary.
|
||||||
"""
|
"""
|
||||||
if speckle_type in cls._type_registry:
|
cls._speckle_type_override = speckle_type
|
||||||
|
cls.speckle_type = cls._determine_speckle_type()
|
||||||
|
if cls._full_name() in cls._type_registry:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The speckle_type: {speckle_type} is already registered for type: "
|
f"The speckle_type: {speckle_type} is already registered for type: "
|
||||||
f"{cls._type_registry[speckle_type].__name__}. "
|
f"{cls._type_registry[cls._full_name()].__name__}. "
|
||||||
f"Please choose a different type name."
|
"Please choose a different type name."
|
||||||
)
|
)
|
||||||
cls.speckle_type = speckle_type or cls.__name__
|
cls._type_registry[cls._full_name()] = cls # type: ignore
|
||||||
cls._type_registry[cls.speckle_type] = cls # type: ignore
|
|
||||||
try:
|
try:
|
||||||
cls._attr_types = get_type_hints(cls)
|
cls._attr_types = get_type_hints(cls)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -145,11 +190,124 @@ class _RegisteringBase:
|
|||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# T = TypeVar("T")
|
||||||
|
|
||||||
|
# how i wish the code below would be correct, but we're also parsing into floats
|
||||||
|
# and converting into strings if the original type is string, but the value isn't
|
||||||
|
# def _validate_type(t: type, value: T) -> Tuple[bool, T]:
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
|
||||||
|
# this should be reworked. Its only ok to return null for Optionals...
|
||||||
|
# if t is None and value is None:
|
||||||
|
if value is None:
|
||||||
|
return True, value
|
||||||
|
|
||||||
|
# after fixing the None t above, this should be
|
||||||
|
# if t is Any:
|
||||||
|
# if t is None:
|
||||||
|
|
||||||
|
if t is None or t is Any:
|
||||||
|
return True, value
|
||||||
|
|
||||||
|
if isclass(t) and issubclass(t, Enum):
|
||||||
|
if isinstance(value, t):
|
||||||
|
return True, value
|
||||||
|
if value in t._value2member_map_:
|
||||||
|
return True, t(value)
|
||||||
|
|
||||||
|
if getattr(t, "__module__", None) == "typing":
|
||||||
|
origin = getattr(t, "__origin__")
|
||||||
|
# below is what in nicer for >= py38
|
||||||
|
# origin = get_origin(t)
|
||||||
|
|
||||||
|
# recursive validation for Unions on both types preferring the fist type
|
||||||
|
if origin is Union:
|
||||||
|
# below is what in nicer for >= py38
|
||||||
|
# t_1, t_2 = get_args(t)
|
||||||
|
args = t.__args__ # type: ignore
|
||||||
|
for arg_t in args:
|
||||||
|
t_success, t_value = _validate_type(arg_t, value)
|
||||||
|
if t_success:
|
||||||
|
return True, t_value
|
||||||
|
return False, value
|
||||||
|
if origin is dict:
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
return False, value
|
||||||
|
if value == {}:
|
||||||
|
return True, value
|
||||||
|
if not getattr(t, "__args__", None):
|
||||||
|
return True, value
|
||||||
|
t_key, t_value = t.__args__ # type: ignore
|
||||||
|
|
||||||
|
if (
|
||||||
|
getattr(t_key, "__name__", None),
|
||||||
|
getattr(t_value, "__name__", None),
|
||||||
|
) == ("KT", "VT"):
|
||||||
|
return True, value
|
||||||
|
# we're only checking the first item, but the for loop and return after
|
||||||
|
# evaluating the first item is the fastest way
|
||||||
|
for dict_key, dict_value in value.items():
|
||||||
|
valid_key, _ = _validate_type(t_key, dict_key)
|
||||||
|
valid_value, _ = _validate_type(t_value, dict_value)
|
||||||
|
|
||||||
|
if valid_key and valid_value:
|
||||||
|
return True, value
|
||||||
|
return False, value
|
||||||
|
|
||||||
|
if origin is list:
|
||||||
|
if not isinstance(value, list):
|
||||||
|
return False, value
|
||||||
|
if value == []:
|
||||||
|
return True, value
|
||||||
|
if not hasattr(t, "__args__"):
|
||||||
|
return True, value
|
||||||
|
t_items = t.__args__[0] # type: ignore
|
||||||
|
if getattr(t_items, "__name__", None) == "T":
|
||||||
|
return True, value
|
||||||
|
first_item_valid, _ = _validate_type(t_items, value[0])
|
||||||
|
if first_item_valid:
|
||||||
|
return True, value
|
||||||
|
return False, value
|
||||||
|
|
||||||
|
if origin is tuple:
|
||||||
|
if not isinstance(value, tuple):
|
||||||
|
return False, value
|
||||||
|
if not hasattr(t, "__args__"):
|
||||||
|
return True, value
|
||||||
|
args = t.__args__ # type: ignore
|
||||||
|
if args == tuple():
|
||||||
|
return True, value
|
||||||
|
# we're not checking for empty tuple, cause tuple lengths must match
|
||||||
|
if len(args) != len(value):
|
||||||
|
return False, value
|
||||||
|
values = []
|
||||||
|
for t_item, v_item in zip(args, value):
|
||||||
|
item_valid, item_value = _validate_type(t_item, v_item)
|
||||||
|
if not item_valid:
|
||||||
|
return False, value
|
||||||
|
values.append(item_value)
|
||||||
|
return True, tuple(values)
|
||||||
|
|
||||||
|
if isinstance(value, t):
|
||||||
|
return True, value
|
||||||
|
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
if t is float and value is not None:
|
||||||
|
return True, float(value)
|
||||||
|
# TODO: dafuq, i had to add this not list check
|
||||||
|
# but it would also fail for objects and other complex values
|
||||||
|
if t is str and value and not isinstance(value, list):
|
||||||
|
return True, str(value)
|
||||||
|
|
||||||
|
return False, value
|
||||||
|
|
||||||
|
|
||||||
class Base(_RegisteringBase):
|
class Base(_RegisteringBase):
|
||||||
id: Optional[str] = None
|
id: Union[str, None] = None
|
||||||
totalChildrenCount: Optional[int] = None
|
totalChildrenCount: Union[int, None] = None
|
||||||
applicationId: Optional[str] = None
|
applicationId: Union[str, None] = None
|
||||||
_units: Union[str, None] = None
|
_units: Union[Units, None] = None
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -217,11 +375,14 @@ class Base(_RegisteringBase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def update_forward_refs(cls) -> None:
|
def update_forward_refs(cls) -> None:
|
||||||
"""
|
"""
|
||||||
Attempts to populate the internal defined types dict for type checking sometime after defining the class.
|
Attempts to populate the internal defined types dict for type checking
|
||||||
This is already done when defining the class, but can be called again if references to undefined types were
|
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.
|
included.
|
||||||
|
|
||||||
See `objects.geometry` for an example of how this is used with the Brep class definitions
|
See `objects.geometry` for an example of how this is used with
|
||||||
|
the Brep class definitions.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cls._attr_types = get_type_hints(cls)
|
cls._attr_types = get_type_hints(cls)
|
||||||
@@ -242,55 +403,27 @@ class Base(_RegisteringBase):
|
|||||||
"Invalid Name: Base member names cannot contain characters '.' or '/'",
|
"Invalid Name: Base member names cannot contain characters '.' or '/'",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _type_check(self, name: str, value: Any):
|
def _type_check(self, name: str, value: Any) -> Any:
|
||||||
"""
|
"""
|
||||||
Lightweight type checking of values before setting them
|
Lightweight type checking of values before setting them
|
||||||
|
|
||||||
NOTE: Does not check subscripted types within generics as the performance hit of checking
|
NOTE: Does not check subscripted types within generics as the performance hit
|
||||||
each item within a given collection isn't worth it. Eg if you have a type Dict[str, float],
|
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.
|
we will only check if the value you're trying to set is a dict.
|
||||||
"""
|
"""
|
||||||
types = getattr(self, "_attr_types", {})
|
types = getattr(self, "_attr_types", {})
|
||||||
t = types.get(name, None)
|
t = types.get(name, None)
|
||||||
|
|
||||||
if t is None or t is Any:
|
valid, checked_value = _validate_type(t, value)
|
||||||
return value
|
|
||||||
|
|
||||||
if value is None:
|
if valid:
|
||||||
return None
|
return checked_value
|
||||||
|
|
||||||
if isinstance(t, EnumMeta) and (value in t._value2member_map_):
|
|
||||||
return t(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 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]
|
|
||||||
|
|
||||||
with contextlib.suppress(ValueError):
|
|
||||||
if t is float:
|
|
||||||
return float(value)
|
|
||||||
if t is str and value:
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'"
|
f"Cannot set '{self.__class__.__name__}.{name}':"
|
||||||
|
f"it expects type '{str(t)}',"
|
||||||
|
f"but received type '{type(value).__name__}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_chunkable_attrs(self, **kwargs: int) -> None:
|
def add_chunkable_attrs(self, **kwargs: int) -> None:
|
||||||
@@ -298,7 +431,8 @@ class Base(_RegisteringBase):
|
|||||||
Mark defined attributes as chunkable for serialisation
|
Mark defined attributes as chunkable for serialisation
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
kwargs {int} -- the name of the attribute as the keyword and the chunk size as the arg
|
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)}
|
chunkable = {k: v for k, v in kwargs.items() if isinstance(v, int)}
|
||||||
self._chunkable = dict(self._chunkable, **chunkable)
|
self._chunkable = dict(self._chunkable, **chunkable)
|
||||||
@@ -308,19 +442,28 @@ class Base(_RegisteringBase):
|
|||||||
Mark defined attributes as detachable for serialisation
|
Mark defined attributes as detachable for serialisation
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
names {Set[str]} -- the names of the attributes to detach as a set of strings
|
names {Set[str]} -- the names of the attributes to detach as a set of string
|
||||||
"""
|
"""
|
||||||
self._detachable = self._detachable.union(names)
|
self._detachable = self._detachable.union(names)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def units(self):
|
def units(self) -> Union[str, None]:
|
||||||
return self._units
|
if self._units:
|
||||||
|
return self._units.value
|
||||||
|
return None
|
||||||
|
|
||||||
@units.setter
|
@units.setter
|
||||||
def units(self, value: str):
|
def units(self, value: Union[str, Units, None]):
|
||||||
units = get_units_from_string(value)
|
if value is None:
|
||||||
if units:
|
units = value
|
||||||
self._units = units
|
elif isinstance(value, Units):
|
||||||
|
units: Units = value
|
||||||
|
else:
|
||||||
|
units = get_units_from_string(value)
|
||||||
|
self._units = units
|
||||||
|
# except SpeckleInvalidUnitException as ex:
|
||||||
|
# warn(f"Units are reset to None. Reason {ex.message}")
|
||||||
|
# self._units = None
|
||||||
|
|
||||||
def get_member_names(self) -> List[str]:
|
def get_member_names(self) -> List[str]:
|
||||||
"""Get all of the property names on this object, dynamic or not"""
|
"""Get all of the property names on this object, dynamic or not"""
|
||||||
@@ -350,13 +493,17 @@ class Base(_RegisteringBase):
|
|||||||
|
|
||||||
def get_id(self, decompose: bool = False) -> str:
|
def get_id(self, decompose: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
Gets the id (a unique hash) of this object. ⚠️ This method fully serializes the object which,
|
Gets the id (a unique hash) of this object.
|
||||||
in the case of large objects (with many sub-objects), has a tangible cost. Avoid using it!
|
⚠️ 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
|
Note: the hash of a decomposed object differs from that of a
|
||||||
|
non-decomposed object
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
decompose {bool} -- if True, will decompose the object in the process of hashing it
|
decompose {bool} -- if True, will decompose the object in
|
||||||
|
the process of hashing it
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str -- the hash (id) of the fully serialized object
|
str -- the hash (id) of the fully serialized object
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable, List, Type, Dict
|
from typing import Any, Callable, Dict, List, Optional, Type
|
||||||
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
@@ -43,7 +43,7 @@ def curve_from_list(args: List[float]):
|
|||||||
|
|
||||||
|
|
||||||
class ObjectArray:
|
class ObjectArray:
|
||||||
def __init__(self, data: list = None) -> None:
|
def __init__(self, data: Optional[list] = None) -> None:
|
||||||
self.data = data or []
|
self.data = data or []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -68,7 +68,7 @@ class ObjectArray:
|
|||||||
def decode_data(
|
def decode_data(
|
||||||
data: List[Any], decoder: Callable[[List[Any]], Base], **kwargs: Dict[str, Any]
|
data: List[Any], decoder: Callable[[List[Any]], Base], **kwargs: Dict[str, Any]
|
||||||
) -> List[Base]:
|
) -> List[Base]:
|
||||||
bases = []
|
bases: List[Base] = []
|
||||||
if not data:
|
if not data:
|
||||||
return bases
|
return bases
|
||||||
index = 0
|
index = 0
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
from specklepy.objects.geometry import Point
|
from specklepy.objects.geometry import Point
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base
|
||||||
@@ -16,8 +17,8 @@ DETACHABLE = {"detach_this", "origin", "detached_list"}
|
|||||||
|
|
||||||
|
|
||||||
class FakeGeo(Base, chunkable={"dots": 50}, detachable={"pointslist"}):
|
class FakeGeo(Base, chunkable={"dots": 50}, detachable={"pointslist"}):
|
||||||
pointslist: List[Base] = None
|
pointslist: Optional[List[Base]] = None
|
||||||
dots: List[int] = None
|
dots: Optional[List[int]] = None
|
||||||
|
|
||||||
|
|
||||||
class FakeDirection(Enum):
|
class FakeDirection(Enum):
|
||||||
@@ -28,15 +29,15 @@ class FakeDirection(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class FakeMesh(FakeGeo, chunkable=CHUNKABLE_PROPS, detachable=DETACHABLE):
|
class FakeMesh(FakeGeo, chunkable=CHUNKABLE_PROPS, detachable=DETACHABLE):
|
||||||
vertices: List[float] = None
|
vertices: Optional[List[float]] = None
|
||||||
faces: List[int] = None
|
faces: Optional[List[int]] = None
|
||||||
colors: List[int] = None
|
colors: Optional[List[int]] = None
|
||||||
textureCoordinates: List[float] = None
|
textureCoordinates: Optional[List[float]] = None
|
||||||
cardinal_dir: FakeDirection = None
|
cardinal_dir: Optional[FakeDirection] = None
|
||||||
test_bases: List[Base] = None
|
test_bases: Optional[List[Base]] = None
|
||||||
detach_this: Base = None
|
detach_this: Optional[Base] = None
|
||||||
detached_list: List[Base] = None
|
detached_list: Optional[List[Base]] = None
|
||||||
_origin: Point = None
|
_origin: Optional[Point] = None
|
||||||
|
|
||||||
# def __init__(self, **kwargs) -> None:
|
# def __init__(self, **kwargs) -> None:
|
||||||
# super(FakeMesh, self).__init__(**kwargs)
|
# super(FakeMesh, self).__init__(**kwargs)
|
||||||
@@ -1,39 +1,31 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from .base import Base
|
from specklepy.objects.base import Base
|
||||||
from .encoding import CurveArray, CurveTypeEncoding, ObjectArray
|
from specklepy.objects.encoding import CurveArray, CurveTypeEncoding, ObjectArray
|
||||||
from .units import get_encoding_from_units, get_units_from_encoding
|
from specklepy.objects.primitive import Interval
|
||||||
|
from specklepy.objects.units import get_encoding_from_units, get_units_from_encoding
|
||||||
|
|
||||||
GEOMETRY = "Objects.Geometry."
|
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"):
|
class Point(Base, speckle_type=GEOMETRY + "Point"):
|
||||||
x: float = 0.0
|
x: float = 0.0
|
||||||
y: float = 0.0
|
y: float = 0.0
|
||||||
z: float = 0.0
|
z: float = 0.0
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
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})"
|
return (
|
||||||
|
f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, id:"
|
||||||
|
f" {self.id}, speckle_type: {self.speckle_type})"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[float]) -> "Point":
|
def from_list(cls, args: List[float]) -> "Point":
|
||||||
"""Create a new Point from a list of three floats representing the x, y, and z coordinates"""
|
"""
|
||||||
|
Create a new Point from a list of three floats
|
||||||
|
representing the x, y, and z coordinates
|
||||||
|
"""
|
||||||
return cls(x=args[0], y=args[1], z=args[2])
|
return cls(x=args[0], y=args[1], z=args[2])
|
||||||
|
|
||||||
def to_list(self) -> List[Any]:
|
def to_list(self) -> List[Any]:
|
||||||
@@ -47,12 +39,46 @@ class Point(Base, speckle_type=GEOMETRY + "Point"):
|
|||||||
return pt
|
return pt
|
||||||
|
|
||||||
|
|
||||||
class Vector(Point, speckle_type=GEOMETRY + "Vector"):
|
class Pointcloud(Base, speckle_type=GEOMETRY + "Pointcloud"):
|
||||||
pass
|
points: Optional[List[float]] = None
|
||||||
|
colors: Optional[List[int]] = None
|
||||||
|
sizes: Optional[List[float]] = None
|
||||||
|
bbox: Optional["Box"] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Vector(Base, speckle_type=GEOMETRY + "Vector"):
|
||||||
|
x: float = 0.0
|
||||||
|
y: float = 0.0
|
||||||
|
z: float = 0.0
|
||||||
|
applicationId: Optional[str] = None
|
||||||
|
|
||||||
|
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]) -> "Vector":
|
||||||
|
"""
|
||||||
|
Create from a list of three floats representing the x, y, and z coordinates.
|
||||||
|
"""
|
||||||
|
return cls(x=args[0], y=args[1], z=args[2])
|
||||||
|
|
||||||
|
def to_list(self) -> List[float]:
|
||||||
|
return [self.x, self.y, self.z]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_coords(cls, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> "Vector":
|
||||||
|
"""Create a new Point from x, y, and z values"""
|
||||||
|
v = Vector()
|
||||||
|
v.x, v.y, v.z = x, y, z
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class ControlPoint(Point, speckle_type=GEOMETRY + "ControlPoint"):
|
class ControlPoint(Point, speckle_type=GEOMETRY + "ControlPoint"):
|
||||||
weight: float = None
|
weight: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
class Plane(Base, speckle_type=GEOMETRY + "Plane"):
|
class Plane(Base, speckle_type=GEOMETRY + "Plane"):
|
||||||
@@ -77,25 +103,25 @@ class Plane(Base, speckle_type=GEOMETRY + "Plane"):
|
|||||||
*self.normal.to_list(),
|
*self.normal.to_list(),
|
||||||
*self.xdir.to_list(),
|
*self.xdir.to_list(),
|
||||||
*self.ydir.to_list(),
|
*self.ydir.to_list(),
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Box(Base, speckle_type=GEOMETRY + "Box"):
|
class Box(Base, speckle_type=GEOMETRY + "Box"):
|
||||||
basePlane: Plane = Plane()
|
basePlane: Plane = Plane()
|
||||||
|
xSize: Interval = Interval()
|
||||||
ySize: Interval = Interval()
|
ySize: Interval = Interval()
|
||||||
zSize: Interval = Interval()
|
zSize: Interval = Interval()
|
||||||
xSize: Interval = Interval()
|
area: Optional[float] = None
|
||||||
area: float = None
|
volume: Optional[float] = None
|
||||||
volume: float = None
|
|
||||||
|
|
||||||
|
|
||||||
class Line(Base, speckle_type=GEOMETRY + "Line"):
|
class Line(Base, speckle_type=GEOMETRY + "Line"):
|
||||||
start: Point = Point()
|
start: Point = Point()
|
||||||
end: Point = None
|
end: Optional[Point] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[Any]) -> "Line":
|
def from_list(cls, args: List[Any]) -> "Line":
|
||||||
@@ -113,23 +139,23 @@ class Line(Base, speckle_type=GEOMETRY + "Line"):
|
|||||||
*self.start.to_list(),
|
*self.start.to_list(),
|
||||||
*self.end.to_list(),
|
*self.end.to_list(),
|
||||||
*domain,
|
*domain,
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Arc(Base, speckle_type=GEOMETRY + "Arc"):
|
class Arc(Base, speckle_type=GEOMETRY + "Arc"):
|
||||||
radius: float = None
|
radius: Optional[float] = None
|
||||||
startAngle: float = None
|
startAngle: Optional[float] = None
|
||||||
endAngle: float = None
|
endAngle: Optional[float] = None
|
||||||
angleRadians: float = None
|
angleRadians: Optional[float] = None
|
||||||
plane: Plane = None
|
plane: Optional[Plane] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
startPoint: Point = None
|
startPoint: Optional[Point] = None
|
||||||
midPoint: Point = None
|
midPoint: Optional[Point] = None
|
||||||
endPoint: Point = None
|
endPoint: Optional[Point] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[Any]) -> "Arc":
|
def from_list(cls, args: List[Any]) -> "Arc":
|
||||||
@@ -158,17 +184,17 @@ class Arc(Base, speckle_type=GEOMETRY + "Arc"):
|
|||||||
*self.startPoint.to_list(),
|
*self.startPoint.to_list(),
|
||||||
*self.midPoint.to_list(),
|
*self.midPoint.to_list(),
|
||||||
*self.endPoint.to_list(),
|
*self.endPoint.to_list(),
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Circle(Base, speckle_type=GEOMETRY + "Circle"):
|
class Circle(Base, speckle_type=GEOMETRY + "Circle"):
|
||||||
radius: float = None
|
radius: Optional[float] = None
|
||||||
plane: Plane = None
|
plane: Optional[Plane] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[Any]) -> "Circle":
|
def from_list(cls, args: List[Any]) -> "Circle":
|
||||||
@@ -185,19 +211,19 @@ class Circle(Base, speckle_type=GEOMETRY + "Circle"):
|
|||||||
self.radius,
|
self.radius,
|
||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
*self.plane.to_list(),
|
*self.plane.to_list(),
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"):
|
class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"):
|
||||||
firstRadius: float = None
|
firstRadius: Optional[float] = None
|
||||||
secondRadius: float = None
|
secondRadius: Optional[float] = None
|
||||||
plane: Plane = None
|
plane: Optional[Plane] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
trimDomain: Interval = None
|
trimDomain: Optional[Interval] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[Any]) -> "Ellipse":
|
def from_list(cls, args: List[Any]) -> "Ellipse":
|
||||||
@@ -216,17 +242,17 @@ class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"):
|
|||||||
self.secondRadius,
|
self.secondRadius,
|
||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
*self.plane.to_list(),
|
*self.plane.to_list(),
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 20000}):
|
class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 20000}):
|
||||||
value: List[float] = None
|
value: Optional[List[float]] = None
|
||||||
closed: bool = None
|
closed: Optional[bool] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_points(cls, points: List[Point]):
|
def from_points(cls, points: List[Point]):
|
||||||
@@ -255,7 +281,7 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200
|
|||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
len(self.value),
|
len(self.value),
|
||||||
*self.value,
|
*self.value,
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
def as_points(self) -> List[Point]:
|
def as_points(self) -> List[Point]:
|
||||||
@@ -272,23 +298,50 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SpiralType(Enum):
|
||||||
|
Biquadratic = (0,)
|
||||||
|
BiquadraticParabola = (1,)
|
||||||
|
Bloss = (2,)
|
||||||
|
Clothoid = (3,)
|
||||||
|
Cosine = (4,)
|
||||||
|
Cubic = (5,)
|
||||||
|
CubicParabola = (6,)
|
||||||
|
Radioid = (7,)
|
||||||
|
Sinusoid = (8,)
|
||||||
|
Unknown = 9
|
||||||
|
|
||||||
|
|
||||||
|
class Spiral(Base, speckle_type=GEOMETRY + "Spiral", detachable={"displayValue"}):
|
||||||
|
startPoint: Optional[Point] = None
|
||||||
|
endPoint: Optional[Point]
|
||||||
|
plane: Optional[Plane]
|
||||||
|
turns: Optional[int]
|
||||||
|
pitchAxis: Optional[Vector] = Vector()
|
||||||
|
pitch: float = 0
|
||||||
|
spiralType: Optional[SpiralType] = None
|
||||||
|
displayValue: Optional[Polyline] = None
|
||||||
|
bbox: Optional[Box] = None
|
||||||
|
length: Optional[float] = None
|
||||||
|
domain: Optional[Interval] = None
|
||||||
|
|
||||||
|
|
||||||
class Curve(
|
class Curve(
|
||||||
Base,
|
Base,
|
||||||
speckle_type=GEOMETRY + "Curve",
|
speckle_type=GEOMETRY + "Curve",
|
||||||
chunkable={"points": 20000, "weights": 20000, "knots": 20000},
|
chunkable={"points": 20000, "weights": 20000, "knots": 20000},
|
||||||
):
|
):
|
||||||
degree: int = None
|
degree: Optional[int] = None
|
||||||
periodic: bool = None
|
periodic: Optional[bool] = None
|
||||||
rational: bool = None
|
rational: Optional[bool] = None
|
||||||
points: List[float] = None
|
points: Optional[List[float]] = None
|
||||||
weights: List[float] = None
|
weights: Optional[List[float]] = None
|
||||||
knots: List[float] = None
|
knots: Optional[List[float]] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
displayValue: Polyline = None
|
displayValue: Optional[Polyline] = None
|
||||||
closed: bool = None
|
closed: Optional[bool] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
def as_points(self) -> List[Point]:
|
def as_points(self) -> List[Point]:
|
||||||
"""Converts the `value` attribute to a list of Points"""
|
"""Converts the `value` attribute to a list of Points"""
|
||||||
@@ -340,17 +393,17 @@ class Curve(
|
|||||||
*self.points,
|
*self.points,
|
||||||
*self.weights,
|
*self.weights,
|
||||||
*self.knots,
|
*self.knots,
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"):
|
class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"):
|
||||||
segments: List[Base] = None
|
segments: Optional[List[Base]] = None
|
||||||
domain: Interval = None
|
domain: Optional[Interval] = None
|
||||||
closed: bool = None
|
closed: Optional[bool] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[Any]) -> "Polycurve":
|
def from_list(cls, args: List[Any]) -> "Polycurve":
|
||||||
@@ -370,22 +423,22 @@ class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"):
|
|||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
len(curve_array),
|
len(curve_array),
|
||||||
*curve_array,
|
*curve_array,
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Extrusion(Base, speckle_type=GEOMETRY + "Extrusion"):
|
class Extrusion(Base, speckle_type=GEOMETRY + "Extrusion"):
|
||||||
capped: bool = None
|
capped: Optional[bool] = None
|
||||||
profile: Base = None
|
profile: Optional[Base] = None
|
||||||
pathStart: Point = None
|
pathStart: Optional[Point] = None
|
||||||
pathEnd: Point = None
|
pathEnd: Optional[Point] = None
|
||||||
pathCurve: Base = None
|
pathCurve: Optional[Base] = None
|
||||||
pathTangent: Base = None
|
pathTangent: Optional[Base] = None
|
||||||
profiles: List[Base] = None
|
profiles: Optional[List[Base]] = None
|
||||||
length: float = None
|
length: Optional[float] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
volume: float = None
|
volume: Optional[float] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
|
|
||||||
|
|
||||||
class Mesh(
|
class Mesh(
|
||||||
@@ -398,21 +451,21 @@ class Mesh(
|
|||||||
"textureCoordinates": 2000,
|
"textureCoordinates": 2000,
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
vertices: List[float] = None
|
vertices: Optional[List[float]] = None
|
||||||
faces: List[int] = None
|
faces: Optional[List[int]] = None
|
||||||
colors: List[int] = None
|
colors: Optional[List[int]] = None
|
||||||
textureCoordinates: List[float] = None
|
textureCoordinates: Optional[List[float]] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
volume: float = None
|
volume: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
vertices: List[float],
|
vertices: List[float],
|
||||||
faces: List[int],
|
faces: List[int],
|
||||||
colors: List[int] = None,
|
colors: Optional[List[int]] = None,
|
||||||
texture_coordinates: List[float] = None,
|
texture_coordinates: Optional[List[float]] = None,
|
||||||
) -> "Mesh":
|
) -> "Mesh":
|
||||||
"""
|
"""
|
||||||
Create a new Mesh from lists representing its vertices, faces,
|
Create a new Mesh from lists representing its vertices, faces,
|
||||||
@@ -430,20 +483,20 @@ class Mesh(
|
|||||||
|
|
||||||
|
|
||||||
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
||||||
degreeU: int = None
|
degreeU: Optional[int] = None
|
||||||
degreeV: int = None
|
degreeV: Optional[int] = None
|
||||||
rational: bool = None
|
rational: Optional[bool] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
pointData: List[float] = None
|
pointData: Optional[List[float]] = None
|
||||||
countU: int = None
|
countU: Optional[int] = None
|
||||||
countV: int = None
|
countV: Optional[int] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
closedU: bool = None
|
closedU: Optional[bool] = None
|
||||||
closedV: bool = None
|
closedV: Optional[bool] = None
|
||||||
domainU: Interval = None
|
domainU: Optional[Interval] = None
|
||||||
domainV: Interval = None
|
domainV: Optional[Interval] = None
|
||||||
knotsU: List[float] = None
|
knotsU: Optional[List[float]] = None
|
||||||
knotsV: List[float] = None
|
knotsV: Optional[List[float]] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, args: List[Any]) -> "Surface":
|
def from_list(cls, args: List[Any]) -> "Surface":
|
||||||
@@ -488,16 +541,16 @@ class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
|||||||
*self.pointData,
|
*self.pointData,
|
||||||
*self.knotsU,
|
*self.knotsU,
|
||||||
*self.knotsV,
|
*self.knotsV,
|
||||||
get_encoding_from_units(self.units),
|
get_encoding_from_units(self._units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"):
|
class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"):
|
||||||
_Brep: "Brep" = None
|
_Brep: Optional["Brep"] = None
|
||||||
SurfaceIndex: int = None
|
SurfaceIndex: Optional[int] = None
|
||||||
OuterLoopIndex: int = None
|
OuterLoopIndex: Optional[int] = None
|
||||||
OrientationReversed: bool = None
|
OrientationReversed: Optional[bool] = None
|
||||||
LoopIndices: List[int] = None
|
LoopIndices: Optional[List[int]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _outer_loop(self):
|
def _outer_loop(self):
|
||||||
@@ -533,13 +586,13 @@ class BrepFace(Base, speckle_type=GEOMETRY + "BrepFace"):
|
|||||||
|
|
||||||
|
|
||||||
class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"):
|
class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"):
|
||||||
_Brep: "Brep" = None
|
_Brep: Optional["Brep"] = None
|
||||||
Curve3dIndex: int = None
|
Curve3dIndex: Optional[int] = None
|
||||||
TrimIndices: List[int] = None
|
TrimIndices: Optional[List[int]] = None
|
||||||
StartIndex: int = None
|
StartIndex: Optional[int] = None
|
||||||
EndIndex: int = None
|
EndIndex: Optional[int] = None
|
||||||
ProxyCurveIsReversed: bool = None
|
ProxyCurveIsReversed: Optional[bool] = None
|
||||||
Domain: Interval = None
|
Domain: Optional[Interval] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _start_vertex(self):
|
def _start_vertex(self):
|
||||||
@@ -590,7 +643,6 @@ class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BrepLoopType(int, Enum):
|
class BrepLoopType(int, Enum):
|
||||||
Unknown = 0
|
Unknown = 0
|
||||||
Outer = 1
|
Outer = 1
|
||||||
@@ -601,10 +653,10 @@ class BrepLoopType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class BrepLoop(Base, speckle_type=GEOMETRY + "BrepLoop"):
|
class BrepLoop(Base, speckle_type=GEOMETRY + "BrepLoop"):
|
||||||
_Brep: "Brep" = None
|
_Brep: Optional["Brep"] = None
|
||||||
FaceIndex: int = None
|
FaceIndex: Optional[Optional[int]] = None
|
||||||
TrimIndices: List[int] = None
|
TrimIndices: Optional[List[int]] = None
|
||||||
Type: BrepLoopType = None
|
Type: Optional[BrepLoopType] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _face(self):
|
def _face(self):
|
||||||
@@ -645,17 +697,17 @@ class BrepTrimType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class BrepTrim(Base, speckle_type=GEOMETRY + "BrepTrim"):
|
class BrepTrim(Base, speckle_type=GEOMETRY + "BrepTrim"):
|
||||||
_Brep: "Brep" = None
|
_Brep: Optional["Brep"] = None
|
||||||
EdgeIndex: int = None
|
EdgeIndex: Optional[int] = None
|
||||||
StartIndex: int = None
|
StartIndex: Optional[int] = None
|
||||||
EndIndex: int = None
|
EndIndex: Optional[int] = None
|
||||||
FaceIndex: int = None
|
FaceIndex: Optional[int] = None
|
||||||
LoopIndex: int = None
|
LoopIndex: Optional[int] = None
|
||||||
CurveIndex: int = None
|
CurveIndex: Optional[int] = None
|
||||||
IsoStatus: int = None
|
IsoStatus: Optional[int] = None
|
||||||
TrimType: BrepTrimType = None
|
TrimType: Optional[BrepTrimType] = None
|
||||||
IsReversed: bool = None
|
IsReversed: Optional[bool] = None
|
||||||
Domain: Interval = None
|
Domain: Optional[Interval] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _face(self):
|
def _face(self):
|
||||||
@@ -732,21 +784,21 @@ class Brep(
|
|||||||
"Faces",
|
"Faces",
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
provenance: str = None
|
provenance: Optional[str] = None
|
||||||
bbox: Box = None
|
bbox: Optional[Box] = None
|
||||||
area: float = None
|
area: Optional[float] = None
|
||||||
volume: float = None
|
volume: Optional[float] = None
|
||||||
_displayValue: List[Mesh] = None
|
_displayValue: Optional[List[Mesh]] = None
|
||||||
Surfaces: List[Surface] = None
|
Surfaces: Optional[List[Surface]] = None
|
||||||
Curve3D: List[Base] = None
|
Curve3D: Optional[List[Base]] = None
|
||||||
Curve2D: List[Base] = None
|
Curve2D: Optional[List[Base]] = None
|
||||||
Vertices: List[Point] = None
|
Vertices: Optional[List[Point]] = None
|
||||||
Edges: List[BrepEdge] = None
|
Edges: Optional[List[BrepEdge]] = None
|
||||||
Loops: List[BrepLoop] = None
|
Loops: Optional[List[BrepLoop]] = None
|
||||||
Faces: List[BrepFace] = None
|
Faces: Optional[List[BrepFace]] = None
|
||||||
Trims: List[BrepTrim] = None
|
Trims: Optional[List[BrepTrim]] = None
|
||||||
IsClosed: bool = None
|
IsClosed: Optional[bool] = None
|
||||||
Orientation: int = None
|
Orientation: Optional[int] = None
|
||||||
|
|
||||||
def _inject_self_into_children(self, children: Optional[List[Base]]) -> List[Base]:
|
def _inject_self_into_children(self, children: Optional[List[Base]]) -> List[Base]:
|
||||||
if children is None:
|
if children is None:
|
||||||
@@ -842,7 +894,7 @@ class Brep(
|
|||||||
def VerticesValue(self) -> List[Point]:
|
def VerticesValue(self) -> List[Point]:
|
||||||
if self.Vertices is None:
|
if self.Vertices is None:
|
||||||
return None
|
return None
|
||||||
encoded_unit = get_encoding_from_units(self.Vertices[0].units)
|
encoded_unit = get_encoding_from_units(self.Vertices[0]._units)
|
||||||
values = [encoded_unit]
|
values = [encoded_unit]
|
||||||
for vertex in self.Vertices:
|
for vertex in self.Vertices:
|
||||||
values.extend(vertex.to_list())
|
values.extend(vertex.to_list())
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
from typing import Any, List
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from specklepy.objects.geometry import Point, Vector
|
from specklepy.objects.geometry import Point, Vector
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base
|
||||||
|
|
||||||
OTHER = "Objects.Other."
|
OTHER = "Objects.Other."
|
||||||
@@ -24,8 +26,23 @@ IDENTITY_TRANSFORM = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Material(Base, speckle_type=OTHER + "Material"):
|
||||||
|
"""Generic class for materials containing generic parameters."""
|
||||||
|
|
||||||
|
name: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class RevitMaterial(Material, speckle_type="Objects.Other.Revit." + "RevitMaterial"):
|
||||||
|
materialCategory: Optional[str] = None
|
||||||
|
materialClass: Optional[str] = None
|
||||||
|
shininess: Optional[int] = None
|
||||||
|
smoothness: Optional[int] = None
|
||||||
|
transparency: Optional[int] = None
|
||||||
|
parameters: Optional[Base] = None
|
||||||
|
|
||||||
|
|
||||||
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
opacity: float = 1
|
opacity: float = 1
|
||||||
metalness: float = 0
|
metalness: float = 0
|
||||||
roughness: float = 1
|
roughness: float = 1
|
||||||
@@ -33,6 +50,25 @@ class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
|||||||
emissive: int = -16777216 # black arbg
|
emissive: int = -16777216 # black arbg
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialQuantity(Base, speckle_type=OTHER + "MaterialQuantity"):
|
||||||
|
material: Optional[Material] = None
|
||||||
|
volume: Optional[float] = None
|
||||||
|
area: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayStyle(Base, speckle_type=OTHER + "DisplayStyle"):
|
||||||
|
"""
|
||||||
|
Minimal display style class.
|
||||||
|
Developed primarily for display styles in Rhino and AutoCAD.
|
||||||
|
Rhino object attributes uses OpenNURBS definition for linetypes and lineweights.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: Optional[str] = None
|
||||||
|
color: int = -2894893 # light gray arbg
|
||||||
|
linetype: Optional[str] = None
|
||||||
|
lineweight: float = 0
|
||||||
|
|
||||||
|
|
||||||
class Transform(
|
class Transform(
|
||||||
Base,
|
Base,
|
||||||
speckle_type=OTHER + "Transform",
|
speckle_type=OTHER + "Transform",
|
||||||
@@ -41,10 +77,11 @@ class Transform(
|
|||||||
"""The 4x4 transformation matrix
|
"""The 4x4 transformation matrix
|
||||||
|
|
||||||
The 3x3 sub-matrix determines scaling.
|
The 3x3 sub-matrix determines scaling.
|
||||||
The 4th column defines translation, where the last value is a divisor (usually equal to 1).
|
The 4th column defines translation,
|
||||||
|
where the last value is a divisor (usually equal to 1).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_value: List[float] = None
|
_value: Optional[List[float]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> List[float]:
|
def value(self) -> List[float]:
|
||||||
@@ -57,12 +94,14 @@ class Transform(
|
|||||||
value = [float(x) for x in value]
|
value = [float(x) for x in value]
|
||||||
except (ValueError, TypeError) as error:
|
except (ValueError, TypeError) as error:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Could not create a Transform object with the requested value. Input must be a 16 element list of numbers. Value provided: {value}"
|
"Could not create a Transform object with the requested value. Input"
|
||||||
|
f" must be a 16 element list of numbers. Value provided: {value}"
|
||||||
) from error
|
) from error
|
||||||
|
|
||||||
if len(value) != 16:
|
if len(value) != 16:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Could not create a Transform object: input list should be 16 floats long, but was {len(value)} long"
|
"Could not create a Transform object: input list should be 16 floats"
|
||||||
|
f" long, but was {len(value)} long"
|
||||||
)
|
)
|
||||||
|
|
||||||
self._value = value
|
self._value = value
|
||||||
@@ -127,14 +166,16 @@ class Transform(
|
|||||||
"""Transform a list of speckle Points
|
"""Transform a list of speckle Points
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
points {List[float]} -- a flat list of floats representing points to transform
|
points {List[float]}
|
||||||
|
-- a flat list of floats representing points to transform
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[float] -- a new transformed list
|
List[float] -- a new transformed list
|
||||||
"""
|
"""
|
||||||
if len(points_value) % 3 != 0:
|
if len(points_value) % 3 != 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Cannot apply transform as the points list is malformed: expected length to be multiple of 3"
|
"Cannot apply transform as the points list is malformed: expected"
|
||||||
|
" length to be multiple of 3"
|
||||||
)
|
)
|
||||||
transformed = []
|
transformed = []
|
||||||
for i in range(0, len(points_value), 3):
|
for i in range(0, len(points_value), 3):
|
||||||
@@ -171,11 +212,13 @@ class Transform(
|
|||||||
][:3]
|
][:3]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, value: List[float] = None) -> "Transform":
|
def from_list(cls, value: Optional[List[float]] = None) -> "Transform":
|
||||||
"""Returns a Transform object from a list of 16 numbers. If no value is provided, an identity transform will be returned.
|
"""Returns a Transform object from a list of 16 numbers.
|
||||||
|
If no value is provided, an identity transform will be returned.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
value {List[float]} -- the matrix as a flat list of 16 numbers (defaults to the identity transform)
|
value {List[float]} -- the matrix as a flat list of 16 numbers
|
||||||
|
(defaults to the identity transform)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Transform -- a complete transform object
|
Transform -- a complete transform object
|
||||||
@@ -188,27 +231,27 @@ class Transform(
|
|||||||
class BlockDefinition(
|
class BlockDefinition(
|
||||||
Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"}
|
Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"}
|
||||||
):
|
):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
basePoint: Point = None
|
basePoint: Optional[Point] = None
|
||||||
geometry: List[Base] = None
|
geometry: Optional[List[Base]] = None
|
||||||
|
|
||||||
|
|
||||||
class BlockInstance(
|
class BlockInstance(
|
||||||
Base, speckle_type=OTHER + "BlockInstance", detachable={"blockDefinition"}
|
Base, speckle_type=OTHER + "BlockInstance", detachable={"blockDefinition"}
|
||||||
):
|
):
|
||||||
blockDefinition: BlockDefinition = None
|
blockDefinition: Optional[BlockDefinition] = None
|
||||||
transform: Transform = None
|
transform: Optional[Transform] = None
|
||||||
|
|
||||||
|
|
||||||
# TODO: prob move this into a built elements module, but just trialling this for now
|
# TODO: prob move this into a built elements module, but just trialling this for now
|
||||||
class RevitParameter(Base, speckle_type="Objects.BuiltElements.Revit.Parameter"):
|
class RevitParameter(Base, speckle_type="Objects.BuiltElements.Revit.Parameter"):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
value: Any = None
|
value: Any = None
|
||||||
applicationUnitType: str = None # eg UnitType UT_Length
|
applicationUnitType: Optional[str] = None # eg UnitType UT_Length
|
||||||
applicationUnit: str = None # DisplayUnitType eg DUT_MILLIMITERS
|
applicationUnit: Optional[str] = None # DisplayUnitType eg DUT_MILLIMITERS
|
||||||
applicationInternalName: str = (
|
applicationInternalName: Optional[
|
||||||
None # BuiltInParameterName or GUID for shared parameter
|
str
|
||||||
)
|
] = None # BuiltInParameterName or GUID for shared parameter
|
||||||
isShared: bool = False
|
isShared: bool = False
|
||||||
isReadOnly: bool = False
|
isReadOnly: bool = False
|
||||||
isTypeParameter: bool = False
|
isTypeParameter: bool = False
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
|
||||||
|
NAMESPACE = "Objects.Primitive"
|
||||||
|
|
||||||
|
|
||||||
|
class Interval(Base, speckle_type=f"{NAMESPACE}.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 Interval2d(Base, speckle_type=f"{NAMESPACE}.Interval2d"):
|
||||||
|
u: Interval
|
||||||
|
v: Interval
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
"""Builtin Speckle object kit."""
|
||||||
|
|
||||||
|
from specklepy.objects.structural.analysis import (
|
||||||
|
Model,
|
||||||
|
ModelInfo,
|
||||||
|
ModelSettings,
|
||||||
|
ModelUnits,
|
||||||
|
)
|
||||||
|
from specklepy.objects.structural.axis import Axis
|
||||||
|
from specklepy.objects.structural.geometry import (
|
||||||
|
Element1D,
|
||||||
|
Element2D,
|
||||||
|
Element3D,
|
||||||
|
ElementType1D,
|
||||||
|
ElementType2D,
|
||||||
|
ElementType3D,
|
||||||
|
Node,
|
||||||
|
Restraint,
|
||||||
|
)
|
||||||
|
from specklepy.objects.structural.loading import (
|
||||||
|
ActionType,
|
||||||
|
BeamLoadType,
|
||||||
|
CombinationType,
|
||||||
|
FaceLoadType,
|
||||||
|
Load,
|
||||||
|
LoadAxisType,
|
||||||
|
LoadBeam,
|
||||||
|
LoadCase,
|
||||||
|
LoadCombinations,
|
||||||
|
LoadDirection,
|
||||||
|
LoadDirection2D,
|
||||||
|
LoadFace,
|
||||||
|
LoadGravity,
|
||||||
|
LoadNode,
|
||||||
|
LoadType,
|
||||||
|
)
|
||||||
|
from specklepy.objects.structural.materials import (
|
||||||
|
Concrete,
|
||||||
|
MaterialType,
|
||||||
|
Steel,
|
||||||
|
StructuralMaterial,
|
||||||
|
Timber,
|
||||||
|
)
|
||||||
|
from specklepy.objects.structural.properties import (
|
||||||
|
BaseReferencePoint,
|
||||||
|
MemberType,
|
||||||
|
Property,
|
||||||
|
Property1D,
|
||||||
|
Property2D,
|
||||||
|
Property3D,
|
||||||
|
PropertyDamper,
|
||||||
|
PropertyMass,
|
||||||
|
PropertySpring,
|
||||||
|
PropertyType2D,
|
||||||
|
PropertyType3D,
|
||||||
|
PropertyTypeDamper,
|
||||||
|
PropertyTypeSpring,
|
||||||
|
ReferenceSurface,
|
||||||
|
ReferenceSurfaceEnum,
|
||||||
|
SectionProfile,
|
||||||
|
ShapeType,
|
||||||
|
shapeType,
|
||||||
|
)
|
||||||
|
from specklepy.objects.structural.results import (
|
||||||
|
Result,
|
||||||
|
Result1D,
|
||||||
|
Result2D,
|
||||||
|
Result3D,
|
||||||
|
ResultGlobal,
|
||||||
|
ResultNode,
|
||||||
|
ResultSet1D,
|
||||||
|
ResultSet2D,
|
||||||
|
ResultSet3D,
|
||||||
|
ResultSetAll,
|
||||||
|
ResultSetNode,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Element1D",
|
||||||
|
"Element2D",
|
||||||
|
"Element3D",
|
||||||
|
"ElementType1D",
|
||||||
|
"ElementType2D",
|
||||||
|
"ElementType3D",
|
||||||
|
"Axis",
|
||||||
|
"Node",
|
||||||
|
"Restraint",
|
||||||
|
"Load",
|
||||||
|
"LoadType",
|
||||||
|
"ActionType",
|
||||||
|
"BeamLoadType",
|
||||||
|
"FaceLoadType",
|
||||||
|
"LoadDirection",
|
||||||
|
"LoadDirection2D",
|
||||||
|
"LoadAxisType",
|
||||||
|
"CombinationType",
|
||||||
|
"LoadBeam",
|
||||||
|
"LoadCase",
|
||||||
|
"LoadCombinations",
|
||||||
|
"LoadFace",
|
||||||
|
"LoadGravity",
|
||||||
|
"LoadNode",
|
||||||
|
"Model",
|
||||||
|
"ModelInfo",
|
||||||
|
"ModelSettings",
|
||||||
|
"ModelUnits",
|
||||||
|
"MaterialType",
|
||||||
|
"Concrete",
|
||||||
|
"StructuralMaterial",
|
||||||
|
"Steel",
|
||||||
|
"Timber",
|
||||||
|
"Property",
|
||||||
|
"Property1D",
|
||||||
|
"Property2D",
|
||||||
|
"Property3D",
|
||||||
|
"PropertyDamper",
|
||||||
|
"PropertyMass",
|
||||||
|
"PropertySpring",
|
||||||
|
"SectionProfile",
|
||||||
|
"MemberType",
|
||||||
|
"BaseReferencePoint",
|
||||||
|
"ReferenceSurface",
|
||||||
|
"PropertyType2D",
|
||||||
|
"PropertyType3D",
|
||||||
|
"ShapeType",
|
||||||
|
"PropertyTypeSpring",
|
||||||
|
"PropertyTypeDamper",
|
||||||
|
"ReferenceSurfaceEnum",
|
||||||
|
"shapeType",
|
||||||
|
"Result",
|
||||||
|
"Result1D",
|
||||||
|
"ResultSet1D",
|
||||||
|
"Result2D",
|
||||||
|
"ResultSet2D",
|
||||||
|
"Result3D",
|
||||||
|
"ResultSet3D",
|
||||||
|
"ResultGlobal",
|
||||||
|
"ResultSetNode",
|
||||||
|
"ResultNode",
|
||||||
|
"ResultSetAll",
|
||||||
|
]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
|
||||||
|
STRUCTURAL_ANALYSIS = "Objects.Structural.Analysis."
|
||||||
|
|
||||||
|
|
||||||
|
class ModelUnits(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelUnits"):
|
||||||
|
length: Optional[str] = None
|
||||||
|
sections: Optional[str] = None
|
||||||
|
displacements: Optional[str] = None
|
||||||
|
stress: Optional[str] = None
|
||||||
|
force: Optional[str] = None
|
||||||
|
mass: Optional[str] = None
|
||||||
|
time: Optional[str] = None
|
||||||
|
temperature: Optional[str] = None
|
||||||
|
velocity: Optional[str] = None
|
||||||
|
acceleration: Optional[str] = None
|
||||||
|
energy: Optional[str] = None
|
||||||
|
angle: Optional[str] = None
|
||||||
|
strain: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ModelSettings(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelSettings"):
|
||||||
|
modelUnits: Optional[ModelUnits] = None
|
||||||
|
steelCode: Optional[str] = None
|
||||||
|
concreteCode: Optional[str] = None
|
||||||
|
coincidenceTolerance: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class ModelInfo(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelInfo"):
|
||||||
|
name: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
projectNumber: Optional[str] = None
|
||||||
|
projectName: Optional[str] = None
|
||||||
|
settings: Optional[ModelSettings] = None
|
||||||
|
initials: Optional[str] = None
|
||||||
|
application: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Model(Base, speckle_type=STRUCTURAL_ANALYSIS + "Model"):
|
||||||
|
specs: Optional[ModelInfo] = None
|
||||||
|
nodes: Optional[List] = None
|
||||||
|
elements: Optional[List] = None
|
||||||
|
loads: Optional[List] = None
|
||||||
|
restraints: Optional[List] = None
|
||||||
|
properties: Optional[List] = None
|
||||||
|
materials: Optional[List] = None
|
||||||
|
layerDescription: Optional[str] = None
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.geometry import Plane
|
||||||
|
|
||||||
|
|
||||||
|
class Axis(Base, speckle_type="Objects.Structural.Geometry.Axis"):
|
||||||
|
name: Optional[str] = None
|
||||||
|
axisType: Optional[str] = None
|
||||||
|
plane: Optional[Plane] = None
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.geometry import Line, Mesh, Plane, Point, Vector
|
||||||
|
from specklepy.objects.structural.axis import Axis
|
||||||
|
from specklepy.objects.structural.properties import (
|
||||||
|
Property1D,
|
||||||
|
Property2D,
|
||||||
|
Property3D,
|
||||||
|
PropertyDamper,
|
||||||
|
PropertyMass,
|
||||||
|
PropertySpring,
|
||||||
|
)
|
||||||
|
|
||||||
|
STRUCTURAL_GEOMETRY = "Objects.Structural.Geometry"
|
||||||
|
|
||||||
|
|
||||||
|
class ElementType1D(int, Enum):
|
||||||
|
Beam = 0
|
||||||
|
Brace = 1
|
||||||
|
Bar = 2
|
||||||
|
Column = 3
|
||||||
|
Rod = 4
|
||||||
|
Spring = 5
|
||||||
|
Tie = 6
|
||||||
|
Strut = 7
|
||||||
|
Link = 8
|
||||||
|
Damper = 9
|
||||||
|
Cable = 10
|
||||||
|
Spacer = 11
|
||||||
|
Other = 12
|
||||||
|
Null = 13
|
||||||
|
|
||||||
|
|
||||||
|
class ElementType2D(int, Enum):
|
||||||
|
Quad4 = 0
|
||||||
|
Quad8 = 1
|
||||||
|
Triangle3 = 2
|
||||||
|
Triangle6 = 3
|
||||||
|
|
||||||
|
|
||||||
|
class ElementType3D(int, Enum):
|
||||||
|
Brick8 = 0
|
||||||
|
Wedge6 = 1
|
||||||
|
Pyramid5 = 2
|
||||||
|
Tetra4 = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Restraint(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Restraint"):
|
||||||
|
code: Optional[str] = None
|
||||||
|
stiffnessX: float = 0.0
|
||||||
|
stiffnessY: float = 0.0
|
||||||
|
stiffnessZ: float = 0.0
|
||||||
|
stiffnessXX: float = 0.0
|
||||||
|
stiffnessYY: float = 0.0
|
||||||
|
stiffnessZZ: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class Node(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Node"):
|
||||||
|
name: Optional[str] = None
|
||||||
|
basePoint: Optional[Point] = None
|
||||||
|
constraintAxis: Optional[Axis] = None
|
||||||
|
restraint: Optional[Restraint] = None
|
||||||
|
springProperty: Optional[PropertySpring] = None
|
||||||
|
massProperty: Optional[PropertyMass] = None
|
||||||
|
damperProperty: Optional[PropertyDamper] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Element1D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element1D"):
|
||||||
|
name: Optional[str] = None
|
||||||
|
baseLine: Optional[Line] = None
|
||||||
|
property: Optional[Property1D] = None
|
||||||
|
type: Optional[ElementType1D] = None
|
||||||
|
end1Releases: Optional[Restraint] = None
|
||||||
|
end2Releases: Optional[Restraint] = None
|
||||||
|
end1Offset: Optional[Vector] = None
|
||||||
|
end2Offset: Optional[Vector] = None
|
||||||
|
orientationNode: Optional[Node] = None
|
||||||
|
orinetationAngle: float = 0.0
|
||||||
|
localAxis: Optional[Plane] = None
|
||||||
|
parent: Optional[Base] = None
|
||||||
|
end1Node: Optional[Node] = None
|
||||||
|
end2Node: Optional[Node] = None
|
||||||
|
topology: Optional[List] = None
|
||||||
|
displayMesh: Optional[Mesh] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Element2D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element2D"):
|
||||||
|
name: Optional[str] = None
|
||||||
|
property: Optional[Property2D] = None
|
||||||
|
type: Optional[ElementType2D] = None
|
||||||
|
offset: float = 0.0
|
||||||
|
orientationAngle: float = 0.0
|
||||||
|
parent: Optional[Base] = None
|
||||||
|
topology: Optional[List] = None
|
||||||
|
displayMesh: Optional[Mesh] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Element3D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element3D"):
|
||||||
|
name: Optional[str] = None
|
||||||
|
baseMesh: Optional[Mesh] = None
|
||||||
|
property: Optional[Property3D] = None
|
||||||
|
type: Optional[ElementType3D] = None
|
||||||
|
orientationAngle: float = 0.0
|
||||||
|
parent: Optional[Base] = None
|
||||||
|
topology: List
|
||||||
|
|
||||||
|
|
||||||
|
# class Storey needs ependency on built elements first
|
||||||
+34
-41
@@ -1,14 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
from ..base import Base
|
from specklepy.objects.base import Base
|
||||||
from .geometry import *
|
from specklepy.objects.geometry import Vector
|
||||||
|
from specklepy.objects.structural.axis import Axis
|
||||||
|
|
||||||
STRUCTURAL_LOADING = "Objects.Structural.Loading."
|
STRUCTURAL_LOADING = "Objects.Structural.Loading."
|
||||||
|
|
||||||
|
|
||||||
class LoadType(int, Enum):
|
class LoadType(int, Enum):
|
||||||
|
|
||||||
none = 0
|
none = 0
|
||||||
Dead = 1
|
Dead = 1
|
||||||
SuperDead = 2
|
SuperDead = 2
|
||||||
@@ -31,7 +31,6 @@ class LoadType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class ActionType(int, Enum):
|
class ActionType(int, Enum):
|
||||||
|
|
||||||
none = 0
|
none = 0
|
||||||
Permanent = 1
|
Permanent = 1
|
||||||
Variable = 2
|
Variable = 2
|
||||||
@@ -39,7 +38,6 @@ class ActionType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class BeamLoadType(int, Enum):
|
class BeamLoadType(int, Enum):
|
||||||
|
|
||||||
Point = 0
|
Point = 0
|
||||||
Uniform = 1
|
Uniform = 1
|
||||||
Linear = 2
|
Linear = 2
|
||||||
@@ -48,21 +46,18 @@ class BeamLoadType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class FaceLoadType(int, Enum):
|
class FaceLoadType(int, Enum):
|
||||||
|
|
||||||
Constant = 0
|
Constant = 0
|
||||||
Variable = 1
|
Variable = 1
|
||||||
Point = 2
|
Point = 2
|
||||||
|
|
||||||
|
|
||||||
class LoadDirection2D(int, Enum):
|
class LoadDirection2D(int, Enum):
|
||||||
|
|
||||||
X = 0
|
X = 0
|
||||||
Y = 1
|
Y = 1
|
||||||
Z = 2
|
Z = 2
|
||||||
|
|
||||||
|
|
||||||
class LoadDirection(int, Enum):
|
class LoadDirection(int, Enum):
|
||||||
|
|
||||||
X = 0
|
X = 0
|
||||||
Y = 1
|
Y = 1
|
||||||
Z = 2
|
Z = 2
|
||||||
@@ -80,7 +75,6 @@ class LoadAxisType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class CombinationType(int, Enum):
|
class CombinationType(int, Enum):
|
||||||
|
|
||||||
LinearAdd = 0
|
LinearAdd = 0
|
||||||
Envelope = 1
|
Envelope = 1
|
||||||
AbsoluteAdd = 2
|
AbsoluteAdd = 2
|
||||||
@@ -89,56 +83,55 @@ class CombinationType(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class LoadCase(Base, speckle_type=STRUCTURAL_LOADING + "LoadCase"):
|
class LoadCase(Base, speckle_type=STRUCTURAL_LOADING + "LoadCase"):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
loadType: LoadType = None
|
loadType: Optional[LoadType] = None
|
||||||
group: str = None
|
group: Optional[str] = None
|
||||||
actionType: ActionType = None
|
actionType: Optional[ActionType] = None
|
||||||
description: str = None
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class Load(Base, speckle_type=STRUCTURAL_LOADING + "Load"):
|
class Load(Base, speckle_type=STRUCTURAL_LOADING + "Load"):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
units: str = None
|
loadCase: Optional[LoadCase] = None
|
||||||
loadCase: LoadCase = None
|
|
||||||
|
|
||||||
|
|
||||||
class LoadBeam(Load, speckle_type=STRUCTURAL_LOADING + "LoadBeam"):
|
class LoadBeam(Load, speckle_type=STRUCTURAL_LOADING + "LoadBeam"):
|
||||||
elements: List = None
|
elements: Optional[List] = None
|
||||||
loadType: BeamLoadType = None
|
loadType: Optional[BeamLoadType] = None
|
||||||
direction: LoadDirection = None
|
direction: Optional[LoadDirection] = None
|
||||||
loadAxis: Axis = None
|
loadAxis: Optional[Axis] = None
|
||||||
loadAxisType: LoadAxisType = None
|
loadAxisType: Optional[LoadAxisType] = None
|
||||||
isProjected: bool = None
|
isProjected: Optional[bool] = None
|
||||||
values: List = None
|
values: Optional[List] = None
|
||||||
positions: List = None
|
positions: Optional[List] = None
|
||||||
|
|
||||||
|
|
||||||
class LoadCombinations(Base, speckle_type=STRUCTURAL_LOADING + "LoadCombination"):
|
class LoadCombinations(Base, speckle_type=STRUCTURAL_LOADING + "LoadCombination"):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
loadCases: List
|
loadCases: List
|
||||||
loadFactors: List
|
loadFactors: List
|
||||||
combinationType: CombinationType
|
combinationType: CombinationType
|
||||||
|
|
||||||
|
|
||||||
class LoadFace(Load, speckle_type=STRUCTURAL_LOADING + "LoadFace"):
|
class LoadFace(Load, speckle_type=STRUCTURAL_LOADING + "LoadFace"):
|
||||||
elements: List = None
|
elements: Optional[List] = None
|
||||||
loadType: FaceLoadType = None
|
loadType: Optional[FaceLoadType] = None
|
||||||
direction: LoadDirection2D = None
|
direction: Optional[LoadDirection2D] = None
|
||||||
loadAxis: Axis = None
|
loadAxis: Optional[Axis] = None
|
||||||
loadAxisType: LoadAxisType = None
|
loadAxisType: Optional[LoadAxisType] = None
|
||||||
isProjected: bool = None
|
isProjected: Optional[bool] = None
|
||||||
values: List = None
|
values: Optional[List] = None
|
||||||
positions: List = None
|
positions: Optional[List] = None
|
||||||
|
|
||||||
|
|
||||||
class LoadGravity(Load, speckle_type=STRUCTURAL_LOADING + "LoadGravity"):
|
class LoadGravity(Load, speckle_type=STRUCTURAL_LOADING + "LoadGravity"):
|
||||||
elements: List = None
|
elements: Optional[List] = None
|
||||||
nodes: List = None
|
nodes: Optional[List] = None
|
||||||
gravityFactors: Vector = None
|
gravityFactors: Optional[Vector] = None
|
||||||
|
|
||||||
|
|
||||||
class LoadNode(Load, speckle_type=STRUCTURAL_LOADING + "LoadNode"):
|
class LoadNode(Load, speckle_type=STRUCTURAL_LOADING + "LoadNode"):
|
||||||
nodes: List = None
|
nodes: Optional[List] = None
|
||||||
loadAxis: Axis = None
|
loadAxis: Optional[Axis] = None
|
||||||
direction: LoadDirection = None
|
direction: Optional[LoadDirection] = None
|
||||||
value: float = 0.0
|
value: float = 0.0
|
||||||
+17
-15
@@ -1,7 +1,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from ..base import Base
|
from specklepy.objects.base import Base
|
||||||
|
|
||||||
|
|
||||||
STRUCTURAL_MATERIALS = "Objects.Structural.Materials"
|
STRUCTURAL_MATERIALS = "Objects.Structural.Materials"
|
||||||
|
|
||||||
@@ -21,12 +21,14 @@ class MaterialType(int, Enum):
|
|||||||
Other = 11
|
Other = 11
|
||||||
|
|
||||||
|
|
||||||
class Material(Base, speckle_type=STRUCTURAL_MATERIALS):
|
class StructuralMaterial(
|
||||||
name: str = None
|
Base, speckle_type=STRUCTURAL_MATERIALS + ".StructuralMaterial"
|
||||||
grade: str = None
|
):
|
||||||
materialType: MaterialType = None
|
name: Optional[str] = None
|
||||||
designCode: str = None
|
grade: Optional[str] = None
|
||||||
codeYear: str = None
|
materialType: Optional[MaterialType] = None
|
||||||
|
designCode: Optional[str] = None
|
||||||
|
codeYear: Optional[str] = None
|
||||||
strength: float = 0.0
|
strength: float = 0.0
|
||||||
elasticModulus: float = 0.0
|
elasticModulus: float = 0.0
|
||||||
poissonsRatio: float = 0.0
|
poissonsRatio: float = 0.0
|
||||||
@@ -38,22 +40,22 @@ class Material(Base, speckle_type=STRUCTURAL_MATERIALS):
|
|||||||
materialSafetyFactor: float = 0.0
|
materialSafetyFactor: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
class Concrete(Material, speckle_type=STRUCTURAL_MATERIALS + ".Concrete"):
|
class Concrete(StructuralMaterial):
|
||||||
compressiveStrength: float = 0.0
|
compressiveStrength: float = 0.0
|
||||||
tensileStrength: float = 0.0
|
tensileStrength: float = 0.0
|
||||||
flexuralStrength: float = 0.0
|
flexuralStrength: float = 0.0
|
||||||
maxCompressiveStrength: float = 0.0
|
maxCompressiveStrain: float = 0.0
|
||||||
maxTensileStrength: float = 0.0
|
maxTensileStrain: float = 0.0
|
||||||
maxAggregateSize: float = 0.0
|
maxAggregateSize: float = 0.0
|
||||||
lightweight: bool = None
|
lightweight: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
class Steel(Material, speckle_type=STRUCTURAL_MATERIALS + ".Steel"):
|
class Steel(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Steel"):
|
||||||
yieldStrength: float = 0.0
|
yieldStrength: float = 0.0
|
||||||
ultimateStrength: float = 0.0
|
ultimateStrength: float = 0.0
|
||||||
maxStrain: float = 0.0
|
maxStrain: float = 0.0
|
||||||
strainHardeningModulus: float = 0.0
|
strainHardeningModulus: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
class Timber(Material, speckle_type=STRUCTURAL_MATERIALS + ".Timber"):
|
class Timber(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Timber"):
|
||||||
species: str = None
|
species: Optional[str] = None
|
||||||
+22
-25
@@ -1,10 +1,9 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from ..base import Base
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.structural.axis import Axis
|
||||||
from .material import *
|
from specklepy.objects.structural.materials import StructuralMaterial
|
||||||
from .axis import Axis
|
|
||||||
|
|
||||||
|
|
||||||
STRUCTURAL_PROPERTY = "Objectives.Structural.Properties"
|
STRUCTURAL_PROPERTY = "Objectives.Structural.Properties"
|
||||||
|
|
||||||
@@ -39,7 +38,6 @@ class ReferenceSurface(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class PropertyType2D(int, Enum):
|
class PropertyType2D(int, Enum):
|
||||||
|
|
||||||
Stress = 0
|
Stress = 0
|
||||||
Fabric = 1
|
Fabric = 1
|
||||||
Plate = 2
|
Plate = 2
|
||||||
@@ -59,7 +57,7 @@ class PropertyType3D(int, Enum):
|
|||||||
class ShapeType(int, Enum):
|
class ShapeType(int, Enum):
|
||||||
Rectangular = 0
|
Rectangular = 0
|
||||||
Circular = 1
|
Circular = 1
|
||||||
I = 2
|
I = 2 # noqa: E741
|
||||||
Tee = 3
|
Tee = 3
|
||||||
Angle = 4
|
Angle = 4
|
||||||
Channel = 5
|
Channel = 5
|
||||||
@@ -89,36 +87,35 @@ class PropertyTypeDamper(int, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Property(Base, speckle_type=STRUCTURAL_PROPERTY):
|
class Property(Base, speckle_type=STRUCTURAL_PROPERTY):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class SectionProfile(Base, speckle_type=STRUCTURAL_PROPERTY + ".SectionProfile"):
|
class SectionProfile(Base, speckle_type=STRUCTURAL_PROPERTY + ".SectionProfile"):
|
||||||
name: str = None
|
name: Optional[str] = None
|
||||||
shapeType: ShapeType = None
|
shapeType: Optional[ShapeType] = None
|
||||||
area: float = 0.0
|
area: float = 0.0
|
||||||
Iyy: float = 0.0
|
Iyy: float = 0.0
|
||||||
Izz: float = 0.0
|
Izz: float = 0.0
|
||||||
J: float = 0.0
|
J: float = 0.0
|
||||||
Ky: float = 0.0
|
Ky: float = 0.0
|
||||||
weight: float = 0.0
|
weight: float = 0.0
|
||||||
units: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Property1D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property1D"):
|
class Property1D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property1D"):
|
||||||
memberType: MemberType = None
|
memberType: Optional[MemberType] = None
|
||||||
Material: Material = None
|
material: Optional[StructuralMaterial] = None
|
||||||
SectionProfile: SectionProfile = None
|
profile: Optional[SectionProfile] = None
|
||||||
BaseReferencePoint: BaseReferencePoint = None
|
referencePoint: Optional[BaseReferencePoint] = None
|
||||||
offsetY: float = 0.0
|
offsetY: float = 0.0
|
||||||
offsetZ: float = 0.0
|
offsetZ: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"):
|
class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"):
|
||||||
PropertyType2D: PropertyType2D = None
|
type: Optional[PropertyType2D] = None
|
||||||
thickness: float = 0.0
|
thickness: float = 0.0
|
||||||
Material: Material = None
|
material: Optional[StructuralMaterial] = None
|
||||||
axis: Axis = None
|
orientationAxis: Optional[Axis] = None
|
||||||
referenceSurface: ReferenceSurface = None
|
refSurface: Optional[ReferenceSurface] = None
|
||||||
zOffset: float = 0.0
|
zOffset: float = 0.0
|
||||||
modifierInPlane: float = 0.0
|
modifierInPlane: float = 0.0
|
||||||
modifierBending: float = 0.0
|
modifierBending: float = 0.0
|
||||||
@@ -127,13 +124,13 @@ class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"):
|
|||||||
|
|
||||||
|
|
||||||
class Property3D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property3D"):
|
class Property3D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property3D"):
|
||||||
PropertyType3D: PropertyType3D = None
|
type: Optional[PropertyType3D] = None
|
||||||
Material: Material = None
|
material: Optional[StructuralMaterial] = None
|
||||||
axis: Axis = None
|
orientationAxis: Optional[Axis] = None
|
||||||
|
|
||||||
|
|
||||||
class PropertyDamper(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyDamper"):
|
class PropertyDamper(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyDamper"):
|
||||||
damperType: PropertyTypeDamper = None
|
damperType: Optional[PropertyTypeDamper] = None
|
||||||
dampingX: float = 0.0
|
dampingX: float = 0.0
|
||||||
dampingY: float = 0.0
|
dampingY: float = 0.0
|
||||||
dampingZ: float = 0.0
|
dampingZ: float = 0.0
|
||||||
@@ -150,14 +147,14 @@ class PropertyMass(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyMass")
|
|||||||
inertiaXY: float = 0.0
|
inertiaXY: float = 0.0
|
||||||
inertiaYZ: float = 0.0
|
inertiaYZ: float = 0.0
|
||||||
inertiaZX: float = 0.0
|
inertiaZX: float = 0.0
|
||||||
massModified: bool = None
|
massModified: Optional[bool] = None
|
||||||
massModifierX: float = 0.0
|
massModifierX: float = 0.0
|
||||||
massModifierY: float = 0.0
|
massModifierY: float = 0.0
|
||||||
massModifierZ: float = 0.0
|
massModifierZ: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
class PropertySpring(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertySpring"):
|
class PropertySpring(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertySpring"):
|
||||||
springType: PropertyTypeSpring = None
|
springType: Optional[PropertyTypeSpring] = None
|
||||||
springCurveX: float = 0.0
|
springCurveX: float = 0.0
|
||||||
stiffnessX: float = 0.0
|
stiffnessX: float = 0.0
|
||||||
springCurveY: float = 0.0
|
springCurveY: float = 0.0
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.structural.analysis import Model
|
||||||
|
from specklepy.objects.structural.geometry import Element1D, Element2D, Element3D, Node
|
||||||
|
|
||||||
|
STRUCTURAL_RESULTS = "Objects.Structural.Results."
|
||||||
|
|
||||||
|
|
||||||
|
class Result(Base, speckle_type=STRUCTURAL_RESULTS + "Result"):
|
||||||
|
resultCase: Optional[Base] = None
|
||||||
|
permutation: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"):
|
||||||
|
results1D: List
|
||||||
|
|
||||||
|
|
||||||
|
class Result1D(Result, speckle_type=STRUCTURAL_RESULTS + "Result1D"):
|
||||||
|
element: Optional[Element1D] = None
|
||||||
|
position: Optional[float] = None
|
||||||
|
dispX: Optional[float] = None
|
||||||
|
dispY: Optional[float] = None
|
||||||
|
dispZ: Optional[float] = None
|
||||||
|
rotXX: Optional[float] = None
|
||||||
|
rotYY: Optional[float] = None
|
||||||
|
rotZZ: Optional[float] = None
|
||||||
|
forceX: Optional[float] = None
|
||||||
|
forceY: Optional[float] = None
|
||||||
|
forceZ: Optional[float] = None
|
||||||
|
momentXX: Optional[float] = None
|
||||||
|
momentYY: Optional[float] = None
|
||||||
|
momentZZ: Optional[float] = None
|
||||||
|
axialStress: Optional[float] = None
|
||||||
|
shearStressY: Optional[float] = None
|
||||||
|
shearStressZ: Optional[float] = None
|
||||||
|
bendingStressYPos: Optional[float] = None
|
||||||
|
bendingStressYNeg: Optional[float] = None
|
||||||
|
bendingStressZPos: Optional[float] = None
|
||||||
|
bendingStressZNeg: Optional[float] = None
|
||||||
|
combinedStressMax: Optional[float] = None
|
||||||
|
combinedStressMin: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultSet2D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet2D"):
|
||||||
|
results2D: List
|
||||||
|
|
||||||
|
|
||||||
|
class Result2D(Result, speckle_type=STRUCTURAL_RESULTS + "Result2D"):
|
||||||
|
element: Optional[Element2D] = None
|
||||||
|
position: List
|
||||||
|
dispX: Optional[float] = None
|
||||||
|
dispY: Optional[float] = None
|
||||||
|
dispZ: Optional[float] = None
|
||||||
|
forceXX: Optional[float] = None
|
||||||
|
forceYY: Optional[float] = None
|
||||||
|
forceXY: Optional[float] = None
|
||||||
|
momentXX: Optional[float] = None
|
||||||
|
momentYY: Optional[float] = None
|
||||||
|
momentXY: Optional[float] = None
|
||||||
|
shearX: Optional[float] = None
|
||||||
|
shearY: Optional[float] = None
|
||||||
|
stressTopXX: Optional[float] = None
|
||||||
|
stressTopYY: Optional[float] = None
|
||||||
|
stressTopZZ: Optional[float] = None
|
||||||
|
stressTopXY: Optional[float] = None
|
||||||
|
stressTopYZ: Optional[float] = None
|
||||||
|
stressTopZX: Optional[float] = None
|
||||||
|
stressMidXX: Optional[float] = None
|
||||||
|
stressMidYY: Optional[float] = None
|
||||||
|
stressMidZZ: Optional[float] = None
|
||||||
|
stressMidXY: Optional[float] = None
|
||||||
|
stressMidYZ: Optional[float] = None
|
||||||
|
stressMidZX: Optional[float] = None
|
||||||
|
stressBotXX: Optional[float] = None
|
||||||
|
stressBotYY: Optional[float] = None
|
||||||
|
stressBotZZ: Optional[float] = None
|
||||||
|
stressBotXY: Optional[float] = None
|
||||||
|
stressBotYZ: Optional[float] = None
|
||||||
|
stressBotZX: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultSet3D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet3D"):
|
||||||
|
results3D: List
|
||||||
|
|
||||||
|
|
||||||
|
class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"):
|
||||||
|
element: Optional[Element3D] = None
|
||||||
|
position: List
|
||||||
|
dispX: Optional[float] = None
|
||||||
|
dispY: Optional[float] = None
|
||||||
|
dispZ: Optional[float] = None
|
||||||
|
stressXX: Optional[float] = None
|
||||||
|
stressYY: Optional[float] = None
|
||||||
|
stressZZ: Optional[float] = None
|
||||||
|
stressXY: Optional[float] = None
|
||||||
|
stressYZ: Optional[float] = None
|
||||||
|
stressZX: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultGlobal(Result, speckle_type=STRUCTURAL_RESULTS + "ResultGlobal"):
|
||||||
|
model: Optional[Model] = None
|
||||||
|
loadX: Optional[float] = None
|
||||||
|
loadY: Optional[float] = None
|
||||||
|
loadZ: Optional[float] = None
|
||||||
|
loadXX: Optional[float] = None
|
||||||
|
loadYY: Optional[float] = None
|
||||||
|
loadZZ: Optional[float] = None
|
||||||
|
reactionX: Optional[float] = None
|
||||||
|
reactionY: Optional[float] = None
|
||||||
|
reactionZ: Optional[float] = None
|
||||||
|
reactionXX: Optional[float] = None
|
||||||
|
reactionYY: Optional[float] = None
|
||||||
|
reactionZZ: Optional[float] = None
|
||||||
|
mode: Optional[float] = None
|
||||||
|
frequency: Optional[float] = None
|
||||||
|
loadFactor: Optional[float] = None
|
||||||
|
modalStiffness: Optional[float] = None
|
||||||
|
modalGeoStiffness: Optional[float] = None
|
||||||
|
effMassX: Optional[float] = None
|
||||||
|
effMassY: Optional[float] = None
|
||||||
|
effMassZ: Optional[float] = None
|
||||||
|
effMassXX: Optional[float] = None
|
||||||
|
effMassYY: Optional[float] = None
|
||||||
|
effMassZZ: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultSetNode(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSetNode"):
|
||||||
|
resultsNode: List
|
||||||
|
|
||||||
|
|
||||||
|
class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"):
|
||||||
|
node: Optional[Node] = None
|
||||||
|
dispX: Optional[float] = None
|
||||||
|
dispY: Optional[float] = None
|
||||||
|
dispZ: Optional[float] = None
|
||||||
|
rotXX: Optional[float] = None
|
||||||
|
rotYY: Optional[float] = None
|
||||||
|
rotZZ: Optional[float] = None
|
||||||
|
reactionX: Optional[float] = None
|
||||||
|
reactionY: Optional[float] = None
|
||||||
|
reactionZ: Optional[float] = None
|
||||||
|
reactionXX: Optional[float] = None
|
||||||
|
reactionYY: Optional[float] = None
|
||||||
|
reactionZZ: Optional[float] = None
|
||||||
|
constraintX: Optional[float] = None
|
||||||
|
constraintY: Optional[float] = None
|
||||||
|
constraintZ: Optional[float] = None
|
||||||
|
constraintXX: Optional[float] = None
|
||||||
|
constraintYY: Optional[float] = None
|
||||||
|
constraintZZ: Optional[float] = None
|
||||||
|
velX: Optional[float] = None
|
||||||
|
velY: Optional[float] = None
|
||||||
|
velZ: Optional[float] = None
|
||||||
|
velXX: Optional[float] = None
|
||||||
|
velYY: Optional[float] = None
|
||||||
|
velZZ: Optional[float] = None
|
||||||
|
accX: Optional[float] = None
|
||||||
|
accY: Optional[float] = None
|
||||||
|
accZ: Optional[float] = None
|
||||||
|
accXX: Optional[float] = None
|
||||||
|
accYY: Optional[float] = None
|
||||||
|
accZZ: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultSetAll(Base, speckle_type=None):
|
||||||
|
resultSet1D: Optional[ResultSet1D] = None
|
||||||
|
resultSet2D: Optional[ResultSet2D] = None
|
||||||
|
resultSet3D: Optional[ResultSet3D] = None
|
||||||
|
resultsGlobal: Optional[ResultGlobal] = None
|
||||||
|
resultsNode: Optional[ResultSetNode] = None
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Units",
|
||||||
|
"get_encoding_from_units",
|
||||||
|
"get_units_from_encoding",
|
||||||
|
"get_units_from_string",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Units(Enum):
|
||||||
|
mm = "mm"
|
||||||
|
cm = "cm"
|
||||||
|
m = "m"
|
||||||
|
km = "km"
|
||||||
|
inches = "in"
|
||||||
|
feet = "ft"
|
||||||
|
yards = "yd"
|
||||||
|
miles = "mi"
|
||||||
|
none = "none"
|
||||||
|
|
||||||
|
|
||||||
|
UNITS_STRINGS = {
|
||||||
|
Units.mm: ["mm", "mil", "millimeters", "millimetres"],
|
||||||
|
Units.cm: ["cm", "centimetre", "centimeter", "centimetres", "centimeters"],
|
||||||
|
Units.m: ["m", "meter", "meters", "metre", "metres"],
|
||||||
|
Units.km: ["km", "kilometer", "kilometre", "kilometers", "kilometres"],
|
||||||
|
Units.inches: ["in", "inch", "inches"],
|
||||||
|
Units.feet: ["ft", "foot", "feet"],
|
||||||
|
Units.yards: ["yd", "yard", "yards"],
|
||||||
|
Units.miles: ["mi", "mile", "miles"],
|
||||||
|
Units.none: ["none", "null"],
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITS_ENCODINGS = {
|
||||||
|
Units.none: 0,
|
||||||
|
None: 0,
|
||||||
|
Units.mm: 1,
|
||||||
|
Units.cm: 2,
|
||||||
|
Units.m: 3,
|
||||||
|
Units.km: 4,
|
||||||
|
Units.inches: 5,
|
||||||
|
Units.feet: 6,
|
||||||
|
Units.yards: 7,
|
||||||
|
Units.miles: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_units_from_string(unit: str) -> Units:
|
||||||
|
if not isinstance(unit, str):
|
||||||
|
raise SpeckleInvalidUnitException(unit)
|
||||||
|
unit = str.lower(unit)
|
||||||
|
for name, alternates in UNITS_STRINGS.items():
|
||||||
|
if unit in alternates:
|
||||||
|
return name
|
||||||
|
raise SpeckleInvalidUnitException(unit)
|
||||||
|
|
||||||
|
|
||||||
|
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."
|
||||||
|
f"Please enter a valid unit encoding (eg {UNITS_ENCODINGS})."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_encoding_from_units(unit: Union[Units, None]):
|
||||||
|
try:
|
||||||
|
return UNITS_ENCODINGS[unit]
|
||||||
|
except KeyError as e:
|
||||||
|
raise SpeckleException(
|
||||||
|
message=(
|
||||||
|
f"No encoding exists for unit {unit}."
|
||||||
|
f"Please enter a valid unit to encode (eg {UNITS_ENCODINGS})."
|
||||||
|
)
|
||||||
|
) from e
|
||||||
+42
-26
@@ -1,21 +1,17 @@
|
|||||||
import re
|
|
||||||
import ujson
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from uuid import uuid4
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
from uuid import uuid4
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
from typing import Any, Dict, List, Tuple
|
|
||||||
from specklepy.objects.base import Base, DataChunk
|
import ujson
|
||||||
from specklepy.logging.exceptions import (
|
|
||||||
SpeckleException,
|
|
||||||
SpeckleWarning,
|
|
||||||
)
|
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
|
||||||
|
|
||||||
# import for serialization
|
# import for serialization
|
||||||
import specklepy.objects.geometry
|
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||||
import specklepy.objects.other
|
from specklepy.objects.base import Base, DataChunk
|
||||||
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
|
|
||||||
PRIMITIVES = (int, float, str, bool)
|
PRIMITIVES = (int, float, str, bool)
|
||||||
|
|
||||||
@@ -31,7 +27,8 @@ def safe_json_loads(obj: str, obj_id=None) -> Any:
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
warn(
|
warn(
|
||||||
f"Failed to deserialise object (id: {obj_id}). This is likely a ujson big int error - falling back to json. \nError: {err}",
|
f"Failed to deserialise object (id: {obj_id}). This is likely a ujson big"
|
||||||
|
f" int error - falling back to json. \nError: {err}",
|
||||||
SpeckleWarning,
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
return json.loads(obj)
|
return json.loads(obj)
|
||||||
@@ -44,9 +41,15 @@ class BaseObjectSerializer:
|
|||||||
lineage: List[str] # keeps track of hash chain through the object tree
|
lineage: List[str] # keeps track of hash chain through the object tree
|
||||||
family_tree: Dict[str, Dict[str, int]]
|
family_tree: Dict[str, Dict[str, int]]
|
||||||
closure_table: Dict[str, Dict[str, int]]
|
closure_table: Dict[str, Dict[str, int]]
|
||||||
deserialized: Dict[str, Base] # holds deserialized objects so objects with same id return the same instance
|
deserialized: Dict[
|
||||||
|
str, Base
|
||||||
|
] # holds deserialized objects so objects with same id return the same instance
|
||||||
|
|
||||||
def __init__(self, write_transports: List[AbstractTransport] = None, read_transport=None) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
write_transports: Optional[List[AbstractTransport]] = None,
|
||||||
|
read_transport: Optional[AbstractTransport] = None,
|
||||||
|
) -> None:
|
||||||
self.write_transports = write_transports or []
|
self.write_transports = write_transports or []
|
||||||
self.read_transport = read_transport
|
self.read_transport = read_transport
|
||||||
self.detach_lineage = []
|
self.detach_lineage = []
|
||||||
@@ -61,21 +64,23 @@ class BaseObjectSerializer:
|
|||||||
base {Base} -- the base object to be decomposed and serialized
|
base {Base} -- the base object to be decomposed and serialized
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(str, str) -- a tuple containing the object id of the base object and the serialized object string
|
(str, str) -- a tuple containing the object id of the base object and
|
||||||
|
the serialized object string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
obj_id, obj = self.traverse_base(base)
|
obj_id, obj = self.traverse_base(base)
|
||||||
|
|
||||||
return obj_id, ujson.dumps(obj)
|
return obj_id, ujson.dumps(obj)
|
||||||
|
|
||||||
def traverse_base(self, base: Base) -> Tuple[str, Dict]:
|
def traverse_base(self, base: Base) -> Tuple[str, Dict[str, Any]]:
|
||||||
"""Decomposes the given base object and builds a serializable dictionary
|
"""Decomposes the given base object and builds a serializable dictionary
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
base {Base} -- the base object to be decomposed and serialized
|
base {Base} -- the base object to be decomposed and serialized
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(str, dict) -- a tuple containing the object id of the base object and the constructed serializable dictionary
|
(str, dict) -- a tuple containing the object id of the base object and
|
||||||
|
the constructed serializable dictionary
|
||||||
"""
|
"""
|
||||||
self.__reset_writer()
|
self.__reset_writer()
|
||||||
|
|
||||||
@@ -208,6 +213,8 @@ class BaseObjectSerializer:
|
|||||||
Returns:
|
Returns:
|
||||||
Any -- a serializable version of the given object
|
Any -- a serializable version of the given object
|
||||||
"""
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
if isinstance(obj, PRIMITIVES):
|
if isinstance(obj, PRIMITIVES):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@@ -245,16 +252,19 @@ class BaseObjectSerializer:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return obj.dict()
|
return obj.dict()
|
||||||
except:
|
except Exception:
|
||||||
warn(
|
warn(
|
||||||
f"Failed to handle {type(obj)} in `BaseObjectSerializer.traverse_value`",
|
f"Failed to handle {type(obj)} in"
|
||||||
|
" `BaseObjectSerializer.traverse_value`",
|
||||||
SpeckleWarning,
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
|
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
def detach_helper(self, ref_id: str) -> Dict[str, str]:
|
def detach_helper(self, ref_id: 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
|
"""
|
||||||
|
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:
|
Arguments:
|
||||||
ref_id {str} -- the id of the fully traversed object
|
ref_id {str} -- the id of the fully traversed object
|
||||||
@@ -277,7 +287,10 @@ class BaseObjectSerializer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __reset_writer(self) -> None:
|
def __reset_writer(self) -> None:
|
||||||
"""Reinitializes the lineage, and other variables that get used during the json writing process"""
|
"""
|
||||||
|
Reinitializes the lineage, and other variables that get used during the json
|
||||||
|
writing process
|
||||||
|
"""
|
||||||
self.detach_lineage = [True]
|
self.detach_lineage = [True]
|
||||||
self.lineage = []
|
self.lineage = []
|
||||||
self.family_tree = {}
|
self.family_tree = {}
|
||||||
@@ -294,7 +307,7 @@ class BaseObjectSerializer:
|
|||||||
"""
|
"""
|
||||||
if not obj_string:
|
if not obj_string:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.deserialized = {}
|
self.deserialized = {}
|
||||||
obj = safe_json_loads(obj_string)
|
obj = safe_json_loads(obj_string)
|
||||||
return self.recompose_base(obj=obj)
|
return self.recompose_base(obj=obj)
|
||||||
@@ -354,7 +367,8 @@ class BaseObjectSerializer:
|
|||||||
base.__setattr__(prop, self.recompose_base(obj=ref_obj))
|
base.__setattr__(prop, self.recompose_base(obj=ref_obj))
|
||||||
else:
|
else:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
f"Could not find the referenced child object of id `{ref_id}` in the given read transport: {self.read_transport.name}",
|
f"Could not find the referenced child object of id `{ref_id}`"
|
||||||
|
f" in the given read transport: {self.read_transport.name}",
|
||||||
SpeckleWarning,
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
base.__setattr__(prop, self.handle_value(value))
|
base.__setattr__(prop, self.handle_value(value))
|
||||||
@@ -369,7 +383,8 @@ class BaseObjectSerializer:
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
def handle_value(self, obj: Any):
|
def handle_value(self, obj: Any):
|
||||||
"""Helper for recomposing a base object by handling the dictionary representation's values
|
"""Helper for recomposing a base object by handling the dictionary
|
||||||
|
representation's values
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
obj {Any} -- a value from the base object dictionary
|
obj {Any} -- a value from the base object dictionary
|
||||||
@@ -415,7 +430,8 @@ class BaseObjectSerializer:
|
|||||||
ref_obj_str = self.read_transport.get_object(id=ref_id)
|
ref_obj_str = self.read_transport.get_object(id=ref_id)
|
||||||
if not ref_obj_str:
|
if not ref_obj_str:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
f"Could not find the referenced child object of id `{ref_id}` in the given read transport: {self.read_transport.name}",
|
f"Could not find the referenced child object of id `{ref_id}` in the"
|
||||||
|
f" given read transport: {self.read_transport.name}",
|
||||||
SpeckleWarning,
|
SpeckleWarning,
|
||||||
)
|
)
|
||||||
return obj
|
return obj
|
||||||
+14
-16
@@ -1,16 +1,8 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional, List, Dict
|
from typing import Dict, List, Optional
|
||||||
from pydantic import BaseModel
|
|
||||||
from pydantic.main import Extra
|
|
||||||
|
|
||||||
# __________________
|
from pydantic import BaseModel
|
||||||
# | |
|
from pydantic.config import Extra
|
||||||
# | this is v wip |
|
|
||||||
# | pls be careful |
|
|
||||||
# |__________________|
|
|
||||||
# (\__/) ||
|
|
||||||
# (•ㅅ•) ||
|
|
||||||
# / づ
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractTransport(ABC, BaseModel):
|
class AbstractTransport(ABC, BaseModel):
|
||||||
@@ -27,7 +19,9 @@ class AbstractTransport(ABC, BaseModel):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def end_write(self) -> None:
|
def end_write(self) -> None:
|
||||||
"""Optional: signals to the transport that no more items will need to be written."""
|
"""
|
||||||
|
Optional: signals to the transport that no more items will need to be written.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -48,7 +42,8 @@ class AbstractTransport(ABC, BaseModel):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the hash of the object
|
id {str} -- the hash of the object
|
||||||
source_transport {AbstractTransport) -- the transport through which the object can be found
|
source_transport {AbstractTransport)
|
||||||
|
-- the transport through which the object can be found
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -60,7 +55,8 @@ class AbstractTransport(ABC, BaseModel):
|
|||||||
id {str} -- the hash of the object
|
id {str} -- the hash of the object
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str -- the full string representation of the object (or null if no object is found)
|
str -- the full string representation
|
||||||
|
of the object (or null if no object is found)
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -72,7 +68,8 @@ class AbstractTransport(ABC, BaseModel):
|
|||||||
id_list -- List of object id to be checked
|
id_list -- List of object id to be checked
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, bool] -- keys: input ids, values: whether the transport has that object
|
Dict[str, bool] -- keys: input ids, values:
|
||||||
|
whether the transport has that object
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -84,7 +81,8 @@ class AbstractTransport(ABC, BaseModel):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the id of the object you want to copy
|
id {str} -- the id of the object you want to copy
|
||||||
target_transport {AbstractTransport} -- the transport you want to copy the object to
|
target_transport {AbstractTransport}
|
||||||
|
-- the transport you want to copy the object to
|
||||||
Returns:
|
Returns:
|
||||||
str -- the string representation of the root object
|
str -- the string representation of the root object
|
||||||
"""
|
"""
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from typing import Any, List, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from specklepy.transports.server.server import ServerTransport
|
||||||
|
|
||||||
|
__all__ = ["ServerTransport"]
|
||||||
+12
-5
@@ -1,10 +1,11 @@
|
|||||||
|
import gzip
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
|
||||||
import queue
|
import queue
|
||||||
import gzip
|
import threading
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -107,7 +108,8 @@ class BatchSender(object):
|
|||||||
|
|
||||||
if not new_objects:
|
if not new_objects:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Uploading batch of {len(batch)} objects: all objects are already in the server"
|
f"Uploading batch of {len(batch)} objects: all objects are already in"
|
||||||
|
" the server"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -126,11 +128,16 @@ class BatchSender(object):
|
|||||||
if r.status_code != 201:
|
if r.status_code != 201:
|
||||||
LOG.warning("Upload server response: %s", r.text)
|
LOG.warning("Upload server response: %s", r.text)
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
message=f"Could not save the object to the server - status code {r.status_code}"
|
message=(
|
||||||
|
"Could not save the object to the server - status code"
|
||||||
|
f" {r.status_code}"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
except json.JSONDecodeError as error:
|
except json.JSONDecodeError as error:
|
||||||
return SpeckleException(
|
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.",
|
f"Failed to send objects to {self.server_url}. Please ensure this"
|
||||||
|
f" stream ({self.stream_id}) exists on this server and that you have"
|
||||||
|
" permission to send to it.",
|
||||||
error,
|
error,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
import requests
|
from typing import Any, Dict, List, Optional
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from typing import Any, Dict, List
|
import requests
|
||||||
|
|
||||||
from specklepy.api.client import SpeckleClient
|
from specklepy.api.client import SpeckleClient
|
||||||
from specklepy.api.credentials import Account, get_account_from_token
|
from specklepy.api.credentials import Account, get_account_from_token
|
||||||
@@ -14,10 +14,10 @@ from .batch_sender import BatchSender
|
|||||||
|
|
||||||
class ServerTransport(AbstractTransport):
|
class ServerTransport(AbstractTransport):
|
||||||
"""
|
"""
|
||||||
The `ServerTransport` is the vehicle through which you transport objects to and from a Speckle Server. Provide it to
|
The `ServerTransport` is the vehicle through which you transport objects to and
|
||||||
`operations.send()` or `operations.receive()`.
|
from a Speckle Server. Provide it to `operations.send()` or `operations.receive()`.
|
||||||
|
|
||||||
The `ServerTransport` can be authenticted two different ways:
|
The `ServerTransport` can be authenticated two different ways:
|
||||||
1. by providing a `SpeckleClient`
|
1. by providing a `SpeckleClient`
|
||||||
2. by providing an `Account`
|
2. by providing an `Account`
|
||||||
3. by providing a `token` and `url`
|
3. by providing a `token` and `url`
|
||||||
@@ -29,14 +29,15 @@ class ServerTransport(AbstractTransport):
|
|||||||
# here's the data you want to send
|
# here's the data you want to send
|
||||||
block = Block(length=2, height=4)
|
block = Block(length=2, height=4)
|
||||||
|
|
||||||
# next create the server transport - this is the vehicle through which you will send and receive
|
# next create the server transport - this is the vehicle through which
|
||||||
|
# you will send and receive
|
||||||
transport = ServerTransport(stream_id=new_stream_id, client=client)
|
transport = ServerTransport(stream_id=new_stream_id, client=client)
|
||||||
|
|
||||||
# this serialises the block and sends it to the transport
|
# this serialises the block and sends it to the transport
|
||||||
hash = operations.send(base=block, transports=[transport])
|
hash = operations.send(base=block, transports=[transport])
|
||||||
|
|
||||||
# you can now create a commit on your stream with this object
|
# you can now create a commit on your stream with this object
|
||||||
commid_id = client.commit.create(
|
commit_id = client.commit.create(
|
||||||
stream_id=new_stream_id,
|
stream_id=new_stream_id,
|
||||||
obj_id=hash,
|
obj_id=hash,
|
||||||
message="this is a block I made in speckle-py",
|
message="this is a block I made in speckle-py",
|
||||||
@@ -45,25 +46,26 @@ class ServerTransport(AbstractTransport):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_name = "RemoteTransport"
|
_name = "RemoteTransport"
|
||||||
url: str = None
|
url: Optional[str] = None
|
||||||
stream_id: str = None
|
stream_id: Optional[str] = None
|
||||||
account: Account = None
|
account: Optional[Account] = None
|
||||||
saved_obj_count: int = 0
|
saved_obj_count: int = 0
|
||||||
session: requests.Session = None
|
session: Optional[requests.Session] = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
stream_id: str,
|
stream_id: str,
|
||||||
client: SpeckleClient = None,
|
client: Optional[SpeckleClient] = None,
|
||||||
account: Account = None,
|
account: Optional[Account] = None,
|
||||||
token: str = None,
|
token: Optional[str] = None,
|
||||||
url: str = None,
|
url: Optional[str] = None,
|
||||||
**data: Any,
|
**data: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
if client is None and account is None and token is None and url is None:
|
if client is None and account is None and token is None and url is None:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
"You must provide either a client or a token and url to construct a ServerTransport."
|
"You must provide either a client or a token and url to construct a"
|
||||||
|
" ServerTransport."
|
||||||
)
|
)
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
@@ -74,7 +76,8 @@ class ServerTransport(AbstractTransport):
|
|||||||
if not client.account.token:
|
if not client.account.token:
|
||||||
warn(
|
warn(
|
||||||
SpeckleWarning(
|
SpeckleWarning(
|
||||||
f"Unauthenticated Speckle Client provided to Server Transport for {self.url}. Receiving from private streams will fail."
|
"Unauthenticated Speckle Client provided to Server Transport"
|
||||||
|
f" for {self.url}. Receiving from private streams will fail."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -118,8 +121,10 @@ class ServerTransport(AbstractTransport):
|
|||||||
# return obj
|
# return obj
|
||||||
|
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
"Getting a single object using `ServerTransport.get_object()` is not implemented. To get an object from the server, please use the `SpeckleClient.object.get()` route",
|
"Getting a single object using `ServerTransport.get_object()` is not"
|
||||||
NotImplementedError,
|
" implemented. To get an object from the server, please use the"
|
||||||
|
" `SpeckleClient.object.get()` route",
|
||||||
|
NotImplementedError(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
def has_objects(self, id_list: List[str]) -> Dict[str, bool]:
|
||||||
@@ -134,7 +139,8 @@ class ServerTransport(AbstractTransport):
|
|||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Can't get object {self.stream_id}/{id}: HTTP error {r.status_code} ({r.text[:1000]})"
|
f"Can't get object {self.stream_id}/{id}: HTTP error"
|
||||||
|
f" {r.status_code} ({r.text[:1000]})"
|
||||||
)
|
)
|
||||||
root_obj_serialized = r.text
|
root_obj_serialized = r.text
|
||||||
root_obj = json.loads(root_obj_serialized)
|
root_obj = json.loads(root_obj_serialized)
|
||||||
@@ -1,32 +1,30 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import sched
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from typing import Any, List, Dict, Tuple
|
|
||||||
from appdirs import user_data_dir
|
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from specklepy.core.helpers import speckle_path_provider
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
|
|
||||||
|
|
||||||
class SQLiteTransport(AbstractTransport):
|
class SQLiteTransport(AbstractTransport):
|
||||||
_name = "SQLite"
|
_name = "SQLite"
|
||||||
_base_path: str = None
|
_base_path: Optional[str] = None
|
||||||
_root_path: str = None
|
_root_path: Optional[str] = None
|
||||||
__connection: sqlite3.Connection = None
|
__connection: Optional[sqlite3.Connection] = None
|
||||||
app_name: str = ""
|
app_name: str = ""
|
||||||
scope: str = ""
|
scope: str = ""
|
||||||
saved_obj_count: int = 0
|
saved_obj_count: int = 0
|
||||||
max_size: int = None
|
max_size: Optional[int] = None
|
||||||
_current_batch: List[Tuple[str, str]] = None
|
_current_batch: Optional[List[Tuple[str, str]]] = None
|
||||||
_current_batch_size: int = None
|
_current_batch_size: Optional[int] = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_path: str = None,
|
base_path: Optional[str] = None,
|
||||||
app_name: str = None,
|
app_name: Optional[str] = None,
|
||||||
scope: str = None,
|
scope: Optional[str] = None,
|
||||||
max_batch_size_mb: float = 10.0,
|
max_batch_size_mb: float = 10.0,
|
||||||
**data: Any,
|
**data: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -47,7 +45,9 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
self.__initialise()
|
self.__initialise()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"SQLiteTransport could not initialise {self.scope}.db at {self._base_path}. Either provide a different `base_path` or use an alternative transport.",
|
f"SQLiteTransport could not initialise {self.scope}.db at"
|
||||||
|
f" {self._base_path}. Either provide a different `base_path` or use an"
|
||||||
|
" alternative transport.",
|
||||||
ex,
|
ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,21 +56,25 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_base_path(app_name):
|
def get_base_path(app_name):
|
||||||
# from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
# # from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
||||||
# default mac path is not the one we use (we use unix path), so using special case for this
|
# # default mac path is not the one we use (we use unix path), so using special case for this
|
||||||
system = sys.platform
|
# system = sys.platform
|
||||||
if system.startswith("java"):
|
# if system.startswith("java"):
|
||||||
import platform
|
# import platform
|
||||||
|
|
||||||
os_name = platform.java_ver()[3][0]
|
# os_name = platform.java_ver()[3][0]
|
||||||
if os_name.startswith("Mac"):
|
# if os_name.startswith("Mac"):
|
||||||
system = "darwin"
|
# system = "darwin"
|
||||||
|
|
||||||
if system != "darwin":
|
# if system != "darwin":
|
||||||
return user_data_dir(appname=app_name, appauthor=False, roaming=True)
|
# return user_data_dir(appname=app_name, appauthor=False, roaming=True)
|
||||||
|
|
||||||
path = os.path.expanduser("~/.config/")
|
# path = os.path.expanduser("~/.config/")
|
||||||
return os.path.join(path, app_name)
|
# return os.path.join(path, app_name)
|
||||||
|
|
||||||
|
return str(
|
||||||
|
speckle_path_provider.user_application_data_path().joinpath(app_name)
|
||||||
|
)
|
||||||
|
|
||||||
def save_object_from_transport(
|
def save_object_from_transport(
|
||||||
self, id: str, source_transport: AbstractTransport
|
self, id: str, source_transport: AbstractTransport
|
||||||
@@ -79,14 +83,16 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the object id
|
id {str} -- the object id
|
||||||
source_transport {AbstractTransport) -- the transport through which the object can be found
|
source_transport {AbstractTransport)
|
||||||
|
-- the transport through which the object can be found
|
||||||
"""
|
"""
|
||||||
serialized_object = source_transport.get_object(id)
|
serialized_object = source_transport.get_object(id)
|
||||||
self.save_object(id, serialized_object)
|
self.save_object(id, serialized_object)
|
||||||
|
|
||||||
def save_object(self, id: str, serialized_object: str) -> None:
|
def save_object(self, id: str, serialized_object: str) -> None:
|
||||||
"""
|
"""
|
||||||
Adds an object to the current batch to be written to the db. If the current batch is full,
|
Adds an object to the current batch to be written to the db.
|
||||||
|
If the current batch is full,
|
||||||
the batch is written to the db and the current batch is reset.
|
the batch is written to the db and the current batch is reset.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -118,7 +124,8 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
self.__connection.commit()
|
self.__connection.commit()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Could not save the batch of objects to the local db. Inner exception: {ex}",
|
"Could not save the batch of objects to the local db. Inner exception:"
|
||||||
|
f" {ex}",
|
||||||
ex,
|
ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -157,7 +164,10 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_all_objects(self):
|
def get_all_objects(self):
|
||||||
"""Returns all the objects in the store. NOTE: do not use for large collections!"""
|
"""
|
||||||
|
Returns all the objects in the store.
|
||||||
|
NOTE: do not use for large collections!
|
||||||
|
"""
|
||||||
self.__check_connection()
|
self.__check_connection()
|
||||||
with closing(self.__connection.cursor()) as c:
|
with closing(self.__connection.cursor()) as c:
|
||||||
rows = c.execute("SELECT * FROM objects").fetchall()
|
rows = c.execute("SELECT * FROM objects").fetchall()
|
||||||
+2
-102
@@ -1,108 +1,8 @@
|
|||||||
import uuid
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
|
||||||
from specklepy.api.models import Stream
|
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
from specklepy.objects.geometry import Point
|
|
||||||
from specklepy.objects.fakemesh import FakeDirection, FakeMesh
|
|
||||||
from specklepy.logging import metrics
|
|
||||||
|
|
||||||
metrics.disable()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def host():
|
|
||||||
return "localhost:3000"
|
|
||||||
|
|
||||||
|
|
||||||
def seed_user(host):
|
|
||||||
seed = uuid.uuid4().hex
|
|
||||||
user_dict = {
|
|
||||||
"email": f"{seed[0:7]}@spockle.com",
|
|
||||||
"password": "$uper$3cr3tP@ss",
|
|
||||||
"name": f"{seed[0:7]} Name",
|
|
||||||
"company": "test spockle",
|
|
||||||
}
|
|
||||||
|
|
||||||
r = requests.post(
|
|
||||||
url=f"http://{host}/auth/local/register?challenge=pyspeckletests",
|
|
||||||
data=user_dict,
|
|
||||||
)
|
|
||||||
print(r.url)
|
|
||||||
access_code = r.url.split("access_code=")[1]
|
|
||||||
|
|
||||||
r_tokens = requests.post(
|
|
||||||
url=f"http://{host}/auth/token",
|
|
||||||
json={
|
|
||||||
"appSecret": "spklwebapp",
|
|
||||||
"appId": "spklwebapp",
|
|
||||||
"accessCode": access_code,
|
|
||||||
"challenge": "pyspeckletests",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
user_dict.update(**r_tokens.json())
|
|
||||||
|
|
||||||
return user_dict
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def user_dict(host):
|
|
||||||
return seed_user(host)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def second_user_dict(host):
|
|
||||||
return seed_user(host)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def client(host, user_dict):
|
|
||||||
client = SpeckleClient(host=host, use_ssl=False)
|
|
||||||
client.authenticate_with_token(user_dict["token"])
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def second_client(host, second_user_dict):
|
|
||||||
client = SpeckleClient(host=host, use_ssl=False)
|
|
||||||
client.authenticate_with_token(second_user_dict["token"])
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def sample_stream(client):
|
|
||||||
stream = Stream(
|
|
||||||
name="a sample stream for testing",
|
|
||||||
description="a stream created for testing",
|
|
||||||
isPublic=True,
|
|
||||||
)
|
|
||||||
stream.id = client.stream.create(stream.name, stream.description, stream.isPublic)
|
|
||||||
return stream
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def mesh():
|
|
||||||
mesh = FakeMesh()
|
|
||||||
mesh.name = "my_mesh"
|
|
||||||
mesh.vertices = [random.uniform(0, 10) for _ in range(1, 210)]
|
|
||||||
mesh.faces = list(range(1, 210))
|
|
||||||
mesh["@(100)colours"] = [random.uniform(0, 10) for _ in range(1, 210)]
|
|
||||||
mesh["@()default_chunk"] = [random.uniform(0, 10) for _ in range(1, 210)]
|
|
||||||
mesh.cardinal_dir = FakeDirection.WEST
|
|
||||||
mesh.test_bases = [Base(name=f"test {i}") for i in range(1, 22)]
|
|
||||||
mesh.detach_this = Base(name="predefined detached base")
|
|
||||||
mesh["@detach"] = Base(name="detached base")
|
|
||||||
mesh["@detached_list"] = [
|
|
||||||
42,
|
|
||||||
"some text",
|
|
||||||
[1, 2, 3],
|
|
||||||
Base(name="detached within a list"),
|
|
||||||
]
|
|
||||||
mesh.origin = Point(x=4, y=2)
|
|
||||||
return mesh
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import random
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from specklepy.api.models import Stream
|
||||||
|
from specklepy.logging import metrics
|
||||||
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.fakemesh import FakeDirection, FakeMesh
|
||||||
|
from specklepy.objects.geometry import Point
|
||||||
|
|
||||||
|
metrics.disable()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def host():
|
||||||
|
return "localhost:3000"
|
||||||
|
|
||||||
|
|
||||||
|
def seed_user(host):
|
||||||
|
seed = uuid.uuid4().hex
|
||||||
|
user_dict = {
|
||||||
|
"email": f"{seed[0:7]}@spockle.com",
|
||||||
|
"password": "$uper$3cr3tP@ss",
|
||||||
|
"name": f"{seed[0:7]} Name",
|
||||||
|
"company": "test spockle",
|
||||||
|
}
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
url=f"http://{host}/auth/local/register?challenge=pyspeckletests",
|
||||||
|
data=user_dict,
|
||||||
|
# do not follow redirects here, they lead to the frontend, which might not be
|
||||||
|
# running in a test environment
|
||||||
|
# causing the response to not be OK in the end
|
||||||
|
allow_redirects=False,
|
||||||
|
)
|
||||||
|
if not r.ok:
|
||||||
|
raise Exception(f"Cannot seed user: {r.reason}")
|
||||||
|
access_code = r.text.split("access_code=")[1]
|
||||||
|
|
||||||
|
r_tokens = requests.post(
|
||||||
|
url=f"http://{host}/auth/token",
|
||||||
|
json={
|
||||||
|
"appSecret": "spklwebapp",
|
||||||
|
"appId": "spklwebapp",
|
||||||
|
"accessCode": access_code,
|
||||||
|
"challenge": "pyspeckletests",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
user_dict.update(**r_tokens.json())
|
||||||
|
|
||||||
|
return user_dict
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def user_dict(host):
|
||||||
|
return seed_user(host)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def second_user_dict(host):
|
||||||
|
return seed_user(host)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def client(host, user_dict):
|
||||||
|
client = SpeckleClient(host=host, use_ssl=False)
|
||||||
|
client.authenticate_with_token(user_dict["token"])
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def second_client(host, second_user_dict):
|
||||||
|
client = SpeckleClient(host=host, use_ssl=False)
|
||||||
|
client.authenticate_with_token(second_user_dict["token"])
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def sample_stream(client):
|
||||||
|
stream = Stream(
|
||||||
|
name="a sample stream for testing",
|
||||||
|
description="a stream created for testing",
|
||||||
|
isPublic=True,
|
||||||
|
)
|
||||||
|
stream.id = client.stream.create(stream.name, stream.description, stream.isPublic)
|
||||||
|
return stream
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def mesh():
|
||||||
|
mesh = FakeMesh()
|
||||||
|
mesh.name = "my_mesh"
|
||||||
|
mesh.vertices = [random.uniform(0, 10) for _ in range(1, 210)]
|
||||||
|
mesh.faces = list(range(1, 210))
|
||||||
|
mesh["@(100)colours"] = [random.uniform(0, 10) for _ in range(1, 210)]
|
||||||
|
mesh["@()default_chunk"] = [random.uniform(0, 10) for _ in range(1, 210)]
|
||||||
|
mesh.cardinal_dir = FakeDirection.WEST
|
||||||
|
mesh.test_bases = [Base(name=f"test {i}") for i in range(1, 22)]
|
||||||
|
mesh.detach_this = Base(name="predefined detached base")
|
||||||
|
mesh["@detach"] = Base(name="detached base")
|
||||||
|
mesh["@detached_list"] = [
|
||||||
|
42,
|
||||||
|
"some text",
|
||||||
|
[1, 2, 3],
|
||||||
|
Base(name="detached within a list"),
|
||||||
|
]
|
||||||
|
mesh.origin = Point(x=4, y=2)
|
||||||
|
return mesh
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from specklepy.api.models import Activity, ActivityCollection, User
|
||||||
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
class TestUser:
|
||||||
|
def test_user_get_self(self, client: SpeckleClient, user_dict):
|
||||||
|
fetched_user = client.active_user.get()
|
||||||
|
|
||||||
|
assert isinstance(fetched_user, User)
|
||||||
|
assert fetched_user.name == user_dict["name"]
|
||||||
|
assert fetched_user.email == user_dict["email"]
|
||||||
|
|
||||||
|
user_dict["id"] = fetched_user.id
|
||||||
|
|
||||||
|
def test_user_update(self, client: SpeckleClient):
|
||||||
|
bio = "i am a ghost in the machine"
|
||||||
|
|
||||||
|
failed_update = client.active_user.update()
|
||||||
|
assert isinstance(failed_update, SpeckleException)
|
||||||
|
|
||||||
|
updated = client.active_user.update(bio=bio)
|
||||||
|
|
||||||
|
me = client.active_user.get()
|
||||||
|
|
||||||
|
assert updated is True
|
||||||
|
assert me.bio == bio
|
||||||
|
|
||||||
|
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
||||||
|
my_activity = client.active_user.activity(limit=10)
|
||||||
|
their_activity = client.other_user.activity(second_user_dict["id"])
|
||||||
|
|
||||||
|
assert isinstance(my_activity, ActivityCollection)
|
||||||
|
assert my_activity.items
|
||||||
|
assert isinstance(my_activity.items[0], Activity)
|
||||||
|
assert my_activity.totalCount
|
||||||
|
assert isinstance(their_activity, ActivityCollection)
|
||||||
|
|
||||||
|
older_activity = client.user.activity(before=my_activity.items[0].time)
|
||||||
|
|
||||||
|
assert isinstance(older_activity, ActivityCollection)
|
||||||
|
assert older_activity.totalCount
|
||||||
|
assert older_activity.totalCount < my_activity.totalCount
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.transports.server import ServerTransport
|
|
||||||
from specklepy.api.models import Branch, Commit, Stream
|
from specklepy.api.models import Branch, Commit, Stream
|
||||||
|
from specklepy.transports.server import ServerTransport
|
||||||
|
|
||||||
|
|
||||||
class TestBranch:
|
class TestBranch:
|
||||||
@@ -1,18 +1,28 @@
|
|||||||
|
from tempfile import gettempdir
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.api.client import SpeckleClient
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from specklepy.api.credentials import Account, get_account_from_token
|
||||||
|
from specklepy.core.helpers import speckle_path_provider
|
||||||
|
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
from specklepy.transports.server import ServerTransport
|
from specklepy.transports.server import ServerTransport
|
||||||
from specklepy.api.credentials import Account, get_account_from_token
|
|
||||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_authentication():
|
def test_invalid_authentication():
|
||||||
|
# overriding the appdata path (it would be enough to just override the Accounts)
|
||||||
|
# but this way its cleaner
|
||||||
|
speckle_path_provider.override_application_data_path(gettempdir())
|
||||||
client = SpeckleClient()
|
client = SpeckleClient()
|
||||||
|
|
||||||
with pytest.warns(SpeckleWarning):
|
with pytest.warns(SpeckleWarning):
|
||||||
client.authenticate_with_token("fake token")
|
client.authenticate_with_token("fake token")
|
||||||
|
|
||||||
|
# remove path override
|
||||||
|
speckle_path_provider.override_application_data_path(None)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_send():
|
def test_invalid_send():
|
||||||
client = SpeckleClient()
|
client = SpeckleClient()
|
||||||
@@ -45,4 +55,4 @@ def test_account_from_token_and_url():
|
|||||||
acct = get_account_from_token(token, url)
|
acct = get_account_from_token(token, url)
|
||||||
|
|
||||||
assert acct.token == token
|
assert acct.token == token
|
||||||
assert acct.serverInfo.url == url
|
assert acct.serverInfo.url == url
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.api.models import Commit, Stream
|
from specklepy.api.models import Commit, Stream
|
||||||
from specklepy.transports.server.server import ServerTransport
|
from specklepy.transports.server.server import ServerTransport
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=4)
|
@pytest.mark.run(order=6)
|
||||||
class TestCommit:
|
class TestCommit:
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def commit(self):
|
def commit(self):
|
||||||
@@ -84,4 +85,4 @@ class TestCommit:
|
|||||||
message="testing received",
|
message="testing received",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert commit_marked_received == True
|
assert commit_marked_received is True
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api.models import Stream
|
from specklepy.api.models import Stream
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
from specklepy.objects.encoding import ObjectArray
|
from specklepy.objects.encoding import ObjectArray
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from specklepy.api.models import Activity, ActivityCollection, LimitedUser
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
class TestOtherUser:
|
||||||
|
def test_user_get_self(self, client):
|
||||||
|
"""
|
||||||
|
Test, that a limited user query cannot query the active user.
|
||||||
|
"""
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
client.other_user.get()
|
||||||
|
|
||||||
|
def test_user_search(self, client, second_user_dict):
|
||||||
|
search_results = client.other_user.search(
|
||||||
|
search_query=second_user_dict["name"][:5]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(search_results, list)
|
||||||
|
assert len(search_results) > 0
|
||||||
|
result_user = search_results[0]
|
||||||
|
assert isinstance(result_user, LimitedUser)
|
||||||
|
assert result_user.name == second_user_dict["name"]
|
||||||
|
|
||||||
|
second_user_dict["id"] = result_user.id
|
||||||
|
assert getattr(result_user, "email", None) is None
|
||||||
|
|
||||||
|
def test_user_get(self, client, second_user_dict):
|
||||||
|
fetched_user = client.other_user.get(id=second_user_dict["id"])
|
||||||
|
|
||||||
|
assert isinstance(fetched_user, LimitedUser)
|
||||||
|
assert fetched_user.name == second_user_dict["name"]
|
||||||
|
# changed in the server, now you cannot get emails of other users
|
||||||
|
# not checking this, since the first user could or could not be an admin on the server
|
||||||
|
# admins can get emails of others, regular users can't
|
||||||
|
# assert fetched_user.email == None
|
||||||
|
|
||||||
|
second_user_dict["id"] = fetched_user.id
|
||||||
|
|
||||||
|
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
||||||
|
their_activity = client.other_user.activity(second_user_dict["id"])
|
||||||
|
|
||||||
|
assert isinstance(their_activity, ActivityCollection)
|
||||||
|
assert isinstance(their_activity.items, list)
|
||||||
|
assert isinstance(their_activity.items[0], Activity)
|
||||||
|
assert their_activity.totalCount
|
||||||
|
assert their_activity.totalCount > 0
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.transports.server import ServerTransport
|
|
||||||
from specklepy.transports.memory import MemoryTransport
|
|
||||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
from specklepy.objects.geometry import Point
|
|
||||||
from specklepy.objects.fakemesh import FakeMesh
|
from specklepy.objects.fakemesh import FakeMesh
|
||||||
|
from specklepy.objects.geometry import Point
|
||||||
|
from specklepy.transports.memory import MemoryTransport
|
||||||
|
from specklepy.transports.server import ServerTransport
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=3)
|
@pytest.mark.run(order=5)
|
||||||
class TestSerialization:
|
class TestSerialization:
|
||||||
def test_serialize(self, base):
|
def test_serialize(self, base):
|
||||||
serialized = operations.serialize(base)
|
serialized = operations.serialize(base)
|
||||||
@@ -91,7 +92,7 @@ class TestSerialization:
|
|||||||
assert deserialised == {"foo": "bar"}
|
assert deserialised == {"foo": "bar"}
|
||||||
|
|
||||||
def test_big_int(self):
|
def test_big_int(self):
|
||||||
big_int = '{"big": ' + str(2 ** 64) + "}"
|
big_int = '{"big": ' + str(2**64) + "}"
|
||||||
deserialised = operations.deserialize(big_int)
|
deserialised = operations.deserialize(big_int)
|
||||||
|
|
||||||
assert deserialised == {"big": 2 ** 64}
|
assert deserialised == {"big": 2**64}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from specklepy.api.models import ServerInfo
|
|
||||||
from specklepy.api.client import SpeckleClient
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from specklepy.api.models import ServerInfo
|
||||||
|
|
||||||
|
|
||||||
class TestServer:
|
class TestServer:
|
||||||
@@ -22,8 +23,11 @@ class TestServer:
|
|||||||
version = client.server.version()
|
version = client.server.version()
|
||||||
|
|
||||||
assert isinstance(version, tuple)
|
assert isinstance(version, tuple)
|
||||||
assert isinstance(version[0], int)
|
if len(version) == 1:
|
||||||
assert len(version) >= 3
|
assert version[0] == "dev"
|
||||||
|
else:
|
||||||
|
assert isinstance(version[0], int)
|
||||||
|
assert len(version) >= 3
|
||||||
|
|
||||||
def test_server_apps(self, client: SpeckleClient):
|
def test_server_apps(self, client: SpeckleClient):
|
||||||
apps = client.server.apps()
|
apps = client.server.apps()
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
|
||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
from specklepy.api.models import (
|
from specklepy.api.models import (
|
||||||
ActivityCollection,
|
|
||||||
Activity,
|
Activity,
|
||||||
|
ActivityCollection,
|
||||||
PendingStreamCollaborator,
|
PendingStreamCollaborator,
|
||||||
Stream,
|
Stream,
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.logging.exceptions import (
|
from specklepy.logging.exceptions import (
|
||||||
GraphQLException,
|
GraphQLException,
|
||||||
SpeckleException,
|
SpeckleException,
|
||||||
@@ -15,7 +15,7 @@ from specklepy.logging.exceptions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=2)
|
@pytest.mark.run(order=3)
|
||||||
class TestStream:
|
class TestStream:
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def stream(self):
|
def stream(self):
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from specklepy.api.models import Activity, ActivityCollection, User
|
||||||
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
class TestUser:
|
||||||
|
def test_user_get_self(self, client: SpeckleClient, user_dict):
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
fetched_user = client.user.get()
|
||||||
|
|
||||||
|
assert isinstance(fetched_user, User)
|
||||||
|
assert fetched_user.name == user_dict["name"]
|
||||||
|
assert fetched_user.email == user_dict["email"]
|
||||||
|
|
||||||
|
user_dict["id"] = fetched_user.id
|
||||||
|
|
||||||
|
def test_user_search(self, client, second_user_dict):
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
search_results = client.user.search(
|
||||||
|
search_query=second_user_dict["name"][:5]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(search_results, list)
|
||||||
|
assert isinstance(search_results[0], User)
|
||||||
|
assert search_results[0].name == second_user_dict["name"]
|
||||||
|
|
||||||
|
second_user_dict["id"] = search_results[0].id
|
||||||
|
|
||||||
|
def test_user_get(self, client, second_user_dict):
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
fetched_user = client.user.get(id=second_user_dict["id"])
|
||||||
|
|
||||||
|
assert isinstance(fetched_user, User)
|
||||||
|
assert fetched_user.name == second_user_dict["name"]
|
||||||
|
# changed in the server, now you cannot get emails of other users
|
||||||
|
# not checking this, since the first user could or could not be an admin on the server
|
||||||
|
# admins can get emails of others, regular users can't
|
||||||
|
# assert fetched_user.email == None
|
||||||
|
|
||||||
|
second_user_dict["id"] = fetched_user.id
|
||||||
|
|
||||||
|
def test_user_update(self, client):
|
||||||
|
bio = "i am a ghost in the machine"
|
||||||
|
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
failed_update = client.user.update()
|
||||||
|
assert isinstance(failed_update, SpeckleException)
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
updated = client.user.update(bio=bio)
|
||||||
|
assert updated is True
|
||||||
|
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
me = client.user.get()
|
||||||
|
assert me.bio == bio
|
||||||
|
|
||||||
|
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
my_activity = client.user.activity(limit=10)
|
||||||
|
their_activity = client.user.activity(second_user_dict["id"])
|
||||||
|
|
||||||
|
assert isinstance(my_activity, ActivityCollection)
|
||||||
|
assert my_activity.items
|
||||||
|
assert isinstance(my_activity.items[0], Activity)
|
||||||
|
assert my_activity.totalCount
|
||||||
|
assert isinstance(their_activity, ActivityCollection)
|
||||||
|
|
||||||
|
older_activity = client.user.activity(before=my_activity.items[0].time)
|
||||||
|
|
||||||
|
assert isinstance(older_activity, ActivityCollection)
|
||||||
|
assert older_activity.totalCount
|
||||||
|
assert older_activity.totalCount < my_activity.totalCount
|
||||||
@@ -1,5 +1,32 @@
|
|||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api.wrapper import StreamWrapper
|
from specklepy.api.wrapper import StreamWrapper
|
||||||
|
from specklepy.core.helpers import speckle_path_provider
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module", autouse=True)
|
||||||
|
def user_path() -> Iterable[Path]:
|
||||||
|
speckle_path_provider.override_application_data_path(tempfile.gettempdir())
|
||||||
|
path = speckle_path_provider.accounts_folder_path().joinpath("test_acc.json")
|
||||||
|
# hey, py37 doesn't support the missing_ok argument
|
||||||
|
try:
|
||||||
|
path.unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
path.parent.absolute().mkdir(exist_ok=True)
|
||||||
|
yield path
|
||||||
|
if path.exists():
|
||||||
|
path.unlink()
|
||||||
|
speckle_path_provider.override_application_data_path(None)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_stream():
|
def test_parse_stream():
|
||||||
@@ -54,7 +81,8 @@ def test_parse_globals_as_commit():
|
|||||||
assert wrap.type == "commit"
|
assert wrap.type == "commit"
|
||||||
|
|
||||||
|
|
||||||
#! NOTE: the following three tests may not pass locally if you have a `speckle.xyz` account in manager
|
#! NOTE: the following three tests may not pass locally
|
||||||
|
# if you have a `speckle.xyz` account in manager
|
||||||
def test_get_client_without_auth():
|
def test_get_client_without_auth():
|
||||||
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
||||||
client = wrap.get_client()
|
client = wrap.get_client()
|
||||||
@@ -62,7 +90,7 @@ def test_get_client_without_auth():
|
|||||||
assert client is not None
|
assert client is not None
|
||||||
|
|
||||||
|
|
||||||
def test_get_new_client_with_token():
|
def test_get_new_client_with_token(user_path):
|
||||||
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
||||||
client = wrap.get_client()
|
client = wrap.get_client()
|
||||||
client = wrap.get_client(token="super-secret-token")
|
client = wrap.get_client(token="super-secret-token")
|
||||||
@@ -79,3 +107,22 @@ def test_get_transport_with_token():
|
|||||||
|
|
||||||
assert transport is not None
|
assert transport is not None
|
||||||
assert client.account.token == "super-secret-token"
|
assert client.account.token == "super-secret-token"
|
||||||
|
|
||||||
|
|
||||||
|
def test_wrapper_url_match(user_path) -> None:
|
||||||
|
"""
|
||||||
|
The stream wrapper should only recognize exact url matches for the account
|
||||||
|
definitions and not match for subdomains.
|
||||||
|
"""
|
||||||
|
account = {
|
||||||
|
"token": "foobar",
|
||||||
|
"serverInfo": {"name": "foo", "url": "http://foo.bar.baz", "company": "Foo"},
|
||||||
|
"userInfo": {"id": "bla", "name": "A rando tester", "email": "rando@tester.me"},
|
||||||
|
}
|
||||||
|
|
||||||
|
user_path.write_text(json.dumps(account))
|
||||||
|
wrap = StreamWrapper("http://bar.baz/streams/bogus")
|
||||||
|
|
||||||
|
account = wrap.get_account()
|
||||||
|
|
||||||
|
assert account.userInfo.email is None
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.api.models import Activity, ActivityCollection, User
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=1)
|
|
||||||
class TestUser:
|
|
||||||
def test_user_get_self(self, client, user_dict):
|
|
||||||
fetched_user = client.user.get()
|
|
||||||
|
|
||||||
assert isinstance(fetched_user, User)
|
|
||||||
assert fetched_user.name == user_dict["name"]
|
|
||||||
assert fetched_user.email == user_dict["email"]
|
|
||||||
|
|
||||||
user_dict["id"] = fetched_user.id
|
|
||||||
|
|
||||||
def test_user_search(self, client, second_user_dict):
|
|
||||||
search_results = client.user.search(search_query=second_user_dict["name"][:5])
|
|
||||||
|
|
||||||
assert isinstance(search_results, list)
|
|
||||||
assert isinstance(search_results[0], User)
|
|
||||||
assert search_results[0].name == second_user_dict["name"]
|
|
||||||
|
|
||||||
second_user_dict["id"] = search_results[0].id
|
|
||||||
|
|
||||||
def test_user_get(self, client, second_user_dict):
|
|
||||||
fetched_user = client.user.get(id=second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(fetched_user, User)
|
|
||||||
assert fetched_user.name == second_user_dict["name"]
|
|
||||||
assert fetched_user.email == second_user_dict["email"]
|
|
||||||
|
|
||||||
second_user_dict["id"] = fetched_user.id
|
|
||||||
|
|
||||||
def test_user_update(self, client):
|
|
||||||
bio = "i am a ghost in the machine"
|
|
||||||
|
|
||||||
failed_update = client.user.update()
|
|
||||||
updated = client.user.update(bio=bio)
|
|
||||||
|
|
||||||
me = client.user.get()
|
|
||||||
|
|
||||||
assert isinstance(failed_update, SpeckleException)
|
|
||||||
assert updated is True
|
|
||||||
assert me.bio == bio
|
|
||||||
|
|
||||||
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
|
||||||
my_activity = client.user.activity(limit=10)
|
|
||||||
their_activity = client.user.activity(second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(my_activity, ActivityCollection)
|
|
||||||
assert isinstance(my_activity.items[0], Activity)
|
|
||||||
assert my_activity.totalCount > 0
|
|
||||||
assert isinstance(their_activity, ActivityCollection)
|
|
||||||
|
|
||||||
older_activity = client.user.activity(before=my_activity.items[0].time)
|
|
||||||
|
|
||||||
assert isinstance(older_activity, ActivityCollection)
|
|
||||||
assert older_activity.totalCount < my_activity.totalCount
|
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
|
from contextlib import ExitStack as does_not_raise
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Dict, List, Optional, Union
|
||||||
from contextlib import ExitStack as does_not_raise
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
|
||||||
from specklepy.objects.base import Base, DataChunk
|
from specklepy.objects.base import Base
|
||||||
|
from specklepy.objects.units import Units
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"invalid_prop_name",
|
"invalid_prop_name",
|
||||||
[
|
[
|
||||||
(""),
|
"",
|
||||||
("@"),
|
"@",
|
||||||
("@@wow"),
|
"@@wow",
|
||||||
("this.is.bad"),
|
"this.is.bad",
|
||||||
("super/bad"),
|
"super/bad",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_empty_prop_names(invalid_prop_name: str) -> None:
|
def test_empty_prop_names(invalid_prop_name: str) -> None:
|
||||||
@@ -27,12 +29,15 @@ def test_empty_prop_names(invalid_prop_name: str) -> None:
|
|||||||
class FakeModel(Base):
|
class FakeModel(Base):
|
||||||
"""Just a test class type."""
|
"""Just a test class type."""
|
||||||
|
|
||||||
foo: str = ""
|
|
||||||
|
class FakeSub(FakeModel):
|
||||||
|
"""Just a test class type."""
|
||||||
|
|
||||||
|
|
||||||
def test_new_type_registration() -> None:
|
def test_new_type_registration() -> None:
|
||||||
"""Test if a new subclass is registered into the type register."""
|
"""Test if a new subclass is registered into the type register."""
|
||||||
assert Base.get_registered_type("FakeModel") == FakeModel
|
assert Base.get_registered_type(FakeModel.speckle_type) == FakeModel
|
||||||
|
assert Base.get_registered_type(FakeSub.speckle_type) == FakeSub
|
||||||
assert Base.get_registered_type("🐺️") is None
|
assert Base.get_registered_type("🐺️") is None
|
||||||
|
|
||||||
|
|
||||||
@@ -82,13 +87,22 @@ def test_setting_units():
|
|||||||
b = Base(units="foot")
|
b = Base(units="foot")
|
||||||
assert b.units == "ft"
|
assert b.units == "ft"
|
||||||
|
|
||||||
with pytest.raises(SpeckleException):
|
with pytest.raises(SpeckleInvalidUnitException):
|
||||||
b.units = "big"
|
b.units = "big"
|
||||||
|
|
||||||
b.units = None # invalid args are skipped
|
with pytest.raises(SpeckleInvalidUnitException):
|
||||||
b.units = 7
|
b.units = 7 # invalid args are skipped
|
||||||
assert b.units == "ft"
|
assert b.units == "ft"
|
||||||
|
|
||||||
|
b.units = None # None should be a valid arg
|
||||||
|
assert b.units is None
|
||||||
|
|
||||||
|
b.units = Units.none
|
||||||
|
assert b.units == "none"
|
||||||
|
|
||||||
|
b.units = Units.cm
|
||||||
|
assert b.units == Units.cm.value
|
||||||
|
|
||||||
|
|
||||||
def test_base_of_custom_speckle_type() -> None:
|
def test_base_of_custom_speckle_type() -> None:
|
||||||
b1 = Base.of_type("BirdHouse", name="Tweety's Crib")
|
b1 = Base.of_type("BirdHouse", name="Tweety's Crib")
|
||||||
@@ -139,6 +153,7 @@ def test_type_checking() -> None:
|
|||||||
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
|
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
|
||||||
|
|
||||||
assert order.price == 7.0
|
assert order.price == 7.0
|
||||||
|
assert order.dietary == DietaryRestrictions.VEGAN
|
||||||
|
|
||||||
|
|
||||||
def test_cached_deserialization() -> None:
|
def test_cached_deserialization() -> None:
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user