Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ab787bfb1 | |||
| bbbf373b50 | |||
| f34e4a2874 | |||
| 45ebc375ad | |||
| 4c41fa79fc | |||
| 0aa14ca077 | |||
| 6bfdf8850c | |||
| 22ecd2c2b3 | |||
| f7f9f73e7b | |||
| a7bada391b | |||
| 81ff5d82cb | |||
| d25edbb3d7 | |||
| 7dedff68f4 | |||
| d6e31a9752 | |||
| 09c61424d7 | |||
| e9bdf0ceb8 | |||
| 7e6174ebc1 | |||
| b8ae3ca8c8 | |||
| d690c45b35 | |||
| 5d3a824986 | |||
| 6f56ecb0c0 | |||
| 6c33c61a6d | |||
| 71afb1275f |
@@ -11,5 +11,7 @@ jobs:
|
||||
# Orchestrate our job run sequence
|
||||
workflows:
|
||||
build_and_test:
|
||||
when:
|
||||
false
|
||||
jobs:
|
||||
- build
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
|
||||
ARG VARIANT="3.10"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
|
||||
|
||||
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
|
||||
ARG NODE_VERSION="16"
|
||||
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||
|
||||
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
|
||||
# COPY requirements.txt /tmp/pip-tmp/
|
||||
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
|
||||
# && rm -rf /tmp/pip-tmp
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
||||
|
||||
USER vscode
|
||||
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
ENV PATH=$PATH:$HOME/.poetry/env
|
||||
@@ -1,55 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/python-3
|
||||
{
|
||||
"name": "Python 3",
|
||||
// "build": {
|
||||
// "dockerfile": "Dockerfile",
|
||||
// "context": "..",
|
||||
// "args": {
|
||||
// // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
|
||||
// "VARIANT": "3.6",
|
||||
// // Options
|
||||
// "NODE_VERSION": "lts/*"
|
||||
// }
|
||||
// },
|
||||
"dockerComposeFile": "./docker-compose.yaml",
|
||||
"service": "specklepy",
|
||||
"workspaceFolder": "/workspaces/specklepy",
|
||||
"shutdownAction": "stopCompose",
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.pylintArgs": [
|
||||
"--max-line-length=120"
|
||||
],
|
||||
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
|
||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
|
||||
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
|
||||
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
|
||||
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
||||
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
|
||||
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
|
||||
"python.testing.pytestArgs": [
|
||||
"tests/",
|
||||
"-s"
|
||||
],
|
||||
"python.testing.pytestEnabled": true,
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "poetry config virtualenvs.create false && poetry install",
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
version: "3.3" # optional since v1.27.0
|
||||
services:
|
||||
postgres:
|
||||
image: cimg/postgres:14.2
|
||||
environment:
|
||||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
network_mode: host
|
||||
redis:
|
||||
image: cimg/redis:6.2
|
||||
network_mode: host
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
command: ["bash", "-c", "/wait && node bin/www"]
|
||||
environment:
|
||||
POSTGRES_URL: "localhost"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle2_test"
|
||||
REDIS_URL: "redis://localhost"
|
||||
SESSION_SECRET: "keyboard cat"
|
||||
STRATEGY_LOCAL: "true"
|
||||
CANONICAL_URL: "http://localhost:3000"
|
||||
WAIT_HOSTS: localhost:5432, localhost:6379
|
||||
DISABLE_FILE_UPLOADS: "true"
|
||||
network_mode: host
|
||||
|
||||
specklepy:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
args:
|
||||
VARIANT: 3.9
|
||||
NODE_VERSION: lts/*
|
||||
volumes:
|
||||
# Mounts the project folder to '/workspace'. While this file is in .devcontainer,
|
||||
# mounts are relative to the first file in the list, which is a level up.
|
||||
- ..:/workspaces/specklepy:cached
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
network_mode: host
|
||||
# networks:
|
||||
# default:
|
||||
@@ -3,12 +3,10 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "v3-dev"
|
||||
push:
|
||||
branches:
|
||||
- "v3-dev"
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: continuous-integration
|
||||
build-and-test:
|
||||
name: build-and-test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -39,8 +37,8 @@ jobs:
|
||||
- name: Run pre-commit
|
||||
run: uv run pre-commit run --all-files
|
||||
|
||||
# - name: Run Speckle Server
|
||||
# run: docker compose up -d
|
||||
- name: Run Speckle Server
|
||||
run: docker compose up -d
|
||||
|
||||
# - name: Run tests
|
||||
# run: uv run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
|
||||
@@ -1,33 +1,98 @@
|
||||
# Publish a release to PyPI.
|
||||
name: "Publish to PyPI"
|
||||
|
||||
name: "Publish Python Package"
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Specklepy test and build"]
|
||||
branches: [v3-dev]
|
||||
types:
|
||||
- completed
|
||||
push:
|
||||
branches:
|
||||
- "v3-dev"
|
||||
tags:
|
||||
- "3.*.*"
|
||||
|
||||
jobs:
|
||||
pypi-publish:
|
||||
name: Upload to PyPI
|
||||
build-and-test:
|
||||
name: continuous-integration
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv and set the python version
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pre-commit/
|
||||
key: ${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
|
||||
- name: Run pre-commit
|
||||
run: uv run pre-commit run --all-files
|
||||
|
||||
- name: Run Speckle Server
|
||||
run: docker compose up -d
|
||||
|
||||
# - name: Run tests
|
||||
# run: uv run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
|
||||
|
||||
# - uses: codecov/codecov-action@v5
|
||||
# if: matrix.python-version == 3.13
|
||||
# with:
|
||||
# fail_ci_if_error: true # optional (default = false)
|
||||
# files: ./reports/test-results.xml # optional
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Minimize uv cache
|
||||
run: uv cache prune --ci
|
||||
|
||||
publish-package:
|
||||
name: "Build and Publish Python Package"
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
|
||||
# set the environment based on what triggered the workflow
|
||||
environment:
|
||||
name: testpypi
|
||||
name: ${{ github.ref_type == 'tag' && 'pypi' || 'testpypi' }}
|
||||
|
||||
permissions:
|
||||
# For PyPI's trusted publishing.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# This is necessary so that we have the tags.
|
||||
fetch-depth: 0
|
||||
- name: "Build artifacts"
|
||||
|
||||
- name: "Build package artifacts"
|
||||
run: uv build
|
||||
- name: Publish to PyPi
|
||||
|
||||
# Logic for TestPyPI (on v3-dev branch push)
|
||||
- name: "Publish to TestPyPI"
|
||||
if: ${{ github.ref_type == 'branch' }}
|
||||
run: uv publish --index test
|
||||
|
||||
- name: Test package install
|
||||
- name: "Verify TestPyPI package installation"
|
||||
if: ${{ github.ref_type == 'branch' }}
|
||||
run: uv run --index test --with specklepy --no-project -- python -c "import specklepy"
|
||||
|
||||
# Logic for PyPI (on v3* tag creation)
|
||||
- name: "Publish to PyPI"
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
run: uv publish
|
||||
|
||||
- name: "Verify PyPI package installation"
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
run: uv run --with specklepy --no-project -- python -c "import specklepy"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
.envrc
|
||||
reports/
|
||||
|
||||
.volumes/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
@@ -15,7 +15,6 @@ dependencies = [
|
||||
"httpx>=0.28.1",
|
||||
"pydantic>=2.10.5",
|
||||
"pydantic-settings>=2.7.1",
|
||||
"stringcase>=1.2.0",
|
||||
"ujson>=5.10.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from stringcase import camelcase
|
||||
from pydantic.alias_generators import to_camel
|
||||
|
||||
|
||||
class AutomateBase(BaseModel):
|
||||
"""Use this class as a base model for automate related DTO."""
|
||||
|
||||
model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True)
|
||||
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
||||
|
||||
|
||||
class VersionCreationTriggerPayload(AutomateBase):
|
||||
@@ -39,7 +39,7 @@ class AutomationRunData(BaseModel):
|
||||
triggers: List[VersionCreationTrigger]
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=camelcase, populate_by_name=True, protected_namespaces=()
|
||||
alias_generator=to_camel, populate_by_name=True, protected_namespaces=()
|
||||
)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class TestAutomationRunData(BaseModel):
|
||||
triggers: List[VersionCreationTrigger]
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=camelcase, populate_by_name=True, protected_namespaces=()
|
||||
alias_generator=to_camel, populate_by_name=True, protected_namespaces=()
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
class ProjectVisibility(str, Enum):
|
||||
PRIVATE = "PRIVATE"
|
||||
PUBLIC = "PUBLIC"
|
||||
UNLISTEd = "UNLISTED"
|
||||
UNLISTED = "UNLISTED"
|
||||
|
||||
|
||||
class UserProjectsUpdatedMessageType(str, Enum):
|
||||
|
||||
@@ -82,6 +82,16 @@ class LimitedUser(GraphQLBaseModel):
|
||||
verified: Optional[bool]
|
||||
role: Optional[str]
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"(name: {self.name}, "
|
||||
f"id: {self.id}, "
|
||||
f"bio: {self.bio}, "
|
||||
f"company: {self.company}, "
|
||||
f"verified: {self.verified}, "
|
||||
f"role: {self.role})"
|
||||
)
|
||||
|
||||
|
||||
class PendingStreamCollaborator(GraphQLBaseModel):
|
||||
id: str
|
||||
|
||||
@@ -17,7 +17,7 @@ from typing import (
|
||||
)
|
||||
from warnings import warn
|
||||
|
||||
from stringcase import pascalcase
|
||||
from pydantic.alias_generators import to_pascal
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.transports.memory import MemoryTransport
|
||||
@@ -147,7 +147,7 @@ class _RegisteringBase:
|
||||
# convert the module names to PascalCase to match c# namespace naming convention
|
||||
# also drop specklepy from the beginning
|
||||
namespace = ".".join(
|
||||
pascalcase(m)
|
||||
to_pascal(m)
|
||||
for m in filter(lambda name: name != "specklepy", cls.__module__.split("."))
|
||||
)
|
||||
return f"{namespace}.{cls.__name__}"
|
||||
|
||||
@@ -2,6 +2,7 @@ from .arc import Arc
|
||||
from .box import Box
|
||||
from .circle import Circle
|
||||
from .control_point import ControlPoint
|
||||
from .curve import Curve
|
||||
from .ellipse import Ellipse
|
||||
from .line import Line
|
||||
from .mesh import Mesh
|
||||
@@ -10,6 +11,7 @@ from .point import Point
|
||||
from .point_cloud import PointCloud
|
||||
from .polycurve import Polycurve
|
||||
from .polyline import Polyline
|
||||
from .region import Region
|
||||
from .spiral import Spiral
|
||||
from .surface import Surface
|
||||
from .vector import Vector
|
||||
@@ -22,6 +24,7 @@ __all__ = [
|
||||
"Plane",
|
||||
"Point",
|
||||
"Polyline",
|
||||
"Region",
|
||||
"Vector",
|
||||
"Box",
|
||||
"Circle",
|
||||
@@ -31,4 +34,5 @@ __all__ = [
|
||||
"Polycurve",
|
||||
"Spiral",
|
||||
"Surface",
|
||||
"Curve",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.box import Box
|
||||
from specklepy.objects.geometry.polyline import Polyline
|
||||
from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Curve(
|
||||
Base,
|
||||
ICurve,
|
||||
IHasArea,
|
||||
IHasUnits,
|
||||
speckle_type="Objects.Geometry.Curve",
|
||||
detachable={"points", "weights", "knots", "displayValue"},
|
||||
chunkable={"points": 31250, "weights": 31250, "knots": 31250},
|
||||
):
|
||||
"""
|
||||
a NURBS curve
|
||||
"""
|
||||
|
||||
degree: int
|
||||
periodic: bool
|
||||
rational: bool
|
||||
points: List[float]
|
||||
weights: List[float]
|
||||
knots: List[float]
|
||||
closed: bool
|
||||
displayValue: Polyline
|
||||
bbox: Optional[Box] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"degree: {self.degree}, "
|
||||
f"periodic: {self.periodic}, "
|
||||
f"rational: {self.rational}, "
|
||||
f"closed: {self.closed}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.__dict__.get("_length", 0.0)
|
||||
|
||||
@length.setter
|
||||
def length(self, value: float) -> None:
|
||||
self.__dict__["_length"] = value
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__["_area"] = value
|
||||
@@ -49,9 +49,8 @@ class Mesh(
|
||||
|
||||
if len(self.vertices) % 3 != 0:
|
||||
raise ValueError(
|
||||
f"Invalid vertices list: length ({
|
||||
len(self.vertices)
|
||||
}) must be a multiple of 3"
|
||||
f"Invalid vertices list: length {len(self.vertices)} "
|
||||
f"must be a multiple of 3"
|
||||
)
|
||||
return len(self.vertices) // 3
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.box import Box
|
||||
from specklepy.objects.geometry.mesh import Mesh
|
||||
from specklepy.objects.interfaces import ICurve, IDisplayValue, IHasArea, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Region(
|
||||
Base,
|
||||
IHasArea,
|
||||
IDisplayValue[List[Mesh]],
|
||||
IHasUnits,
|
||||
speckle_type="Objects.Geometry.Region",
|
||||
detachable={"displayValue"},
|
||||
):
|
||||
"""
|
||||
Flat shape, defined by an outer boundary and inner loops.
|
||||
"""
|
||||
|
||||
boundary: ICurve
|
||||
innerLoops: List[ICurve]
|
||||
hasHatchPattern: bool
|
||||
bbox: Box | None = None
|
||||
# unlike C#, constructor will require displayValue, even if it's empty
|
||||
displayValue: List[Mesh]
|
||||
_displayValue: List[Mesh] = field(repr=False, init=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"units: {self.units}, "
|
||||
f"has_hatch_pattern: {self.hasHatchPattern}, "
|
||||
f"inner_loops: {len(self.innerLoops)})"
|
||||
)
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__["_area"] = value
|
||||
|
||||
@property
|
||||
def displayValue(self) -> List[Mesh]:
|
||||
print(self._displayValue)
|
||||
return self._displayValue
|
||||
|
||||
@displayValue.setter
|
||||
def displayValue(self, value: list):
|
||||
if isinstance(value, list):
|
||||
self._displayValue = value
|
||||
else:
|
||||
raise SpeckleException(
|
||||
f"'displayValue' value should be List, received {type(value)}"
|
||||
)
|
||||
@@ -7,7 +7,6 @@ from specklepy.objects.base import Base
|
||||
class RenderMaterial(
|
||||
Base,
|
||||
speckle_type="Objects.Other.RenderMaterial",
|
||||
serialize_ignore={"diffuse", "emissive"},
|
||||
):
|
||||
"""
|
||||
Minimal physically based material DTO class. Based on references from
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Curve, Plane, Point, Polyline, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_polyline():
|
||||
"""
|
||||
sample polyline
|
||||
"""
|
||||
return Polyline(value=[0, 0, 0, 1, 0, 0, 1, 1, 0], units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane():
|
||||
"""
|
||||
sample plane for bbox creation
|
||||
"""
|
||||
origin = Point(x=0, y=0, z=0, units=Units.m)
|
||||
normal = Vector(x=0, y=0, z=1, units=Units.m)
|
||||
xdir = Vector(x=1, y=0, z=0, units=Units.m)
|
||||
ydir = Vector(x=0, y=1, z=0, units=Units.m)
|
||||
return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_curve(sample_polyline):
|
||||
"""
|
||||
sample curve for testing
|
||||
"""
|
||||
return Curve(
|
||||
degree=3,
|
||||
periodic=False,
|
||||
rational=False,
|
||||
points=[0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0],
|
||||
weights=[1, 1, 1, 1],
|
||||
knots=[0, 0, 0, 0, 1, 1, 1, 1],
|
||||
closed=False,
|
||||
displayValue=sample_polyline,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
def test_curve_creation(sample_polyline):
|
||||
"""
|
||||
test curve initialization
|
||||
"""
|
||||
curve = Curve(
|
||||
degree=3,
|
||||
periodic=False,
|
||||
rational=False,
|
||||
points=[0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0],
|
||||
weights=[1, 1, 1, 1],
|
||||
knots=[0, 0, 0, 0, 1, 1, 1, 1],
|
||||
closed=False,
|
||||
displayValue=sample_polyline,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
assert curve.degree == 3
|
||||
assert curve.periodic is False
|
||||
assert curve.rational is False
|
||||
assert curve.points == [0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0]
|
||||
assert curve.weights == [1, 1, 1, 1]
|
||||
assert curve.knots == [0, 0, 0, 0, 1, 1, 1, 1]
|
||||
assert curve.closed is False
|
||||
assert curve.units == Units.m.value
|
||||
assert curve.displayValue == sample_polyline
|
||||
|
||||
|
||||
def test_length_property(sample_polyline):
|
||||
"""
|
||||
test the length property setter and getter
|
||||
"""
|
||||
curve = Curve(
|
||||
degree=1,
|
||||
periodic=False,
|
||||
rational=False,
|
||||
points=[0, 0, 0, 1, 0, 0],
|
||||
weights=[1, 1],
|
||||
knots=[0, 0, 1, 1],
|
||||
closed=False,
|
||||
displayValue=sample_polyline,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
assert curve.length == 0.0
|
||||
|
||||
curve.length = 1.5
|
||||
assert curve.length == 1.5
|
||||
|
||||
|
||||
def test_area_property(sample_polyline):
|
||||
"""
|
||||
test the area property setter and getter
|
||||
"""
|
||||
polyline = Polyline(
|
||||
value=[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0], units=Units.m
|
||||
)
|
||||
|
||||
curve = Curve(
|
||||
degree=1,
|
||||
periodic=False,
|
||||
rational=False,
|
||||
points=[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0],
|
||||
weights=[1, 1, 1, 1, 1],
|
||||
knots=[0, 0, 1, 2, 3, 4, 4],
|
||||
closed=True,
|
||||
displayValue=polyline,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
assert curve.area == 0.0
|
||||
|
||||
curve.area = 1.0
|
||||
assert curve.area == 1.0
|
||||
|
||||
|
||||
def test_curve_serialization(sample_curve):
|
||||
"""
|
||||
test serialization and deserialization of the curve
|
||||
"""
|
||||
serialized = serialize(sample_curve)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.degree == sample_curve.degree
|
||||
assert deserialized.periodic == sample_curve.periodic
|
||||
assert deserialized.rational == sample_curve.rational
|
||||
assert deserialized.points == sample_curve.points
|
||||
assert deserialized.weights == sample_curve.weights
|
||||
assert deserialized.knots == sample_curve.knots
|
||||
assert deserialized.closed == sample_curve.closed
|
||||
assert deserialized.units == sample_curve.units
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_curve_units(sample_polyline, new_units):
|
||||
"""
|
||||
test changing units of a curve
|
||||
"""
|
||||
curve = Curve(
|
||||
degree=3,
|
||||
periodic=False,
|
||||
rational=False,
|
||||
points=[0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0],
|
||||
weights=[1, 1, 1, 1],
|
||||
knots=[0, 0, 0, 0, 1, 1, 1, 1],
|
||||
closed=False,
|
||||
displayValue=sample_polyline,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
assert curve.units == Units.m.value
|
||||
|
||||
curve.units = new_units
|
||||
assert curve.units == new_units
|
||||
@@ -0,0 +1,84 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry.polyline import Polyline
|
||||
from specklepy.objects.geometry.region import Region
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_boundary():
|
||||
return Polyline(
|
||||
# possibly replace same start-end Polyline point with "closed" property
|
||||
value=[-10, -10, 0, 10, -10, 0, 10, 10, 0, -10, 10, 0, -10, -10, 0],
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_loop1():
|
||||
return Polyline(
|
||||
# possibly replace same start-end Polyline point with "closed" property
|
||||
value=[-9, -9, 0, -5, -9, 0, -5, -5, 0, -9, -5, 0, -9, -9, 0],
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_loop2():
|
||||
return Polyline(
|
||||
# possibly replace same start-end Polyline point with "closed" property
|
||||
value=[5, 5, 0, 9, 5, 0, 9, 9, 0, 5, 9, 0, 5, 5, 0],
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_region(sample_boundary, sample_loop1, sample_loop2):
|
||||
return Region(
|
||||
boundary=sample_boundary,
|
||||
innerLoops=[sample_loop1, sample_loop2],
|
||||
hasHatchPattern=True,
|
||||
units=Units.m,
|
||||
displayValue=[],
|
||||
)
|
||||
|
||||
|
||||
def test_region_creation(sample_boundary, sample_loop1, sample_loop2):
|
||||
has_hatch_pattern = True
|
||||
region = Region(
|
||||
boundary=sample_boundary,
|
||||
innerLoops=[sample_loop1, sample_loop2],
|
||||
hasHatchPattern=has_hatch_pattern,
|
||||
units=Units.m,
|
||||
displayValue=[],
|
||||
)
|
||||
assert region.boundary == sample_boundary
|
||||
assert region.innerLoops[0] == sample_loop1
|
||||
assert region.innerLoops[1] == sample_loop2
|
||||
assert region.hasHatchPattern == has_hatch_pattern
|
||||
assert len(region.displayValue) == 0
|
||||
assert region.units == Units.m.value
|
||||
|
||||
|
||||
def test_region_serialization(sample_region):
|
||||
serialized = serialize(sample_region)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.hasHatchPattern == sample_region.hasHatchPattern
|
||||
assert deserialized.units == sample_region.units
|
||||
|
||||
assert deserialized.boundary.length == sample_region.boundary.length
|
||||
assert deserialized.boundary.domain.length == sample_region.boundary.domain.length
|
||||
assert deserialized.boundary.domain.start == sample_region.boundary.domain.start
|
||||
assert deserialized.boundary.domain.end == sample_region.boundary.domain.end
|
||||
|
||||
for i, loop in enumerate(sample_region.innerLoops):
|
||||
assert deserialized.innerLoops[i].length == loop.length
|
||||
assert deserialized.innerLoops[i].domain.length == loop.domain.length
|
||||
assert deserialized.innerLoops[i].domain.start == loop.domain.start
|
||||
assert deserialized.innerLoops[i].domain.end == loop.domain.end
|
||||
@@ -94,6 +94,12 @@ class ServerTransport(AbstractTransport):
|
||||
|
||||
self.session = requests.Session()
|
||||
|
||||
self.session.headers.update(
|
||||
{
|
||||
"Accept": "text/plain",
|
||||
}
|
||||
)
|
||||
|
||||
if self.account is not None:
|
||||
self._batch_sender = BatchSender(
|
||||
self.url, self.stream_id, self.account.token, max_batch_size_mb=1
|
||||
@@ -101,7 +107,6 @@ class ServerTransport(AbstractTransport):
|
||||
self.session.headers.update(
|
||||
{
|
||||
"Authorization": f"Bearer {self.account.token}",
|
||||
"Accept": "text/plain",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class TestProjectResource:
|
||||
"name, description, visibility",
|
||||
[
|
||||
("Very private project", "My secret project", ProjectVisibility.PRIVATE),
|
||||
("Very discoverable project", None, ProjectVisibility.UNLISTED),
|
||||
("Very public project", None, ProjectVisibility.PUBLIC),
|
||||
],
|
||||
)
|
||||
@@ -48,7 +49,11 @@ class TestProjectResource:
|
||||
assert result.id is not None
|
||||
assert result.name == name
|
||||
assert result.description == (description or "")
|
||||
assert result.visibility == visibility
|
||||
# we've disabled creation of public projects for now, they fall back to unlisted
|
||||
if visibility == ProjectVisibility.PUBLIC:
|
||||
assert result.visibility == ProjectVisibility.UNLISTED
|
||||
else:
|
||||
assert result.visibility == visibility
|
||||
|
||||
def test_project_get(self, client: SpeckleClient, test_project: Project):
|
||||
result = client.project.get(test_project.id)
|
||||
@@ -78,7 +83,11 @@ class TestProjectResource:
|
||||
assert updated_project.id == test_project.id
|
||||
assert updated_project.name == new_name
|
||||
assert updated_project.description == new_description
|
||||
assert updated_project.visibility == new_visibility
|
||||
# we've disabled creation of public projects for now, they fall back to unlisted
|
||||
if new_visibility == ProjectVisibility.PUBLIC:
|
||||
assert updated_project.visibility == ProjectVisibility.UNLISTED
|
||||
else:
|
||||
assert updated_project.visibility == new_visibility
|
||||
|
||||
def test_project_delete(self, client: SpeckleClient):
|
||||
"""Test deleting a project."""
|
||||
|
||||
@@ -1392,7 +1392,6 @@ dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "stringcase" },
|
||||
{ name = "ujson" },
|
||||
]
|
||||
|
||||
@@ -1422,7 +1421,6 @@ requires-dist = [
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "pydantic", specifier = ">=2.10.5" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.7.1" },
|
||||
{ name = "stringcase", specifier = ">=1.2.0" },
|
||||
{ name = "ujson", specifier = ">=5.10.0" },
|
||||
]
|
||||
|
||||
@@ -1443,12 +1441,6 @@ dev = [
|
||||
{ name = "types-ujson", specifier = ">=5.10.0.20240515" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringcase"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple/" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/1f/1241aa3d66e8dc1612427b17885f5fcd9c9ee3079fc0d28e9a3aeeb36fa3/stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008", size = 2958 }
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "2.5.0"
|
||||
|
||||
Reference in New Issue
Block a user