diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 06a222e..bc67374 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/.github/workflows/generate.yml b/.github/workflows/generate.yml index 957512f..d7b250f 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -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: diff --git a/package-lock.json b/package-lock.json index 6adae30..bd70fed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,833 @@ "": { "name": "@seamapi/python", "devDependencies": { + "@seamapi/nextlove-sdk-generator": "1.7.12", + "@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.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": { + "@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 2c6f41f..1c76644 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,13 @@ "private": true, "type": "module", "scripts": { - "generate": "true", - "format": "prettier --write --ignore-path .gitignore ." + "generate": "./scripts/generate.sh", + "format": "prettier --write --ignore-path .gitignore .", + "preformat": "make format" }, "devDependencies": { + "@seamapi/nextlove-sdk-generator": "1.7.12", + "@seamapi/types": "1.149.0", "prettier": "^3.2.5" } } diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..686b90c --- /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/scripts/generate.sh b/scripts/generate.sh new file mode 100755 index 0000000..f2e36b5 --- /dev/null +++ b/scripts/generate.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e # Exit immediately if any command exits with a non-zero status. + +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 ./tmp/nextlove-sdk-generator-output + +# Move only the 'seam' folder +rm -rf ./seam +mv ./tmp/nextlove-sdk-generator-output/seam ./seam + +# Clean up the temporary SDK generation folder +rm -rf ./tmp/nextlove-sdk-generator-output diff --git a/seam/__init__.py b/seam/__init__.py index cd6366f..50537ff 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 0000000..2215d5b --- /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 0000000..dbc3198 --- /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 0000000..64dcd83 --- /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 0000000..616b4b2 --- /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 0000000..fc25915 --- /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 0000000..25985d1 --- /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 0000000..cde5159 --- /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 0000000..31f5b59 --- /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 0000000..a73ce49 --- /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 0000000..c093544 --- /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 0000000..3b978d5 --- /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 0000000..61046fd --- /dev/null +++ b/seam/action_attempts.py @@ -0,0 +1,95 @@ +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: Optional[Union[bool, Dict[str, float]]] = None, + ) -> 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) + + 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 = {} + + 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 + + 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 + ) + 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), + polling_interval=wait_decision.get("polling_interval", None), + ) + + return action_attempt diff --git a/seam/client_sessions.py b/seam/client_sessions.py new file mode 100644 index 0000000..8d48daa --- /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 0000000..7f1d6f0 --- /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 0000000..0c27810 --- /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 0000000..7d1aacd --- /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 0000000..ec8f76d --- /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 0000000..d4f5c7a --- /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 0000000..34a59ea --- /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/locks.py b/seam/locks.py new file mode 100644 index 0000000..2623691 --- /dev/null +++ b/seam/locks.py @@ -0,0 +1,115 @@ +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: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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) + + 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: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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) + + 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/networks.py b/seam/networks.py new file mode 100644 index 0000000..2944b95 --- /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 0000000..46c43ae --- /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 0000000..23cda5a --- /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 0000000..03dc979 --- /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 0000000..02a0f7b --- /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 0000000..daf1053 --- /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 0000000..4ef1fb6 --- /dev/null +++ b/seam/routes.py @@ -0,0 +1,40 @@ +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 .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.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, method: str, path: str, **kwargs): + raise NotImplementedError() diff --git a/seam/seam.py b/seam/seam.py new file mode 100644 index 0000000..1efc215 --- /dev/null +++ b/seam/seam.py @@ -0,0 +1,104 @@ +import os + +from .routes import Routes +import requests +from importlib.metadata import version +from typing import Optional, Union, Dict, 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = False, + ): + """ + 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 + self.wait_for_action_attempt = wait_for_action_attempt + + 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 = version("seam") + 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 0000000..b5c013b --- /dev/null +++ b/seam/thermostats.py @@ -0,0 +1,234 @@ +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: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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) + + 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 + ) -> 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: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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) + + 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, + *, + 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: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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 + ) + + 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, + *, + 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: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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) + + 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, + *, + device_id: str, + fan_mode: Optional[str] = None, + fan_mode_setting: Optional[str] = None, + sync: Optional[bool] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> 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 + ) + + 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] + ) -> 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 0000000..b677942 --- /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 37d7f08..0000000 --- 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 4a9d725..0000000 --- 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 0000000..79ea643 --- /dev/null +++ b/seam/types.py @@ -0,0 +1,1902 @@ +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 + workspace_id: str + created_at: str + connected_account_id: str + url: str + device_selection_mode: str + accepted_providers: List[str] + accepted_devices: List[str] + any_provider_allowed: bool + any_device_allowed: bool + 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), + 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), + 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), + 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> 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() + + @abc.abstractmethod + def decide_and_wait( + self, + *, + action_attempt: ActionAttempt, + wait_for_action_attempt: Union[bool, Dict[str, float]], + ) -> 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None, + ) -> ActionAttempt: + raise NotImplementedError() + + @abc.abstractmethod + def unlock_door( + self, + *, + device_id: str, + sync: Optional[bool] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = 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 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 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = 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, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = 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 + 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 + wait_for_action_attempt: bool + + @abc.abstractmethod + def __init__( + self, + api_key: Optional[str] = None, + *, + workspace_id: Optional[str] = None, + api_url: Optional[str] = None, + wait_for_action_attempt: Optional[bool] = False, + ): + raise NotImplementedError diff --git a/seam/user_identities.py b/seam/user_identities.py new file mode 100644 index 0000000..574b0a7 --- /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 0000000..41c0c40 --- /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 0000000..341f0a9 --- /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 0000000..69f4b7d --- /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 0000000..c2087d5 --- /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 diff --git a/test/access_codes/test_access_codes.py b/test/access_codes/test_access_codes.py new file mode 100644 index 0000000..a325a4c --- /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 0000000..b2d9c2b --- /dev/null +++ b/test/action_attempts/test_action_attempts.py @@ -0,0 +1,52 @@ +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, + wait_for_action_attempt={ + "timeout": 10.0, + "polling_interval": 2.0, + }, + ) + + # 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 0000000..92f2212 --- /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 0000000..da425e6 --- /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 0000000..d45e5f1 --- /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 0000000..4e15938 --- /dev/null +++ b/test/devices/test_devices.py @@ -0,0 +1,114 @@ +from seam import Seam +from seam.types import SeamApiException + + +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 0000000..02f1cf7 --- /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 0000000..a869b56 --- /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 0000000..4fc67b6 --- /dev/null +++ b/test/test_init_seam.py @@ -0,0 +1,9 @@ +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 + assert seam.wait_for_action_attempt is False diff --git a/test/thermostats/test_climate_setting_schedules.py b/test/thermostats/test_climate_setting_schedules.py new file mode 100644 index 0000000..877c7e5 --- /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 0000000..3e005a9 --- /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 45ef86f..0000000 --- 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 0000000..afdc331 --- /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 0000000..8541ff5 --- /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 0000000..d1c0970 --- /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 0000000..9d42b0a --- /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