From 09d7fbf51328e06e6d9b9f390a6d391a82c6372c Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 15:52:30 +0200 Subject: [PATCH 01/27] Setup generation flow --- .github/dependabot.yml | 20 +- package-lock.json | 1067 ++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 3 files changed, 1080 insertions(+), 13 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 06a222e9..bc67374f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,20 @@ ---- version: 2 updates: - - package-ecosystem: npm + - package-ecosystem: 'npm' directory: '/' schedule: - interval: daily - commit-message: - prefix: feat - include: scope + interval: 'daily' + target-branch: 'main' + versioning-strategy: 'auto' allow: - - dependency-type: production + - dependency-name: '@seamapi/*' + dependency-type: 'development' ignore: - - dependency-name: '*' - update-types: - - 'version-update:semver-major' + - dependency-name: '@seamapi/*' + update-types: ['version-update:semver-major'] groups: seam: - dependency-type: production + dependency-type: development update-types: - patch - minor diff --git a/package-lock.json b/package-lock.json index 6adae308..0cc46912 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,833 @@ "": { "name": "@seamapi/python", "devDependencies": { + "@seamapi/nextlove-sdk-generator": "^1.7.4", + "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-3.0.0.tgz", + "integrity": "sha512-ktI9+PxfHYtKjF3cLTUAh2N+b8MijCRPNwKJNqTVdL0gB0QxLU2rIRaZ1t71oEa3YBDE6bukH1sR0+CDnpp/Mg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "3.0.0", + "run-parallel": "^1.2.0" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-3.0.0.tgz", + "integrity": "sha512-2tQOI38s19P9i7X/Drt0v8iMA+KMsgdhB/dyPER+e+2Y8L1Z7QvnuRdW/uLuf5YRFUYmnj4bMA6qCuZHFI1GDQ==", + "dev": true, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-2.0.0.tgz", + "integrity": "sha512-54voNDBobGdMl3BUXSu7UaDh1P85PGHWlJ5e0XhPugo1JulOyCtp2I+5ri4wplGDJ8QGwPEQW7/x3yTLU7yF1A==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "3.0.0", + "fastq": "^1.15.0" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/@seamapi/nextlove-sdk-generator": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.4.tgz", + "integrity": "sha512-lC5qsUuo6yG9RvX3QxX9LI5tF1XzCkMFA5+D1fqH9m3RQS1JE0fIT+xyaDyQmyq7Iwl7io4GMD5iVyPHRxgzvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.walk": "^2.0.0", + "axios": "^1.5.0", + "change-case": "^4.1.2", + "lodash": "^4.17.21", + "tsx": "^4.7.0", + "yargs": "^17.7.2" + }, + "bin": { + "nextlove-sdk-generator": "lib/cli.js" + }, + "engines": { + "node": ">=16.13.0", + "npm": ">= 8.1.0" + } + }, + "node_modules/@seamapi/types": { + "version": "1.149.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.149.0.tgz", + "integrity": "sha512-Nh12SwA88nMNHypynBLOO6nNhYe1lixHlnDyvmcbJ6hdfVrHFZJ+bk4AgISpQsjmICcEb70BkhEaoLrEVmhN8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0", + "npm": ">= 9.0.0" + }, + "peerDependencies": { + "type-fest": "^4.3.1", + "zod": "^3.21.4" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/prettier": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", @@ -23,6 +847,249 @@ "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tsx": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-fest": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.15.0.tgz", + "integrity": "sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 2c6f41f4..7ca40a77 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "private": true, "type": "module", "scripts": { - "generate": "true", - "format": "prettier --write --ignore-path .gitignore ." + "generate": "./scripts/generate.sh && npm run format", + "format": "poetry run black ." }, "devDependencies": { + "@seamapi/nextlove-sdk-generator": "^1.7.4", + "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } } From 62c0b771f22c6e918cb1457a03bcf80a8ae5696f Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 15:53:02 +0200 Subject: [PATCH 02/27] Add generate shell script --- scripts/generate.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 scripts/generate.sh diff --git a/scripts/generate.sh b/scripts/generate.sh new file mode 100755 index 00000000..18e96f30 --- /dev/null +++ b/scripts/generate.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Remove the existing 'seam' directory +rm -rf ./seam + +# Generate SDK files +nextlove-sdk-generator generate python ./temp_sdk + +# Move only the 'seam' folder +mv ./temp_sdk/seam ./seam + +# Clean up the temporary SDK generation folder +rm -rf ./temp_sdk From 0af87efa5d67ac7152d68617d107dab47159ae19 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 15:53:22 +0200 Subject: [PATCH 03/27] Generate SDK --- seam/__init__.py | 8 +- seam/access_codes.py | 298 +++ seam/access_codes_simulate.py | 33 + seam/access_codes_unmanaged.py | 125 ++ seam/acs.py | 57 + seam/acs_access_groups.py | 71 + seam/acs_credential_pools.py | 19 + ...acs_credential_provisioning_automations.py | 48 + seam/acs_credentials.py | 132 ++ seam/acs_entrances.py | 64 + seam/acs_systems.py | 29 + seam/acs_users.py | 195 ++ seam/action_attempts.py | 80 + seam/client_sessions.py | 156 ++ seam/connect_webviews.py | 87 + seam/connected_accounts.py | 75 + seam/devices.py | 135 ++ seam/devices_simulate.py | 19 + seam/devices_unmanaged.py | 89 + seam/events.py | 70 + seam/health.py | 34 + seam/health_service.py | 21 + seam/locks.py | 135 ++ seam/networks.py | 28 + seam/noise_sensors.py | 21 + seam/noise_sensors_noise_thresholds.py | 130 ++ seam/noise_sensors_simulate.py | 21 + seam/phones.py | 35 + seam/phones_simulate.py | 34 + seam/routes.py | 42 + seam/seam.py | 102 + seam/thermostats.py | 284 +++ seam/thermostats_climate_setting_schedules.py | 167 ++ seam/todo.py | 8 - seam/todo_test.py | 10 - seam/types.py | 1861 +++++++++++++++++ seam/user_identities.py | 197 ++ .../user_identities_enrollment_automations.py | 79 + seam/utils/deep_attr_dict.py | 26 + seam/webhooks.py | 62 + seam/workspaces.py | 62 + 41 files changed, 5127 insertions(+), 22 deletions(-) create mode 100644 seam/access_codes.py create mode 100644 seam/access_codes_simulate.py create mode 100644 seam/access_codes_unmanaged.py create mode 100644 seam/acs.py create mode 100644 seam/acs_access_groups.py create mode 100644 seam/acs_credential_pools.py create mode 100644 seam/acs_credential_provisioning_automations.py create mode 100644 seam/acs_credentials.py create mode 100644 seam/acs_entrances.py create mode 100644 seam/acs_systems.py create mode 100644 seam/acs_users.py create mode 100644 seam/action_attempts.py create mode 100644 seam/client_sessions.py create mode 100644 seam/connect_webviews.py create mode 100644 seam/connected_accounts.py create mode 100644 seam/devices.py create mode 100644 seam/devices_simulate.py create mode 100644 seam/devices_unmanaged.py create mode 100644 seam/events.py create mode 100644 seam/health.py create mode 100644 seam/health_service.py create mode 100644 seam/locks.py create mode 100644 seam/networks.py create mode 100644 seam/noise_sensors.py create mode 100644 seam/noise_sensors_noise_thresholds.py create mode 100644 seam/noise_sensors_simulate.py create mode 100644 seam/phones.py create mode 100644 seam/phones_simulate.py create mode 100644 seam/routes.py create mode 100644 seam/seam.py create mode 100644 seam/thermostats.py create mode 100644 seam/thermostats_climate_setting_schedules.py delete mode 100644 seam/todo.py delete mode 100644 seam/todo_test.py create mode 100644 seam/types.py create mode 100644 seam/user_identities.py create mode 100644 seam/user_identities_enrollment_automations.py create mode 100644 seam/utils/deep_attr_dict.py create mode 100644 seam/webhooks.py create mode 100644 seam/workspaces.py diff --git a/seam/__init__.py b/seam/__init__.py index cd6366f7..50537ff1 100644 --- a/seam/__init__.py +++ b/seam/__init__.py @@ -1,5 +1,5 @@ -""" -SDK for the Seam API written in Python. -""" +# flake8: noqa +# type: ignore -from .todo import todo +from seam.seam import Seam +from seam.seam import SeamApiException diff --git a/seam/access_codes.py b/seam/access_codes.py new file mode 100644 index 00000000..dcd3ffc7 --- /dev/null +++ b/seam/access_codes.py @@ -0,0 +1,298 @@ +from seam.types import AbstractAccessCodes, AbstractSeam as Seam, AccessCode +from typing import Optional, Any, List, Dict, Union +from seam.access_codes_simulate import AccessCodesSimulate +from seam.access_codes_unmanaged import AccessCodesUnmanaged + + +class AccessCodes(AbstractAccessCodes): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._simulate = AccessCodesSimulate(seam=seam) + self._unmanaged = AccessCodesUnmanaged(seam=seam) + + @property + def simulate(self) -> AccessCodesSimulate: + return self._simulate + + @property + def unmanaged(self) -> AccessCodesUnmanaged: + return self._unmanaged + + def create( + self, + *, + device_id: str, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + code: Optional[str] = None, + sync: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + common_code_key: Optional[str] = None, + prefer_native_scheduling: Optional[bool] = None, + use_backup_access_code_pool: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None + ) -> AccessCode: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + if starts_at is not None: + json_payload["starts_at"] = starts_at + if ends_at is not None: + json_payload["ends_at"] = ends_at + if code is not None: + json_payload["code"] = code + if sync is not None: + json_payload["sync"] = sync + if attempt_for_offline_device is not None: + json_payload["attempt_for_offline_device"] = attempt_for_offline_device + if common_code_key is not None: + json_payload["common_code_key"] = common_code_key + if prefer_native_scheduling is not None: + json_payload["prefer_native_scheduling"] = prefer_native_scheduling + if use_backup_access_code_pool is not None: + json_payload["use_backup_access_code_pool"] = use_backup_access_code_pool + if allow_external_modification is not None: + json_payload["allow_external_modification"] = allow_external_modification + if is_external_modification_allowed is not None: + json_payload[ + "is_external_modification_allowed" + ] = is_external_modification_allowed + if use_offline_access_code is not None: + json_payload["use_offline_access_code"] = use_offline_access_code + if is_offline_access_code is not None: + json_payload["is_offline_access_code"] = is_offline_access_code + if is_one_time_use is not None: + json_payload["is_one_time_use"] = is_one_time_use + if max_time_rounding is not None: + json_payload["max_time_rounding"] = max_time_rounding + + res = self.seam.make_request("POST", "/access_codes/create", json=json_payload) + + return AccessCode.from_dict(res["access_code"]) + + def create_multiple( + self, + *, + device_ids: List[str], + behavior_when_code_cannot_be_shared: Optional[str] = None, + preferred_code_length: Optional[float] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + code: Optional[str] = None, + attempt_for_offline_device: Optional[bool] = None, + prefer_native_scheduling: Optional[bool] = None, + use_backup_access_code_pool: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None + ) -> List[AccessCode]: + json_payload = {} + + if device_ids is not None: + json_payload["device_ids"] = device_ids + if behavior_when_code_cannot_be_shared is not None: + json_payload[ + "behavior_when_code_cannot_be_shared" + ] = behavior_when_code_cannot_be_shared + if preferred_code_length is not None: + json_payload["preferred_code_length"] = preferred_code_length + if name is not None: + json_payload["name"] = name + if starts_at is not None: + json_payload["starts_at"] = starts_at + if ends_at is not None: + json_payload["ends_at"] = ends_at + if code is not None: + json_payload["code"] = code + if attempt_for_offline_device is not None: + json_payload["attempt_for_offline_device"] = attempt_for_offline_device + if prefer_native_scheduling is not None: + json_payload["prefer_native_scheduling"] = prefer_native_scheduling + if use_backup_access_code_pool is not None: + json_payload["use_backup_access_code_pool"] = use_backup_access_code_pool + if allow_external_modification is not None: + json_payload["allow_external_modification"] = allow_external_modification + if is_external_modification_allowed is not None: + json_payload[ + "is_external_modification_allowed" + ] = is_external_modification_allowed + if use_offline_access_code is not None: + json_payload["use_offline_access_code"] = use_offline_access_code + if is_offline_access_code is not None: + json_payload["is_offline_access_code"] = is_offline_access_code + if is_one_time_use is not None: + json_payload["is_one_time_use"] = is_one_time_use + if max_time_rounding is not None: + json_payload["max_time_rounding"] = max_time_rounding + + res = self.seam.make_request( + "POST", "/access_codes/create_multiple", json=json_payload + ) + + return [AccessCode.from_dict(item) for item in res["access_codes"]] + + def delete( + self, + *, + access_code_id: str, + device_id: Optional[str] = None, + sync: Optional[bool] = None + ) -> None: + json_payload = {} + + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if device_id is not None: + json_payload["device_id"] = device_id + if sync is not None: + json_payload["sync"] = sync + + self.seam.make_request("POST", "/access_codes/delete", json=json_payload) + + return None + + def generate_code(self, *, device_id: str) -> AccessCode: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + + res = self.seam.make_request( + "POST", "/access_codes/generate_code", json=json_payload + ) + + return AccessCode.from_dict(res["generated_code"]) + + def get( + self, + *, + device_id: Optional[str] = None, + access_code_id: Optional[str] = None, + code: Optional[str] = None + ) -> AccessCode: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if code is not None: + json_payload["code"] = code + + res = self.seam.make_request("POST", "/access_codes/get", json=json_payload) + + return AccessCode.from_dict(res["access_code"]) + + def list( + self, + *, + device_id: Optional[str] = None, + access_code_ids: Optional[List[str]] = None, + user_identifier_key: Optional[str] = None + ) -> List[AccessCode]: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if access_code_ids is not None: + json_payload["access_code_ids"] = access_code_ids + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + + res = self.seam.make_request("POST", "/access_codes/list", json=json_payload) + + return [AccessCode.from_dict(item) for item in res["access_codes"]] + + def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: + json_payload = {} + + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + + res = self.seam.make_request( + "POST", "/access_codes/pull_backup_access_code", json=json_payload + ) + + return AccessCode.from_dict(res["backup_access_code"]) + + def update( + self, + *, + access_code_id: str, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + code: Optional[str] = None, + sync: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + prefer_native_scheduling: Optional[bool] = None, + use_backup_access_code_pool: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None, + device_id: Optional[str] = None, + type: Optional[str] = None, + is_managed: Optional[bool] = None + ) -> None: + json_payload = {} + + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if name is not None: + json_payload["name"] = name + if starts_at is not None: + json_payload["starts_at"] = starts_at + if ends_at is not None: + json_payload["ends_at"] = ends_at + if code is not None: + json_payload["code"] = code + if sync is not None: + json_payload["sync"] = sync + if attempt_for_offline_device is not None: + json_payload["attempt_for_offline_device"] = attempt_for_offline_device + if prefer_native_scheduling is not None: + json_payload["prefer_native_scheduling"] = prefer_native_scheduling + if use_backup_access_code_pool is not None: + json_payload["use_backup_access_code_pool"] = use_backup_access_code_pool + if allow_external_modification is not None: + json_payload["allow_external_modification"] = allow_external_modification + if is_external_modification_allowed is not None: + json_payload[ + "is_external_modification_allowed" + ] = is_external_modification_allowed + if use_offline_access_code is not None: + json_payload["use_offline_access_code"] = use_offline_access_code + if is_offline_access_code is not None: + json_payload["is_offline_access_code"] = is_offline_access_code + if is_one_time_use is not None: + json_payload["is_one_time_use"] = is_one_time_use + if max_time_rounding is not None: + json_payload["max_time_rounding"] = max_time_rounding + if device_id is not None: + json_payload["device_id"] = device_id + if type is not None: + json_payload["type"] = type + if is_managed is not None: + json_payload["is_managed"] = is_managed + + self.seam.make_request("POST", "/access_codes/update", json=json_payload) + + return None diff --git a/seam/access_codes_simulate.py b/seam/access_codes_simulate.py new file mode 100644 index 00000000..dbc31983 --- /dev/null +++ b/seam/access_codes_simulate.py @@ -0,0 +1,33 @@ +from seam.types import ( + AbstractAccessCodesSimulate, + AbstractSeam as Seam, + UnmanagedAccessCode, +) +from typing import Optional, Any, List, Dict, Union + + +class AccessCodesSimulate(AbstractAccessCodesSimulate): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create_unmanaged_access_code( + self, *, device_id: str, name: str, code: str + ) -> UnmanagedAccessCode: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + if code is not None: + json_payload["code"] = code + + res = self.seam.make_request( + "POST", + "/access_codes/simulate/create_unmanaged_access_code", + json=json_payload, + ) + + return UnmanagedAccessCode.from_dict(res["access_code"]) diff --git a/seam/access_codes_unmanaged.py b/seam/access_codes_unmanaged.py new file mode 100644 index 00000000..2231203a --- /dev/null +++ b/seam/access_codes_unmanaged.py @@ -0,0 +1,125 @@ +from seam.types import ( + AbstractAccessCodesUnmanaged, + AbstractSeam as Seam, + UnmanagedAccessCode, +) +from typing import Optional, Any, List, Dict, Union + + +class AccessCodesUnmanaged(AbstractAccessCodesUnmanaged): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def convert_to_managed( + self, + *, + access_code_id: str, + is_external_modification_allowed: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + force: Optional[bool] = None, + sync: Optional[bool] = None + ) -> None: + json_payload = {} + + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if is_external_modification_allowed is not None: + json_payload[ + "is_external_modification_allowed" + ] = is_external_modification_allowed + if allow_external_modification is not None: + json_payload["allow_external_modification"] = allow_external_modification + if force is not None: + json_payload["force"] = force + if sync is not None: + json_payload["sync"] = sync + + self.seam.make_request( + "POST", "/access_codes/unmanaged/convert_to_managed", json=json_payload + ) + + return None + + def delete(self, *, access_code_id: str, sync: Optional[bool] = None) -> None: + json_payload = {} + + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if sync is not None: + json_payload["sync"] = sync + + self.seam.make_request( + "POST", "/access_codes/unmanaged/delete", json=json_payload + ) + + return None + + def get( + self, + *, + device_id: Optional[str] = None, + access_code_id: Optional[str] = None, + code: Optional[str] = None + ) -> UnmanagedAccessCode: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if code is not None: + json_payload["code"] = code + + res = self.seam.make_request( + "POST", "/access_codes/unmanaged/get", json=json_payload + ) + + return UnmanagedAccessCode.from_dict(res["access_code"]) + + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[UnmanagedAccessCode]: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + + res = self.seam.make_request( + "POST", "/access_codes/unmanaged/list", json=json_payload + ) + + return [UnmanagedAccessCode.from_dict(item) for item in res["access_codes"]] + + def update( + self, + *, + access_code_id: str, + is_managed: bool, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + force: Optional[bool] = None + ) -> None: + json_payload = {} + + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if is_managed is not None: + json_payload["is_managed"] = is_managed + if allow_external_modification is not None: + json_payload["allow_external_modification"] = allow_external_modification + if is_external_modification_allowed is not None: + json_payload[ + "is_external_modification_allowed" + ] = is_external_modification_allowed + if force is not None: + json_payload["force"] = force + + self.seam.make_request( + "POST", "/access_codes/unmanaged/update", json=json_payload + ) + + return None diff --git a/seam/acs.py b/seam/acs.py new file mode 100644 index 00000000..616b4b20 --- /dev/null +++ b/seam/acs.py @@ -0,0 +1,57 @@ +from seam.types import AbstractAcs, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union +from seam.acs_access_groups import AcsAccessGroups +from seam.acs_credential_pools import AcsCredentialPools +from seam.acs_credential_provisioning_automations import ( + AcsCredentialProvisioningAutomations, +) +from seam.acs_credentials import AcsCredentials +from seam.acs_entrances import AcsEntrances +from seam.acs_systems import AcsSystems +from seam.acs_users import AcsUsers + + +class Acs(AbstractAcs): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._access_groups = AcsAccessGroups(seam=seam) + self._credential_pools = AcsCredentialPools(seam=seam) + self._credential_provisioning_automations = ( + AcsCredentialProvisioningAutomations(seam=seam) + ) + self._credentials = AcsCredentials(seam=seam) + self._entrances = AcsEntrances(seam=seam) + self._systems = AcsSystems(seam=seam) + self._users = AcsUsers(seam=seam) + + @property + def access_groups(self) -> AcsAccessGroups: + return self._access_groups + + @property + def credential_pools(self) -> AcsCredentialPools: + return self._credential_pools + + @property + def credential_provisioning_automations( + self, + ) -> AcsCredentialProvisioningAutomations: + return self._credential_provisioning_automations + + @property + def credentials(self) -> AcsCredentials: + return self._credentials + + @property + def entrances(self) -> AcsEntrances: + return self._entrances + + @property + def systems(self) -> AcsSystems: + return self._systems + + @property + def users(self) -> AcsUsers: + return self._users diff --git a/seam/acs_access_groups.py b/seam/acs_access_groups.py new file mode 100644 index 00000000..fc259151 --- /dev/null +++ b/seam/acs_access_groups.py @@ -0,0 +1,71 @@ +from seam.types import AbstractAcsAccessGroups, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class AcsAccessGroups(AbstractAcsAccessGroups): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def add_user(self, *, acs_access_group_id: str, acs_user_id: str) -> None: + json_payload = {} + + if acs_access_group_id is not None: + json_payload["acs_access_group_id"] = acs_access_group_id + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/access_groups/add_user", json=json_payload) + + return None + + def get(self, *, acs_access_group_id: str) -> None: + json_payload = {} + + if acs_access_group_id is not None: + json_payload["acs_access_group_id"] = acs_access_group_id + + self.seam.make_request("POST", "/acs/access_groups/get", json=json_payload) + + return None + + def list( + self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None + ) -> None: + json_payload = {} + + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/access_groups/list", json=json_payload) + + return None + + def list_users(self, *, acs_access_group_id: str) -> None: + json_payload = {} + + if acs_access_group_id is not None: + json_payload["acs_access_group_id"] = acs_access_group_id + + self.seam.make_request( + "POST", "/acs/access_groups/list_users", json=json_payload + ) + + return None + + def remove_user(self, *, acs_access_group_id: str, acs_user_id: str) -> None: + json_payload = {} + + if acs_access_group_id is not None: + json_payload["acs_access_group_id"] = acs_access_group_id + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request( + "POST", "/acs/access_groups/remove_user", json=json_payload + ) + + return None diff --git a/seam/acs_credential_pools.py b/seam/acs_credential_pools.py new file mode 100644 index 00000000..25985d1f --- /dev/null +++ b/seam/acs_credential_pools.py @@ -0,0 +1,19 @@ +from seam.types import AbstractAcsCredentialPools, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class AcsCredentialPools(AbstractAcsCredentialPools): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def list(self, *, acs_system_id: str) -> None: + json_payload = {} + + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + + self.seam.make_request("POST", "/acs/credential_pools/list", json=json_payload) + + return None diff --git a/seam/acs_credential_provisioning_automations.py b/seam/acs_credential_provisioning_automations.py new file mode 100644 index 00000000..479111e9 --- /dev/null +++ b/seam/acs_credential_provisioning_automations.py @@ -0,0 +1,48 @@ +from seam.types import ( + AbstractAcsCredentialProvisioningAutomations, + AbstractSeam as Seam, +) +from typing import Optional, Any, List, Dict, Union + + +class AcsCredentialProvisioningAutomations( + AbstractAcsCredentialProvisioningAutomations +): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def launch( + self, + *, + user_identity_id: str, + credential_manager_acs_system_id: str, + acs_credential_pool_id: Optional[str] = None, + create_credential_manager_user: Optional[bool] = None, + credential_manager_acs_user_id: Optional[str] = None + ) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if credential_manager_acs_system_id is not None: + json_payload[ + "credential_manager_acs_system_id" + ] = credential_manager_acs_system_id + if acs_credential_pool_id is not None: + json_payload["acs_credential_pool_id"] = acs_credential_pool_id + if create_credential_manager_user is not None: + json_payload[ + "create_credential_manager_user" + ] = create_credential_manager_user + if credential_manager_acs_user_id is not None: + json_payload[ + "credential_manager_acs_user_id" + ] = credential_manager_acs_user_id + + self.seam.make_request( + "POST", "/acs/credential_provisioning_automations/launch", json=json_payload + ) + + return None diff --git a/seam/acs_credentials.py b/seam/acs_credentials.py new file mode 100644 index 00000000..6dc08562 --- /dev/null +++ b/seam/acs_credentials.py @@ -0,0 +1,132 @@ +from seam.types import AbstractAcsCredentials, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class AcsCredentials(AbstractAcsCredentials): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def assign(self, *, acs_user_id: str, acs_credential_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if acs_credential_id is not None: + json_payload["acs_credential_id"] = acs_credential_id + + self.seam.make_request("POST", "/acs/credentials/assign", json=json_payload) + + return None + + def create( + self, + *, + acs_user_id: str, + access_method: str, + credential_manager_acs_system_id: Optional[str] = None, + code: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + allowed_acs_entrance_ids: Optional[List[str]] = None, + visionline_metadata: Optional[Dict[str, Any]] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None + ) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if access_method is not None: + json_payload["access_method"] = access_method + if credential_manager_acs_system_id is not None: + json_payload[ + "credential_manager_acs_system_id" + ] = credential_manager_acs_system_id + if code is not None: + json_payload["code"] = code + if is_multi_phone_sync_credential is not None: + json_payload[ + "is_multi_phone_sync_credential" + ] = is_multi_phone_sync_credential + if allowed_acs_entrance_ids is not None: + json_payload["allowed_acs_entrance_ids"] = allowed_acs_entrance_ids + if visionline_metadata is not None: + json_payload["visionline_metadata"] = visionline_metadata + if starts_at is not None: + json_payload["starts_at"] = starts_at + if ends_at is not None: + json_payload["ends_at"] = ends_at + + self.seam.make_request("POST", "/acs/credentials/create", json=json_payload) + + return None + + def delete(self, *, acs_credential_id: str) -> None: + json_payload = {} + + if acs_credential_id is not None: + json_payload["acs_credential_id"] = acs_credential_id + + self.seam.make_request("POST", "/acs/credentials/delete", json=json_payload) + + return None + + def get(self, *, acs_credential_id: str) -> None: + json_payload = {} + + if acs_credential_id is not None: + json_payload["acs_credential_id"] = acs_credential_id + + self.seam.make_request("POST", "/acs/credentials/get", json=json_payload) + + return None + + def list( + self, + *, + acs_user_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None + ) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if is_multi_phone_sync_credential is not None: + json_payload[ + "is_multi_phone_sync_credential" + ] = is_multi_phone_sync_credential + + self.seam.make_request("POST", "/acs/credentials/list", json=json_payload) + + return None + + def unassign(self, *, acs_user_id: str, acs_credential_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if acs_credential_id is not None: + json_payload["acs_credential_id"] = acs_credential_id + + self.seam.make_request("POST", "/acs/credentials/unassign", json=json_payload) + + return None + + def update(self, *, acs_credential_id: str, code: str) -> None: + json_payload = {} + + if acs_credential_id is not None: + json_payload["acs_credential_id"] = acs_credential_id + if code is not None: + json_payload["code"] = code + + self.seam.make_request("POST", "/acs/credentials/update", json=json_payload) + + return None diff --git a/seam/acs_entrances.py b/seam/acs_entrances.py new file mode 100644 index 00000000..a73ce49a --- /dev/null +++ b/seam/acs_entrances.py @@ -0,0 +1,64 @@ +from seam.types import AbstractAcsEntrances, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class AcsEntrances(AbstractAcsEntrances): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get(self, *, acs_entrance_id: str) -> None: + json_payload = {} + + if acs_entrance_id is not None: + json_payload["acs_entrance_id"] = acs_entrance_id + + self.seam.make_request("POST", "/acs/entrances/get", json=json_payload) + + return None + + def grant_access(self, *, acs_entrance_id: str, acs_user_id: str) -> None: + json_payload = {} + + if acs_entrance_id is not None: + json_payload["acs_entrance_id"] = acs_entrance_id + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/entrances/grant_access", json=json_payload) + + return None + + def list( + self, + *, + acs_system_id: Optional[str] = None, + acs_credential_id: Optional[str] = None + ) -> None: + json_payload = {} + + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + if acs_credential_id is not None: + json_payload["acs_credential_id"] = acs_credential_id + + self.seam.make_request("POST", "/acs/entrances/list", json=json_payload) + + return None + + def list_credentials_with_access( + self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None + ) -> None: + json_payload = {} + + if acs_entrance_id is not None: + json_payload["acs_entrance_id"] = acs_entrance_id + if include_if is not None: + json_payload["include_if"] = include_if + + self.seam.make_request( + "POST", "/acs/entrances/list_credentials_with_access", json=json_payload + ) + + return None diff --git a/seam/acs_systems.py b/seam/acs_systems.py new file mode 100644 index 00000000..c093544f --- /dev/null +++ b/seam/acs_systems.py @@ -0,0 +1,29 @@ +from seam.types import AbstractAcsSystems, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class AcsSystems(AbstractAcsSystems): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get(self, *, acs_system_id: str) -> None: + json_payload = {} + + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + + self.seam.make_request("POST", "/acs/systems/get", json=json_payload) + + return None + + def list(self, *, connected_account_id: Optional[str] = None) -> None: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + + self.seam.make_request("POST", "/acs/systems/list", json=json_payload) + + return None diff --git a/seam/acs_users.py b/seam/acs_users.py new file mode 100644 index 00000000..3b978d53 --- /dev/null +++ b/seam/acs_users.py @@ -0,0 +1,195 @@ +from seam.types import AbstractAcsUsers, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class AcsUsers(AbstractAcsUsers): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def add_to_access_group( + self, *, acs_user_id: str, acs_access_group_id: str + ) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if acs_access_group_id is not None: + json_payload["acs_access_group_id"] = acs_access_group_id + + self.seam.make_request( + "POST", "/acs/users/add_to_access_group", json=json_payload + ) + + return None + + def create( + self, + *, + acs_system_id: str, + acs_access_group_ids: Optional[List[str]] = None, + user_identity_id: Optional[str] = None, + access_schedule: Optional[Dict[str, Any]] = None, + full_name: Optional[str] = None, + email: Optional[str] = None, + phone_number: Optional[str] = None, + email_address: Optional[str] = None + ) -> None: + json_payload = {} + + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + if acs_access_group_ids is not None: + json_payload["acs_access_group_ids"] = acs_access_group_ids + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if access_schedule is not None: + json_payload["access_schedule"] = access_schedule + if full_name is not None: + json_payload["full_name"] = full_name + if email is not None: + json_payload["email"] = email + if phone_number is not None: + json_payload["phone_number"] = phone_number + if email_address is not None: + json_payload["email_address"] = email_address + + self.seam.make_request("POST", "/acs/users/create", json=json_payload) + + return None + + def delete(self, *, acs_user_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/users/delete", json=json_payload) + + return None + + def get(self, *, acs_user_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/users/get", json=json_payload) + + return None + + def list( + self, + *, + user_identity_id: Optional[str] = None, + user_identity_phone_number: Optional[str] = None, + user_identity_email_address: Optional[str] = None, + acs_system_id: Optional[str] = None + ) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if user_identity_phone_number is not None: + json_payload["user_identity_phone_number"] = user_identity_phone_number + if user_identity_email_address is not None: + json_payload["user_identity_email_address"] = user_identity_email_address + if acs_system_id is not None: + json_payload["acs_system_id"] = acs_system_id + + self.seam.make_request("POST", "/acs/users/list", json=json_payload) + + return None + + def list_accessible_entrances(self, *, acs_user_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request( + "POST", "/acs/users/list_accessible_entrances", json=json_payload + ) + + return None + + def remove_from_access_group( + self, *, acs_user_id: str, acs_access_group_id: str + ) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if acs_access_group_id is not None: + json_payload["acs_access_group_id"] = acs_access_group_id + + self.seam.make_request( + "POST", "/acs/users/remove_from_access_group", json=json_payload + ) + + return None + + def revoke_access_to_all_entrances(self, *, acs_user_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request( + "POST", "/acs/users/revoke_access_to_all_entrances", json=json_payload + ) + + return None + + def suspend(self, *, acs_user_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/users/suspend", json=json_payload) + + return None + + def unsuspend(self, *, acs_user_id: str) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request("POST", "/acs/users/unsuspend", json=json_payload) + + return None + + def update( + self, + *, + acs_user_id: str, + access_schedule: Optional[Dict[str, Any]] = None, + full_name: Optional[str] = None, + email: Optional[str] = None, + phone_number: Optional[str] = None, + email_address: Optional[str] = None, + hid_acs_system_id: Optional[str] = None + ) -> None: + json_payload = {} + + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + if access_schedule is not None: + json_payload["access_schedule"] = access_schedule + if full_name is not None: + json_payload["full_name"] = full_name + if email is not None: + json_payload["email"] = email + if phone_number is not None: + json_payload["phone_number"] = phone_number + if email_address is not None: + json_payload["email_address"] = email_address + if hid_acs_system_id is not None: + json_payload["hid_acs_system_id"] = hid_acs_system_id + + self.seam.make_request("POST", "/acs/users/update", json=json_payload) + + return None diff --git a/seam/action_attempts.py b/seam/action_attempts.py new file mode 100644 index 00000000..c2afb894 --- /dev/null +++ b/seam/action_attempts.py @@ -0,0 +1,80 @@ +from seam.types import AbstractActionAttempts, AbstractSeam as Seam, ActionAttempt +from typing import Optional, Any, List, Dict, Union + +import time + + +class ActionAttempts(AbstractActionAttempts): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get( + self, + *, + action_attempt_id: str, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + ) -> ActionAttempt: + json_payload = {} + + if action_attempt_id is not None: + json_payload["action_attempt_id"] = action_attempt_id + + res = self.seam.make_request("POST", "/action_attempts/get", json=json_payload) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def list(self, *, action_attempt_ids: List[str]) -> List[ActionAttempt]: + json_payload = {} + + if action_attempt_ids is not None: + json_payload["action_attempt_ids"] = action_attempt_ids + + res = self.seam.make_request("POST", "/action_attempts/list", json=json_payload) + + return [ActionAttempt.from_dict(item) for item in res["action_attempts"]] + + def poll_until_ready( + self, + *, + action_attempt_id: str, + timeout: Optional[float] = 5.0, + polling_interval: Optional[float] = 0.5, + ) -> ActionAttempt: + seam = self.seam + time_waiting = 0.0 + + action_attempt = seam.action_attempts.get( + action_attempt_id=action_attempt_id, wait_for_action_attempt=False + ) + + while action_attempt.status == "pending": + time.sleep(polling_interval) + time_waiting += polling_interval + + if time_waiting > timeout: + raise Exception("Timed out waiting for action attempt to be ready") + + action_attempt = seam.action_attempts.get( + action_attempt_id=action_attempt.action_attempt_id, + wait_for_action_attempt=False, + ) + + if action_attempt.status == "failed": + raise Exception(f"Action Attempt failed: {action_attempt.error.message}") + + return action_attempt diff --git a/seam/client_sessions.py b/seam/client_sessions.py new file mode 100644 index 00000000..8d48daa9 --- /dev/null +++ b/seam/client_sessions.py @@ -0,0 +1,156 @@ +from seam.types import AbstractClientSessions, AbstractSeam as Seam, ClientSession +from typing import Optional, Any, List, Dict, Union + + +class ClientSessions(AbstractClientSessions): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create( + self, + *, + user_identifier_key: Optional[str] = None, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + user_identity_ids: Optional[List[str]] = None, + expires_at: Optional[str] = None + ) -> ClientSession: + json_payload = {} + + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if connect_webview_ids is not None: + json_payload["connect_webview_ids"] = connect_webview_ids + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if user_identity_ids is not None: + json_payload["user_identity_ids"] = user_identity_ids + if expires_at is not None: + json_payload["expires_at"] = expires_at + + res = self.seam.make_request( + "POST", "/client_sessions/create", json=json_payload + ) + + return ClientSession.from_dict(res["client_session"]) + + def delete(self, *, client_session_id: str) -> None: + json_payload = {} + + if client_session_id is not None: + json_payload["client_session_id"] = client_session_id + + self.seam.make_request("POST", "/client_sessions/delete", json=json_payload) + + return None + + def get( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None + ) -> ClientSession: + json_payload = {} + + if client_session_id is not None: + json_payload["client_session_id"] = client_session_id + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + + res = self.seam.make_request("POST", "/client_sessions/get", json=json_payload) + + return ClientSession.from_dict(res["client_session"]) + + def get_or_create( + self, + *, + user_identifier_key: Optional[str] = None, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + user_identity_ids: Optional[List[str]] = None, + expires_at: Optional[str] = None + ) -> ClientSession: + json_payload = {} + + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if connect_webview_ids is not None: + json_payload["connect_webview_ids"] = connect_webview_ids + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if user_identity_ids is not None: + json_payload["user_identity_ids"] = user_identity_ids + if expires_at is not None: + json_payload["expires_at"] = expires_at + + res = self.seam.make_request( + "POST", "/client_sessions/get_or_create", json=json_payload + ) + + return ClientSession.from_dict(res["client_session"]) + + def grant_access( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_ids: Optional[List[str]] = None, + user_identity_ids: Optional[List[str]] = None + ) -> None: + json_payload = {} + + if client_session_id is not None: + json_payload["client_session_id"] = client_session_id + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if connect_webview_ids is not None: + json_payload["connect_webview_ids"] = connect_webview_ids + if user_identity_ids is not None: + json_payload["user_identity_ids"] = user_identity_ids + + self.seam.make_request( + "POST", "/client_sessions/grant_access", json=json_payload + ) + + return None + + def list( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + connect_webview_id: Optional[str] = None, + without_user_identifier_key: Optional[bool] = None, + user_identity_id: Optional[str] = None + ) -> List[ClientSession]: + json_payload = {} + + if client_session_id is not None: + json_payload["client_session_id"] = client_session_id + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + if without_user_identifier_key is not None: + json_payload["without_user_identifier_key"] = without_user_identifier_key + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + + res = self.seam.make_request("POST", "/client_sessions/list", json=json_payload) + + return [ClientSession.from_dict(item) for item in res["client_sessions"]] + + def revoke(self, *, client_session_id: str) -> None: + json_payload = {} + + if client_session_id is not None: + json_payload["client_session_id"] = client_session_id + + self.seam.make_request("POST", "/client_sessions/revoke", json=json_payload) + + return None diff --git a/seam/connect_webviews.py b/seam/connect_webviews.py new file mode 100644 index 00000000..8c67f998 --- /dev/null +++ b/seam/connect_webviews.py @@ -0,0 +1,87 @@ +from seam.types import AbstractConnectWebviews, AbstractSeam as Seam, ConnectWebview +from typing import Optional, Any, List, Dict, Union + + +class ConnectWebviews(AbstractConnectWebviews): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create( + self, + *, + device_selection_mode: Optional[str] = None, + custom_redirect_url: Optional[str] = None, + custom_redirect_failure_url: Optional[str] = None, + accepted_providers: Optional[List[str]] = None, + provider_category: Optional[str] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + automatically_manage_new_devices: Optional[bool] = None, + wait_for_device_creation: Optional[bool] = None + ) -> ConnectWebview: + json_payload = {} + + if device_selection_mode is not None: + json_payload["device_selection_mode"] = device_selection_mode + if custom_redirect_url is not None: + json_payload["custom_redirect_url"] = custom_redirect_url + if custom_redirect_failure_url is not None: + json_payload["custom_redirect_failure_url"] = custom_redirect_failure_url + if accepted_providers is not None: + json_payload["accepted_providers"] = accepted_providers + if provider_category is not None: + json_payload["provider_category"] = provider_category + if custom_metadata is not None: + json_payload["custom_metadata"] = custom_metadata + if automatically_manage_new_devices is not None: + json_payload[ + "automatically_manage_new_devices" + ] = automatically_manage_new_devices + if wait_for_device_creation is not None: + json_payload["wait_for_device_creation"] = wait_for_device_creation + + res = self.seam.make_request( + "POST", "/connect_webviews/create", json=json_payload + ) + + return ConnectWebview.from_dict(res["connect_webview"]) + + def delete(self, *, connect_webview_id: str) -> None: + json_payload = {} + + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + + self.seam.make_request("POST", "/connect_webviews/delete", json=json_payload) + + return None + + def get(self, *, connect_webview_id: str) -> ConnectWebview: + json_payload = {} + + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + + res = self.seam.make_request("POST", "/connect_webviews/get", json=json_payload) + + return ConnectWebview.from_dict(res["connect_webview"]) + + def list( + self, + *, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None + ) -> List[ConnectWebview]: + json_payload = {} + + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if custom_metadata_has is not None: + json_payload["custom_metadata_has"] = custom_metadata_has + + res = self.seam.make_request( + "POST", "/connect_webviews/list", json=json_payload + ) + + return [ConnectWebview.from_dict(item) for item in res["connect_webviews"]] diff --git a/seam/connected_accounts.py b/seam/connected_accounts.py new file mode 100644 index 00000000..78b1d11a --- /dev/null +++ b/seam/connected_accounts.py @@ -0,0 +1,75 @@ +from seam.types import AbstractConnectedAccounts, AbstractSeam as Seam, ConnectedAccount +from typing import Optional, Any, List, Dict, Union + + +class ConnectedAccounts(AbstractConnectedAccounts): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def delete(self, *, connected_account_id: str, sync: Optional[bool] = None) -> None: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if sync is not None: + json_payload["sync"] = sync + + self.seam.make_request("POST", "/connected_accounts/delete", json=json_payload) + + return None + + def get( + self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None + ) -> ConnectedAccount: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if email is not None: + json_payload["email"] = email + + res = self.seam.make_request( + "POST", "/connected_accounts/get", json=json_payload + ) + + return ConnectedAccount.from_dict(res["connected_account"]) + + def list( + self, *, custom_metadata_has: Optional[Dict[str, Any]] = None + ) -> List[ConnectedAccount]: + json_payload = {} + + if custom_metadata_has is not None: + json_payload["custom_metadata_has"] = custom_metadata_has + + res = self.seam.make_request( + "POST", "/connected_accounts/list", json=json_payload + ) + + return [ConnectedAccount.from_dict(item) for item in res["connected_accounts"]] + + def update( + self, + *, + connected_account_id: str, + automatically_manage_new_devices: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None + ) -> ConnectedAccount: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if automatically_manage_new_devices is not None: + json_payload[ + "automatically_manage_new_devices" + ] = automatically_manage_new_devices + if custom_metadata is not None: + json_payload["custom_metadata"] = custom_metadata + + res = self.seam.make_request( + "POST", "/connected_accounts/update", json=json_payload + ) + + return ConnectedAccount.from_dict(res["connected_account"]) diff --git a/seam/devices.py b/seam/devices.py new file mode 100644 index 00000000..7d1aacd6 --- /dev/null +++ b/seam/devices.py @@ -0,0 +1,135 @@ +from seam.types import AbstractDevices, AbstractSeam as Seam, Device, DeviceProvider +from typing import Optional, Any, List, Dict, Union +from seam.devices_simulate import DevicesSimulate +from seam.devices_unmanaged import DevicesUnmanaged + + +class Devices(AbstractDevices): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._simulate = DevicesSimulate(seam=seam) + self._unmanaged = DevicesUnmanaged(seam=seam) + + @property + def simulate(self) -> DevicesSimulate: + return self._simulate + + @property + def unmanaged(self) -> DevicesUnmanaged: + return self._unmanaged + + def delete(self, *, device_id: str) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + + self.seam.make_request("POST", "/devices/delete", json=json_payload) + + return None + + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + + res = self.seam.make_request("POST", "/devices/get", json=json_payload) + + return Device.from_dict(res["device"]) + + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None + ) -> List[Device]: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + if device_type is not None: + json_payload["device_type"] = device_type + if device_types is not None: + json_payload["device_types"] = device_types + if manufacturer is not None: + json_payload["manufacturer"] = manufacturer + if device_ids is not None: + json_payload["device_ids"] = device_ids + if limit is not None: + json_payload["limit"] = limit + if created_before is not None: + json_payload["created_before"] = created_before + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if custom_metadata_has is not None: + json_payload["custom_metadata_has"] = custom_metadata_has + if include_if is not None: + json_payload["include_if"] = include_if + if exclude_if is not None: + json_payload["exclude_if"] = exclude_if + + res = self.seam.make_request("POST", "/devices/list", json=json_payload) + + return [Device.from_dict(item) for item in res["devices"]] + + def list_device_providers( + self, *, provider_category: Optional[str] = None + ) -> List[DeviceProvider]: + json_payload = {} + + if provider_category is not None: + json_payload["provider_category"] = provider_category + + res = self.seam.make_request( + "POST", "/devices/list_device_providers", json=json_payload + ) + + return [DeviceProvider.from_dict(item) for item in res["device_providers"]] + + def update( + self, + *, + device_id: str, + properties: Optional[Dict[str, Any]] = None, + name: Optional[str] = None, + is_managed: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None + ) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if properties is not None: + json_payload["properties"] = properties + if name is not None: + json_payload["name"] = name + if is_managed is not None: + json_payload["is_managed"] = is_managed + if custom_metadata is not None: + json_payload["custom_metadata"] = custom_metadata + + self.seam.make_request("POST", "/devices/update", json=json_payload) + + return None diff --git a/seam/devices_simulate.py b/seam/devices_simulate.py new file mode 100644 index 00000000..ec8f76db --- /dev/null +++ b/seam/devices_simulate.py @@ -0,0 +1,19 @@ +from seam.types import AbstractDevicesSimulate, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class DevicesSimulate(AbstractDevicesSimulate): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def remove(self, *, device_id: str) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + + self.seam.make_request("POST", "/devices/simulate/remove", json=json_payload) + + return None diff --git a/seam/devices_unmanaged.py b/seam/devices_unmanaged.py new file mode 100644 index 00000000..d4f5c7a2 --- /dev/null +++ b/seam/devices_unmanaged.py @@ -0,0 +1,89 @@ +from seam.types import AbstractDevicesUnmanaged, AbstractSeam as Seam, UnmanagedDevice +from typing import Optional, Any, List, Dict, Union + + +class DevicesUnmanaged(AbstractDevicesUnmanaged): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> UnmanagedDevice: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + + res = self.seam.make_request( + "POST", "/devices/unmanaged/get", json=json_payload + ) + + return UnmanagedDevice.from_dict(res["device"]) + + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None + ) -> List[UnmanagedDevice]: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + if device_type is not None: + json_payload["device_type"] = device_type + if device_types is not None: + json_payload["device_types"] = device_types + if manufacturer is not None: + json_payload["manufacturer"] = manufacturer + if device_ids is not None: + json_payload["device_ids"] = device_ids + if limit is not None: + json_payload["limit"] = limit + if created_before is not None: + json_payload["created_before"] = created_before + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if custom_metadata_has is not None: + json_payload["custom_metadata_has"] = custom_metadata_has + if include_if is not None: + json_payload["include_if"] = include_if + if exclude_if is not None: + json_payload["exclude_if"] = exclude_if + + res = self.seam.make_request( + "POST", "/devices/unmanaged/list", json=json_payload + ) + + return [UnmanagedDevice.from_dict(item) for item in res["devices"]] + + def update(self, *, device_id: str, is_managed: bool) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if is_managed is not None: + json_payload["is_managed"] = is_managed + + self.seam.make_request("POST", "/devices/unmanaged/update", json=json_payload) + + return None diff --git a/seam/events.py b/seam/events.py new file mode 100644 index 00000000..34a59eac --- /dev/null +++ b/seam/events.py @@ -0,0 +1,70 @@ +from seam.types import AbstractEvents, AbstractSeam as Seam, Event +from typing import Optional, Any, List, Dict, Union + + +class Events(AbstractEvents): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get( + self, + *, + event_id: Optional[str] = None, + event_type: Optional[str] = None, + device_id: Optional[str] = None + ) -> Event: + json_payload = {} + + if event_id is not None: + json_payload["event_id"] = event_id + if event_type is not None: + json_payload["event_type"] = event_type + if device_id is not None: + json_payload["device_id"] = device_id + + res = self.seam.make_request("POST", "/events/get", json=json_payload) + + return Event.from_dict(res["event"]) + + def list( + self, + *, + since: Optional[str] = None, + between: Optional[List[str]] = None, + device_id: Optional[str] = None, + device_ids: Optional[List[str]] = None, + access_code_id: Optional[str] = None, + access_code_ids: Optional[List[str]] = None, + event_type: Optional[str] = None, + event_types: Optional[List[str]] = None, + connected_account_id: Optional[str] = None, + limit: Optional[float] = None + ) -> List[Event]: + json_payload = {} + + if since is not None: + json_payload["since"] = since + if between is not None: + json_payload["between"] = between + if device_id is not None: + json_payload["device_id"] = device_id + if device_ids is not None: + json_payload["device_ids"] = device_ids + if access_code_id is not None: + json_payload["access_code_id"] = access_code_id + if access_code_ids is not None: + json_payload["access_code_ids"] = access_code_ids + if event_type is not None: + json_payload["event_type"] = event_type + if event_types is not None: + json_payload["event_types"] = event_types + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if limit is not None: + json_payload["limit"] = limit + + res = self.seam.make_request("POST", "/events/list", json=json_payload) + + return [Event.from_dict(item) for item in res["events"]] diff --git a/seam/health.py b/seam/health.py new file mode 100644 index 00000000..7cf31ee5 --- /dev/null +++ b/seam/health.py @@ -0,0 +1,34 @@ +from seam.types import AbstractHealth, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union +from seam.health_service import HealthService + + +class Health(AbstractHealth): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._service = HealthService(seam=seam) + + @property + def service(self) -> HealthService: + return self._service + + def get_health( + self, + ) -> None: + json_payload = {} + + self.seam.make_request("POST", "/health/get_health", json=json_payload) + + return None + + def get_service_health(self, *, service: str) -> None: + json_payload = {} + + if service is not None: + json_payload["service"] = service + + self.seam.make_request("POST", "/health/get_service_health", json=json_payload) + + return None diff --git a/seam/health_service.py b/seam/health_service.py new file mode 100644 index 00000000..1d0fab97 --- /dev/null +++ b/seam/health_service.py @@ -0,0 +1,21 @@ +from seam.types import AbstractHealthService, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class HealthService(AbstractHealthService): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def by_service_name(self, *, service_name: str) -> None: + json_payload = {} + + if service_name is not None: + json_payload["service_name"] = service_name + + self.seam.make_request( + "POST", "/health/service/[service_name]", json=json_payload + ) + + return None diff --git a/seam/locks.py b/seam/locks.py new file mode 100644 index 00000000..ec50d497 --- /dev/null +++ b/seam/locks.py @@ -0,0 +1,135 @@ +from seam.types import AbstractLocks, AbstractSeam as Seam, Device, ActionAttempt +from typing import Optional, Any, List, Dict, Union + + +class Locks(AbstractLocks): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + + res = self.seam.make_request("POST", "/locks/get", json=json_payload) + + return Device.from_dict(res["device"]) + + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None + ) -> List[Device]: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + if device_type is not None: + json_payload["device_type"] = device_type + if device_types is not None: + json_payload["device_types"] = device_types + if manufacturer is not None: + json_payload["manufacturer"] = manufacturer + if device_ids is not None: + json_payload["device_ids"] = device_ids + if limit is not None: + json_payload["limit"] = limit + if created_before is not None: + json_payload["created_before"] = created_before + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if custom_metadata_has is not None: + json_payload["custom_metadata_has"] = custom_metadata_has + if include_if is not None: + json_payload["include_if"] = include_if + if exclude_if is not None: + json_payload["exclude_if"] = exclude_if + + res = self.seam.make_request("POST", "/locks/list", json=json_payload) + + return [Device.from_dict(item) for item in res["devices"]] + + def lock_door( + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request("POST", "/locks/lock_door", json=json_payload) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def unlock_door( + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request("POST", "/locks/unlock_door", json=json_payload) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt diff --git a/seam/networks.py b/seam/networks.py new file mode 100644 index 00000000..2944b95e --- /dev/null +++ b/seam/networks.py @@ -0,0 +1,28 @@ +from seam.types import AbstractNetworks, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class Networks(AbstractNetworks): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def get(self, *, network_id: str) -> None: + json_payload = {} + + if network_id is not None: + json_payload["network_id"] = network_id + + self.seam.make_request("POST", "/networks/get", json=json_payload) + + return None + + def list( + self, + ) -> None: + json_payload = {} + + self.seam.make_request("POST", "/networks/list", json=json_payload) + + return None diff --git a/seam/noise_sensors.py b/seam/noise_sensors.py new file mode 100644 index 00000000..46c43aed --- /dev/null +++ b/seam/noise_sensors.py @@ -0,0 +1,21 @@ +from seam.types import AbstractNoiseSensors, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union +from seam.noise_sensors_noise_thresholds import NoiseSensorsNoiseThresholds +from seam.noise_sensors_simulate import NoiseSensorsSimulate + + +class NoiseSensors(AbstractNoiseSensors): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._noise_thresholds = NoiseSensorsNoiseThresholds(seam=seam) + self._simulate = NoiseSensorsSimulate(seam=seam) + + @property + def noise_thresholds(self) -> NoiseSensorsNoiseThresholds: + return self._noise_thresholds + + @property + def simulate(self) -> NoiseSensorsSimulate: + return self._simulate diff --git a/seam/noise_sensors_noise_thresholds.py b/seam/noise_sensors_noise_thresholds.py new file mode 100644 index 00000000..23cda5ae --- /dev/null +++ b/seam/noise_sensors_noise_thresholds.py @@ -0,0 +1,130 @@ +from seam.types import ( + AbstractNoiseSensorsNoiseThresholds, + AbstractSeam as Seam, + NoiseThreshold, +) +from typing import Optional, Any, List, Dict, Union + + +class NoiseSensorsNoiseThresholds(AbstractNoiseSensorsNoiseThresholds): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create( + self, + *, + device_id: str, + starts_daily_at: str, + ends_daily_at: str, + sync: Optional[bool] = None, + name: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None + ) -> NoiseThreshold: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if starts_daily_at is not None: + json_payload["starts_daily_at"] = starts_daily_at + if ends_daily_at is not None: + json_payload["ends_daily_at"] = ends_daily_at + if sync is not None: + json_payload["sync"] = sync + if name is not None: + json_payload["name"] = name + if noise_threshold_decibels is not None: + json_payload["noise_threshold_decibels"] = noise_threshold_decibels + if noise_threshold_nrs is not None: + json_payload["noise_threshold_nrs"] = noise_threshold_nrs + + res = self.seam.make_request( + "POST", "/noise_sensors/noise_thresholds/create", json=json_payload + ) + + return NoiseThreshold.from_dict(res["noise_threshold"]) + + def delete( + self, *, noise_threshold_id: str, device_id: str, sync: Optional[bool] = None + ) -> None: + json_payload = {} + + if noise_threshold_id is not None: + json_payload["noise_threshold_id"] = noise_threshold_id + if device_id is not None: + json_payload["device_id"] = device_id + if sync is not None: + json_payload["sync"] = sync + + self.seam.make_request( + "POST", "/noise_sensors/noise_thresholds/delete", json=json_payload + ) + + return None + + def get(self, *, noise_threshold_id: str) -> NoiseThreshold: + json_payload = {} + + if noise_threshold_id is not None: + json_payload["noise_threshold_id"] = noise_threshold_id + + res = self.seam.make_request( + "POST", "/noise_sensors/noise_thresholds/get", json=json_payload + ) + + return NoiseThreshold.from_dict(res["noise_threshold"]) + + def list( + self, *, device_id: str, is_programmed: Optional[bool] = None + ) -> List[NoiseThreshold]: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if is_programmed is not None: + json_payload["is_programmed"] = is_programmed + + res = self.seam.make_request( + "POST", "/noise_sensors/noise_thresholds/list", json=json_payload + ) + + return [NoiseThreshold.from_dict(item) for item in res["noise_thresholds"]] + + def update( + self, + *, + noise_threshold_id: str, + device_id: str, + sync: Optional[bool] = None, + name: Optional[str] = None, + starts_daily_at: Optional[str] = None, + ends_daily_at: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None + ) -> None: + json_payload = {} + + if noise_threshold_id is not None: + json_payload["noise_threshold_id"] = noise_threshold_id + if device_id is not None: + json_payload["device_id"] = device_id + if sync is not None: + json_payload["sync"] = sync + if name is not None: + json_payload["name"] = name + if starts_daily_at is not None: + json_payload["starts_daily_at"] = starts_daily_at + if ends_daily_at is not None: + json_payload["ends_daily_at"] = ends_daily_at + if noise_threshold_decibels is not None: + json_payload["noise_threshold_decibels"] = noise_threshold_decibels + if noise_threshold_nrs is not None: + json_payload["noise_threshold_nrs"] = noise_threshold_nrs + + self.seam.make_request( + "POST", "/noise_sensors/noise_thresholds/update", json=json_payload + ) + + return None diff --git a/seam/noise_sensors_simulate.py b/seam/noise_sensors_simulate.py new file mode 100644 index 00000000..03dc9796 --- /dev/null +++ b/seam/noise_sensors_simulate.py @@ -0,0 +1,21 @@ +from seam.types import AbstractNoiseSensorsSimulate, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class NoiseSensorsSimulate(AbstractNoiseSensorsSimulate): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def trigger_noise_threshold(self, *, device_id: str) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + + self.seam.make_request( + "POST", "/noise_sensors/simulate/trigger_noise_threshold", json=json_payload + ) + + return None diff --git a/seam/phones.py b/seam/phones.py new file mode 100644 index 00000000..02a0f7bb --- /dev/null +++ b/seam/phones.py @@ -0,0 +1,35 @@ +from seam.types import AbstractPhones, AbstractSeam as Seam, Phone +from typing import Optional, Any, List, Dict, Union +from seam.phones_simulate import PhonesSimulate + + +class Phones(AbstractPhones): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._simulate = PhonesSimulate(seam=seam) + + @property + def simulate(self) -> PhonesSimulate: + return self._simulate + + def deactivate(self, *, device_id: str) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + + self.seam.make_request("POST", "/phones/deactivate", json=json_payload) + + return None + + def list(self, *, owner_user_identity_id: Optional[str] = None) -> List[Phone]: + json_payload = {} + + if owner_user_identity_id is not None: + json_payload["owner_user_identity_id"] = owner_user_identity_id + + res = self.seam.make_request("POST", "/phones/list", json=json_payload) + + return [Phone.from_dict(item) for item in res["phones"]] diff --git a/seam/phones_simulate.py b/seam/phones_simulate.py new file mode 100644 index 00000000..daf1053e --- /dev/null +++ b/seam/phones_simulate.py @@ -0,0 +1,34 @@ +from seam.types import AbstractPhonesSimulate, AbstractSeam as Seam, Phone +from typing import Optional, Any, List, Dict, Union + + +class PhonesSimulate(AbstractPhonesSimulate): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create_sandbox_phone( + self, + *, + user_identity_id: str, + custom_sdk_installation_id: Optional[str] = None, + phone_metadata: Optional[Dict[str, Any]] = None, + assa_abloy_metadata: Optional[Dict[str, Any]] = None + ) -> Phone: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if custom_sdk_installation_id is not None: + json_payload["custom_sdk_installation_id"] = custom_sdk_installation_id + if phone_metadata is not None: + json_payload["phone_metadata"] = phone_metadata + if assa_abloy_metadata is not None: + json_payload["assa_abloy_metadata"] = assa_abloy_metadata + + res = self.seam.make_request( + "POST", "/phones/simulate/create_sandbox_phone", json=json_payload + ) + + return Phone.from_dict(res["phone"]) diff --git a/seam/routes.py b/seam/routes.py new file mode 100644 index 00000000..d5446f81 --- /dev/null +++ b/seam/routes.py @@ -0,0 +1,42 @@ +from .types import AbstractRoutes +from .access_codes import AccessCodes +from .action_attempts import ActionAttempts +from .client_sessions import ClientSessions +from .connect_webviews import ConnectWebviews +from .connected_accounts import ConnectedAccounts +from .devices import Devices +from .events import Events +from .health import Health +from .locks import Locks +from .networks import Networks +from .phones import Phones +from .thermostats import Thermostats +from .user_identities import UserIdentities +from .webhooks import Webhooks +from .workspaces import Workspaces +from .acs import Acs +from .noise_sensors import NoiseSensors + + +class Routes(AbstractRoutes): + def __init__(self): + self.access_codes = AccessCodes(seam=self) + self.action_attempts = ActionAttempts(seam=self) + self.client_sessions = ClientSessions(seam=self) + self.connect_webviews = ConnectWebviews(seam=self) + self.connected_accounts = ConnectedAccounts(seam=self) + self.devices = Devices(seam=self) + self.events = Events(seam=self) + self.health = Health(seam=self) + self.locks = Locks(seam=self) + self.networks = Networks(seam=self) + self.phones = Phones(seam=self) + self.thermostats = Thermostats(seam=self) + self.user_identities = UserIdentities(seam=self) + self.webhooks = Webhooks(seam=self) + self.workspaces = Workspaces(seam=self) + self.acs = Acs(seam=self) + self.noise_sensors = NoiseSensors(seam=self) + + def make_request(self): + raise NotImplementedError() diff --git a/seam/seam.py b/seam/seam.py new file mode 100644 index 00000000..ebdfb439 --- /dev/null +++ b/seam/seam.py @@ -0,0 +1,102 @@ +import os + +from .routes import Routes +import requests +import pkg_resources +from typing import Optional, cast +from .types import AbstractSeam, SeamApiException + + +class Seam(AbstractSeam): + """ + Initial Seam class used to interact with Seam API + """ + + api_key: str + api_url: str = "https://connect.getseam.com" + lts_version: str = "1.0.0" + + def __init__( + self, + api_key: Optional[str] = None, + *, + workspace_id: Optional[str] = None, + api_url: Optional[str] = None, + ): + """ + Parameters + ---------- + api_key : str, optional + API key + workspace_id : str, optional + Workspace id + api_url : str, optional + API url + """ + Routes.__init__(self) + + if api_key is None: + api_key = os.environ.get("SEAM_API_KEY", None) + if api_key is None: + raise Exception( + "SEAM_API_KEY not found in environment, and api_key not provided" + ) + if workspace_id is None: + workspace_id = os.environ.get("SEAM_WORKSPACE_ID", None) + self.api_key = api_key + self.workspace_id = workspace_id + self.lts_version = Seam.lts_version + + if os.environ.get("SEAM_API_URL", None) is not None: + print( + "\n" + "\033[93m" + "Using the SEAM_API_URL environment variable is deprecated. " + "Support will be removed in a later major version. Use SEAM_ENDPOINT instead." + "\033[0m" + ) + api_url = ( + os.environ.get("SEAM_API_URL", None) + or os.environ.get("SEAM_ENDPOINT", None) + or api_url + ) + if api_url is not None: + self.api_url = cast(str, api_url) + + def make_request(self, method: str, path: str, **kwargs): + """ + Makes a request to the API + + Parameters + ---------- + method : str + Request method + path : str + Request path + **kwargs + Keyword arguments passed to requests.request + """ + + url = self.api_url + path + sdk_version = pkg_resources.get_distribution("seam").version + headers = { + "Authorization": "Bearer " + self.api_key, + "Content-Type": "application/json", + "User-Agent": "Python SDK v" + + sdk_version + + " (https://github.com/seamapi/python)", + "seam-sdk-name": "seamapi/python", + "seam-sdk-version": sdk_version, + "seam-lts-version": self.lts_version, + } + if self.workspace_id is not None: + headers["seam-workspace"] = self.workspace_id + response = requests.request(method, url, headers=headers, **kwargs) + + if response.status_code != 200: + raise SeamApiException(response) + + if "application/json" in response.headers["content-type"]: + return response.json() + + return response.text diff --git a/seam/thermostats.py b/seam/thermostats.py new file mode 100644 index 00000000..d3de0c17 --- /dev/null +++ b/seam/thermostats.py @@ -0,0 +1,284 @@ +from seam.types import AbstractThermostats, AbstractSeam as Seam, ActionAttempt, Device +from typing import Optional, Any, List, Dict, Union +from seam.thermostats_climate_setting_schedules import ( + ThermostatsClimateSettingSchedules, +) + + +class Thermostats(AbstractThermostats): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._climate_setting_schedules = ThermostatsClimateSettingSchedules(seam=seam) + + @property + def climate_setting_schedules(self) -> ThermostatsClimateSettingSchedules: + return self._climate_setting_schedules + + def cool( + self, + *, + device_id: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if cooling_set_point_celsius is not None: + json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius + if cooling_set_point_fahrenheit is not None: + json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request("POST", "/thermostats/cool", json=json_payload) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + + res = self.seam.make_request("POST", "/thermostats/get", json=json_payload) + + return Device.from_dict(res["thermostat"]) + + def heat( + self, + *, + device_id: str, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if heating_set_point_celsius is not None: + json_payload["heating_set_point_celsius"] = heating_set_point_celsius + if heating_set_point_fahrenheit is not None: + json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request("POST", "/thermostats/heat", json=json_payload) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def heat_cool( + self, + *, + device_id: str, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if heating_set_point_celsius is not None: + json_payload["heating_set_point_celsius"] = heating_set_point_celsius + if heating_set_point_fahrenheit is not None: + json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit + if cooling_set_point_celsius is not None: + json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius + if cooling_set_point_fahrenheit is not None: + json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request( + "POST", "/thermostats/heat_cool", json=json_payload + ) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None + ) -> List[Device]: + json_payload = {} + + if connected_account_id is not None: + json_payload["connected_account_id"] = connected_account_id + if connected_account_ids is not None: + json_payload["connected_account_ids"] = connected_account_ids + if connect_webview_id is not None: + json_payload["connect_webview_id"] = connect_webview_id + if device_type is not None: + json_payload["device_type"] = device_type + if device_types is not None: + json_payload["device_types"] = device_types + if manufacturer is not None: + json_payload["manufacturer"] = manufacturer + if device_ids is not None: + json_payload["device_ids"] = device_ids + if limit is not None: + json_payload["limit"] = limit + if created_before is not None: + json_payload["created_before"] = created_before + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + if custom_metadata_has is not None: + json_payload["custom_metadata_has"] = custom_metadata_has + if include_if is not None: + json_payload["include_if"] = include_if + if exclude_if is not None: + json_payload["exclude_if"] = exclude_if + + res = self.seam.make_request("POST", "/thermostats/list", json=json_payload) + + return [Device.from_dict(item) for item in res["thermostats"]] + + def off( + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request("POST", "/thermostats/off", json=json_payload) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def set_fan_mode( + self, + *, + device_id: str, + fan_mode: Optional[str] = None, + fan_mode_setting: Optional[str] = None, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if fan_mode is not None: + json_payload["fan_mode"] = fan_mode + if fan_mode_setting is not None: + json_payload["fan_mode_setting"] = fan_mode_setting + if sync is not None: + json_payload["sync"] = sync + + res = self.seam.make_request( + "POST", "/thermostats/set_fan_mode", json=json_payload + ) + + if isinstance(wait_for_action_attempt, dict): + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"], + timeout=wait_for_action_attempt.get("timeout", None), + polling_interval=wait_for_action_attempt.get("polling_interval", None), + ) + elif wait_for_action_attempt is True: + updated_action_attempt = self.seam.action_attempts.poll_until_ready( + action_attempt_id=res["action_attempt"]["action_attempt_id"] + ) + else: + return ActionAttempt.from_dict(res["action_attempt"]) + + return updated_action_attempt + + def update( + self, *, device_id: str, default_climate_setting: Dict[str, Any] + ) -> None: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if default_climate_setting is not None: + json_payload["default_climate_setting"] = default_climate_setting + + self.seam.make_request("POST", "/thermostats/update", json=json_payload) + + return None diff --git a/seam/thermostats_climate_setting_schedules.py b/seam/thermostats_climate_setting_schedules.py new file mode 100644 index 00000000..b6779420 --- /dev/null +++ b/seam/thermostats_climate_setting_schedules.py @@ -0,0 +1,167 @@ +from seam.types import ( + AbstractThermostatsClimateSettingSchedules, + AbstractSeam as Seam, + ClimateSettingSchedule, +) +from typing import Optional, Any, List, Dict, Union + + +class ThermostatsClimateSettingSchedules(AbstractThermostatsClimateSettingSchedules): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create( + self, + *, + device_id: str, + schedule_starts_at: str, + schedule_ends_at: str, + schedule_type: Optional[str] = None, + name: Optional[str] = None, + automatic_heating_enabled: Optional[bool] = None, + automatic_cooling_enabled: Optional[bool] = None, + hvac_mode_setting: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + manual_override_allowed: Optional[bool] = None + ) -> ClimateSettingSchedule: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if schedule_starts_at is not None: + json_payload["schedule_starts_at"] = schedule_starts_at + if schedule_ends_at is not None: + json_payload["schedule_ends_at"] = schedule_ends_at + if schedule_type is not None: + json_payload["schedule_type"] = schedule_type + if name is not None: + json_payload["name"] = name + if automatic_heating_enabled is not None: + json_payload["automatic_heating_enabled"] = automatic_heating_enabled + if automatic_cooling_enabled is not None: + json_payload["automatic_cooling_enabled"] = automatic_cooling_enabled + if hvac_mode_setting is not None: + json_payload["hvac_mode_setting"] = hvac_mode_setting + if cooling_set_point_celsius is not None: + json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius + if heating_set_point_celsius is not None: + json_payload["heating_set_point_celsius"] = heating_set_point_celsius + if cooling_set_point_fahrenheit is not None: + json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit + if heating_set_point_fahrenheit is not None: + json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit + if manual_override_allowed is not None: + json_payload["manual_override_allowed"] = manual_override_allowed + + res = self.seam.make_request( + "POST", "/thermostats/climate_setting_schedules/create", json=json_payload + ) + + return ClimateSettingSchedule.from_dict(res["climate_setting_schedule"]) + + def delete(self, *, climate_setting_schedule_id: str) -> None: + json_payload = {} + + if climate_setting_schedule_id is not None: + json_payload["climate_setting_schedule_id"] = climate_setting_schedule_id + + self.seam.make_request( + "POST", "/thermostats/climate_setting_schedules/delete", json=json_payload + ) + + return None + + def get( + self, + *, + climate_setting_schedule_id: Optional[str] = None, + device_id: Optional[str] = None + ) -> ClimateSettingSchedule: + json_payload = {} + + if climate_setting_schedule_id is not None: + json_payload["climate_setting_schedule_id"] = climate_setting_schedule_id + if device_id is not None: + json_payload["device_id"] = device_id + + res = self.seam.make_request( + "POST", "/thermostats/climate_setting_schedules/get", json=json_payload + ) + + return ClimateSettingSchedule.from_dict(res["climate_setting_schedule"]) + + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[ClimateSettingSchedule]: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + + res = self.seam.make_request( + "POST", "/thermostats/climate_setting_schedules/list", json=json_payload + ) + + return [ + ClimateSettingSchedule.from_dict(item) + for item in res["climate_setting_schedules"] + ] + + def update( + self, + *, + climate_setting_schedule_id: str, + schedule_type: Optional[str] = None, + name: Optional[str] = None, + schedule_starts_at: Optional[str] = None, + schedule_ends_at: Optional[str] = None, + automatic_heating_enabled: Optional[bool] = None, + automatic_cooling_enabled: Optional[bool] = None, + hvac_mode_setting: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + manual_override_allowed: Optional[bool] = None + ) -> None: + json_payload = {} + + if climate_setting_schedule_id is not None: + json_payload["climate_setting_schedule_id"] = climate_setting_schedule_id + if schedule_type is not None: + json_payload["schedule_type"] = schedule_type + if name is not None: + json_payload["name"] = name + if schedule_starts_at is not None: + json_payload["schedule_starts_at"] = schedule_starts_at + if schedule_ends_at is not None: + json_payload["schedule_ends_at"] = schedule_ends_at + if automatic_heating_enabled is not None: + json_payload["automatic_heating_enabled"] = automatic_heating_enabled + if automatic_cooling_enabled is not None: + json_payload["automatic_cooling_enabled"] = automatic_cooling_enabled + if hvac_mode_setting is not None: + json_payload["hvac_mode_setting"] = hvac_mode_setting + if cooling_set_point_celsius is not None: + json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius + if heating_set_point_celsius is not None: + json_payload["heating_set_point_celsius"] = heating_set_point_celsius + if cooling_set_point_fahrenheit is not None: + json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit + if heating_set_point_fahrenheit is not None: + json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit + if manual_override_allowed is not None: + json_payload["manual_override_allowed"] = manual_override_allowed + + self.seam.make_request( + "POST", "/thermostats/climate_setting_schedules/update", json=json_payload + ) + + return None diff --git a/seam/todo.py b/seam/todo.py deleted file mode 100644 index 37d7f080..00000000 --- a/seam/todo.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -TODO -""" - - -def todo(arg): - "TODO" - return arg diff --git a/seam/todo_test.py b/seam/todo_test.py deleted file mode 100644 index 4a9d7258..00000000 --- a/seam/todo_test.py +++ /dev/null @@ -1,10 +0,0 @@ -# pylint: disable=missing-docstring -# pylint: disable=unused-import - -import pytest - -from .todo import todo - - -def test_todo(): - assert todo(True) is True diff --git a/seam/types.py b/seam/types.py new file mode 100644 index 00000000..f4db1dd3 --- /dev/null +++ b/seam/types.py @@ -0,0 +1,1861 @@ +from typing import Any, Dict, List, Optional, Union +import abc +from dataclasses import dataclass +from seam.utils.deep_attr_dict import DeepAttrDict + + +@dataclass +class AccessCode: + common_code_key: str + is_scheduled_on_device: bool + type: str + is_waiting_for_code_assignment: bool + access_code_id: str + device_id: str + name: str + code: str + created_at: str + errors: Any + warnings: Any + is_managed: bool + starts_at: str + ends_at: str + status: str + is_backup_access_code_available: bool + is_backup: bool + pulled_backup_access_code_id: str + is_external_modification_allowed: bool + is_one_time_use: bool + is_offline_access_code: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return AccessCode( + common_code_key=d.get("common_code_key", None), + is_scheduled_on_device=d.get("is_scheduled_on_device", None), + type=d.get("type", None), + is_waiting_for_code_assignment=d.get( + "is_waiting_for_code_assignment", None + ), + access_code_id=d.get("access_code_id", None), + device_id=d.get("device_id", None), + name=d.get("name", None), + code=d.get("code", None), + created_at=d.get("created_at", None), + errors=d.get("errors", None), + warnings=d.get("warnings", None), + is_managed=d.get("is_managed", None), + starts_at=d.get("starts_at", None), + ends_at=d.get("ends_at", None), + status=d.get("status", None), + is_backup_access_code_available=d.get( + "is_backup_access_code_available", None + ), + is_backup=d.get("is_backup", None), + pulled_backup_access_code_id=d.get("pulled_backup_access_code_id", None), + is_external_modification_allowed=d.get( + "is_external_modification_allowed", None + ), + is_one_time_use=d.get("is_one_time_use", None), + is_offline_access_code=d.get("is_offline_access_code", None), + ) + + +@dataclass +class UnmanagedAccessCode: + type: str + access_code_id: str + device_id: str + name: str + code: str + created_at: str + errors: Any + warnings: Any + is_managed: bool + starts_at: str + ends_at: str + status: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return UnmanagedAccessCode( + type=d.get("type", None), + access_code_id=d.get("access_code_id", None), + device_id=d.get("device_id", None), + name=d.get("name", None), + code=d.get("code", None), + created_at=d.get("created_at", None), + errors=d.get("errors", None), + warnings=d.get("warnings", None), + is_managed=d.get("is_managed", None), + starts_at=d.get("starts_at", None), + ends_at=d.get("ends_at", None), + status=d.get("status", None), + ) + + +@dataclass +class ActionAttempt: + status: str + action_type: str + action_attempt_id: str + result: str + error: Dict[str, Any] + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ActionAttempt( + status=d.get("status", None), + action_type=d.get("action_type", None), + action_attempt_id=d.get("action_attempt_id", None), + result=d.get("result", None), + error=DeepAttrDict(d.get("error", None)), + ) + + +@dataclass +class ClientSession: + client_session_id: str + user_identifier_key: str + created_at: str + token: str + device_count: float + connected_account_ids: List[str] + connect_webview_ids: List[str] + user_identity_ids: List[str] + workspace_id: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ClientSession( + client_session_id=d.get("client_session_id", None), + user_identifier_key=d.get("user_identifier_key", None), + created_at=d.get("created_at", None), + token=d.get("token", None), + device_count=d.get("device_count", None), + connected_account_ids=d.get("connected_account_ids", None), + connect_webview_ids=d.get("connect_webview_ids", None), + user_identity_ids=d.get("user_identity_ids", None), + workspace_id=d.get("workspace_id", None), + ) + + +@dataclass +class ClimateSettingSchedule: + climate_setting_schedule_id: str + schedule_type: str + device_id: str + name: str + schedule_starts_at: str + schedule_ends_at: str + created_at: str + errors: Any + automatic_heating_enabled: bool + automatic_cooling_enabled: bool + hvac_mode_setting: str + cooling_set_point_celsius: float + heating_set_point_celsius: float + cooling_set_point_fahrenheit: float + heating_set_point_fahrenheit: float + manual_override_allowed: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ClimateSettingSchedule( + climate_setting_schedule_id=d.get("climate_setting_schedule_id", None), + schedule_type=d.get("schedule_type", None), + device_id=d.get("device_id", None), + name=d.get("name", None), + schedule_starts_at=d.get("schedule_starts_at", None), + schedule_ends_at=d.get("schedule_ends_at", None), + created_at=d.get("created_at", None), + errors=d.get("errors", None), + automatic_heating_enabled=d.get("automatic_heating_enabled", None), + automatic_cooling_enabled=d.get("automatic_cooling_enabled", None), + hvac_mode_setting=d.get("hvac_mode_setting", None), + cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), + heating_set_point_celsius=d.get("heating_set_point_celsius", None), + cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), + heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), + manual_override_allowed=d.get("manual_override_allowed", None), + ) + + +@dataclass +class ConnectWebview: + connect_webview_id: str + connected_account_id: str + url: str + workspace_id: str + device_selection_mode: str + accepted_providers: List[str] + accepted_devices: List[str] + any_provider_allowed: bool + any_device_allowed: bool + created_at: str + login_successful: bool + status: str + custom_redirect_url: str + custom_redirect_failure_url: str + custom_metadata: Dict[str, Any] + automatically_manage_new_devices: bool + wait_for_device_creation: bool + authorized_at: str + selected_provider: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ConnectWebview( + connect_webview_id=d.get("connect_webview_id", None), + connected_account_id=d.get("connected_account_id", None), + url=d.get("url", None), + workspace_id=d.get("workspace_id", None), + device_selection_mode=d.get("device_selection_mode", None), + accepted_providers=d.get("accepted_providers", None), + accepted_devices=d.get("accepted_devices", None), + any_provider_allowed=d.get("any_provider_allowed", None), + any_device_allowed=d.get("any_device_allowed", None), + created_at=d.get("created_at", None), + login_successful=d.get("login_successful", None), + status=d.get("status", None), + custom_redirect_url=d.get("custom_redirect_url", None), + custom_redirect_failure_url=d.get("custom_redirect_failure_url", None), + custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), + automatically_manage_new_devices=d.get( + "automatically_manage_new_devices", None + ), + wait_for_device_creation=d.get("wait_for_device_creation", None), + authorized_at=d.get("authorized_at", None), + selected_provider=d.get("selected_provider", None), + ) + + +@dataclass +class ConnectedAccount: + connected_account_id: str + created_at: str + user_identifier: Dict[str, Any] + account_type: str + account_type_display_name: str + errors: Any + warnings: Any + custom_metadata: Dict[str, Any] + automatically_manage_new_devices: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ConnectedAccount( + connected_account_id=d.get("connected_account_id", None), + created_at=d.get("created_at", None), + user_identifier=DeepAttrDict(d.get("user_identifier", None)), + account_type=d.get("account_type", None), + account_type_display_name=d.get("account_type_display_name", None), + errors=d.get("errors", None), + warnings=d.get("warnings", None), + custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), + automatically_manage_new_devices=d.get( + "automatically_manage_new_devices", None + ), + ) + + +@dataclass +class Device: + device_id: str + device_type: Any + nickname: str + display_name: str + capabilities_supported: List[str] + properties: Any + location: Dict[str, Any] + connected_account_id: str + workspace_id: str + errors: List[Dict[str, Any]] + warnings: List[Dict[str, Any]] + created_at: str + is_managed: bool + custom_metadata: Dict[str, Any] + can_remotely_unlock: bool + can_remotely_lock: bool + can_program_online_access_codes: bool + can_simulate_removal: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return Device( + device_id=d.get("device_id", None), + device_type=d.get("device_type", None), + nickname=d.get("nickname", None), + display_name=d.get("display_name", None), + capabilities_supported=d.get("capabilities_supported", None), + properties=DeepAttrDict(d.get("properties", None)), + location=DeepAttrDict(d.get("location", None)), + connected_account_id=d.get("connected_account_id", None), + workspace_id=d.get("workspace_id", None), + errors=d.get("errors", None), + warnings=d.get("warnings", None), + created_at=d.get("created_at", None), + is_managed=d.get("is_managed", None), + custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), + can_remotely_unlock=d.get("can_remotely_unlock", None), + can_remotely_lock=d.get("can_remotely_lock", None), + can_program_online_access_codes=d.get( + "can_program_online_access_codes", None + ), + can_simulate_removal=d.get("can_simulate_removal", None), + ) + + +@dataclass +class UnmanagedDevice: + device_id: str + device_type: Any + connected_account_id: str + capabilities_supported: List[str] + workspace_id: str + errors: List[Dict[str, Any]] + warnings: List[Dict[str, Any]] + created_at: str + is_managed: bool + properties: Dict[str, Any] + can_remotely_unlock: bool + can_remotely_lock: bool + can_program_online_access_codes: bool + can_simulate_removal: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return UnmanagedDevice( + device_id=d.get("device_id", None), + device_type=d.get("device_type", None), + connected_account_id=d.get("connected_account_id", None), + capabilities_supported=d.get("capabilities_supported", None), + workspace_id=d.get("workspace_id", None), + errors=d.get("errors", None), + warnings=d.get("warnings", None), + created_at=d.get("created_at", None), + is_managed=d.get("is_managed", None), + properties=DeepAttrDict(d.get("properties", None)), + can_remotely_unlock=d.get("can_remotely_unlock", None), + can_remotely_lock=d.get("can_remotely_lock", None), + can_program_online_access_codes=d.get( + "can_program_online_access_codes", None + ), + can_simulate_removal=d.get("can_simulate_removal", None), + ) + + +@dataclass +class DeviceProvider: + device_provider_name: str + display_name: str + image_url: str + provider_categories: List[str] + + @staticmethod + def from_dict(d: Dict[str, Any]): + return DeviceProvider( + device_provider_name=d.get("device_provider_name", None), + display_name=d.get("display_name", None), + image_url=d.get("image_url", None), + provider_categories=d.get("provider_categories", None), + ) + + +@dataclass +class Event: + event_id: str + device_id: str + event_type: str + workspace_id: str + created_at: str + occurred_at: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return Event( + event_id=d.get("event_id", None), + device_id=d.get("device_id", None), + event_type=d.get("event_type", None), + workspace_id=d.get("workspace_id", None), + created_at=d.get("created_at", None), + occurred_at=d.get("occurred_at", None), + ) + + +@dataclass +class NoiseThreshold: + noise_threshold_id: str + device_id: str + name: str + noise_threshold_nrs: float + starts_daily_at: str + ends_daily_at: str + noise_threshold_decibels: float + + @staticmethod + def from_dict(d: Dict[str, Any]): + return NoiseThreshold( + noise_threshold_id=d.get("noise_threshold_id", None), + device_id=d.get("device_id", None), + name=d.get("name", None), + noise_threshold_nrs=d.get("noise_threshold_nrs", None), + starts_daily_at=d.get("starts_daily_at", None), + ends_daily_at=d.get("ends_daily_at", None), + noise_threshold_decibels=d.get("noise_threshold_decibels", None), + ) + + +@dataclass +class ServiceHealth: + service: str + status: str + description: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ServiceHealth( + service=d.get("service", None), + status=d.get("status", None), + description=d.get("description", None), + ) + + +@dataclass +class Webhook: + webhook_id: str + url: str + event_types: List[str] + secret: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return Webhook( + webhook_id=d.get("webhook_id", None), + url=d.get("url", None), + event_types=d.get("event_types", None), + secret=d.get("secret", None), + ) + + +@dataclass +class Workspace: + workspace_id: str + name: str + is_sandbox: bool + connect_partner_name: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return Workspace( + workspace_id=d.get("workspace_id", None), + name=d.get("name", None), + is_sandbox=d.get("is_sandbox", None), + connect_partner_name=d.get("connect_partner_name", None), + ) + + +@dataclass +class AcsSystem: + acs_system_id: str + external_type: str + external_type_display_name: str + system_type: str + system_type_display_name: str + name: str + created_at: str + workspace_id: str + connected_account_ids: List[str] + image_url: str + image_alt_text: str + can_automate_enrollment: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return AcsSystem( + acs_system_id=d.get("acs_system_id", None), + external_type=d.get("external_type", None), + external_type_display_name=d.get("external_type_display_name", None), + system_type=d.get("system_type", None), + system_type_display_name=d.get("system_type_display_name", None), + name=d.get("name", None), + created_at=d.get("created_at", None), + workspace_id=d.get("workspace_id", None), + connected_account_ids=d.get("connected_account_ids", None), + image_url=d.get("image_url", None), + image_alt_text=d.get("image_alt_text", None), + can_automate_enrollment=d.get("can_automate_enrollment", None), + ) + + +@dataclass +class AcsAccessGroup: + acs_access_group_id: str + acs_system_id: str + workspace_id: str + name: str + access_group_type: str + access_group_type_display_name: str + external_type: str + external_type_display_name: str + created_at: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return AcsAccessGroup( + acs_access_group_id=d.get("acs_access_group_id", None), + acs_system_id=d.get("acs_system_id", None), + workspace_id=d.get("workspace_id", None), + name=d.get("name", None), + access_group_type=d.get("access_group_type", None), + access_group_type_display_name=d.get( + "access_group_type_display_name", None + ), + external_type=d.get("external_type", None), + external_type_display_name=d.get("external_type_display_name", None), + created_at=d.get("created_at", None), + ) + + +@dataclass +class AcsUser: + acs_user_id: str + acs_system_id: str + hid_acs_system_id: str + workspace_id: str + created_at: str + display_name: str + external_type: str + external_type_display_name: str + is_suspended: bool + access_schedule: Dict[str, Any] + user_identity_id: str + user_identity_email_address: str + user_identity_phone_number: str + full_name: str + email: str + email_address: str + phone_number: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return AcsUser( + acs_user_id=d.get("acs_user_id", None), + acs_system_id=d.get("acs_system_id", None), + hid_acs_system_id=d.get("hid_acs_system_id", None), + workspace_id=d.get("workspace_id", None), + created_at=d.get("created_at", None), + display_name=d.get("display_name", None), + external_type=d.get("external_type", None), + external_type_display_name=d.get("external_type_display_name", None), + is_suspended=d.get("is_suspended", None), + access_schedule=DeepAttrDict(d.get("access_schedule", None)), + user_identity_id=d.get("user_identity_id", None), + user_identity_email_address=d.get("user_identity_email_address", None), + user_identity_phone_number=d.get("user_identity_phone_number", None), + full_name=d.get("full_name", None), + email=d.get("email", None), + email_address=d.get("email_address", None), + phone_number=d.get("phone_number", None), + ) + + +@dataclass +class EnrollmentAutomation: + credential_manager_acs_system_id: str + user_identity_id: str + created_at: str + workspace_id: str + enrollment_automation_id: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return EnrollmentAutomation( + credential_manager_acs_system_id=d.get( + "credential_manager_acs_system_id", None + ), + user_identity_id=d.get("user_identity_id", None), + created_at=d.get("created_at", None), + workspace_id=d.get("workspace_id", None), + enrollment_automation_id=d.get("enrollment_automation_id", None), + ) + + +@dataclass +class Phone: + device_id: str + device_type: str + nickname: str + display_name: str + capabilities_supported: List[str] + properties: Dict[str, Any] + location: Dict[str, Any] + workspace_id: str + errors: List[Dict[str, Any]] + warnings: List[Dict[str, Any]] + created_at: str + is_managed: bool + custom_metadata: Dict[str, Any] + can_remotely_unlock: bool + can_remotely_lock: bool + can_program_online_access_codes: bool + can_simulate_removal: bool + + @staticmethod + def from_dict(d: Dict[str, Any]): + return Phone( + device_id=d.get("device_id", None), + device_type=d.get("device_type", None), + nickname=d.get("nickname", None), + display_name=d.get("display_name", None), + capabilities_supported=d.get("capabilities_supported", None), + properties=DeepAttrDict(d.get("properties", None)), + location=DeepAttrDict(d.get("location", None)), + workspace_id=d.get("workspace_id", None), + errors=d.get("errors", None), + warnings=d.get("warnings", None), + created_at=d.get("created_at", None), + is_managed=d.get("is_managed", None), + custom_metadata=DeepAttrDict(d.get("custom_metadata", None)), + can_remotely_unlock=d.get("can_remotely_unlock", None), + can_remotely_lock=d.get("can_remotely_lock", None), + can_program_online_access_codes=d.get( + "can_program_online_access_codes", None + ), + can_simulate_removal=d.get("can_simulate_removal", None), + ) + + +class SeamApiException(Exception): + def __init__( + self, + response, + ): + self.status_code = response.status_code + self.request_id = response.headers.get("seam-request-id", None) + + self.metadata = None + if "application/json" in response.headers["content-type"]: + parsed_response = response.json() + self.metadata = parsed_response.get("error", None) + + super().__init__( + f"SeamApiException: status={self.status_code}, request_id={self.request_id}, metadata={self.metadata}" + ) + + +class AbstractActionAttempts(abc.ABC): + @abc.abstractmethod + def get(self, *, action_attempt_id: str) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def list(self, *, action_attempt_ids: List[str]) -> List[ActionAttempt]: + raise NotImplementedError() + + @abc.abstractmethod + def poll_until_ready( + self, + *, + action_attempt_id: str, + timeout: Optional[float] = 5.0, + polling_interval: Optional[float] = 0.5, + ) -> ActionAttempt: + raise NotImplementedError() + + +class AbstractClientSessions(abc.ABC): + @abc.abstractmethod + def create( + self, + *, + user_identifier_key: Optional[str] = None, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + user_identity_ids: Optional[List[str]] = None, + expires_at: Optional[str] = None, + ) -> ClientSession: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, client_session_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + ) -> ClientSession: + raise NotImplementedError() + + @abc.abstractmethod + def get_or_create( + self, + *, + user_identifier_key: Optional[str] = None, + connect_webview_ids: Optional[List[str]] = None, + connected_account_ids: Optional[List[str]] = None, + user_identity_ids: Optional[List[str]] = None, + expires_at: Optional[str] = None, + ) -> ClientSession: + raise NotImplementedError() + + @abc.abstractmethod + def grant_access( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_ids: Optional[List[str]] = None, + user_identity_ids: Optional[List[str]] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + client_session_id: Optional[str] = None, + user_identifier_key: Optional[str] = None, + connect_webview_id: Optional[str] = None, + without_user_identifier_key: Optional[bool] = None, + user_identity_id: Optional[str] = None, + ) -> List[ClientSession]: + raise NotImplementedError() + + @abc.abstractmethod + def revoke(self, *, client_session_id: str) -> None: + raise NotImplementedError() + + +class AbstractConnectWebviews(abc.ABC): + @abc.abstractmethod + def create( + self, + *, + device_selection_mode: Optional[str] = None, + custom_redirect_url: Optional[str] = None, + custom_redirect_failure_url: Optional[str] = None, + accepted_providers: Optional[List[str]] = None, + provider_category: Optional[str] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + automatically_manage_new_devices: Optional[bool] = None, + wait_for_device_creation: Optional[bool] = None, + ) -> ConnectWebview: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, connect_webview_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, connect_webview_id: str) -> ConnectWebview: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + ) -> List[ConnectWebview]: + raise NotImplementedError() + + +class AbstractConnectedAccounts(abc.ABC): + @abc.abstractmethod + def delete(self, *, connected_account_id: str, sync: Optional[bool] = None) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, *, connected_account_id: Optional[str] = None, email: Optional[str] = None + ) -> ConnectedAccount: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, *, custom_metadata_has: Optional[Dict[str, Any]] = None + ) -> List[ConnectedAccount]: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + connected_account_id: str, + automatically_manage_new_devices: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + ) -> ConnectedAccount: + raise NotImplementedError() + + +class AbstractEvents(abc.ABC): + @abc.abstractmethod + def get( + self, + *, + event_id: Optional[str] = None, + event_type: Optional[str] = None, + device_id: Optional[str] = None, + ) -> Event: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + since: Optional[str] = None, + between: Optional[List[str]] = None, + device_id: Optional[str] = None, + device_ids: Optional[List[str]] = None, + access_code_id: Optional[str] = None, + access_code_ids: Optional[List[str]] = None, + event_type: Optional[str] = None, + event_types: Optional[List[str]] = None, + connected_account_id: Optional[str] = None, + limit: Optional[float] = None, + ) -> List[Event]: + raise NotImplementedError() + + +class AbstractLocks(abc.ABC): + @abc.abstractmethod + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None, + ) -> List[Device]: + raise NotImplementedError() + + @abc.abstractmethod + def lock_door( + self, *, device_id: str, sync: Optional[bool] = None + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def unlock_door( + self, *, device_id: str, sync: Optional[bool] = None + ) -> ActionAttempt: + raise NotImplementedError() + + +class AbstractNetworks(abc.ABC): + @abc.abstractmethod + def get(self, *, network_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + ) -> None: + raise NotImplementedError() + + +class AbstractWebhooks(abc.ABC): + @abc.abstractmethod + def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhook: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, webhook_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, webhook_id: str) -> Webhook: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + ) -> List[Webhook]: + raise NotImplementedError() + + @abc.abstractmethod + def update(self, *, webhook_id: str, event_types: List[str]) -> None: + raise NotImplementedError() + + +class AbstractWorkspaces(abc.ABC): + @abc.abstractmethod + def create( + self, + *, + name: str, + connect_partner_name: str, + is_sandbox: Optional[bool] = None, + webview_primary_button_color: Optional[str] = None, + webview_logo_shape: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, + ) -> Workspace: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + ) -> List[Workspace]: + raise NotImplementedError() + + @abc.abstractmethod + def reset_sandbox( + self, + ) -> None: + raise NotImplementedError() + + +class AbstractAccessCodesSimulate(abc.ABC): + @abc.abstractmethod + def create_unmanaged_access_code( + self, *, device_id: str, name: str, code: str + ) -> UnmanagedAccessCode: + raise NotImplementedError() + + +class AbstractAccessCodesUnmanaged(abc.ABC): + @abc.abstractmethod + def convert_to_managed( + self, + *, + access_code_id: str, + is_external_modification_allowed: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + force: Optional[bool] = None, + sync: Optional[bool] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, access_code_id: str, sync: Optional[bool] = None) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, + *, + device_id: Optional[str] = None, + access_code_id: Optional[str] = None, + code: Optional[str] = None, + ) -> UnmanagedAccessCode: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[UnmanagedAccessCode]: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + access_code_id: str, + is_managed: bool, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + force: Optional[bool] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractAcsAccessGroups(abc.ABC): + @abc.abstractmethod + def add_user(self, *, acs_access_group_id: str, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, acs_access_group_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, *, acs_system_id: Optional[str] = None, acs_user_id: Optional[str] = None + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list_users(self, *, acs_access_group_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def remove_user(self, *, acs_access_group_id: str, acs_user_id: str) -> None: + raise NotImplementedError() + + +class AbstractAcsCredentialPools(abc.ABC): + @abc.abstractmethod + def list(self, *, acs_system_id: str) -> None: + raise NotImplementedError() + + +class AbstractAcsCredentialProvisioningAutomations(abc.ABC): + @abc.abstractmethod + def launch( + self, + *, + user_identity_id: str, + credential_manager_acs_system_id: str, + acs_credential_pool_id: Optional[str] = None, + create_credential_manager_user: Optional[bool] = None, + credential_manager_acs_user_id: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractAcsCredentials(abc.ABC): + @abc.abstractmethod + def assign(self, *, acs_user_id: str, acs_credential_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def create( + self, + *, + acs_user_id: str, + access_method: str, + credential_manager_acs_system_id: Optional[str] = None, + code: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + allowed_acs_entrance_ids: Optional[List[str]] = None, + visionline_metadata: Optional[Dict[str, Any]] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, acs_credential_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, acs_credential_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + acs_user_id: Optional[str] = None, + acs_system_id: Optional[str] = None, + user_identity_id: Optional[str] = None, + is_multi_phone_sync_credential: Optional[bool] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def unassign(self, *, acs_user_id: str, acs_credential_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def update(self, *, acs_credential_id: str, code: str) -> None: + raise NotImplementedError() + + +class AbstractAcsEntrances(abc.ABC): + @abc.abstractmethod + def get(self, *, acs_entrance_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def grant_access(self, *, acs_entrance_id: str, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + acs_system_id: Optional[str] = None, + acs_credential_id: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list_credentials_with_access( + self, *, acs_entrance_id: str, include_if: Optional[List[str]] = None + ) -> None: + raise NotImplementedError() + + +class AbstractAcsSystems(abc.ABC): + @abc.abstractmethod + def get(self, *, acs_system_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list(self, *, connected_account_id: Optional[str] = None) -> None: + raise NotImplementedError() + + +class AbstractAcsUsers(abc.ABC): + @abc.abstractmethod + def add_to_access_group( + self, *, acs_user_id: str, acs_access_group_id: str + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def create( + self, + *, + acs_system_id: str, + acs_access_group_ids: Optional[List[str]] = None, + user_identity_id: Optional[str] = None, + access_schedule: Optional[Dict[str, Any]] = None, + full_name: Optional[str] = None, + email: Optional[str] = None, + phone_number: Optional[str] = None, + email_address: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + user_identity_id: Optional[str] = None, + user_identity_phone_number: Optional[str] = None, + user_identity_email_address: Optional[str] = None, + acs_system_id: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list_accessible_entrances(self, *, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def remove_from_access_group( + self, *, acs_user_id: str, acs_access_group_id: str + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def revoke_access_to_all_entrances(self, *, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def suspend(self, *, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def unsuspend(self, *, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + acs_user_id: str, + access_schedule: Optional[Dict[str, Any]] = None, + full_name: Optional[str] = None, + email: Optional[str] = None, + phone_number: Optional[str] = None, + email_address: Optional[str] = None, + hid_acs_system_id: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractDevicesSimulate(abc.ABC): + @abc.abstractmethod + def remove(self, *, device_id: str) -> None: + raise NotImplementedError() + + +class AbstractDevicesUnmanaged(abc.ABC): + @abc.abstractmethod + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> UnmanagedDevice: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None, + ) -> List[UnmanagedDevice]: + raise NotImplementedError() + + @abc.abstractmethod + def update(self, *, device_id: str, is_managed: bool) -> None: + raise NotImplementedError() + + +class AbstractHealthService(abc.ABC): + @abc.abstractmethod + def by_service_name(self, *, service_name: str) -> None: + raise NotImplementedError() + + +class AbstractNoiseSensorsNoiseThresholds(abc.ABC): + @abc.abstractmethod + def create( + self, + *, + device_id: str, + starts_daily_at: str, + ends_daily_at: str, + sync: Optional[bool] = None, + name: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None, + ) -> NoiseThreshold: + raise NotImplementedError() + + @abc.abstractmethod + def delete( + self, *, noise_threshold_id: str, device_id: str, sync: Optional[bool] = None + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, noise_threshold_id: str) -> NoiseThreshold: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, *, device_id: str, is_programmed: Optional[bool] = None + ) -> List[NoiseThreshold]: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + noise_threshold_id: str, + device_id: str, + sync: Optional[bool] = None, + name: Optional[str] = None, + starts_daily_at: Optional[str] = None, + ends_daily_at: Optional[str] = None, + noise_threshold_decibels: Optional[float] = None, + noise_threshold_nrs: Optional[float] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractNoiseSensorsSimulate(abc.ABC): + @abc.abstractmethod + def trigger_noise_threshold(self, *, device_id: str) -> None: + raise NotImplementedError() + + +class AbstractPhonesSimulate(abc.ABC): + @abc.abstractmethod + def create_sandbox_phone( + self, + *, + user_identity_id: str, + custom_sdk_installation_id: Optional[str] = None, + phone_metadata: Optional[Dict[str, Any]] = None, + assa_abloy_metadata: Optional[Dict[str, Any]] = None, + ) -> Phone: + raise NotImplementedError() + + +class AbstractThermostatsClimateSettingSchedules(abc.ABC): + @abc.abstractmethod + def create( + self, + *, + device_id: str, + schedule_starts_at: str, + schedule_ends_at: str, + schedule_type: Optional[str] = None, + name: Optional[str] = None, + automatic_heating_enabled: Optional[bool] = None, + automatic_cooling_enabled: Optional[bool] = None, + hvac_mode_setting: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + manual_override_allowed: Optional[bool] = None, + ) -> ClimateSettingSchedule: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, climate_setting_schedule_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, + *, + climate_setting_schedule_id: Optional[str] = None, + device_id: Optional[str] = None, + ) -> ClimateSettingSchedule: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[ClimateSettingSchedule]: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + climate_setting_schedule_id: str, + schedule_type: Optional[str] = None, + name: Optional[str] = None, + schedule_starts_at: Optional[str] = None, + schedule_ends_at: Optional[str] = None, + automatic_heating_enabled: Optional[bool] = None, + automatic_cooling_enabled: Optional[bool] = None, + hvac_mode_setting: Optional[str] = None, + cooling_set_point_celsius: Optional[float] = None, + heating_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + manual_override_allowed: Optional[bool] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractUserIdentitiesEnrollmentAutomations(abc.ABC): + @abc.abstractmethod + def delete(self, *, enrollment_automation_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get(self, *, enrollment_automation_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def launch( + self, + *, + user_identity_id: str, + credential_manager_acs_system_id: str, + acs_credential_pool_id: Optional[str] = None, + create_credential_manager_user: Optional[bool] = None, + credential_manager_acs_user_id: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list(self, *, user_identity_id: str) -> None: + raise NotImplementedError() + + +class AbstractHealth(abc.ABC): + @property + @abc.abstractmethod + def service(self) -> AbstractHealthService: + raise NotImplementedError() + + @abc.abstractmethod + def get_health( + self, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get_service_health(self, *, service: str) -> None: + raise NotImplementedError() + + +class AbstractPhones(abc.ABC): + @property + @abc.abstractmethod + def simulate(self) -> AbstractPhonesSimulate: + raise NotImplementedError() + + @abc.abstractmethod + def deactivate(self, *, device_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list(self, *, owner_user_identity_id: Optional[str] = None) -> List[Phone]: + raise NotImplementedError() + + +class AbstractThermostats(abc.ABC): + @property + @abc.abstractmethod + def climate_setting_schedules(self) -> AbstractThermostatsClimateSettingSchedules: + raise NotImplementedError() + + @abc.abstractmethod + def cool( + self, + *, + device_id: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + sync: Optional[bool] = None, + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: + raise NotImplementedError() + + @abc.abstractmethod + def heat( + self, + *, + device_id: str, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + sync: Optional[bool] = None, + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def heat_cool( + self, + *, + device_id: str, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + sync: Optional[bool] = None, + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None, + ) -> List[Device]: + raise NotImplementedError() + + @abc.abstractmethod + def off(self, *, device_id: str, sync: Optional[bool] = None) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def set_fan_mode( + self, + *, + device_id: str, + fan_mode: Optional[str] = None, + fan_mode_setting: Optional[str] = None, + sync: Optional[bool] = None, + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, *, device_id: str, default_climate_setting: Dict[str, Any] + ) -> None: + raise NotImplementedError() + + +class AbstractUserIdentities(abc.ABC): + @property + @abc.abstractmethod + def enrollment_automations(self) -> AbstractUserIdentitiesEnrollmentAutomations: + raise NotImplementedError() + + @abc.abstractmethod + def add_acs_user(self, *, user_identity_id: str, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def create( + self, + *, + user_identity_key: Optional[str] = None, + email_address: Optional[str] = None, + phone_number: Optional[str] = None, + full_name: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, user_identity_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, + *, + user_identity_id: Optional[str] = None, + user_identity_key: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def grant_access_to_device(self, *, user_identity_id: str, device_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list(self, *, credential_manager_acs_system_id: Optional[str] = None) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list_accessible_devices(self, *, user_identity_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list_acs_systems(self, *, user_identity_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def list_acs_users(self, *, user_identity_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def remove_acs_user(self, *, user_identity_id: str, acs_user_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def revoke_access_to_device(self, *, user_identity_id: str, device_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + user_identity_id: str, + user_identity_key: Optional[str] = None, + email_address: Optional[str] = None, + phone_number: Optional[str] = None, + full_name: Optional[str] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractAccessCodes(abc.ABC): + @property + @abc.abstractmethod + def simulate(self) -> AbstractAccessCodesSimulate: + raise NotImplementedError() + + @property + @abc.abstractmethod + def unmanaged(self) -> AbstractAccessCodesUnmanaged: + raise NotImplementedError() + + @abc.abstractmethod + def create( + self, + *, + device_id: str, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + code: Optional[str] = None, + sync: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + common_code_key: Optional[str] = None, + prefer_native_scheduling: Optional[bool] = None, + use_backup_access_code_pool: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None, + ) -> AccessCode: + raise NotImplementedError() + + @abc.abstractmethod + def create_multiple( + self, + *, + device_ids: List[str], + behavior_when_code_cannot_be_shared: Optional[str] = None, + preferred_code_length: Optional[float] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + code: Optional[str] = None, + attempt_for_offline_device: Optional[bool] = None, + prefer_native_scheduling: Optional[bool] = None, + use_backup_access_code_pool: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None, + ) -> List[AccessCode]: + raise NotImplementedError() + + @abc.abstractmethod + def delete( + self, + *, + access_code_id: str, + device_id: Optional[str] = None, + sync: Optional[bool] = None, + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def generate_code(self, *, device_id: str) -> AccessCode: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, + *, + device_id: Optional[str] = None, + access_code_id: Optional[str] = None, + code: Optional[str] = None, + ) -> AccessCode: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + device_id: Optional[str] = None, + access_code_ids: Optional[List[str]] = None, + user_identifier_key: Optional[str] = None, + ) -> List[AccessCode]: + raise NotImplementedError() + + @abc.abstractmethod + def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + access_code_id: str, + name: Optional[str] = None, + starts_at: Optional[str] = None, + ends_at: Optional[str] = None, + code: Optional[str] = None, + sync: Optional[bool] = None, + attempt_for_offline_device: Optional[bool] = None, + prefer_native_scheduling: Optional[bool] = None, + use_backup_access_code_pool: Optional[bool] = None, + allow_external_modification: Optional[bool] = None, + is_external_modification_allowed: Optional[bool] = None, + use_offline_access_code: Optional[bool] = None, + is_offline_access_code: Optional[bool] = None, + is_one_time_use: Optional[bool] = None, + max_time_rounding: Optional[str] = None, + device_id: Optional[str] = None, + type: Optional[str] = None, + is_managed: Optional[bool] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractDevices(abc.ABC): + @property + @abc.abstractmethod + def simulate(self) -> AbstractDevicesSimulate: + raise NotImplementedError() + + @property + @abc.abstractmethod + def unmanaged(self) -> AbstractDevicesUnmanaged: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, device_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def get( + self, *, device_id: Optional[str] = None, name: Optional[str] = None + ) -> Device: + raise NotImplementedError() + + @abc.abstractmethod + def list( + self, + *, + connected_account_id: Optional[str] = None, + connected_account_ids: Optional[List[str]] = None, + connect_webview_id: Optional[str] = None, + device_type: Optional[str] = None, + device_types: Optional[List[str]] = None, + manufacturer: Optional[str] = None, + device_ids: Optional[List[str]] = None, + limit: Optional[float] = None, + created_before: Optional[str] = None, + user_identifier_key: Optional[str] = None, + custom_metadata_has: Optional[Dict[str, Any]] = None, + include_if: Optional[List[str]] = None, + exclude_if: Optional[List[str]] = None, + ) -> List[Device]: + raise NotImplementedError() + + @abc.abstractmethod + def list_device_providers( + self, *, provider_category: Optional[str] = None + ) -> List[DeviceProvider]: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + device_id: str, + properties: Optional[Dict[str, Any]] = None, + name: Optional[str] = None, + is_managed: Optional[bool] = None, + custom_metadata: Optional[Dict[str, Any]] = None, + ) -> None: + raise NotImplementedError() + + +class AbstractNoiseSensors(abc.ABC): + pass + + @property + @abc.abstractmethod + def noise_thresholds(self) -> AbstractNoiseSensorsNoiseThresholds: + raise NotImplementedError() + + @property + @abc.abstractmethod + def simulate(self) -> AbstractNoiseSensorsSimulate: + raise NotImplementedError() + + +class AbstractAcs(abc.ABC): + pass + + @property + @abc.abstractmethod + def access_groups(self) -> AbstractAcsAccessGroups: + raise NotImplementedError() + + @property + @abc.abstractmethod + def credential_pools(self) -> AbstractAcsCredentialPools: + raise NotImplementedError() + + @property + @abc.abstractmethod + def credential_provisioning_automations( + self, + ) -> AbstractAcsCredentialProvisioningAutomations: + raise NotImplementedError() + + @property + @abc.abstractmethod + def credentials(self) -> AbstractAcsCredentials: + raise NotImplementedError() + + @property + @abc.abstractmethod + def entrances(self) -> AbstractAcsEntrances: + raise NotImplementedError() + + @property + @abc.abstractmethod + def systems(self) -> AbstractAcsSystems: + raise NotImplementedError() + + @property + @abc.abstractmethod + def users(self) -> AbstractAcsUsers: + raise NotImplementedError() + + +@dataclass +class AbstractRoutes(abc.ABC): + access_codes: AbstractAccessCodes + action_attempts: AbstractActionAttempts + client_sessions: AbstractClientSessions + connect_webviews: AbstractConnectWebviews + connected_accounts: AbstractConnectedAccounts + devices: AbstractDevices + events: AbstractEvents + health: AbstractHealth + locks: AbstractLocks + networks: AbstractNetworks + phones: AbstractPhones + thermostats: AbstractThermostats + user_identities: AbstractUserIdentities + webhooks: AbstractWebhooks + workspaces: AbstractWorkspaces + acs: AbstractAcs + noise_sensors: AbstractNoiseSensors + + @abc.abstractmethod + def make_request(self, method: str, path: str, **kwargs) -> Any: + raise NotImplementedError + + +@dataclass +class AbstractSeam(AbstractRoutes): + api_key: str + workspace_id: str + api_url: str + lts_version: str + + @abc.abstractmethod + def __init__( + self, + api_key: Optional[str] = None, + workspace_id: Optional[str] = None, + api_url: Optional[str] = None, + ): + raise NotImplementedError diff --git a/seam/user_identities.py b/seam/user_identities.py new file mode 100644 index 00000000..2c1fe4fd --- /dev/null +++ b/seam/user_identities.py @@ -0,0 +1,197 @@ +from seam.types import AbstractUserIdentities, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union +from seam.user_identities_enrollment_automations import ( + UserIdentitiesEnrollmentAutomations, +) + + +class UserIdentities(AbstractUserIdentities): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + self._enrollment_automations = UserIdentitiesEnrollmentAutomations(seam=seam) + + @property + def enrollment_automations(self) -> UserIdentitiesEnrollmentAutomations: + return self._enrollment_automations + + def add_acs_user(self, *, user_identity_id: str, acs_user_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request( + "POST", "/user_identities/add_acs_user", json=json_payload + ) + + return None + + def create( + self, + *, + user_identity_key: Optional[str] = None, + email_address: Optional[str] = None, + phone_number: Optional[str] = None, + full_name: Optional[str] = None + ) -> None: + json_payload = {} + + if user_identity_key is not None: + json_payload["user_identity_key"] = user_identity_key + if email_address is not None: + json_payload["email_address"] = email_address + if phone_number is not None: + json_payload["phone_number"] = phone_number + if full_name is not None: + json_payload["full_name"] = full_name + + self.seam.make_request("POST", "/user_identities/create", json=json_payload) + + return None + + def delete(self, *, user_identity_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + + self.seam.make_request("POST", "/user_identities/delete", json=json_payload) + + return None + + def get( + self, + *, + user_identity_id: Optional[str] = None, + user_identity_key: Optional[str] = None + ) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if user_identity_key is not None: + json_payload["user_identity_key"] = user_identity_key + + self.seam.make_request("POST", "/user_identities/get", json=json_payload) + + return None + + def grant_access_to_device(self, *, user_identity_id: str, device_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if device_id is not None: + json_payload["device_id"] = device_id + + self.seam.make_request( + "POST", "/user_identities/grant_access_to_device", json=json_payload + ) + + return None + + def list(self, *, credential_manager_acs_system_id: Optional[str] = None) -> None: + json_payload = {} + + if credential_manager_acs_system_id is not None: + json_payload[ + "credential_manager_acs_system_id" + ] = credential_manager_acs_system_id + + self.seam.make_request("POST", "/user_identities/list", json=json_payload) + + return None + + def list_accessible_devices(self, *, user_identity_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + + self.seam.make_request( + "POST", "/user_identities/list_accessible_devices", json=json_payload + ) + + return None + + def list_acs_systems(self, *, user_identity_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + + self.seam.make_request( + "POST", "/user_identities/list_acs_systems", json=json_payload + ) + + return None + + def list_acs_users(self, *, user_identity_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + + self.seam.make_request( + "POST", "/user_identities/list_acs_users", json=json_payload + ) + + return None + + def remove_acs_user(self, *, user_identity_id: str, acs_user_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if acs_user_id is not None: + json_payload["acs_user_id"] = acs_user_id + + self.seam.make_request( + "POST", "/user_identities/remove_acs_user", json=json_payload + ) + + return None + + def revoke_access_to_device(self, *, user_identity_id: str, device_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if device_id is not None: + json_payload["device_id"] = device_id + + self.seam.make_request( + "POST", "/user_identities/revoke_access_to_device", json=json_payload + ) + + return None + + def update( + self, + *, + user_identity_id: str, + user_identity_key: Optional[str] = None, + email_address: Optional[str] = None, + phone_number: Optional[str] = None, + full_name: Optional[str] = None + ) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if user_identity_key is not None: + json_payload["user_identity_key"] = user_identity_key + if email_address is not None: + json_payload["email_address"] = email_address + if phone_number is not None: + json_payload["phone_number"] = phone_number + if full_name is not None: + json_payload["full_name"] = full_name + + self.seam.make_request("POST", "/user_identities/update", json=json_payload) + + return None diff --git a/seam/user_identities_enrollment_automations.py b/seam/user_identities_enrollment_automations.py new file mode 100644 index 00000000..77100bfd --- /dev/null +++ b/seam/user_identities_enrollment_automations.py @@ -0,0 +1,79 @@ +from seam.types import AbstractUserIdentitiesEnrollmentAutomations, AbstractSeam as Seam +from typing import Optional, Any, List, Dict, Union + + +class UserIdentitiesEnrollmentAutomations(AbstractUserIdentitiesEnrollmentAutomations): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def delete(self, *, enrollment_automation_id: str) -> None: + json_payload = {} + + if enrollment_automation_id is not None: + json_payload["enrollment_automation_id"] = enrollment_automation_id + + self.seam.make_request( + "POST", "/user_identities/enrollment_automations/delete", json=json_payload + ) + + return None + + def get(self, *, enrollment_automation_id: str) -> None: + json_payload = {} + + if enrollment_automation_id is not None: + json_payload["enrollment_automation_id"] = enrollment_automation_id + + self.seam.make_request( + "POST", "/user_identities/enrollment_automations/get", json=json_payload + ) + + return None + + def launch( + self, + *, + user_identity_id: str, + credential_manager_acs_system_id: str, + acs_credential_pool_id: Optional[str] = None, + create_credential_manager_user: Optional[bool] = None, + credential_manager_acs_user_id: Optional[str] = None + ) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + if credential_manager_acs_system_id is not None: + json_payload[ + "credential_manager_acs_system_id" + ] = credential_manager_acs_system_id + if acs_credential_pool_id is not None: + json_payload["acs_credential_pool_id"] = acs_credential_pool_id + if create_credential_manager_user is not None: + json_payload[ + "create_credential_manager_user" + ] = create_credential_manager_user + if credential_manager_acs_user_id is not None: + json_payload[ + "credential_manager_acs_user_id" + ] = credential_manager_acs_user_id + + self.seam.make_request( + "POST", "/user_identities/enrollment_automations/launch", json=json_payload + ) + + return None + + def list(self, *, user_identity_id: str) -> None: + json_payload = {} + + if user_identity_id is not None: + json_payload["user_identity_id"] = user_identity_id + + self.seam.make_request( + "POST", "/user_identities/enrollment_automations/list", json=json_payload + ) + + return None diff --git a/seam/utils/deep_attr_dict.py b/seam/utils/deep_attr_dict.py new file mode 100644 index 00000000..341f0a99 --- /dev/null +++ b/seam/utils/deep_attr_dict.py @@ -0,0 +1,26 @@ +# https://stackoverflow.com/a/3031270/559475 +class DeepAttrDict(dict): + MARKER = object() + + def __init__(self, value=None): + if value is None: + pass + elif isinstance(value, dict): + for key in value: + self.__setitem__(key, value[key]) + else: + raise TypeError("expected dict") + + def __setitem__(self, key, value): + if isinstance(value, dict) and not isinstance(value, DeepAttrDict): + value = DeepAttrDict(value) + super(DeepAttrDict, self).__setitem__(key, value) + + def __getitem__(self, key): + found = self.get(key, DeepAttrDict.MARKER) + if found is DeepAttrDict.MARKER: + found = DeepAttrDict() + super(DeepAttrDict, self).__setitem__(key, found) + return found + + __setattr__, __getattr__ = __setitem__, __getitem__ diff --git a/seam/webhooks.py b/seam/webhooks.py new file mode 100644 index 00000000..69f4b7dd --- /dev/null +++ b/seam/webhooks.py @@ -0,0 +1,62 @@ +from seam.types import AbstractWebhooks, AbstractSeam as Seam, Webhook +from typing import Optional, Any, List, Dict, Union + + +class Webhooks(AbstractWebhooks): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhook: + json_payload = {} + + if url is not None: + json_payload["url"] = url + if event_types is not None: + json_payload["event_types"] = event_types + + res = self.seam.make_request("POST", "/webhooks/create", json=json_payload) + + return Webhook.from_dict(res["webhook"]) + + def delete(self, *, webhook_id: str) -> None: + json_payload = {} + + if webhook_id is not None: + json_payload["webhook_id"] = webhook_id + + self.seam.make_request("POST", "/webhooks/delete", json=json_payload) + + return None + + def get(self, *, webhook_id: str) -> Webhook: + json_payload = {} + + if webhook_id is not None: + json_payload["webhook_id"] = webhook_id + + res = self.seam.make_request("POST", "/webhooks/get", json=json_payload) + + return Webhook.from_dict(res["webhook"]) + + def list( + self, + ) -> List[Webhook]: + json_payload = {} + + res = self.seam.make_request("POST", "/webhooks/list", json=json_payload) + + return [Webhook.from_dict(item) for item in res["webhooks"]] + + def update(self, *, webhook_id: str, event_types: List[str]) -> None: + json_payload = {} + + if webhook_id is not None: + json_payload["webhook_id"] = webhook_id + if event_types is not None: + json_payload["event_types"] = event_types + + self.seam.make_request("POST", "/webhooks/update", json=json_payload) + + return None diff --git a/seam/workspaces.py b/seam/workspaces.py new file mode 100644 index 00000000..c2087d54 --- /dev/null +++ b/seam/workspaces.py @@ -0,0 +1,62 @@ +from seam.types import AbstractWorkspaces, AbstractSeam as Seam, Workspace +from typing import Optional, Any, List, Dict, Union + + +class Workspaces(AbstractWorkspaces): + seam: Seam + + def __init__(self, seam: Seam): + self.seam = seam + + def create( + self, + *, + name: str, + connect_partner_name: str, + is_sandbox: Optional[bool] = None, + webview_primary_button_color: Optional[str] = None, + webview_logo_shape: Optional[str] = None + ) -> None: + json_payload = {} + + if name is not None: + json_payload["name"] = name + if connect_partner_name is not None: + json_payload["connect_partner_name"] = connect_partner_name + if is_sandbox is not None: + json_payload["is_sandbox"] = is_sandbox + if webview_primary_button_color is not None: + json_payload["webview_primary_button_color"] = webview_primary_button_color + if webview_logo_shape is not None: + json_payload["webview_logo_shape"] = webview_logo_shape + + self.seam.make_request("POST", "/workspaces/create", json=json_payload) + + return None + + def get( + self, + ) -> Workspace: + json_payload = {} + + res = self.seam.make_request("POST", "/workspaces/get", json=json_payload) + + return Workspace.from_dict(res["workspace"]) + + def list( + self, + ) -> List[Workspace]: + json_payload = {} + + res = self.seam.make_request("POST", "/workspaces/list", json=json_payload) + + return [Workspace.from_dict(item) for item in res["workspaces"]] + + def reset_sandbox( + self, + ) -> None: + json_payload = {} + + self.seam.make_request("POST", "/workspaces/reset_sandbox", json=json_payload) + + return None From 635da0d1abc5fecc29bbe90a13cb90bf1e8b2562 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 15:53:38 +0200 Subject: [PATCH 04/27] Transfer tests --- test/access_codes/test_access_codes.py | 64 ++++++++++ test/action_attempts/test_action_attempts.py | 46 +++++++ test/conftest.py | 14 +++ .../connect_webviews/test_connect_webviews.py | 23 ++++ .../test_connected_accounts.py | 31 +++++ test/devices/test_devices.py | 115 ++++++++++++++++++ test/events/test_events.py | 28 +++++ .../noise_thresholds/test_noise_thresholds.py | 50 ++++++++ test/test_init_seam.py | 8 ++ .../test_climate_setting_schedules.py | 66 ++++++++++ test/thermostats/test_thermostats.py | 85 +++++++++++++ test/todo_test.py | 10 -- test/utils/test_deep_attr_dict.py | 7 ++ test/webhooks/test_webhooks.py | 18 +++ test/workspaces/test_workspaces.py | 12 ++ test/workspaces/test_workspaces_create.py | 20 +++ 16 files changed, 587 insertions(+), 10 deletions(-) create mode 100644 test/access_codes/test_access_codes.py create mode 100644 test/action_attempts/test_action_attempts.py create mode 100755 test/conftest.py create mode 100644 test/connect_webviews/test_connect_webviews.py create mode 100644 test/connected_accounts/test_connected_accounts.py create mode 100644 test/devices/test_devices.py create mode 100644 test/events/test_events.py create mode 100644 test/noise_sensors/noise_thresholds/test_noise_thresholds.py create mode 100644 test/test_init_seam.py create mode 100644 test/thermostats/test_climate_setting_schedules.py create mode 100644 test/thermostats/test_thermostats.py delete mode 100644 test/todo_test.py create mode 100644 test/utils/test_deep_attr_dict.py create mode 100644 test/webhooks/test_webhooks.py create mode 100644 test/workspaces/test_workspaces.py create mode 100644 test/workspaces/test_workspaces_create.py diff --git a/test/access_codes/test_access_codes.py b/test/access_codes/test_access_codes.py new file mode 100644 index 00000000..a325a4cb --- /dev/null +++ b/test/access_codes/test_access_codes.py @@ -0,0 +1,64 @@ +from seam import Seam +from seam.types import SeamApiException +import pytest + + +def test_access_codes(seam: Seam): + + all_devices = seam.devices.list() + some_device = all_devices[0] + + created_access_code = seam.access_codes.create( + device_id=some_device.device_id, name="Test code", code="4444" + ) + assert created_access_code.name == "Test code" + assert created_access_code.status == "setting" + + seam.access_codes.create( + device_id=some_device.device_id, name="Test code 2", code="5555" + ) + + access_codes = seam.access_codes.list(device_id=some_device.device_id) + assert len(access_codes) == 2 + access_codes = seam.access_codes.list( + device_id=some_device.device_id, + access_code_ids=[created_access_code.access_code_id], + ) + assert len(access_codes) == 1 + + access_code = seam.access_codes.get( + access_code_id=created_access_code.access_code_id + ) + assert access_code.code == "4444" + + with pytest.raises(SeamApiException): + seam.access_codes.create( + device_id=some_device.device_id, name="Duplicate Access Code", code="4444" + ) + + delete_action_attempt = seam.access_codes.delete( + access_code_id=created_access_code.access_code_id + ) + assert delete_action_attempt == None + + access_codes = seam.access_codes.create_multiple( + device_ids=[device.device_id for device in all_devices] + ) + assert len(set([ac.common_code_key for ac in access_codes])) == 1 + + # Preferred Code Length Tests + device_ids = [device.device_id for device in all_devices] + + access_codes_of_preferred_length = seam.access_codes.create_multiple( + device_ids=device_ids, preferred_code_length=4 + ) + + for access_codes in access_codes_of_preferred_length: + assert len(access_codes.code) == 4 + + access_codes_of_longer_length = seam.access_codes.create_multiple( + device_ids=device_ids, preferred_code_length=6 + ) + + for access_codes in access_codes_of_longer_length: + assert len(access_codes.code) == 6 diff --git a/test/action_attempts/test_action_attempts.py b/test/action_attempts/test_action_attempts.py new file mode 100644 index 00000000..de87f2a7 --- /dev/null +++ b/test/action_attempts/test_action_attempts.py @@ -0,0 +1,46 @@ +from seam import Seam + + +def test_action_attempts(seam: Seam): + + # Create an ActionAttempt + some_device = seam.devices.list()[0] + unlock_door = seam.locks.unlock_door(device_id=some_device.device_id) + + # Retrieve the ActionAttempt + retrieved_action_attempt = seam.action_attempts.get( + action_attempt_id=unlock_door.action_attempt_id + ) + + # Check that the retrieved ActionAttempt has the expected properties + assert retrieved_action_attempt.action_attempt_id == unlock_door.action_attempt_id + + # Create multiple ActionAttempts + some_device = seam.devices.list()[0] + unlock_door1 = seam.locks.unlock_door(device_id=some_device.device_id) + unlock_door2 = seam.locks.unlock_door(device_id=some_device.device_id) + + # Retrieve the list of ActionAttempts + action_attempts = seam.action_attempts.list( + action_attempt_ids=[ + unlock_door1.action_attempt_id, + unlock_door2.action_attempt_id, + ] + ) + + # Check that the retrieved ActionAttempts have the expected properties + assert len(action_attempts) == 2 + assert action_attempts[0].action_attempt_id == unlock_door1.action_attempt_id + assert action_attempts[1].action_attempt_id == unlock_door2.action_attempt_id + + # Create an ActionAttempt + some_device = seam.devices.list()[0] + unlock_door = seam.locks.unlock_door(device_id=some_device.device_id) + + # Poll until the ActionAttempt is ready + action_attempt = seam.action_attempts.poll_until_ready( + action_attempt_id=unlock_door.action_attempt_id + ) + + # Check that the ActionAttempt is not pending + assert action_attempt.status != "pending" diff --git a/test/conftest.py b/test/conftest.py new file mode 100755 index 00000000..92f2212a --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,14 @@ +import pytest +from seam import Seam +from typing import Any +import random +import string + + +@pytest.fixture(scope="function") +def seam(): + r = "".join(random.choices(string.ascii_uppercase + string.digits, k=10)) + seam = Seam( + api_url=f"https://{r}.fakeseamconnect.seam.vc", api_key="seam_apikey1_token" + ) + yield seam diff --git a/test/connect_webviews/test_connect_webviews.py b/test/connect_webviews/test_connect_webviews.py new file mode 100644 index 00000000..da425e65 --- /dev/null +++ b/test/connect_webviews/test_connect_webviews.py @@ -0,0 +1,23 @@ +from seam import Seam + + +def test_connect_webviews(seam: Seam): + created_webview = seam.connect_webviews.create(accepted_providers=["schlage"]) + assert created_webview.url is not None + + webview = seam.connect_webviews.get( + connect_webview_id=created_webview.connect_webview_id + ) + assert webview.connect_webview_id == created_webview.connect_webview_id + + webviews = seam.connect_webviews.list() + assert len(webviews) > 0 + + # Test with provider_category + new_webview = seam.connect_webviews.create(provider_category="stable") + assert created_webview.url is not None + + webview = seam.connect_webviews.get( + connect_webview_id=new_webview.connect_webview_id + ) + assert len(webview.accepted_providers) > 0 diff --git a/test/connected_accounts/test_connected_accounts.py b/test/connected_accounts/test_connected_accounts.py new file mode 100644 index 00000000..d45e5f17 --- /dev/null +++ b/test/connected_accounts/test_connected_accounts.py @@ -0,0 +1,31 @@ +from seam import Seam +from seam.types import SeamApiException + +EMAIL = "john@example.com" + + +def test_connected_accounts(seam: Seam): + + connected_accounts = seam.connected_accounts.list() + assert len(connected_accounts) > 0 + + connected_account_id = connected_accounts[0].connected_account_id + connected_account = seam.connected_accounts.get( + connected_account_id=connected_account_id + ) + email_account = seam.connected_accounts.get(email=EMAIL) + + assert connected_account.connected_account_id == connected_account_id + assert email_account.connected_account_id == connected_account_id + + deleted_account = seam.connected_accounts.delete( + connected_account_id=connected_account_id + ) + assert deleted_account == None + + # Assert that an Exception is raised for the .get() method when + # connected_account and email parameters are not provided. + try: + seam.connected_accounts.get() + except SeamApiException as e: + assert e.metadata["message"] == "Invalid input" diff --git a/test/devices/test_devices.py b/test/devices/test_devices.py new file mode 100644 index 00000000..f78a7754 --- /dev/null +++ b/test/devices/test_devices.py @@ -0,0 +1,115 @@ +from seam import Seam +from seam.types import SeamApiException +from seam.utils.deep_attr_dict import DeepAttrDict + + +def test_devices(seam: Seam): + devices = seam.devices.list() + assert len(devices) > 0 + + connected_account = seam.connected_accounts.list()[0] + connected_accounts = seam.connected_accounts.list() + devices = seam.devices.list( + connected_account_id=connected_account.connected_account_id + ) + assert len(devices) > 0 + connected_account_ids = [ + account.connected_account_id for account in connected_accounts + ] + devices = seam.devices.list(connected_account_ids=connected_account_ids) + assert len(devices) > 0 + + devices = seam.devices.list(device_types="august_lock") + assert len(devices) > 0 + devices = seam.devices.list(device_types=["august_lock"]) + assert len(devices) > 0 + + devices = seam.devices.list(manufacturer="august") + assert len(devices) > 0 + + device_ids = [devices[0].device_id] + devices = seam.devices.list(device_ids=device_ids) + assert len(devices) == 1 + + locks = seam.locks.list() + assert len(locks) > 0 + + some_device = seam.devices.get(name="Fake August Lock 1") + assert some_device.properties.name == "Fake August Lock 1" + + some_lock = seam.locks.get(device_id=(some_device.device_id)) + assert some_lock.device_id == some_device.device_id + + assert some_lock.properties.locked == True + + seam.locks.unlock_door(device_id=(some_device.device_id)) + some_unlocked_lock = seam.locks.get(device_id=(some_device.device_id)) + assert some_unlocked_lock.properties.locked == False + + seam.locks.lock_door(device_id=(some_device.device_id)) + some_locked_lock = seam.locks.get(device_id=(some_device.device_id)) + assert some_locked_lock.properties.locked == True + + seam.devices.update(device_id=(some_device.device_id), name="Updated lock") + some_updated_lock = seam.locks.get(device_id=(some_device.device_id)) + assert some_updated_lock.properties.name == "Updated lock" + + devices = seam.devices.list() + seam.devices.delete(device_id=(some_updated_lock.device_id)) + assert len(seam.devices.list()) == len(devices) - 1 + + # Test custom exception + try: + seam.devices.get(name="foo") + assert False + except SeamApiException as error: + assert error.status_code == 404 + assert type(error.request_id) == str + assert error.metadata["type"] == "device_not_found" + + stable_device_providers = seam.devices.list_device_providers( + provider_category="stable" + ) + assert len(stable_device_providers) > 0 + + +def test_unmanaged_devices(seam: Seam): + + devices = seam.devices.list() + assert len(devices) > 0 + + unmanaged_devices = seam.devices.unmanaged.list() + assert len(unmanaged_devices) == 0 + + device = devices[0] + + seam.devices.update(device_id=device.device_id, is_managed=False) + unmanaged_devices = seam.devices.unmanaged.list() + assert len(unmanaged_devices) == 1 + + unmanaged_device = seam.devices.unmanaged.get(device_id=device.device_id) + assert unmanaged_device.device_id == device.device_id + unmanaged_device = seam.devices.unmanaged.get(name=device.properties.name) + assert unmanaged_device.properties.name == device.properties.name + + connected_account = seam.connected_accounts.list()[0] + devices = seam.devices.unmanaged.list( + connected_account_id=connected_account.connected_account_id + ) + assert len(devices) > 0 + devices = seam.devices.unmanaged.list( + connected_account_ids=[connected_account.connected_account_id] + ) + assert len(devices) > 0 + + devices = seam.devices.unmanaged.list(device_type="august_lock") + assert len(devices) > 0 + devices = seam.devices.unmanaged.list(device_types=["august_lock"]) + assert len(devices) > 0 + + devices = seam.devices.unmanaged.list(manufacturer="august") + assert len(devices) > 0 + + seam.devices.unmanaged.update(device_id=device.device_id, is_managed=True) + unmanaged_devices = seam.devices.unmanaged.list() + assert len(unmanaged_devices) == 0 diff --git a/test/events/test_events.py b/test/events/test_events.py new file mode 100644 index 00000000..02f1cf71 --- /dev/null +++ b/test/events/test_events.py @@ -0,0 +1,28 @@ +import time +from seam import Seam + +from seam.types import SeamApiException + +SINCE = "2021-01-01T00:00:00.000Z" +EVENT_TYPE = "device.connected" +FAKE_UUID = "00000000-0000-0000-0000-000000000000" + + +def test_events(seam: Seam): + + events = seam.events.list(since=SINCE) + event = events[0] + + event_by_id = seam.events.get(event_id=event.event_id) + assert event_by_id.event_id == event.event_id + + event_by_type = seam.events.get(event_type=event.event_type) + assert event_by_type.event_type == event.event_type + + event_by_device_id = seam.events.get(device_id=event.device_id) + assert event_by_device_id.device_id == event.device_id + + try: + seam.events.get(event_id=FAKE_UUID) + except SeamApiException as e: + assert e.metadata["message"] == "Event not found" diff --git a/test/noise_sensors/noise_thresholds/test_noise_thresholds.py b/test/noise_sensors/noise_thresholds/test_noise_thresholds.py new file mode 100644 index 00000000..a869b56b --- /dev/null +++ b/test/noise_sensors/noise_thresholds/test_noise_thresholds.py @@ -0,0 +1,50 @@ +import time +from seam import Seam +from seam.types import SeamApiException +import pytest + + +def test_noise_thresholds(seam: Seam): + + # Get "minut_device_1" because it's seeded with a noise threshold + device = seam.devices.get(device_id="minut_device_1") + + def get_minut_device_noise_thresholds(): + return seam.noise_sensors.noise_thresholds.list(device_id=device.device_id) + + noise_thresholds = get_minut_device_noise_thresholds() + + assert noise_thresholds != None + assert noise_thresholds[0].name == "builtin_normal_hours" + + normal_hours_threshold = next( + (nt for nt in noise_thresholds if nt.name == "builtin_normal_hours"), + None, + ) + + deleted_noise_threshold = seam.noise_sensors.noise_thresholds.delete( + device_id=device.device_id, + noise_threshold_id=normal_hours_threshold.noise_threshold_id, + ) + assert deleted_noise_threshold not in noise_thresholds + + noise_threshold = seam.noise_sensors.noise_thresholds.create( + device_id=device.device_id, + starts_daily_at="20:00:00[America/Los_Angeles]", + ends_daily_at="08:00:00[America/Los_Angeles]", + noise_threshold_decibels=75, + ) + noise_thresholds = get_minut_device_noise_thresholds() + assert len(noise_thresholds) == 1 + + seam.noise_sensors.noise_thresholds.update( + device_id=device.device_id, + noise_threshold_id=noise_threshold.noise_threshold_id, + noise_threshold_decibels=80, + ) + + updated_noise_threshold = seam.noise_sensors.noise_thresholds.get( + noise_threshold_id=noise_threshold.noise_threshold_id, + ) + + assert updated_noise_threshold.noise_threshold_decibels == 80 diff --git a/test/test_init_seam.py b/test/test_init_seam.py new file mode 100644 index 00000000..d3c07ed1 --- /dev/null +++ b/test/test_init_seam.py @@ -0,0 +1,8 @@ +from seam import Seam + + +def test_init_seam_with_fixture(seam: Seam): + assert seam.api_key + assert seam.api_url + assert seam.lts_version + assert "http" in seam.api_url diff --git a/test/thermostats/test_climate_setting_schedules.py b/test/thermostats/test_climate_setting_schedules.py new file mode 100644 index 00000000..877c7e56 --- /dev/null +++ b/test/thermostats/test_climate_setting_schedules.py @@ -0,0 +1,66 @@ +from seam import Seam +import datetime + + +def add_month_to_date(date: datetime.date, months: int) -> datetime.date: + return datetime.datetime( + date.year + int(date.month / 12), ((date.month % 12) + months), 1 + ) + + +def test_climate_setting_schedules(seam: Seam): + + thermostat = seam.thermostats.list()[0] + + base_date = datetime.date.today() + + schedule_starts_at = add_month_to_date(base_date, months=1).isoformat() + schedule_ends_at = add_month_to_date(base_date, months=2).isoformat() + + # Test Create + climate_setting_schedule = seam.thermostats.climate_setting_schedules.create( + device_id=thermostat.device_id, + name="Vacation Setting", + schedule_starts_at=schedule_starts_at, + schedule_ends_at=schedule_ends_at, + schedule_type="time_bound", + automatic_heating_enabled=True, + automatic_cooling_enabled=True, + heating_set_point_fahrenheit=40, + cooling_set_point_fahrenheit=80, + manual_override_allowed=True, + ) + + assert climate_setting_schedule.name == "Vacation Setting" + + # Test List + climate_setting_schedules = seam.thermostats.climate_setting_schedules.list( + device_id=thermostat.device_id + ) + assert len(climate_setting_schedules) == 1 + + # Test Get + climate_setting_schedule = seam.thermostats.climate_setting_schedules.get( + climate_setting_schedule_id=climate_setting_schedule.climate_setting_schedule_id, + ) + + assert climate_setting_schedule.name == "Vacation Setting" + + # Test Update + seam.thermostats.climate_setting_schedules.update( + climate_setting_schedule_id=climate_setting_schedule.climate_setting_schedule_id, + name="Vacation Setting 2", + ) + + updated_climate_setting_schedule = seam.thermostats.climate_setting_schedules.get( + climate_setting_schedule_id=climate_setting_schedule.climate_setting_schedule_id, + ) + + assert updated_climate_setting_schedule.name == "Vacation Setting 2" + + # Test Delete + deleted_climate_setting_schedule = seam.thermostats.climate_setting_schedules.delete( + climate_setting_schedule_id=climate_setting_schedule.climate_setting_schedule_id, + ) + + assert deleted_climate_setting_schedule == None diff --git a/test/thermostats/test_thermostats.py b/test/thermostats/test_thermostats.py new file mode 100644 index 00000000..3e005a90 --- /dev/null +++ b/test/thermostats/test_thermostats.py @@ -0,0 +1,85 @@ +from seam import Seam +import json + + +def test_thermostats(seam: Seam): + + # Test List + thermostats = seam.thermostats.list() + + thermostat = thermostats[0] + + assert thermostat.device_type == "ecobee_thermostat" + + # Test Get + thermostat = seam.thermostats.get(device_id=thermostat.device_id) + assert thermostat.device_type == "ecobee_thermostat" + + # Test Update + result = seam.thermostats.update( + device_id=thermostat.device_id, + default_climate_setting={ + "hvac_mode_setting": "cool", + "cooling_set_point_celsius": 20, + "manual_override_allowed": True, + }, + ) + assert result == None + + # Test Cool + seam.thermostats.cool( + device_id=thermostat.device_id, + cooling_set_point_celsius=27, + ) + thermostat = seam.thermostats.get(device_id=thermostat.device_id) + assert ( + round(thermostat.properties.current_climate_setting.cooling_set_point_celsius) + == 27 + ) + assert thermostat.properties.current_climate_setting.hvac_mode_setting == "cool" + + # Test Heat + seam.thermostats.heat( + device_id=thermostat.device_id, + heating_set_point_celsius=18, + ) + thermostat = seam.thermostats.get(device_id=thermostat.device_id) + assert ( + round(thermostat.properties.current_climate_setting.heating_set_point_celsius) + == 18 + ) + + # Test Heat Cool + seam.thermostats.heat_cool( + device_id=thermostat.device_id, + cooling_set_point_celsius=28, + heating_set_point_celsius=19, + ) + thermostat = seam.thermostats.get(device_id=thermostat.device_id) + assert ( + thermostat.properties.current_climate_setting.hvac_mode_setting == "heat_cool" + ) + assert ( + round(thermostat.properties.current_climate_setting.cooling_set_point_celsius) + == 28 + ) + assert ( + round(thermostat.properties.current_climate_setting.heating_set_point_celsius) + == 19 + ) + + # Test Off + seam.thermostats.off( + device_id=thermostat.device_id, + ) + thermostat = seam.thermostats.get(device_id=thermostat.device_id) + assert thermostat.properties.current_climate_setting.hvac_mode_setting == "off" + + # Test Set Fan Mode + seam.thermostats.set_fan_mode( + device_id=thermostat.device_id, + fan_mode="on", + fan_mode_setting="auto", + ) + thermostat = seam.thermostats.get(device_id=thermostat.device_id) + assert thermostat.properties.is_fan_running == True diff --git a/test/todo_test.py b/test/todo_test.py deleted file mode 100644 index 45ef86f1..00000000 --- a/test/todo_test.py +++ /dev/null @@ -1,10 +0,0 @@ -# pylint: disable=missing-docstring -# pylint: disable=unused-import - -import pytest - -from seam import todo - - -def test_todo(): - assert todo(True) is True diff --git a/test/utils/test_deep_attr_dict.py b/test/utils/test_deep_attr_dict.py new file mode 100644 index 00000000..afdc331f --- /dev/null +++ b/test/utils/test_deep_attr_dict.py @@ -0,0 +1,7 @@ +from seam.utils.deep_attr_dict import DeepAttrDict + + +def test_deep_attr_dict(): + attrdict = DeepAttrDict({"a": {"b": {"c": 5}}}) + + assert attrdict.a.b.c == 5 diff --git a/test/webhooks/test_webhooks.py b/test/webhooks/test_webhooks.py new file mode 100644 index 00000000..8541ff5e --- /dev/null +++ b/test/webhooks/test_webhooks.py @@ -0,0 +1,18 @@ +from seam import Seam + + +def test_webhooks(seam: Seam): + webhook = seam.webhooks.create( + url="https://example.com", event_types=["connected_account.connected"] + ) + assert webhook.url == "https://example.com" + + webhook = seam.webhooks.get(webhook_id=webhook.webhook_id) + assert webhook is not None + + webhook_list = seam.webhooks.list() + assert len(webhook_list) > 0 + + seam.webhooks.delete(webhook_id=webhook.webhook_id) + webhook_list = seam.webhooks.list() + assert len(webhook_list) == 0 diff --git a/test/workspaces/test_workspaces.py b/test/workspaces/test_workspaces.py new file mode 100644 index 00000000..d1c09701 --- /dev/null +++ b/test/workspaces/test_workspaces.py @@ -0,0 +1,12 @@ +from seam import Seam + + +def test_workspaces(seam: Seam): + ws = seam.workspaces.get() + assert ws.is_sandbox == True + + ws_list = seam.workspaces.list() + assert len(ws_list) > 0 + + reset_sandbox_result = seam.workspaces.reset_sandbox() + assert reset_sandbox_result is None diff --git a/test/workspaces/test_workspaces_create.py b/test/workspaces/test_workspaces_create.py new file mode 100644 index 00000000..9d42b0a4 --- /dev/null +++ b/test/workspaces/test_workspaces_create.py @@ -0,0 +1,20 @@ +import random +import string +from seam import Seam + + +def test_workspaces_create(seam: Seam): + r = "".join(random.choices(string.ascii_uppercase + string.digits, k=10)) + seam = Seam( + api_url=f"https://{r}.fakeseamconnect.seam.vc", + api_key="seam_at1_shorttoken_longtoken", + ) + + workspace = seam.workspaces.create( + name="Test Workspace", + connect_partner_name="Example Partner", + is_sandbox=True, + ) + + # Improve the assertion when `x-fern-sdk-return-value` of `/workspaces/create` is fixed on the API side. + assert workspace is None From 7405b3d90fda03fe74b2e575c2e16115d57ddbf9 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Wed, 3 Apr 2024 13:54:23 +0000 Subject: [PATCH 05/27] ci: Format code --- seam/access_codes.py | 24 +++++++++---------- seam/access_codes_unmanaged.py | 12 +++++----- ...acs_credential_provisioning_automations.py | 18 +++++++------- seam/acs_credentials.py | 18 +++++++------- seam/connect_webviews.py | 6 ++--- seam/connected_accounts.py | 6 ++--- seam/user_identities.py | 6 ++--- .../user_identities_enrollment_automations.py | 18 +++++++------- 8 files changed, 54 insertions(+), 54 deletions(-) diff --git a/seam/access_codes.py b/seam/access_codes.py index dcd3ffc7..2215d5b1 100644 --- a/seam/access_codes.py +++ b/seam/access_codes.py @@ -65,9 +65,9 @@ def create( if allow_external_modification is not None: json_payload["allow_external_modification"] = allow_external_modification if is_external_modification_allowed is not None: - json_payload[ - "is_external_modification_allowed" - ] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if use_offline_access_code is not None: json_payload["use_offline_access_code"] = use_offline_access_code if is_offline_access_code is not None: @@ -106,9 +106,9 @@ def create_multiple( if device_ids is not None: json_payload["device_ids"] = device_ids if behavior_when_code_cannot_be_shared is not None: - json_payload[ - "behavior_when_code_cannot_be_shared" - ] = behavior_when_code_cannot_be_shared + json_payload["behavior_when_code_cannot_be_shared"] = ( + behavior_when_code_cannot_be_shared + ) if preferred_code_length is not None: json_payload["preferred_code_length"] = preferred_code_length if name is not None: @@ -128,9 +128,9 @@ def create_multiple( if allow_external_modification is not None: json_payload["allow_external_modification"] = allow_external_modification if is_external_modification_allowed is not None: - json_payload[ - "is_external_modification_allowed" - ] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if use_offline_access_code is not None: json_payload["use_offline_access_code"] = use_offline_access_code if is_offline_access_code is not None: @@ -275,9 +275,9 @@ def update( if allow_external_modification is not None: json_payload["allow_external_modification"] = allow_external_modification if is_external_modification_allowed is not None: - json_payload[ - "is_external_modification_allowed" - ] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if use_offline_access_code is not None: json_payload["use_offline_access_code"] = use_offline_access_code if is_offline_access_code is not None: diff --git a/seam/access_codes_unmanaged.py b/seam/access_codes_unmanaged.py index 2231203a..64dcd839 100644 --- a/seam/access_codes_unmanaged.py +++ b/seam/access_codes_unmanaged.py @@ -26,9 +26,9 @@ def convert_to_managed( if access_code_id is not None: json_payload["access_code_id"] = access_code_id if is_external_modification_allowed is not None: - json_payload[ - "is_external_modification_allowed" - ] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if allow_external_modification is not None: json_payload["allow_external_modification"] = allow_external_modification if force is not None: @@ -112,9 +112,9 @@ def update( if allow_external_modification is not None: json_payload["allow_external_modification"] = allow_external_modification if is_external_modification_allowed is not None: - json_payload[ - "is_external_modification_allowed" - ] = is_external_modification_allowed + json_payload["is_external_modification_allowed"] = ( + is_external_modification_allowed + ) if force is not None: json_payload["force"] = force diff --git a/seam/acs_credential_provisioning_automations.py b/seam/acs_credential_provisioning_automations.py index 479111e9..cde51596 100644 --- a/seam/acs_credential_provisioning_automations.py +++ b/seam/acs_credential_provisioning_automations.py @@ -27,19 +27,19 @@ def launch( if user_identity_id is not None: json_payload["user_identity_id"] = user_identity_id if credential_manager_acs_system_id is not None: - json_payload[ - "credential_manager_acs_system_id" - ] = credential_manager_acs_system_id + json_payload["credential_manager_acs_system_id"] = ( + credential_manager_acs_system_id + ) if acs_credential_pool_id is not None: json_payload["acs_credential_pool_id"] = acs_credential_pool_id if create_credential_manager_user is not None: - json_payload[ - "create_credential_manager_user" - ] = create_credential_manager_user + json_payload["create_credential_manager_user"] = ( + create_credential_manager_user + ) if credential_manager_acs_user_id is not None: - json_payload[ - "credential_manager_acs_user_id" - ] = credential_manager_acs_user_id + json_payload["credential_manager_acs_user_id"] = ( + credential_manager_acs_user_id + ) self.seam.make_request( "POST", "/acs/credential_provisioning_automations/launch", json=json_payload diff --git a/seam/acs_credentials.py b/seam/acs_credentials.py index 6dc08562..31f5b59c 100644 --- a/seam/acs_credentials.py +++ b/seam/acs_credentials.py @@ -40,15 +40,15 @@ def create( if access_method is not None: json_payload["access_method"] = access_method if credential_manager_acs_system_id is not None: - json_payload[ - "credential_manager_acs_system_id" - ] = credential_manager_acs_system_id + json_payload["credential_manager_acs_system_id"] = ( + credential_manager_acs_system_id + ) if code is not None: json_payload["code"] = code if is_multi_phone_sync_credential is not None: - json_payload[ - "is_multi_phone_sync_credential" - ] = is_multi_phone_sync_credential + json_payload["is_multi_phone_sync_credential"] = ( + is_multi_phone_sync_credential + ) if allowed_acs_entrance_ids is not None: json_payload["allowed_acs_entrance_ids"] = allowed_acs_entrance_ids if visionline_metadata is not None: @@ -99,9 +99,9 @@ def list( if user_identity_id is not None: json_payload["user_identity_id"] = user_identity_id if is_multi_phone_sync_credential is not None: - json_payload[ - "is_multi_phone_sync_credential" - ] = is_multi_phone_sync_credential + json_payload["is_multi_phone_sync_credential"] = ( + is_multi_phone_sync_credential + ) self.seam.make_request("POST", "/acs/credentials/list", json=json_payload) diff --git a/seam/connect_webviews.py b/seam/connect_webviews.py index 8c67f998..7f1d6f08 100644 --- a/seam/connect_webviews.py +++ b/seam/connect_webviews.py @@ -35,9 +35,9 @@ def create( if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata if automatically_manage_new_devices is not None: - json_payload[ - "automatically_manage_new_devices" - ] = automatically_manage_new_devices + json_payload["automatically_manage_new_devices"] = ( + automatically_manage_new_devices + ) if wait_for_device_creation is not None: json_payload["wait_for_device_creation"] = wait_for_device_creation diff --git a/seam/connected_accounts.py b/seam/connected_accounts.py index 78b1d11a..0c278106 100644 --- a/seam/connected_accounts.py +++ b/seam/connected_accounts.py @@ -62,9 +62,9 @@ def update( if connected_account_id is not None: json_payload["connected_account_id"] = connected_account_id if automatically_manage_new_devices is not None: - json_payload[ - "automatically_manage_new_devices" - ] = automatically_manage_new_devices + json_payload["automatically_manage_new_devices"] = ( + automatically_manage_new_devices + ) if custom_metadata is not None: json_payload["custom_metadata"] = custom_metadata diff --git a/seam/user_identities.py b/seam/user_identities.py index 2c1fe4fd..574b0a77 100644 --- a/seam/user_identities.py +++ b/seam/user_identities.py @@ -98,9 +98,9 @@ def list(self, *, credential_manager_acs_system_id: Optional[str] = None) -> Non json_payload = {} if credential_manager_acs_system_id is not None: - json_payload[ - "credential_manager_acs_system_id" - ] = credential_manager_acs_system_id + json_payload["credential_manager_acs_system_id"] = ( + credential_manager_acs_system_id + ) self.seam.make_request("POST", "/user_identities/list", json=json_payload) diff --git a/seam/user_identities_enrollment_automations.py b/seam/user_identities_enrollment_automations.py index 77100bfd..41c0c40b 100644 --- a/seam/user_identities_enrollment_automations.py +++ b/seam/user_identities_enrollment_automations.py @@ -46,19 +46,19 @@ def launch( if user_identity_id is not None: json_payload["user_identity_id"] = user_identity_id if credential_manager_acs_system_id is not None: - json_payload[ - "credential_manager_acs_system_id" - ] = credential_manager_acs_system_id + json_payload["credential_manager_acs_system_id"] = ( + credential_manager_acs_system_id + ) if acs_credential_pool_id is not None: json_payload["acs_credential_pool_id"] = acs_credential_pool_id if create_credential_manager_user is not None: - json_payload[ - "create_credential_manager_user" - ] = create_credential_manager_user + json_payload["create_credential_manager_user"] = ( + create_credential_manager_user + ) if credential_manager_acs_user_id is not None: - json_payload[ - "credential_manager_acs_user_id" - ] = credential_manager_acs_user_id + json_payload["credential_manager_acs_user_id"] = ( + credential_manager_acs_user_id + ) self.seam.make_request( "POST", "/user_identities/enrollment_automations/launch", json=json_payload From ca80a5f491885c3f63b9369fb73f0987d96889c8 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 16:18:01 +0200 Subject: [PATCH 06/27] Install deps during generate workflow --- .github/workflows/generate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 957512f6..bb2bc750 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -30,7 +30,7 @@ jobs: - name: Setup uses: ./.github/actions/setup with: - install_dependencies: 'false' + install_dependencies: 'true' - name: Setup Node.js uses: ./.github/actions/setup-node with: From 2311e96612694ce46fba620f68a73e3f377e82ba Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Wed, 3 Apr 2024 14:19:02 +0000 Subject: [PATCH 07/27] ci: Generate code --- seam/types.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/seam/types.py b/seam/types.py index f4db1dd3..65a14f06 100644 --- a/seam/types.py +++ b/seam/types.py @@ -645,6 +645,7 @@ def __init__( class AbstractActionAttempts(abc.ABC): + @abc.abstractmethod def get(self, *, action_attempt_id: str) -> ActionAttempt: raise NotImplementedError() @@ -665,6 +666,7 @@ def poll_until_ready( class AbstractClientSessions(abc.ABC): + @abc.abstractmethod def create( self, @@ -732,6 +734,7 @@ def revoke(self, *, client_session_id: str) -> None: class AbstractConnectWebviews(abc.ABC): + @abc.abstractmethod def create( self, @@ -766,6 +769,7 @@ def list( class AbstractConnectedAccounts(abc.ABC): + @abc.abstractmethod def delete(self, *, connected_account_id: str, sync: Optional[bool] = None) -> None: raise NotImplementedError() @@ -794,6 +798,7 @@ def update( class AbstractEvents(abc.ABC): + @abc.abstractmethod def get( self, @@ -823,6 +828,7 @@ def list( class AbstractLocks(abc.ABC): + @abc.abstractmethod def get( self, *, device_id: Optional[str] = None, name: Optional[str] = None @@ -863,6 +869,7 @@ def unlock_door( class AbstractNetworks(abc.ABC): + @abc.abstractmethod def get(self, *, network_id: str) -> None: raise NotImplementedError() @@ -875,6 +882,7 @@ def list( class AbstractWebhooks(abc.ABC): + @abc.abstractmethod def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhook: raise NotImplementedError() @@ -899,6 +907,7 @@ def update(self, *, webhook_id: str, event_types: List[str]) -> None: class AbstractWorkspaces(abc.ABC): + @abc.abstractmethod def create( self, @@ -931,6 +940,7 @@ def reset_sandbox( class AbstractAccessCodesSimulate(abc.ABC): + @abc.abstractmethod def create_unmanaged_access_code( self, *, device_id: str, name: str, code: str @@ -939,6 +949,7 @@ def create_unmanaged_access_code( class AbstractAccessCodesUnmanaged(abc.ABC): + @abc.abstractmethod def convert_to_managed( self, @@ -985,6 +996,7 @@ def update( class AbstractAcsAccessGroups(abc.ABC): + @abc.abstractmethod def add_user(self, *, acs_access_group_id: str, acs_user_id: str) -> None: raise NotImplementedError() @@ -1009,12 +1021,14 @@ def remove_user(self, *, acs_access_group_id: str, acs_user_id: str) -> None: class AbstractAcsCredentialPools(abc.ABC): + @abc.abstractmethod def list(self, *, acs_system_id: str) -> None: raise NotImplementedError() class AbstractAcsCredentialProvisioningAutomations(abc.ABC): + @abc.abstractmethod def launch( self, @@ -1029,6 +1043,7 @@ def launch( class AbstractAcsCredentials(abc.ABC): + @abc.abstractmethod def assign(self, *, acs_user_id: str, acs_credential_id: str) -> None: raise NotImplementedError() @@ -1078,6 +1093,7 @@ def update(self, *, acs_credential_id: str, code: str) -> None: class AbstractAcsEntrances(abc.ABC): + @abc.abstractmethod def get(self, *, acs_entrance_id: str) -> None: raise NotImplementedError() @@ -1103,6 +1119,7 @@ def list_credentials_with_access( class AbstractAcsSystems(abc.ABC): + @abc.abstractmethod def get(self, *, acs_system_id: str) -> None: raise NotImplementedError() @@ -1113,6 +1130,7 @@ def list(self, *, connected_account_id: Optional[str] = None) -> None: class AbstractAcsUsers(abc.ABC): + @abc.abstractmethod def add_to_access_group( self, *, acs_user_id: str, acs_access_group_id: str @@ -1191,12 +1209,14 @@ def update( class AbstractDevicesSimulate(abc.ABC): + @abc.abstractmethod def remove(self, *, device_id: str) -> None: raise NotImplementedError() class AbstractDevicesUnmanaged(abc.ABC): + @abc.abstractmethod def get( self, *, device_id: Optional[str] = None, name: Optional[str] = None @@ -1229,12 +1249,14 @@ def update(self, *, device_id: str, is_managed: bool) -> None: class AbstractHealthService(abc.ABC): + @abc.abstractmethod def by_service_name(self, *, service_name: str) -> None: raise NotImplementedError() class AbstractNoiseSensorsNoiseThresholds(abc.ABC): + @abc.abstractmethod def create( self, @@ -1282,12 +1304,14 @@ def update( class AbstractNoiseSensorsSimulate(abc.ABC): + @abc.abstractmethod def trigger_noise_threshold(self, *, device_id: str) -> None: raise NotImplementedError() class AbstractPhonesSimulate(abc.ABC): + @abc.abstractmethod def create_sandbox_phone( self, @@ -1301,6 +1325,7 @@ def create_sandbox_phone( class AbstractThermostatsClimateSettingSchedules(abc.ABC): + @abc.abstractmethod def create( self, @@ -1362,6 +1387,7 @@ def update( class AbstractUserIdentitiesEnrollmentAutomations(abc.ABC): + @abc.abstractmethod def delete(self, *, enrollment_automation_id: str) -> None: raise NotImplementedError() @@ -1388,6 +1414,7 @@ def list(self, *, user_identity_id: str) -> None: class AbstractHealth(abc.ABC): + @property @abc.abstractmethod def service(self) -> AbstractHealthService: @@ -1405,6 +1432,7 @@ def get_service_health(self, *, service: str) -> None: class AbstractPhones(abc.ABC): + @property @abc.abstractmethod def simulate(self) -> AbstractPhonesSimulate: @@ -1420,6 +1448,7 @@ def list(self, *, owner_user_identity_id: Optional[str] = None) -> List[Phone]: class AbstractThermostats(abc.ABC): + @property @abc.abstractmethod def climate_setting_schedules(self) -> AbstractThermostatsClimateSettingSchedules: @@ -1509,6 +1538,7 @@ def update( class AbstractUserIdentities(abc.ABC): + @property @abc.abstractmethod def enrollment_automations(self) -> AbstractUserIdentitiesEnrollmentAutomations: @@ -1584,6 +1614,7 @@ def update( class AbstractAccessCodes(abc.ABC): + @property @abc.abstractmethod def simulate(self) -> AbstractAccessCodesSimulate: @@ -1705,6 +1736,7 @@ def update( class AbstractDevices(abc.ABC): + @property @abc.abstractmethod def simulate(self) -> AbstractDevicesSimulate: From dfae493767fc68a8b07b60d0d56d167d72063f81 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 16:21:45 +0200 Subject: [PATCH 08/27] Update generator to fix package version retrieval --- package-lock.json | 8 ++++---- package.json | 2 +- seam/seam.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0cc46912..88828584 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.4", + "@seamapi/nextlove-sdk-generator": "^1.7.5", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } @@ -415,9 +415,9 @@ } }, "node_modules/@seamapi/nextlove-sdk-generator": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.4.tgz", - "integrity": "sha512-lC5qsUuo6yG9RvX3QxX9LI5tF1XzCkMFA5+D1fqH9m3RQS1JE0fIT+xyaDyQmyq7Iwl7io4GMD5iVyPHRxgzvQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.5.tgz", + "integrity": "sha512-YgZinR6IOyxrTEpAWxPFBFQXItYcpoqjYlf8tdGbFZ2eBbdnQo2BRxRHxZoss8Iivumuf0cEdG244AcV7xC3nQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7ca40a77..3aff88e2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "format": "poetry run black ." }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.4", + "@seamapi/nextlove-sdk-generator": "^1.7.5", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } diff --git a/seam/seam.py b/seam/seam.py index ebdfb439..5d4f69f4 100644 --- a/seam/seam.py +++ b/seam/seam.py @@ -2,7 +2,7 @@ from .routes import Routes import requests -import pkg_resources +from importlib.metadata import version from typing import Optional, cast from .types import AbstractSeam, SeamApiException @@ -78,7 +78,7 @@ def make_request(self, method: str, path: str, **kwargs): """ url = self.api_url + path - sdk_version = pkg_resources.get_distribution("seam").version + sdk_version = version("seam") headers = { "Authorization": "Bearer " + self.api_key, "Content-Type": "application/json", From e7cdb29e02470a6e3336dba9d38e4694b848db77 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 16:59:33 +0200 Subject: [PATCH 09/27] Disable some lint rules --- .pylintrc | 23 +++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..686b90cc --- /dev/null +++ b/.pylintrc @@ -0,0 +1,23 @@ +[MESSAGES CONTROL] +disable= + missing-function-docstring, + too-many-arguments, + too-many-locals, + missing-module-docstring, + useless-return, + wrong-import-order, + unused-import, + missing-class-docstring, + too-few-public-methods, + too-many-branches, + super-init-not-called, + redefined-builtin, + too-many-instance-attributes, + non-parent-init-called, + broad-exception-raised, + missing-timeout, + line-too-long, + too-many-lines, + unnecessary-pass, + redefined-outer-name, + duplicate-code diff --git a/Makefile b/Makefile index b77a4ce6..e382cfa2 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ format: @poetry run black . lint: - @poetry run pylint ./seam ./test + @poetry run pylint ./seam ./test --rcfile ./.pylintrc @poetry run black --check . @poetry run rstcheck README.rst From 5232d95974e4b91e28b90e897697fc234ae9770e Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 18:35:44 +0200 Subject: [PATCH 10/27] Update generator to fix lint --- package-lock.json | 8 ++++---- package.json | 2 +- seam/routes.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 88828584..c114ae71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.5", + "@seamapi/nextlove-sdk-generator": "^1.7.6", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } @@ -415,9 +415,9 @@ } }, "node_modules/@seamapi/nextlove-sdk-generator": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.5.tgz", - "integrity": "sha512-YgZinR6IOyxrTEpAWxPFBFQXItYcpoqjYlf8tdGbFZ2eBbdnQo2BRxRHxZoss8Iivumuf0cEdG244AcV7xC3nQ==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.6.tgz", + "integrity": "sha512-JtHdddMRCfW0Y25LpzF3o3O9eXJ6TWh8K3qOEaLim6JxRIu0751g9yM4geL9NIAFdtlXf1EbWeGcTM7fZmNxkA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3aff88e2..eded3f8c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "format": "poetry run black ." }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.5", + "@seamapi/nextlove-sdk-generator": "^1.7.6", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } diff --git a/seam/routes.py b/seam/routes.py index d5446f81..9fed6b9e 100644 --- a/seam/routes.py +++ b/seam/routes.py @@ -38,5 +38,5 @@ def __init__(self): self.acs = Acs(seam=self) self.noise_sensors = NoiseSensors(seam=self) - def make_request(self): + def make_request(self, method: str, path: str, **kwargs): raise NotImplementedError() From 8e6682f9f3c430f05d13eb3c88427bfbafe62f6c Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 3 Apr 2024 19:18:01 +0200 Subject: [PATCH 11/27] Update generator to add missing wait_for_action_attempt to abstract methods --- package-lock.json | 8 ++++---- package.json | 2 +- seam/types.py | 33 +++++++++++++++++++++++++++++---- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index c114ae71..f37e5e4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.6", + "@seamapi/nextlove-sdk-generator": "^1.7.7", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } @@ -415,9 +415,9 @@ } }, "node_modules/@seamapi/nextlove-sdk-generator": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.6.tgz", - "integrity": "sha512-JtHdddMRCfW0Y25LpzF3o3O9eXJ6TWh8K3qOEaLim6JxRIu0751g9yM4geL9NIAFdtlXf1EbWeGcTM7fZmNxkA==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.7.tgz", + "integrity": "sha512-5cNW+zsxsgchfLBCGYGKUjIvbtJ2nt9wuZS5T/O2VYP9LKUN9oGJ1/55GxzVgHH9xS6ZSawcbvS/X910SkjxzQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index eded3f8c..f144689b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "format": "poetry run black ." }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.6", + "@seamapi/nextlove-sdk-generator": "^1.7.7", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } diff --git a/seam/types.py b/seam/types.py index 65a14f06..d08a3e86 100644 --- a/seam/types.py +++ b/seam/types.py @@ -647,7 +647,12 @@ def __init__( class AbstractActionAttempts(abc.ABC): @abc.abstractmethod - def get(self, *, action_attempt_id: str) -> ActionAttempt: + def get( + self, + *, + action_attempt_id: str, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + ) -> ActionAttempt: raise NotImplementedError() @abc.abstractmethod @@ -661,6 +666,7 @@ def poll_until_ready( action_attempt_id: str, timeout: Optional[float] = 5.0, polling_interval: Optional[float] = 0.5, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @@ -857,13 +863,21 @@ def list( @abc.abstractmethod def lock_door( - self, *, device_id: str, sync: Optional[bool] = None + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @abc.abstractmethod def unlock_door( - self, *, device_id: str, sync: Optional[bool] = None + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @@ -1462,6 +1476,7 @@ def cool( cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @@ -1479,6 +1494,7 @@ def heat( heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @@ -1492,6 +1508,7 @@ def heat_cool( cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @@ -1516,7 +1533,13 @@ def list( raise NotImplementedError() @abc.abstractmethod - def off(self, *, device_id: str, sync: Optional[bool] = None) -> ActionAttempt: + def off( + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + ) -> ActionAttempt: raise NotImplementedError() @abc.abstractmethod @@ -1527,6 +1550,7 @@ def set_fan_mode( fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, sync: Optional[bool] = None, + wait_for_action_attempt: Union[bool, Dict[str, float]] = False, ) -> ActionAttempt: raise NotImplementedError() @@ -1887,6 +1911,7 @@ class AbstractSeam(AbstractRoutes): def __init__( self, api_key: Optional[str] = None, + *, workspace_id: Optional[str] = None, api_url: Optional[str] = None, ): From abd1a50344ef3aa7dfe4f1b03914a7f8bc8a80f9 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 14:46:09 +0200 Subject: [PATCH 12/27] Test wait_for_action_attempt options --- test/action_attempts/test_action_attempts.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/action_attempts/test_action_attempts.py b/test/action_attempts/test_action_attempts.py index de87f2a7..b2d9c2b5 100644 --- a/test/action_attempts/test_action_attempts.py +++ b/test/action_attempts/test_action_attempts.py @@ -18,7 +18,13 @@ def test_action_attempts(seam: Seam): # Create multiple ActionAttempts some_device = seam.devices.list()[0] unlock_door1 = seam.locks.unlock_door(device_id=some_device.device_id) - unlock_door2 = seam.locks.unlock_door(device_id=some_device.device_id) + unlock_door2 = seam.locks.unlock_door( + device_id=some_device.device_id, + wait_for_action_attempt={ + "timeout": 10.0, + "polling_interval": 2.0, + }, + ) # Retrieve the list of ActionAttempts action_attempts = seam.action_attempts.list( From 12f92ab441bea0e11fad1f601d370b6960bb4e7c Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 4 Apr 2024 12:46:51 +0000 Subject: [PATCH 13/27] ci: Generate code --- seam/types.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/seam/types.py b/seam/types.py index d08a3e86..bf101f1d 100644 --- a/seam/types.py +++ b/seam/types.py @@ -184,15 +184,15 @@ def from_dict(d: Dict[str, Any]): @dataclass class ConnectWebview: connect_webview_id: str + workspace_id: str + created_at: str connected_account_id: str url: str - workspace_id: str device_selection_mode: str accepted_providers: List[str] accepted_devices: List[str] any_provider_allowed: bool any_device_allowed: bool - created_at: str login_successful: bool status: str custom_redirect_url: str @@ -207,15 +207,15 @@ class ConnectWebview: def from_dict(d: Dict[str, Any]): return ConnectWebview( connect_webview_id=d.get("connect_webview_id", None), + workspace_id=d.get("workspace_id", None), + created_at=d.get("created_at", None), connected_account_id=d.get("connected_account_id", None), url=d.get("url", None), - workspace_id=d.get("workspace_id", None), device_selection_mode=d.get("device_selection_mode", None), accepted_providers=d.get("accepted_providers", None), accepted_devices=d.get("accepted_devices", None), any_provider_allowed=d.get("any_provider_allowed", None), any_device_allowed=d.get("any_device_allowed", None), - created_at=d.get("created_at", None), login_successful=d.get("login_successful", None), status=d.get("status", None), custom_redirect_url=d.get("custom_redirect_url", None), From ea0d0c88a15832aac14d17b165a51f793564bfe9 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 15:16:15 +0200 Subject: [PATCH 14/27] Update generator to allow setting wait_for_action_attempt on client level + refactored polling logic --- package-lock.json | 8 ++-- package.json | 2 +- seam/action_attempts.py | 45 ++++++++++++------ seam/locks.py | 40 ++++------------ seam/seam.py | 2 + seam/thermostats.py | 100 ++++++++++------------------------------ seam/types.py | 28 +++++++---- 7 files changed, 91 insertions(+), 134 deletions(-) diff --git a/package-lock.json b/package-lock.json index f37e5e4c..c14e302d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.7", + "@seamapi/nextlove-sdk-generator": "^1.7.9", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } @@ -415,9 +415,9 @@ } }, "node_modules/@seamapi/nextlove-sdk-generator": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.7.tgz", - "integrity": "sha512-5cNW+zsxsgchfLBCGYGKUjIvbtJ2nt9wuZS5T/O2VYP9LKUN9oGJ1/55GxzVgHH9xS6ZSawcbvS/X910SkjxzQ==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.9.tgz", + "integrity": "sha512-8cS6foQUp/M5f923GWjjA/YC69DfZyMA6kxmK1LcaNigIGGvfdE5R7bI8q4VhoZigwY4JsCyLgcKAARK8HPDeA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index f144689b..242d5928 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "format": "poetry run black ." }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.7", + "@seamapi/nextlove-sdk-generator": "^1.7.9", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } diff --git a/seam/action_attempts.py b/seam/action_attempts.py index c2afb894..bb35efa0 100644 --- a/seam/action_attempts.py +++ b/seam/action_attempts.py @@ -14,7 +14,7 @@ def get( self, *, action_attempt_id: str, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: json_payload = {} @@ -23,20 +23,10 @@ def get( res = self.seam.make_request("POST", "/action_attempts/get", json=json_payload) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def list(self, *, action_attempt_ids: List[str]) -> List[ActionAttempt]: json_payload = {} @@ -78,3 +68,28 @@ def poll_until_ready( raise Exception(f"Action Attempt failed: {action_attempt.error.message}") return action_attempt + + def decide_and_wait( + self, + *, + action_attempt: ActionAttempt, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: + wait_decision = ( + self.seam.wait_for_action_attempt + if wait_for_action_attempt is None + else wait_for_action_attempt + ) + + if wait_decision is True: + return self.seam.action_attempts.poll_until_ready( + action_attempt_id=action_attempt.action_attempt_id + ) + elif isinstance(wait_decision, dict): + return self.seam.action_attempts.poll_until_ready( + action_attempt_id=action_attempt.action_attempt_id, + timeout=wait_decision.get("timeout", None), + polling_interval=wait_decision.get("polling_interval", None), + ) + + return action_attempt diff --git a/seam/locks.py b/seam/locks.py index ec50d497..26236911 100644 --- a/seam/locks.py +++ b/seam/locks.py @@ -77,7 +77,7 @@ def lock_door( *, device_id: str, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -88,27 +88,17 @@ def lock_door( res = self.seam.make_request("POST", "/locks/lock_door", json=json_payload) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def unlock_door( self, *, device_id: str, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -119,17 +109,7 @@ def unlock_door( res = self.seam.make_request("POST", "/locks/unlock_door", json=json_payload) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) diff --git a/seam/seam.py b/seam/seam.py index 5d4f69f4..79338330 100644 --- a/seam/seam.py +++ b/seam/seam.py @@ -22,6 +22,7 @@ def __init__( *, workspace_id: Optional[str] = None, api_url: Optional[str] = None, + wait_for_action_attempt: Optional[bool] = False, ): """ Parameters @@ -46,6 +47,7 @@ def __init__( self.api_key = api_key self.workspace_id = workspace_id self.lts_version = Seam.lts_version + self.wait_for_action_attempt = wait_for_action_attempt if os.environ.get("SEAM_API_URL", None) is not None: print( diff --git a/seam/thermostats.py b/seam/thermostats.py index d3de0c17..b5c013b9 100644 --- a/seam/thermostats.py +++ b/seam/thermostats.py @@ -23,7 +23,7 @@ def cool( cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -38,20 +38,10 @@ def cool( res = self.seam.make_request("POST", "/thermostats/cool", json=json_payload) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def get( self, *, device_id: Optional[str] = None, name: Optional[str] = None @@ -74,7 +64,7 @@ def heat( heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -89,20 +79,10 @@ def heat( res = self.seam.make_request("POST", "/thermostats/heat", json=json_payload) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def heat_cool( self, @@ -113,7 +93,7 @@ def heat_cool( cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -134,20 +114,10 @@ def heat_cool( "POST", "/thermostats/heat_cool", json=json_payload ) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def list( self, @@ -204,7 +174,7 @@ def off( *, device_id: str, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -215,20 +185,10 @@ def off( res = self.seam.make_request("POST", "/thermostats/off", json=json_payload) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def set_fan_mode( self, @@ -237,7 +197,7 @@ def set_fan_mode( fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None ) -> ActionAttempt: json_payload = {} @@ -254,20 +214,10 @@ def set_fan_mode( "POST", "/thermostats/set_fan_mode", json=json_payload ) - if isinstance(wait_for_action_attempt, dict): - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"], - timeout=wait_for_action_attempt.get("timeout", None), - polling_interval=wait_for_action_attempt.get("polling_interval", None), - ) - elif wait_for_action_attempt is True: - updated_action_attempt = self.seam.action_attempts.poll_until_ready( - action_attempt_id=res["action_attempt"]["action_attempt_id"] - ) - else: - return ActionAttempt.from_dict(res["action_attempt"]) - - return updated_action_attempt + return self.seam.action_attempts.decide_and_wait( + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def update( self, *, device_id: str, default_climate_setting: Dict[str, Any] diff --git a/seam/types.py b/seam/types.py index bf101f1d..ecb19387 100644 --- a/seam/types.py +++ b/seam/types.py @@ -651,7 +651,7 @@ def get( self, *, action_attempt_id: str, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -666,7 +666,15 @@ def poll_until_ready( action_attempt_id: str, timeout: Optional[float] = 5.0, polling_interval: Optional[float] = 0.5, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def decide_and_wait( + self, + *, + action_attempt: ActionAttempt, + wait_for_action_attempt: Union[bool, Dict[str, float]], ) -> ActionAttempt: raise NotImplementedError() @@ -867,7 +875,7 @@ def lock_door( *, device_id: str, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -877,7 +885,7 @@ def unlock_door( *, device_id: str, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -1476,7 +1484,7 @@ def cool( cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -1494,7 +1502,7 @@ def heat( heating_set_point_celsius: Optional[float] = None, heating_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -1508,7 +1516,7 @@ def heat_cool( cooling_set_point_celsius: Optional[float] = None, cooling_set_point_fahrenheit: Optional[float] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -1538,7 +1546,7 @@ def off( *, device_id: str, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -1550,7 +1558,7 @@ def set_fan_mode( fan_mode: Optional[str] = None, fan_mode_setting: Optional[str] = None, sync: Optional[bool] = None, - wait_for_action_attempt: Union[bool, Dict[str, float]] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, ) -> ActionAttempt: raise NotImplementedError() @@ -1906,6 +1914,7 @@ class AbstractSeam(AbstractRoutes): workspace_id: str api_url: str lts_version: str + wait_for_action_attempt: bool @abc.abstractmethod def __init__( @@ -1914,5 +1923,6 @@ def __init__( *, workspace_id: Optional[str] = None, api_url: Optional[str] = None, + wait_for_action_attempt: Optional[bool] = False, ): raise NotImplementedError From dbaf715b253e96559635f46dc851f84d760698ad Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 15:42:56 +0200 Subject: [PATCH 15/27] Update generator to fix lint --- package-lock.json | 8 ++++---- package.json | 2 +- seam/action_attempts.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c14e302d..9a996869 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.9", + "@seamapi/nextlove-sdk-generator": "^1.7.10", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } @@ -415,9 +415,9 @@ } }, "node_modules/@seamapi/nextlove-sdk-generator": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.9.tgz", - "integrity": "sha512-8cS6foQUp/M5f923GWjjA/YC69DfZyMA6kxmK1LcaNigIGGvfdE5R7bI8q4VhoZigwY4JsCyLgcKAARK8HPDeA==", + "version": "1.7.10", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.10.tgz", + "integrity": "sha512-d3TnClTewoict7sF9M7UOtSajVxyPUvpc02LXbHTDrl3Rc/L0C/FWxAPJLEXb0BpQyBq9sjdi3kam5WBTfZqrA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 242d5928..3bdcdfe6 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "format": "poetry run black ." }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.9", + "@seamapi/nextlove-sdk-generator": "^1.7.10", "@seamapi/types": "^1.149.0", "prettier": "^3.2.5" } diff --git a/seam/action_attempts.py b/seam/action_attempts.py index bb35efa0..61046fd3 100644 --- a/seam/action_attempts.py +++ b/seam/action_attempts.py @@ -85,7 +85,7 @@ def decide_and_wait( return self.seam.action_attempts.poll_until_ready( action_attempt_id=action_attempt.action_attempt_id ) - elif isinstance(wait_decision, dict): + if isinstance(wait_decision, dict): return self.seam.action_attempts.poll_until_ready( action_attempt_id=action_attempt.action_attempt_id, timeout=wait_decision.get("timeout", None), From 843d6bdc4fb36ad05ac6e52f05758daf9cd51305 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 15:44:37 +0200 Subject: [PATCH 16/27] Update generate workflow --- .github/workflows/generate.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index bb2bc750..d7b250f4 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -30,7 +30,7 @@ jobs: - name: Setup uses: ./.github/actions/setup with: - install_dependencies: 'true' + install_dependencies: 'false' - name: Setup Node.js uses: ./.github/actions/setup-node with: @@ -41,6 +41,10 @@ jobs: run: npm install - name: Generate code run: npm run generate + - name: Install dependencies + run: poetry install --sync + - name: Format + run: make format - name: Commit uses: stefanzweifel/git-auto-commit-action@v5 with: From 3d439f33d5614e4ad441a76c386f75848fe72648 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 15:47:22 +0200 Subject: [PATCH 17/27] Remove --rcfile from lint command --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e382cfa2..b77a4ce6 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ format: @poetry run black . lint: - @poetry run pylint ./seam ./test --rcfile ./.pylintrc + @poetry run pylint ./seam ./test @poetry run black --check . @poetry run rstcheck README.rst From 850fb9a4767781c4f2eac476b9ff374bcd9bac2c Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 15:49:10 +0200 Subject: [PATCH 18/27] Modify generate and format scripts --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3bdcdfe6..d0bc01f5 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,9 @@ "private": true, "type": "module", "scripts": { - "generate": "./scripts/generate.sh && npm run format", - "format": "poetry run black ." + "generate": "./scripts/generate.sh", + "format": "prettier --write --ignore-path .gitignore .", + "preformat": "make format" }, "devDependencies": { "@seamapi/nextlove-sdk-generator": "^1.7.10", From 90a74fec3905cacb0cac698aa60506df679e9487 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 15:52:36 +0200 Subject: [PATCH 19/27] Pin generator and seam types versions --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d0bc01f5..153117e1 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "preformat": "make format" }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.10", - "@seamapi/types": "^1.149.0", + "@seamapi/nextlove-sdk-generator": "1.7.10", + "@seamapi/types": "1.149.0", "prettier": "^3.2.5" } } From dab34d90b3adf1754b088a4081e1c5ee82b77edd Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 4 Apr 2024 13:57:15 +0000 Subject: [PATCH 20/27] ci: Generate code --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a996869..fe5c6493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,8 +6,8 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.10", - "@seamapi/types": "^1.149.0", + "@seamapi/nextlove-sdk-generator": "1.7.10", + "@seamapi/types": "1.149.0", "prettier": "^3.2.5" } }, From 737900133f3246e1f74d4c6d5e08b442fea7d648 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 16:00:26 +0200 Subject: [PATCH 21/27] Fix devices test, extend init seam test --- test/devices/test_devices.py | 3 +-- test/test_init_seam.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/devices/test_devices.py b/test/devices/test_devices.py index f78a7754..4e15938d 100644 --- a/test/devices/test_devices.py +++ b/test/devices/test_devices.py @@ -1,6 +1,5 @@ from seam import Seam from seam.types import SeamApiException -from seam.utils.deep_attr_dict import DeepAttrDict def test_devices(seam: Seam): @@ -19,7 +18,7 @@ def test_devices(seam: Seam): devices = seam.devices.list(connected_account_ids=connected_account_ids) assert len(devices) > 0 - devices = seam.devices.list(device_types="august_lock") + devices = seam.devices.list(device_types=["august_lock"]) assert len(devices) > 0 devices = seam.devices.list(device_types=["august_lock"]) assert len(devices) > 0 diff --git a/test/test_init_seam.py b/test/test_init_seam.py index d3c07ed1..8af62c11 100644 --- a/test/test_init_seam.py +++ b/test/test_init_seam.py @@ -6,3 +6,4 @@ def test_init_seam_with_fixture(seam: Seam): assert seam.api_url assert seam.lts_version assert "http" in seam.api_url + assert seam.wait_for_action_attempt == False From 9f5bb682246d6154f6f6a49a88d5cea52cbf8601 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 16:03:33 +0200 Subject: [PATCH 22/27] Fix init seam test --- test/test_init_seam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_init_seam.py b/test/test_init_seam.py index 8af62c11..4fc67b64 100644 --- a/test/test_init_seam.py +++ b/test/test_init_seam.py @@ -6,4 +6,4 @@ def test_init_seam_with_fixture(seam: Seam): assert seam.api_url assert seam.lts_version assert "http" in seam.api_url - assert seam.wait_for_action_attempt == False + assert seam.wait_for_action_attempt is False From f30b67b4cb1d4aa0d570d9e3884e20c8c75070ff Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 16:21:48 +0200 Subject: [PATCH 23/27] Terminate generate script immediately if any command exits with a non-zero status --- scripts/generate.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/generate.sh b/scripts/generate.sh index 18e96f30..425a1f70 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # Exit immediately if any command exits with a non-zero status. # Remove the existing 'seam' directory rm -rf ./seam From 77cc58561b238b4c8497ce79034333a510a48650 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 4 Apr 2024 20:08:39 +0200 Subject: [PATCH 24/27] Rename .pylintrc to pylintrc --- .pylintrc => pylintrc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .pylintrc => pylintrc (100%) diff --git a/.pylintrc b/pylintrc similarity index 100% rename from .pylintrc rename to pylintrc From 09eedb6ab0203feae7bb40fbc5f7573d9cf5a21f Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Fri, 5 Apr 2024 14:15:51 +0200 Subject: [PATCH 25/27] Update generate script --- scripts/generate.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/generate.sh b/scripts/generate.sh index 425a1f70..f2e36b52 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -1,14 +1,17 @@ #!/bin/bash set -e # Exit immediately if any command exits with a non-zero status. -# Remove the existing 'seam' directory -rm -rf ./seam +mkdir -p ./tmp + +# Remove the existing temporary SDK generation folder +rm -rf ./tmp/nextlove-sdk-generator-output # Generate SDK files -nextlove-sdk-generator generate python ./temp_sdk +nextlove-sdk-generator generate python ./tmp/nextlove-sdk-generator-output # Move only the 'seam' folder -mv ./temp_sdk/seam ./seam +rm -rf ./seam +mv ./tmp/nextlove-sdk-generator-output/seam ./seam # Clean up the temporary SDK generation folder -rm -rf ./temp_sdk +rm -rf ./tmp/nextlove-sdk-generator-output From 811d7dc7982eab800687f296d819ef96ec6651b5 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Fri, 5 Apr 2024 17:29:43 +0200 Subject: [PATCH 26/27] Update generator to remove /health endpoints and fix wait_for_action_attempt type --- package-lock.json | 8 ++++---- package.json | 2 +- seam/health.py | 34 ---------------------------------- seam/health_service.py | 21 --------------------- seam/routes.py | 2 -- seam/seam.py | 4 ++-- seam/types.py | 26 -------------------------- 7 files changed, 7 insertions(+), 90 deletions(-) delete mode 100644 seam/health.py delete mode 100644 seam/health_service.py diff --git a/package-lock.json b/package-lock.json index fe5c6493..f08629db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "1.7.10", + "@seamapi/nextlove-sdk-generator": "^1.7.12", "@seamapi/types": "1.149.0", "prettier": "^3.2.5" } @@ -415,9 +415,9 @@ } }, "node_modules/@seamapi/nextlove-sdk-generator": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.10.tgz", - "integrity": "sha512-d3TnClTewoict7sF9M7UOtSajVxyPUvpc02LXbHTDrl3Rc/L0C/FWxAPJLEXb0BpQyBq9sjdi3kam5WBTfZqrA==", + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/@seamapi/nextlove-sdk-generator/-/nextlove-sdk-generator-1.7.12.tgz", + "integrity": "sha512-saDmFd4BjMwMlS3wowtSLVVGpc9yhI/NYEKBKcagykWVKvljeIEbo+u93c05xNPauqZs2EK/fj8pVD7sdPfYvA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 153117e1..1c76644b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "preformat": "make format" }, "devDependencies": { - "@seamapi/nextlove-sdk-generator": "1.7.10", + "@seamapi/nextlove-sdk-generator": "1.7.12", "@seamapi/types": "1.149.0", "prettier": "^3.2.5" } diff --git a/seam/health.py b/seam/health.py deleted file mode 100644 index 7cf31ee5..00000000 --- a/seam/health.py +++ /dev/null @@ -1,34 +0,0 @@ -from seam.types import AbstractHealth, AbstractSeam as Seam -from typing import Optional, Any, List, Dict, Union -from seam.health_service import HealthService - - -class Health(AbstractHealth): - seam: Seam - - def __init__(self, seam: Seam): - self.seam = seam - self._service = HealthService(seam=seam) - - @property - def service(self) -> HealthService: - return self._service - - def get_health( - self, - ) -> None: - json_payload = {} - - self.seam.make_request("POST", "/health/get_health", json=json_payload) - - return None - - def get_service_health(self, *, service: str) -> None: - json_payload = {} - - if service is not None: - json_payload["service"] = service - - self.seam.make_request("POST", "/health/get_service_health", json=json_payload) - - return None diff --git a/seam/health_service.py b/seam/health_service.py deleted file mode 100644 index 1d0fab97..00000000 --- a/seam/health_service.py +++ /dev/null @@ -1,21 +0,0 @@ -from seam.types import AbstractHealthService, AbstractSeam as Seam -from typing import Optional, Any, List, Dict, Union - - -class HealthService(AbstractHealthService): - seam: Seam - - def __init__(self, seam: Seam): - self.seam = seam - - def by_service_name(self, *, service_name: str) -> None: - json_payload = {} - - if service_name is not None: - json_payload["service_name"] = service_name - - self.seam.make_request( - "POST", "/health/service/[service_name]", json=json_payload - ) - - return None diff --git a/seam/routes.py b/seam/routes.py index 9fed6b9e..4ef1fb61 100644 --- a/seam/routes.py +++ b/seam/routes.py @@ -6,7 +6,6 @@ from .connected_accounts import ConnectedAccounts from .devices import Devices from .events import Events -from .health import Health from .locks import Locks from .networks import Networks from .phones import Phones @@ -27,7 +26,6 @@ def __init__(self): self.connected_accounts = ConnectedAccounts(seam=self) self.devices = Devices(seam=self) self.events = Events(seam=self) - self.health = Health(seam=self) self.locks = Locks(seam=self) self.networks = Networks(seam=self) self.phones = Phones(seam=self) diff --git a/seam/seam.py b/seam/seam.py index 79338330..1efc2153 100644 --- a/seam/seam.py +++ b/seam/seam.py @@ -3,7 +3,7 @@ from .routes import Routes import requests from importlib.metadata import version -from typing import Optional, cast +from typing import Optional, Union, Dict, cast from .types import AbstractSeam, SeamApiException @@ -22,7 +22,7 @@ def __init__( *, workspace_id: Optional[str] = None, api_url: Optional[str] = None, - wait_for_action_attempt: Optional[bool] = False, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = False, ): """ Parameters diff --git a/seam/types.py b/seam/types.py index ecb19387..79ea643d 100644 --- a/seam/types.py +++ b/seam/types.py @@ -1270,13 +1270,6 @@ def update(self, *, device_id: str, is_managed: bool) -> None: raise NotImplementedError() -class AbstractHealthService(abc.ABC): - - @abc.abstractmethod - def by_service_name(self, *, service_name: str) -> None: - raise NotImplementedError() - - class AbstractNoiseSensorsNoiseThresholds(abc.ABC): @abc.abstractmethod @@ -1435,24 +1428,6 @@ def list(self, *, user_identity_id: str) -> None: raise NotImplementedError() -class AbstractHealth(abc.ABC): - - @property - @abc.abstractmethod - def service(self) -> AbstractHealthService: - raise NotImplementedError() - - @abc.abstractmethod - def get_health( - self, - ) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def get_service_health(self, *, service: str) -> None: - raise NotImplementedError() - - class AbstractPhones(abc.ABC): @property @@ -1892,7 +1867,6 @@ class AbstractRoutes(abc.ABC): connected_accounts: AbstractConnectedAccounts devices: AbstractDevices events: AbstractEvents - health: AbstractHealth locks: AbstractLocks networks: AbstractNetworks phones: AbstractPhones From a0d01bbac43d5d06799a748f7aa5ba8e61ab6bd8 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Fri, 5 Apr 2024 15:30:22 +0000 Subject: [PATCH 27/27] ci: Generate code --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index f08629db..bd70fed2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "@seamapi/python", "devDependencies": { - "@seamapi/nextlove-sdk-generator": "^1.7.12", + "@seamapi/nextlove-sdk-generator": "1.7.12", "@seamapi/types": "1.149.0", "prettier": "^3.2.5" }